v0.2.0rc1 #26
swhume
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
[0.2.0] - 2026-05-20
An rc1 - testing release note. Install for soak with:
pip install --pre "odmlib==0.2.0rc1".Added
Context Manager
write_on_exitOpt-Outodmlib/context.py: addedwrite_on_exit: bool = Trueparameter toODMContext,DefineContext,open_odm, andopen_define. Passingwrite_on_exit=Falsesuppresses the auto-save on clean exit, enablingread-only inspection through the context managers without modifying or
creating any file. The default (
True) preserves the documentedin-place save behaviour ? additive change, no compat impact.
tests/test_context_managers.py: 10 new tests covering the opt-out(XML, JSON,
open_odm,open_define, and the input-preservationregression guard for the default-output-file footgun) plus explicit
default-still-writes guards.
ODM v2.0 Model/XSD Alignment (safe subset)
odmlib/data/valuesets.json: added 12 missingodm_2_0value-set keys(
ReturnValue.DataType,ItemRef.Core/Repeat/Other/IsNonStandard/HasNoData,CodeListItem.Other,Telecom.TelecomType,ItemGroupDef.IsNonStandard/HasNoData,CodeList.IsNonStandard,ODM.Context) bound to the ODM 2.0ODM-enumerations.xsdvalue lists.tests/test_odm_2_0_model.py: new construction + XML/JSON round-trip suitefor the major ODM 2.0 classes.
tests/test_odm_2_0_known_gaps.py: new strict-xfailmarkers pinning thefive deferred structural ODM 2.0 model/XSD gaps so CI documents the known
state and fails loudly if a gap is silently fixed or regressed. (Its
ItemDef test is now a passing regression guard ? see Changed below.)
Changed
ODM 2.0
TranslatedText.Typeis now required (odmlib/odm_2_0/model.py),matching the XSD (
use="required", free-text media type).ODMBuildernow defaults
Type="text/plain"for the ODM 2.0 model shape only via a new_translated_text()helper.odmlib/valueset.py:ValueSet.value_set()no longer raises for anunknown attribute ? it returns the new
ValueSet.UNKNOWN_ATTRIBUTEsentinel so
validate()returnsFalseand theSKIP_VALUESETpermissiveguard can bypass an unregistered value set. Unknown version still raises.
Behavioral note: in strict mode an unregistered value-set attribute now
raises
OdmlibTypeError(fromValidValues.__set__) instead of the formerOdmlibValidationError(fromvalue_set()).ODM v2.0
ItemDefaligned with the ODM 2.0 XSD(
odmlib/odm_2_0/model.py). Removed the XSD-rejected attributesFractionDigits,DatasetVarName, andSDSVarName; added theXSD-defined optional attributes
DisplayFormatandVariableSet. Codethat set the removed attributes on an
odm_2_0ItemDefshould migrate ?those values were schema-invalid and are no longer serialized. (Closes
the ROADMAP v0.2.1 ItemDef gap /
ODM20-MODEL-XSD-DIFFERENCES_PLAN.md�3.7; the XSD
ItemDef/ValueListRefchild element remains deferred.)Known Limitations
produce schema-invalid output if used under
model_package="odm_2_0":ConditionDef(no requiredMethodSignature), text-basedFormalExpression,Protocol.StudyEventRef(removed in the 2.0 schema),MetaDataVersion.StudyTimingplacement, andStudyEventGroupDef(missingrequired child group). The affected
ODMBuilderhelpers carry docstringcaveats. See ROADMAP "v0.2.1 ? ODM v2.0 Model/XSD Alignment" and
ODM20-MODEL-XSD-DIFFERENCES_PLAN.md.ODM v2.0 XSD Schema Validation
odmlib/schema_manager.py: registered("odm", "2.0") ? "ODM.xsd"in
_MAIN_SCHEMA.ODMSchemaValidator(standard="odm", version="2.0")now resolves the bundled
odmlib/schemas/odm/2.0/ODM.xsd(targetnamespace
http://www.cdisc.org/ns/odm/v2.0) and exposes the samevalidate_tree()/validate_file()API used for ODM 1.3.2 andDefine-XML.
ODM.xsd+ODM-foundation.xsd+ 7 modularincludes + xlink/xml/xhtml) was already shipping in
odmlib/schemas/odm/2.0/via theschemas/**/*.xsdpackage-dataglob; the registry entry is the only missing wiring.
tests/test_schema_manager.py: 4 new tests verifyingget_schema_dir("odm", "2.0"),get_schema_path("odm", "2.0"),the resolved filename (
ODM.xsd), and the integrationfile-existence check.
tests/test_odm_validator.py: newTestODMv20Validatorclassvalidating
tests/data/odmv2_example.xmlandtests/data/cdash_demo_v20.xmlend to end, plus a regression testthat an ODM 1.3.2 document fails v2.0 validation. New
test_explicit_odm_v20_works()inTestODMValidatorConstructorContract.docs/source/guides/validation.rst: rewrote the "Schema Validation"section to document
ODMSchemaValidator(the previous textreferenced a nonexistent
SchemaManagerclass).Permissive Loading Mode
odmlib/mode.pymodule withValidationModeflag enum andpermissive()context manager for loading non-conformant ODM documentsValidationMode.STRICT(default) ? all validation enforced (existingbehavior, unchanged)
ValidationMode.SKIP_REQUIRED? omit required-attribute checks duringconstruction and access
ValidationMode.SKIP_TYPE? omit type checks (Typed, Integer, Float,ODMObject, ODMListObject, Positive, NonNegative, and unknown-attribute
rejection)
ValidationMode.SKIP_FORMAT? omit format validators (datetime, SASname/format, email, URL, filename, regex, sized string)
ValidationMode.SKIP_VALUESET? omit ValidValues andExtendedValidValues enforcement
ValidationMode.PERMISSIVE? composite flag that skips all validationcategories
permissive()context manager with automatic cleanup viacontextvars.ContextVar; supports graduated control via flagcombinations
open_odm()andopen_define()context managers acceptpermissive=Trueor a specificValidationModecombinationValidationMode,permissive,get_mode,set_modeexported fromodmlibpackage rootdocs/source/guides/permissive_loading.rsttests/test_permissive_mode.py? 70 tests covering all validationcategories, context manager safety, integration with loaders, and the
load-fix-validate workflow
Error Reporting and Diagnostics
odmlib/exceptions.pymodule with a structured exception hierarchyOdmlibError? base class for all odmlib exceptionsOdmlibValidationError? replaces bareValueErrorfor validation failures; includeselement_path,hint,attribute,element_type, andactual_valueattributesOdmlibRequiredAttributeError? raised when a required attribute is missing at constructionOdmlibOIDError? raised for OID uniqueness or ref/def integrity failuresOdmlibConformanceError? raised when Cerberus conformance validation fails; exposesraw
cerberus_errorsdict for programmatic inspectionOdmlibElementOrderError? raised when child elements violate ODM-spec orderingOdmlibTypeError? replaces bareTypeErrorfor type/enum validation failuresOdmlibParsingError? raised when an XML or JSON document cannot be parsedOdmlibLoaderStateError? raised when a loader method is called before the document is openedOdmlibSerializationError? raised when the model cannot be serialized to XML or JSONOdmlibNamespaceError? raised for namespace registration or lookup failuresOdmlibWarning,OdmlibDeprecationWarning,OdmlibInteroperabilityWarning? warning hierarchyErrorCollector? accumulates validation errors instead of raising on the first failure;has_errors,add_error(),add_warning(),raise_if_errors()APIODMElement.validate()method ? unified validation entry point supporting bothfail-fast (default) and collect-all-errors (
collect_errors=True) modesErrorCollectorexported fromodmlibpackage roottests/test_exceptions.py? 47 tests covering hierarchy, formatting, backward compat, and model integrationtests/test_collect_errors.py? 10 tests covering fail-fast and collect-all-errors validation modesDataset-JSON v1.1 ODMElement Model
odmlib/dataset_json_1_1/package ? spec-conformant Dataset-JSON v1.1 support usingthe ODMElement/descriptor pattern (one dataset per file, matching the v1.1 specification)
DatasetJSON? root element withto_json(),from_json(),write_json(),read_json(),write_ndjson(),read_ndjson(),to_dict(),from_dict(),add_row(),add_column(),column_namespropertyColumn? column metadata with validateddataTypeand optionaltargetDataType,length,displayFormat,keySequenceSourceSystem? optional nested source system metadata objectDatasetJSONElementbase class disables XML serialization (to_xml()raisesNotImplementedError) and handles mixed list types into_dict()odmlib/dataset_json_1_1/define_flattener.py? converts Define-XML v2.1 metadata into11 tabular Dataset-JSON datasets (study, standards, datasets, variables, value_level,
where_clauses, methods, comments, documents, codelists, codelist_terms)
DefineFlattener.flatten_all()returns dict of dataset name ? DatasetJSONDefineFlattener.write_all(output_dir)writes individual JSON files_safe_get()utility for traversing deeply nested optional attributesodmlib/dataset_json_1_1/converter.py? bidirectional Dataset-XML ? Dataset-JSON v1.1dataset_xml_to_dataset_json(odm_obj)returns dict[str, DatasetJSON] (one per ItemGroupOID)dataset_json_to_dataset_xml(dataset_json, model)converts back to Dataset-XMLodmlib/dataframe.py? Pandas integration for the new modeldataset_json_to_dataframe()? DatasetJSON v1.1 ? DataFramedefine_metadata_to_dataframes(odm_root)? Define-XML v2.1 ? dict of DataFramesdataframe_to_dataset_json(df, name, label, oid)? DataFrame ? DatasetJSON v1.1DatasetJSON,Column,SourceSystem,DefineFlattener,dataset_xml_to_dataset_json,dataset_json_to_dataset_xmlexported fromodmlibpackage rootdocs/source/guides/dataset_json.rstdocs/source/odmlib.dataset_json_1_1.rsttests/test_dataset_json_1_1_model.py(68 tests),tests/test_define_flattener.py(45 tests),tests/test_dataset_json_1_1_converter.py(21 tests),tests/test_dataframe_phase3.py(39 tests)Interoperability and Format Support
odmlib/dataframe.py? optional Pandas DataFrame integrationmetadata_to_dataframe(mdv, element_type, attributes=None)? exports odmlib metadataelements (ItemDef, ItemGroupDef, CodeList, etc.) as a DataFrame
clinical_data_to_dataframe(clinical_data, item_group_oid)? flattens ODM 1.3.2hierarchical clinical data (SubjectData ? StudyEventData ? FormData ? ItemGroupData)
into a tabular DataFrame
dataset_to_dataframe(clinical_data)? flattens Dataset-XML 1.0.1 ClinicalData(flat structure, no SubjectData) into a DataFrame
dataframe_to_items(df, model_module, element_type, column_mapping=None)? createsodmlib element instances from a DataFrame (one row per element); skips invalid rows
raises
ImportErrorwith install hint when pandas is absentdataframeadded topyproject.toml:pip install odmlib[dataframe]installs pandas ? 1.5devdependency group so the full test suiteruns against pandas in CI development environments
tests/test_dataframe.py? tests for DataFrame integration(skipped automatically when pandas is not installed)
docs/source/guides/interoperability.rstodmlib.dataframeadded to Sphinxindex.rstPackaging Modernization
pyproject.tomlfor modern Python packaging (replacessetup.pyas primary config)CHANGELOG.mdfor tracking changes going forwardChanged
Exception Hierarchy: OdmlibSchemaValidationError
OdmlibSchemaValidationErrornow inherits fromOdmlibValidationError(and therefore from
OdmlibError). Previously it inherited only fromExceptionand was excluded from the unified hierarchy. A singleexcept OdmlibValidationErrornow catches both XSD violations raised byODMSchemaValidator.validate_file()and in-memory model validationfailures (required-attr, OID, conformance, element order).
odmlib/odm_parser.pytoodmlib/exceptions.py.from odmlib.odm_parser import OdmlibSchemaValidationErrorcontinues towork via re-export ? no migration required for existing callers.
from odmlib import OdmlibSchemaValidationError.ex.args[0]still returns the wrappedxmlschemaexception, so callers usingex.args[0].msgare unaffected.The wrapped exception is also accessible via the new
ex.wrappedattribute.
Minimum Python Version
reached end-of-life in October 2025 and is no longer tested in CI.
Users on 3.9 should upgrade; install requires
requires-python = ">=3.10".fail-fast: false(all matrix versions reportindependently), action versions bumped to Node 24-compatible releases
(
actions/checkout@v5,actions/setup-python@v6,peaceiris/actions-gh-pages@v4), least-privilegeGITHUB_TOKENpermissions, and a concurrency group so superseded runs on the same
ref are cancelled.
Permissive Loading Mode
odmlib/descriptor.py:Descriptor.__get__returnsNonefor unsetrequired attributes when
SKIP_REQUIREDmode is active (previouslyalways raised
OdmlibRequiredAttributeError)odmlib/odm_element.py:ODMElement.__init__and__setattr__bypass unknown-attribute rejection and required-attribute enforcement
when appropriate mode flags are active
odmlib/typed.py: all 25__set__methods check the currentValidationModebefore raising validation exceptionsodmlib/context.py:open_odm()andopen_define()accept a newpermissiveparameter;ODMContextandDefineContextmanage modelifecycle in
__enter__/__exit__Structured odmlib Exceptions
ValueError/TypeErrorraises across 16 files replaced with structured odmlib exceptionsodmlib/odm_element.py:verify_order()now raisesOdmlibElementOrderErrorwith a hint to usereorder_object();reorder_object()issues anOdmlibWarningbefore silently reorderingodmlib/odm_1_3_2,odmlib/define_2_0,odmlib/define_2_1conformance checkers now raiseOdmlibConformanceErrorwith structuredcerberus_errorsattribute instead ofValueError(dict)Dynamic OID Ref/Def Generation
odmlib/oid_generator.pymodule with fully dynamic OID ref/def checking derivedfrom model class introspection ? eliminates manual maintenance of
oid_ref.pyfilesDynamicOIDRefclass ? drop-in replacement for the manualOIDRefclasses with thesame
add_oid(),add_oid_ref(),check_oid_refs(), andcheck_unreferenced_oids()APIcreate_oid_checker(model_package, extra_skip_attrs=None, extra_skip_elems=None)factoryfunction ? primary public API for creating OID checkers; exported from
odmlibpackage rootodmlib/oid_generator_config.py? per-model skip-attribute and skip-element configurationcreate_oid_checker("odm_2_0")OIDRefclasses inrules/oid_ref.pyfor all three model packages(
odm_1_3_2,define_2_0,define_2_1) now emitOdmlibDeprecationWarningoninstantiation; they remain functional and will be removed in v0.3.0
tests/test_oid_generator.py? 57 new tests covering model introspection, mappingcorrectness, DynamicOIDRef behavior, end-to-end validation, and deprecation warnings
ARM 1.0 Model Support
odmlib/arm_1_0/package ? CDISC Analysis Results Metadata (ARM) v1.0 model usingthe ODMElement/descriptor pattern
AnalysisResultDisplays,ResultDisplay,AnalysisResult,AnalysisDatasets,AnalysisDataset,AnalysisVariable,ProgrammingCode,Code,Documentation,AnalysisDocumentation, and supporting elementsarm) registered automatically on importValueset Regex Validation
odmlib/valueset.py:ValueSet.validate(value)? validates a value against thevalueset's allowed values, including regex pattern matching for string-type entries
odmlib/valueset.py:ValueSet.describe()? returns a human-readable descriptionof the valid values for error messages
odmlib/data/valuesets.json:MetaDataVersion.DefineVersionconverted from enumeratedlist to regex pattern
^2\.[01](\.\d+)?$for flexible version matchingtests/test_valueset.py? 30 tests for regex validation and describe functionalityElement Search Methods
ODMElement.find_all(element_type, attribute, value)? find all matching child elementsin a list attribute
ODMElement.find_by(**kwargs)? find a child element matching multiple attribute criteriaODMBuilder Fluent API
odmlib/builder.py?ODMBuilderclass providing a fluent/chained API for buildingODM documents programmatically with
add_study(),add_metadata_version(),add_item_group_def(),add_item_def(),add_code_list(), andbuild()methodsUpdated Packaging
0.2.0acrosspyproject.toml,odmlib/__init__.py, anddocs/source/conf.pyodmlib/__version__now read dynamically from installed package metadata viaimportlib.metadatapip install -e ".[dev]"replacespython setup.py developpip install -e .replacespython setup.py installrequirements.txtaligned to matchpyproject.tomldependency minimum versionsODMSchemaValidatorrequires an explicit schema choiceodmlib/odm_parser.py:ODMSchemaValidator.__init__no longer silentlydefaults to
standard="odm",version="1.3.2". The signature is now(xsd_file=None, standard: Optional[str] = None, version: Optional[str] = None).Callers must provide either an
xsd_filepath, or bothstandardandversion; otherwise aValueErroris raised at construction time. Thesilent ODM 1.3.2 fallback was a footgun ? for example, a Define-XML 2.1
document could be validated against the ODM 1.3.2 schema without any
warning. Existing call sites that already pass
standard=andversion=explicitly are unaffected. The
ValueErrorhint explicitly points userswith custom or local schemas (anything not in
schema_manager._MAIN_SCHEMA) at thexsd_file=<path>escape hatch, sothey don't accidentally fall back to a packaged schema lookup that
doesn't apply to them.
tests/test_odm_validator.py: newTestODMValidatorConstructorContractclass with 8 tests locking in the new error contract (no-args raises,
hint mentions
xsd_file=, partial-args raises, both-args works, xsd_fileworks, xsd_file precedence, custom out-of-tree xsd works).
Schema-Ordered Child Serialization in
to_xml()odmlib/odm_element.py:ODMElement.to_xml()now emits child elementsin model declaration order (driven by
_elems) instead of attributeinsertion order (
self.__dict__). Previously, when a user assignedchild attributes in an order that diverged from the schema declaration
? for example mutating
igd.Description.TranslatedTextafter thelist-typed
ItemRefhad already been pre-populated by__init__, orassigning
igd.Classlast ? the saved XML put<Description>afterthe
<ItemRef>block and<def:Class>after<ItemRef>but before<def:leaf>only by coincidence of the__dict__ordering. Define-XML2.1 XSD validation rejected such files.
verify_order()andreorder_object()alreadytrusted: declaration order from the class body, captured by
ODMMetainto
_elems. Users no longer need to callreorder_object()beforeserializing ? assignment order is fully decoupled from emission order.
tests/test_schema_ordered_serialization.py? 7 tests pinning the newbehaviour: ItemDef/ItemGroupDef children come out in schema order
regardless of assignment order; the Define-XML 2.1 ItemGroupDef pattern
from
notebooks/first_define.ipynb(Description?ItemRef*?def:Class?def:leaf) is locked in; unset optional children aresilently skipped; attribute serialization and
_contentemission areunchanged.
Fixed
XML Loader Namespace Handling
XMLDefineLoadernamespace mismatch: the defaultns_uriwas set to a Define-XML 2.0 default regardless ofmodel_package,causing
def:-namespaced child ofMetaDataVersion(
Standard,CommentDef,ValueListDef,WhereClauseDef,leaf,?) to be dropped when loading Define-XML 2.1 documents (when no ns_uri was provided).
The
ns_uriargument is nowOptional[str]and, when omitted, derivedfrom
model_package(define_2_0??/v2.0,define_2_1??/v2.1).Explicit values still override the derived default.
XMLODMLoaderns_uriparameter was dead code: the constructoraccepted an
ns_uriargument but_set_namespaceused thedefault ODM 1.3 URI. The parameter is now stored on the loader and used by
_set_namespace; default is derived frommodel_package(odm_1_3_2?
?/v1.3,odm_2_0??/v2.0).XMLArmLoader._set_registrydocumented in-place: ARM 1.0 isintentionally paired with ODM 1.3 and Define-XML 2.1, no behavior
change.
tests/test_loader_ns_defaults.py? 12 testscovering Define 2.0/2.1 derivation, ODM 1.3.2/2.0 derivation, explicit
override behavior, fallback for unknown
model_package, and theend-to-end Define-XML 2.1 OID-index regression that reproduced the
original bug report.
XMLODMLoader.__init__no longer mutates the globalNamespaceRegistryBorg singleton at construction time. Pre-fix,__init__unconditionally called_set_namespace(None), whichregistered
odm ? self.nos_uriimmediately. When users passed anon-canonical wrapper URI (e.g.
library-xml/v1.0for the CDISCLibrary CDASH endpoint), the canonical
odm ? http://www.cdisc.org/ns/odm/v1.3mapping was silently overwritten in the global Borg, breaking any code
in the same process that depended on it.
XMLODMLoader.__init__nowmirrors
XMLDefineLoader.__init__: it storesself.ns_uribut assignsan empty
NamespaceRegistry()view toself.nsrand defers the actualprefix registration to
create_document/create_document_from_string.create_documentwas also updated tocall
_set_namespace(namespace_registry)unconditionally so that thedeferred registration still happens when no caller-supplied registry
is provided. The
nsr=constructor argument continues to be honoredimmediately.
tests/test_loader_ns_defaults.py: addedTestODMLoaderConstructionDoesNotMutateBorg(4 tests) covering theno-mutation contract, the canonical-URI symmetry case, the explicit
nsr=constructor argument, and deferred-registration-at-parse-time;updated
test_odm_explicit_ns_uri_now_takes_effectto trigger thedeferred registration before asserting on
loader.nsr.Trailing Spaces Removed
odmlib/odm_1_3_2/rules/oid_ref.py_init_def_ref():"SignatureOID "corrected to"SignatureOID"and"ItemOID "corrected to"ItemOID";these would have caused silent failures in
check_unreferenced_oids()Packaging
MANIFEST.intypo:test/datacorrected totests/datasetup.py), 0.1.2 (__init__.py), 0.1.0 (docs/)odmlib.odm_2_0package in build configuration (now auto-discovered viapackages.find)Deprecation Notice
In v0.2.x, odmlib validation and type exceptions dual-inherit from
ValueError/TypeErrorso thatall existing
except ValueErrorandexcept TypeErrorclauses continue to work unchanged.rc1 - testing release. Not promoted to default
pip install odmlib.Install for soak with:
pip install --pre "odmlib==0.2.0rc1".This discussion was created from the release v0.2.0rc1.
All reactions