Permissive Loading Mode

Introduction

odmlib validates attribute values at assignment time, which means that non-conformant ODM or Define-XML files cannot normally be loaded into the object model. The permissive loading mode solves this by selectively bypassing validation during loading, allowing you to inspect, repair, and re-validate documents that contain invalid content.

Quick Start

from odmlib import permissive
import odmlib.loader as LD
import odmlib.odm_loader as OL

loader = LD.ODMLoader(OL.XMLODMLoader())

with permissive():
    loader.open_odm_document("incomplete_define.xml")
    odm = loader.root()

# odm is now loaded -- mode is back to strict

Graduated Control

The permissive() context manager accepts a ValidationMode flag that controls exactly which validation categories to bypass:

from odmlib import permissive, ValidationMode

# Skip only required-attribute checks
with permissive(ValidationMode.SKIP_REQUIRED):
    ...

# Skip required + valueset (but still enforce types and formats)
with permissive(ValidationMode.SKIP_REQUIRED | ValidationMode.SKIP_VALUESET):
    ...

# Skip everything (the default)
with permissive():  # defaults to ValidationMode.PERMISSIVE
    ...
Validation Flags

Flag

What it bypasses

Descriptor classes affected

SKIP_REQUIRED

Required-attribute checks at construction and access

Descriptor.__get__, ODMElement.__init__

SKIP_TYPE

Type checks and unknown-attribute rejection

Typed, Integer, Float, Positive, NonNegative, ODMObject, ODMListObject, ODMElement.__init__, ODMElement.__setattr__

SKIP_FORMAT

Format validators

DateTimeString, DateString, PartialDateTimeString, PartialDateString, PartialTimeString, IncompleteDateTimeString, IncompleteDateString, IncompleteTimeString, DurationDateTimeString, SASName, SASFormat, Email, Url, FileName, Sized, Regex

SKIP_VALUESET

Enumerated value set enforcement

ValidValues, ExtendedValidValues, ValueSetString

PERMISSIVE

All of the above (composite flag)

All descriptor classes

The Load – Inspect – Fix – Validate Workflow

The recommended workflow for handling non-conformant files:

from odmlib import permissive
import odmlib.loader as LD
import odmlib.odm_loader as OL

# Step 1: Load permissively
loader = LD.ODMLoader(OL.XMLODMLoader())
with permissive():
    loader.open_odm_document("broken_define.xml")
    odm = loader.root()

# Step 2: Inspect -- mode is strict again, but objects are populated
mdv = odm.Study[0].MetaDataVersion[0]
for item in mdv.ItemDef:
    print(f"OID={item.OID}, DataType={item.DataType}")

# Step 3: Fix issues (assignments are validated in strict mode)
for item in mdv.ItemDef:
    if item.DataType not in ["text", "integer", "float"]:
        item.DataType = "text"

# Step 4: Validate (max_errors caps a badly broken document)
errors = odm.validate(collect_errors=True, max_errors=100)
if not errors:
    odm.write_xml("fixed_define.xml")
else:
    for err in errors:
        print(err)

Context Manager Integration

The open_odm() and open_define() context managers accept a permissive parameter:

from odmlib.context import open_odm
from odmlib import ValidationMode

with open_odm("broken.xml", output_file="fixed.xml",
              permissive=True) as odm:
    # Fix issues here
    ...
# Saves on clean exit -- but note: save runs in strict mode

# Targeted relaxation
with open_odm("broken.xml", output_file="fixed.xml",
              permissive=ValidationMode.SKIP_REQUIRED) as odm:
    ...

API Reference

class odmlib.mode.ValidationMode(*values)[source]

Bases: Flag

Flag enum controlling which validation categories are enforced.

Members can be combined with | to create targeted relaxation:

ValidationMode.SKIP_REQUIRED | ValidationMode.SKIP_VALUESET
STRICT = 0

All validation enforced (default, preserves existing behavior).

SKIP_REQUIRED = 1

Omit required-attribute checks during construction and access.

SKIP_VALUESET = 2

Omit ValidValues and ExtendedValidValues enforcement.

SKIP_TYPE = 4

Omit type checks and unknown-attribute rejection.

SKIP_FORMAT = 8

Omit format validators (datetime, SAS name, email, URL, etc.).

PERMISSIVE = 15

Composite flag that skips all validation categories.

odmlib.mode.permissive(mode=<ValidationMode.PERMISSIVE: 15>)[source]

Context manager that activates a permissive validation mode.

On entry, sets the validation mode to mode. On exit (including on exception), restores the previous mode via token reset.

Parameters:

mode (ValidationMode) – The validation mode to activate. Defaults to ValidationMode.PERMISSIVE (skip all validation).

Example:

from odmlib import permissive, ValidationMode

# Skip everything
with permissive():
    ...

# Skip only required checks
with permissive(ValidationMode.SKIP_REQUIRED):
    ...
odmlib.mode.get_mode()[source]

Return the current validation mode from the ContextVar.

Return type:

ValidationMode

odmlib.mode.set_mode(mode)[source]

Set the validation mode and return a reset token.

Parameters:

mode (ValidationMode) – The validation mode to activate.

Returns:

A contextvars.Token that can be passed to _validation_mode.reset() to restore the previous mode.

Return type:

Token

odmlib.mode.is_permissive(flag)[source]

Check whether a specific validation flag is active.

Parameters:

flag (ValidationMode) – The validation category to check (e.g., ValidationMode.SKIP_REQUIRED).

Returns:

True if the flag is active in the current mode.

Return type:

bool

Safety Notes

  • Permissively loaded objects may contain invalid data that does not conform to the ODM specification.

  • Always call validate(collect_errors=True) before serializing for production use. Every layer reports all the problems it finds, so a badly broken document can yield a long list — pass max_errors to cap it and work through the issues in batches.

  • The permissive mode affects only the current context (thread or coroutine); other threads and coroutines remain in strict mode.

  • When using open_odm(permissive=True), the auto-save on exit runs in strict mode. If the object tree has unfixed issues that prevent serialization, the save may fail. Call write_xml() or write_json() inside the with block (while permissive mode is still active) if you need to save an un-fixed document.

  • For read-only inspection — for example, loading a known-broken document to enumerate its issues without risking a strict-mode save failure overwriting the input — pass write_on_exit=False. No file is created or modified when the with block exits, regardless of output_file.

    with open_odm("broken.xml", permissive=True,
                  write_on_exit=False) as odm:
        errors = odm.validate(collect_errors=True)
        for err in errors:
            print(err)
    # broken.xml is untouched on exit