oscal — API reference
Version 3.2.2 · Generated 2026-09-01T02:41:26Z · 12 modules · 30 classes · 669 methods · 32 functions
oscal.oscal_support#
oscal_support — OSCAL support-file database and singleton accessor.
Manages the local SQLite database of NIST-published OSCAL support files
(metaschemas and XML/JSON schemas) for every OSCAL version and model, and
provides the ``OSCALSupport`` class along with helpers for validation and
format conversion.
Always obtain the shared instance via ``get_support()`` (optionally configured
first with ``configure_support()`` / ``setup_support()``). This ensures a single
support instance is shared across the application. The OSCAL content classes rely
on this instance to perform validation and conversion.
More information: https://github.com/brian-ruf/oscal-class/blob/main/docs/SUPPORT_MODULE.md
Module constants:
SUPPORT_DATABASE_DEFAULT_FILE (str): Default path to the support DB
(``"./support/oscal_support.db"``), resolved relative to the runtime CWD.
SUPPORT_DATABASE_DEFAULT_TYPE (str): Default database backend (``"sqlite3"``).
COMPRESS_SUPPORT_FILES_IN_DATABASE (bool): Whether support files are stored
compressed in the database.
OSCAL_DEFAULT_XML_NAMESPACE (str): The NIST OSCAL XML namespace URI.
NIST_OSCAL_EXTENSION_NAMESPACE (str): The NIST OSCAL property/extension namespace URI.
NIST_RMF_EXTENSION_NAMESPACE (str): The NIST RMF extension namespace URI.
OSCAL_FORMATS (list): Supported serialization formats
(``["xml", "json", "yaml", "yml"]``).
DEFAULT_EXCLUDE_VERSIONS (list): OSCAL release tags excluded by default
(release candidates and milestones).
METASCHEMA_MIN_VERSION (str): Earliest version with NIST-published resolved
metaschema files (``"v1.1.1"``).
METASCHEMA_INDEX_VERSION (str): Semantic version of the metaschema index schema
(the parsed ``"processed"`` asset structure). Stamped onto every index built by
this library and recorded per OSCAL version in ``oscal_versions.index_version``;
used to detect/reconcile index-schema mismatches between a library and a shared
support database. Current: ``"1.0.0"``.
INDEX_REFRESH (int): Seconds before a cached metaschema index entry is stale
(86400 = 24 hours).
METASCHEMA_FILE_PATTERNS (dict): Filename-suffix → support-type map for
metaschema files.
SCHEMA_FILE_PATTERNS (dict): Filename-suffix → support-type map for XML/JSON
schema files.
OSCAL_SUPPORT_TABLES (dict): Schema definitions for the support database tables
(``oscal_versions``, ``oscal_support``, ``filecache``).
class OSCALSupport#
Access layer for the local OSCAL support-file database.
Manages a SQLite database of NIST-published support files (metaschemas and
XML/JSON schemas) for every OSCAL version and model, and exposes methods for
querying supported versions/models, retrieving assets, building metaschema
indexes, and updating content from NIST's GitHub releases.
The ``datatypes`` attribute holds the OSCAL Metaschema data type definitions
and their validation regexes (see also :meth:`get_datatype`), supporting
field-level input validation.
Prefer the module-level ``get_support()`` accessor over instantiating this
class directly, so a single instance is shared across the application.
Note:
``OSCAL_support`` is a backward-compatible alias for this class.
method
def __init__(self, db_conn='./support/oscal_support.db', db_type='sqlite3', db_init_mode='auto', db_compress_files=True)#
Initialize OSCAL support and run startup (table checks / population).
Args:
db_conn (str, optional): Database connection string or file path.
Defaults to ``SUPPORT_DATABASE_DEFAULT_FILE``.
db_type (str, optional): Database backend type (e.g. "sqlite3").
Defaults to ``SUPPORT_DATABASE_DEFAULT_TYPE``.
db_init_mode (str, optional): Database initialization mode. Defaults to "auto".
- "auto": Extract from packaged resources if the file is missing/empty,
otherwise use the existing file.
- "extract": Always try to extract from resources; create empty if that fails.
- "create": Always create an empty database from scratch.
db_compress_files (bool, optional): Whether to store support files
compressed in the database. Defaults to
``COMPRESS_SUPPORT_FILES_IN_DATABASE``.
method
def add_asset(self, oscal_version, model_name, asset_type, content, filename=None)#
Add an asset to the support database. If the asset already exists, it will be replaced.
This method supports both string and bytes content types.
If the content is a string, it will be converted to bytes.
If the content is already in bytes, it will be used as is.
Args:
oscal_version (str, required): The OSCAL version (e.g. "v1.0.0").
model_name (str, required): The OSCAL model name (e.g. "system-security-plan").
asset_type (str, required): The asset type (e.g. "xml-schema", "json-schema").
content (str | bytes, required): The asset content; strings are encoded
to UTF-8 bytes.
filename (str, optional): Filename to record for the cached asset.
Defaults to ``"{model_name}_{asset_type}"``.
Returns:
bool: True if the asset was added successfully, False otherwise.
method
def asset(self, oscal_version, model_name, asset_type)#
Backward-compatible wrapper for :meth:`get_asset`.
Args:
oscal_version (str, required): The OSCAL version (e.g. "v1.0.0").
model_name (str, required): The OSCAL model name (e.g. "system-security-plan").
asset_type (str, required): The asset type (e.g. "xml-schema", "json-schema").
Returns:
Any: The asset content if found, otherwise None.
method
def download_schemas(self, support_dir: 'str', fetch: 'str' = 'all') -> 'bool'#
Download XML and JSON schema files to the filesystem.
Files are written to ``{support_dir}/{version}_schemas/`` directories and
are not stored in the support database.
Args:
support_dir: Root directory under which per-version schema folders are created.
fetch: ``"all"`` to download every known version, or a specific version
tag (e.g. ``"v1.2.2"``) to download only that version.
Returns:
True if all files were saved without error, False otherwise.
method
def ensure_version(self, version: 'str') -> 'tuple[Optional[str], str]'#
Make support for OSCAL *version* available, acquiring or substituting it.
Invoked when content declares an OSCAL version not present in the local support
database. Resolution order:
1. Already present locally → return it (``"exact"``).
2. Merge that version from the library's bundled database, if present there
(offline; logged INFO).
3. Otherwise fetch it from the NIST OSCAL GitHub repository (logged INFO on success).
4. If it still cannot be obtained → substitute the closest available version within
the same OSCAL major (logged WARN; ``"closest-match"``).
5. If nothing usable exists → ``"unavailable"`` (logged ERROR).
Args:
version (str, required): The requested OSCAL version tag (e.g. ``"v1.2.3"``).
Returns:
tuple[str | None, str]: ``(resolved_version, outcome)`` where ``outcome`` is
``"exact"``, ``"closest-match"``, or ``"unavailable"``. ``resolved_version``
is None only when ``outcome`` is ``"unavailable"``.
method
def enumerate_models(self, version: 'str' = 'all') -> 'list[str]'#
Backward-compatible wrapper for :meth:`list_models`.
Args:
version (str, optional): The OSCAL version to enumerate models for, or
"all". Defaults to "all".
Returns:
list[str]: Supported model-name strings (may be empty).
method
def export_support_files(self, export_path='./support_files')#
Export all cached support files to a directory tree, grouped by version.
Args:
export_path (str, optional): The directory to export support files to.
Defaults to "./support_files".
Returns:
bool: True if the export was successful, False otherwise.
method
def get_asset(self, version, model, asset_type)#
Returns the asset for the specified OSCAL version and model name.
Args:
version (str): The OSCAL version (e.g., "v1.0.0").
model (str): The OSCAL model name (e.g., "system-security-plan").
asset_type (str): The type of asset to retrieve (e.g., "xml-schema", "json-schema").
Returns:
The asset content if found, None otherwise.
method
def get_datatype(self, datatype_name: 'str') -> 'dict | None'#
Return the OSCAL Metaschema definition for a named data type.
Provides the datatype's validation patterns (``xml-pattern``,
``json-pattern``, ``recommended-pattern``), ``base-type``, documentation,
and reference links — e.g. so a UI can validate a field's input against the
regex for that field's declared OSCAL data type. The full table is also
available as the ``datatypes`` attribute.
Args:
datatype_name (str, required): OSCAL data type name (e.g. "uuid",
"date-time-with-timezone", "token").
Returns:
dict | None: A safe copy of the datatype definition, or None if the
name is not a recognized OSCAL data type.
method
def get_latest_version(self)#
Backward-compatible wrapper for :meth:`latest_version`.
Returns:
Optional[str]: The latest OSCAL version tag, or None if none are loaded.
method
def get_metaschema_index(self, version: 'str', model: 'str', index_version: 'str | None' = None) -> 'dict | None'#
Return the parsed metaschema index dict for the given OSCAL version and model.
Results are held in the module-level ``_metaschema_index_cache`` so that
only one copy of each index lives in memory and survives across calls.
A cached entry is reused until it is older than :data:`INDEX_REFRESH`
seconds (24 hours), at which point it is refreshed from the database.
The index is identified by ``(version, model, index_version)``: a stored index
whose ``index_version`` has a **different major** than the requested one is
incompatible with this library and is rebuilt from the raw metaschema (stamping
the current :data:`METASCHEMA_INDEX_VERSION`); a same-major difference is trusted
as backward compatible and used as-is.
Args:
version: OSCAL version string, e.g. ``"v1.1.3"``.
model: OSCAL model name, e.g. ``"catalog"``.
index_version: Metaschema-index-schema version to require. Defaults to this
instance's :attr:`active_index_version` (resolved at startup).
Returns:
The model-specific index dict on success, or ``None`` when the index
is unavailable.
method
def is_model_valid(self, model_name, version='all') -> 'bool'#
Backward-compatible wrapper for :meth:`is_valid_model`.
Args:
model_name (str, required): The OSCAL model name to check.
version (str, optional): The OSCAL version to check against, or "all".
Defaults to "all".
Returns:
bool: True if the model is valid for the version, False otherwise.
method
def is_valid_model(self, model, version='all') -> 'bool'#
Check if the specified OSCAL model is valid for the given version.
Args:
model (str): The OSCAL model name to check (e.g., "system-security-plan").
version (str): The OSCAL version to check against (e.g., "v1.0.0").
Returns:
bool: True if the model is valid for the specified version, False otherwise.
method
def is_valid_version(self, version) -> 'bool'#
Check if the specified OSCAL version is valid and supported.
Args:
version (str): The OSCAL version to check (e.g., "v1.0.0").
Returns:
bool: True if the version is valid and supported, False otherwise.
method
def latest_version(self)#
Return the latest supported OSCAL version.
Returns:
Optional[str]: The highest OSCAL version tag available in the support
database, or None if none are loaded.
method
def list_models(self, version: 'str' = 'all') -> 'list[str]'#
Enumerate the supported models for a given OSCAL version.
Args:
version (str): The OSCAL version to enumerate models for (e.g., "v1.0.0").
Returns:
list[str]: A list of model-name strings supported for the specified OSCAL version
(may be empty).
method
def load_file(self, name, binary=False, *, as_bytes=None)#
Load a file bundled in the ``oscal.data`` package resources, with caching.
Args:
name (str, required): Filename of the resource within ``oscal.data``.
binary (bool, optional): If True, return raw bytes; otherwise return
UTF-8 decoded text. Defaults to False.
as_bytes (bool, optional): Keyword-only alias for ``binary``; overrides
it when provided. Defaults to None.
Returns:
str | bytes | None: The file contents (text or bytes), or None on failure.
method
def remove_asset(self, version: 'str | None' = None, model: 'str | None' = None, asset_type: 'str | None' = None) -> 'int'#
Remove support assets matching any combination of version / model / asset_type.
At least one of the three criteria must be supplied; the criteria are ANDed. Every
matching ``oscal_support`` row is deleted, and each cached file it referenced is
deleted from ``filecache`` **once it is no longer referenced by any surviving
asset row** — a single cached file can back more than one asset row (e.g. a
document model's ``metaschema`` and ``document-model`` rows share one
``filecache_uuid``), so orphan-checking prevents deleting a file another row still
needs.
Args:
version (str | None, optional): OSCAL version tag (e.g. ``"v1.2.3"``).
model (str | None, optional): Model name (e.g. ``"catalog"``).
asset_type (str | None, optional): Asset type (e.g. ``"metaschema"``,
``"document-model"``, ``"processed"``).
Returns:
int: The number of ``oscal_support`` rows removed (0 when nothing matched or
no criterion was supplied).
method
def remove_version(self, version: 'str') -> 'bool'#
Delete all support content for a single OSCAL version from the database.
This is a standalone deletion for withdrawing a version that no longer
belongs in the local support set (e.g. one removed upstream). Unlike
:meth:`update`, it performs no GitHub fetch — it only deletes local
content and keeps in-memory state consistent with the database.
Args:
version (str): The OSCAL version tag to remove (e.g. "v1.2.3"). The
leading "v" is required and the tag is matched case-insensitively.
Returns:
bool: True if the version was found and deleted, False if the tag was
invalid, not present, or the deletion failed.
method
def resolve_index_version(self) -> 'str'#
Select the metaschema-index-schema version this instance will use.
Reads the distinct ``index_version`` values recorded in ``oscal_versions`` and keeps
those in the compatible range ``[METASCHEMA_INDEX_VERSION, next-major)``. When at
least one qualifies, the **lowest** in range is chosen (the most conservative
compatible schema) and assigned to :attr:`active_index_version`. When none qualify,
the library's bundled database is merged into the local one (which supplies indexes
built at this library's index version) and the resolution is retried once. Returns
the resolved value.
method
def set_version_index_version(self, version: 'str', index_version: 'str' = '1.0.0') -> 'bool'#
Record the metaschema-index-schema version used to build *version*'s indexes.
Writes ``index_version`` into the version's ``oscal_versions`` row and updates the
in-memory registry. Called after a version's ``"processed"`` indexes are (re)built
so the database records which index schema they conform to.
Args:
version (str, required): OSCAL version tag (e.g. ``"v1.2.3"``).
index_version (str, optional): Index-schema version; defaults to the library's
current :data:`METASCHEMA_INDEX_VERSION`.
Returns:
bool: True on success.
method
def startup(self, check_for_updates=False, refresh_all=False)#
Perform startup tasks required to provide OSCAL support.
Ensures the support database has the required tables and data, populating
it from NIST's GitHub releases when empty, and sets ``self.ready``.
Args:
check_for_updates (bool, optional): Reserved flag to check for newer
OSCAL versions during startup. Defaults to False.
refresh_all (bool, optional): Reserved flag to force a full refresh of
all support content. Defaults to False.
Returns:
bool: True if the support capability is ready, False otherwise.
Process:
1 Check for tables
- If tables do not exist:
- create tables
- set state to "empty"
- If tables exist, check for data
- If no data, set state to "empty"
- If data exists, set state to "populated"
2 If state is "empty", check for connection to GitHub
- If cannot connect to GitHub, EXIT (cannot proceed)
- If connected to GitHub, update database
- If update fails, EXIT (cannot proceed)
- If update succeeds, set state to "populated"
3 If state is "populated" set self.ready to True
method
def supported(self, oscal_version, assets)#
Check whether the specified OSCAL version and assets are supported.
Note:
Currently not implemented; always returns False.
Args:
oscal_version (str, required): The OSCAL version to check (e.g. "v1.0.0").
assets (list, required): The asset types to check for.
Returns:
bool: True if the version and assets are supported, False otherwise.
method
def update(self, mode='new', fetch=None, save_to_fs=False)#
Update OSCAL support content based on a fetch directive.
Args:
mode (str, optional): The fetch directive. Defaults to "new".
- "all": Clear and re-fetch all OSCAL versions and support files.
- "latest"/"new": Check for new OSCAL versions and fetch any found.
- "vX.Y.Z": Clear and re-fetch a specific OSCAL version.
fetch (str, optional): Legacy alias for ``mode``; when provided it
overrides ``mode``. Defaults to None.
save_to_fs (bool, optional): When True, also emit the parsed
metaschema index files to the local file system in addition to
updating the database. When False (default), only the database
is updated. Defaults to False.
Returns:
bool: True if the update was successful, False otherwise.
method
def vacuum(self) -> 'None'#
Reclaim free space in the support database (SQLite ``VACUUM``).
A public wrapper over the internal VACUUM; a no-op on non-SQLite backends. Useful
after a bulk :meth:`remove_asset` to shrink the database file on disk.
method
def view_detail(self, version: 'str', model: 'str', format: 'str', reference_uuid: 'str') -> 'str'#
Return an HTML ``<div>`` detail view of a single metaschema node.
Given a node's reference id (as produced by :meth:`view_outline`), returns its
formal name and description, a format-appropriate representation, data type and
regex (where available), constraints, and its immediate parent and children —
each parent/child clickable by its own reference id.
Args:
version (str, required): OSCAL version, e.g. ``"v1.1.3"``.
model (str, required): OSCAL model name, e.g. ``"catalog"``.
format (str, required): ``"xml"``, ``"json"``, or ``"yaml"``.
reference_uuid (str, required): The node reference id to describe.
Returns:
str: A detail ``<div>`` fragment, or a ``<div class="ms-error">`` when the
model/version/format or reference is unknown.
method
def view_outline(self, version: 'str', model: 'str', format: 'str') -> 'str'#
Return an HTML ``<div>`` outline of a model's metaschema structure.
The outline is a clickable tree rendered in the requested format's syntax
(``"xml"``, ``"json"``, or ``"yaml"``), annotated with data types and
cardinality. Each element links to its node by a stable reference id, for use
with :meth:`view_detail`. Intended for a front-end: the HTML is a fragment
(wrapped in a ``<div>``), never a full page.
Args:
version (str, required): OSCAL version, e.g. ``"v1.1.3"``.
model (str, required): OSCAL model name, e.g. ``"catalog"``.
format (str, required): ``"xml"``, ``"json"``, or ``"yaml"``.
Returns:
str: An outline ``<div>`` fragment, or a ``<div class="ms-error">`` when
the model/version/format is unknown or the index is unavailable.
class OSCAL_support#
Access layer for the local OSCAL support-file database.
Manages a SQLite database of NIST-published support files (metaschemas and
XML/JSON schemas) for every OSCAL version and model, and exposes methods for
querying supported versions/models, retrieving assets, building metaschema
indexes, and updating content from NIST's GitHub releases.
The ``datatypes`` attribute holds the OSCAL Metaschema data type definitions
and their validation regexes (see also :meth:`get_datatype`), supporting
field-level input validation.
Prefer the module-level ``get_support()`` accessor over instantiating this
class directly, so a single instance is shared across the application.
Note:
``OSCAL_support`` is a backward-compatible alias for this class.
method
def __init__(self, db_conn='./support/oscal_support.db', db_type='sqlite3', db_init_mode='auto', db_compress_files=True)#
Initialize OSCAL support and run startup (table checks / population).
Args:
db_conn (str, optional): Database connection string or file path.
Defaults to ``SUPPORT_DATABASE_DEFAULT_FILE``.
db_type (str, optional): Database backend type (e.g. "sqlite3").
Defaults to ``SUPPORT_DATABASE_DEFAULT_TYPE``.
db_init_mode (str, optional): Database initialization mode. Defaults to "auto".
- "auto": Extract from packaged resources if the file is missing/empty,
otherwise use the existing file.
- "extract": Always try to extract from resources; create empty if that fails.
- "create": Always create an empty database from scratch.
db_compress_files (bool, optional): Whether to store support files
compressed in the database. Defaults to
``COMPRESS_SUPPORT_FILES_IN_DATABASE``.
method
def add_asset(self, oscal_version, model_name, asset_type, content, filename=None)#
Add an asset to the support database. If the asset already exists, it will be replaced.
This method supports both string and bytes content types.
If the content is a string, it will be converted to bytes.
If the content is already in bytes, it will be used as is.
Args:
oscal_version (str, required): The OSCAL version (e.g. "v1.0.0").
model_name (str, required): The OSCAL model name (e.g. "system-security-plan").
asset_type (str, required): The asset type (e.g. "xml-schema", "json-schema").
content (str | bytes, required): The asset content; strings are encoded
to UTF-8 bytes.
filename (str, optional): Filename to record for the cached asset.
Defaults to ``"{model_name}_{asset_type}"``.
Returns:
bool: True if the asset was added successfully, False otherwise.
method
def asset(self, oscal_version, model_name, asset_type)#
Backward-compatible wrapper for :meth:`get_asset`.
Args:
oscal_version (str, required): The OSCAL version (e.g. "v1.0.0").
model_name (str, required): The OSCAL model name (e.g. "system-security-plan").
asset_type (str, required): The asset type (e.g. "xml-schema", "json-schema").
Returns:
Any: The asset content if found, otherwise None.
method
def download_schemas(self, support_dir: 'str', fetch: 'str' = 'all') -> 'bool'#
Download XML and JSON schema files to the filesystem.
Files are written to ``{support_dir}/{version}_schemas/`` directories and
are not stored in the support database.
Args:
support_dir: Root directory under which per-version schema folders are created.
fetch: ``"all"`` to download every known version, or a specific version
tag (e.g. ``"v1.2.2"``) to download only that version.
Returns:
True if all files were saved without error, False otherwise.
method
def ensure_version(self, version: 'str') -> 'tuple[Optional[str], str]'#
Make support for OSCAL *version* available, acquiring or substituting it.
Invoked when content declares an OSCAL version not present in the local support
database. Resolution order:
1. Already present locally → return it (``"exact"``).
2. Merge that version from the library's bundled database, if present there
(offline; logged INFO).
3. Otherwise fetch it from the NIST OSCAL GitHub repository (logged INFO on success).
4. If it still cannot be obtained → substitute the closest available version within
the same OSCAL major (logged WARN; ``"closest-match"``).
5. If nothing usable exists → ``"unavailable"`` (logged ERROR).
Args:
version (str, required): The requested OSCAL version tag (e.g. ``"v1.2.3"``).
Returns:
tuple[str | None, str]: ``(resolved_version, outcome)`` where ``outcome`` is
``"exact"``, ``"closest-match"``, or ``"unavailable"``. ``resolved_version``
is None only when ``outcome`` is ``"unavailable"``.
method
def enumerate_models(self, version: 'str' = 'all') -> 'list[str]'#
Backward-compatible wrapper for :meth:`list_models`.
Args:
version (str, optional): The OSCAL version to enumerate models for, or
"all". Defaults to "all".
Returns:
list[str]: Supported model-name strings (may be empty).
method
def export_support_files(self, export_path='./support_files')#
Export all cached support files to a directory tree, grouped by version.
Args:
export_path (str, optional): The directory to export support files to.
Defaults to "./support_files".
Returns:
bool: True if the export was successful, False otherwise.
method
def get_asset(self, version, model, asset_type)#
Returns the asset for the specified OSCAL version and model name.
Args:
version (str): The OSCAL version (e.g., "v1.0.0").
model (str): The OSCAL model name (e.g., "system-security-plan").
asset_type (str): The type of asset to retrieve (e.g., "xml-schema", "json-schema").
Returns:
The asset content if found, None otherwise.
method
def get_datatype(self, datatype_name: 'str') -> 'dict | None'#
Return the OSCAL Metaschema definition for a named data type.
Provides the datatype's validation patterns (``xml-pattern``,
``json-pattern``, ``recommended-pattern``), ``base-type``, documentation,
and reference links — e.g. so a UI can validate a field's input against the
regex for that field's declared OSCAL data type. The full table is also
available as the ``datatypes`` attribute.
Args:
datatype_name (str, required): OSCAL data type name (e.g. "uuid",
"date-time-with-timezone", "token").
Returns:
dict | None: A safe copy of the datatype definition, or None if the
name is not a recognized OSCAL data type.
method
def get_latest_version(self)#
Backward-compatible wrapper for :meth:`latest_version`.
Returns:
Optional[str]: The latest OSCAL version tag, or None if none are loaded.
method
def get_metaschema_index(self, version: 'str', model: 'str', index_version: 'str | None' = None) -> 'dict | None'#
Return the parsed metaschema index dict for the given OSCAL version and model.
Results are held in the module-level ``_metaschema_index_cache`` so that
only one copy of each index lives in memory and survives across calls.
A cached entry is reused until it is older than :data:`INDEX_REFRESH`
seconds (24 hours), at which point it is refreshed from the database.
The index is identified by ``(version, model, index_version)``: a stored index
whose ``index_version`` has a **different major** than the requested one is
incompatible with this library and is rebuilt from the raw metaschema (stamping
the current :data:`METASCHEMA_INDEX_VERSION`); a same-major difference is trusted
as backward compatible and used as-is.
Args:
version: OSCAL version string, e.g. ``"v1.1.3"``.
model: OSCAL model name, e.g. ``"catalog"``.
index_version: Metaschema-index-schema version to require. Defaults to this
instance's :attr:`active_index_version` (resolved at startup).
Returns:
The model-specific index dict on success, or ``None`` when the index
is unavailable.
method
def is_model_valid(self, model_name, version='all') -> 'bool'#
Backward-compatible wrapper for :meth:`is_valid_model`.
Args:
model_name (str, required): The OSCAL model name to check.
version (str, optional): The OSCAL version to check against, or "all".
Defaults to "all".
Returns:
bool: True if the model is valid for the version, False otherwise.
method
def is_valid_model(self, model, version='all') -> 'bool'#
Check if the specified OSCAL model is valid for the given version.
Args:
model (str): The OSCAL model name to check (e.g., "system-security-plan").
version (str): The OSCAL version to check against (e.g., "v1.0.0").
Returns:
bool: True if the model is valid for the specified version, False otherwise.
method
def is_valid_version(self, version) -> 'bool'#
Check if the specified OSCAL version is valid and supported.
Args:
version (str): The OSCAL version to check (e.g., "v1.0.0").
Returns:
bool: True if the version is valid and supported, False otherwise.
method
def latest_version(self)#
Return the latest supported OSCAL version.
Returns:
Optional[str]: The highest OSCAL version tag available in the support
database, or None if none are loaded.
method
def list_models(self, version: 'str' = 'all') -> 'list[str]'#
Enumerate the supported models for a given OSCAL version.
Args:
version (str): The OSCAL version to enumerate models for (e.g., "v1.0.0").
Returns:
list[str]: A list of model-name strings supported for the specified OSCAL version
(may be empty).
method
def load_file(self, name, binary=False, *, as_bytes=None)#
Load a file bundled in the ``oscal.data`` package resources, with caching.
Args:
name (str, required): Filename of the resource within ``oscal.data``.
binary (bool, optional): If True, return raw bytes; otherwise return
UTF-8 decoded text. Defaults to False.
as_bytes (bool, optional): Keyword-only alias for ``binary``; overrides
it when provided. Defaults to None.
Returns:
str | bytes | None: The file contents (text or bytes), or None on failure.
method
def remove_asset(self, version: 'str | None' = None, model: 'str | None' = None, asset_type: 'str | None' = None) -> 'int'#
Remove support assets matching any combination of version / model / asset_type.
At least one of the three criteria must be supplied; the criteria are ANDed. Every
matching ``oscal_support`` row is deleted, and each cached file it referenced is
deleted from ``filecache`` **once it is no longer referenced by any surviving
asset row** — a single cached file can back more than one asset row (e.g. a
document model's ``metaschema`` and ``document-model`` rows share one
``filecache_uuid``), so orphan-checking prevents deleting a file another row still
needs.
Args:
version (str | None, optional): OSCAL version tag (e.g. ``"v1.2.3"``).
model (str | None, optional): Model name (e.g. ``"catalog"``).
asset_type (str | None, optional): Asset type (e.g. ``"metaschema"``,
``"document-model"``, ``"processed"``).
Returns:
int: The number of ``oscal_support`` rows removed (0 when nothing matched or
no criterion was supplied).
method
def remove_version(self, version: 'str') -> 'bool'#
Delete all support content for a single OSCAL version from the database.
This is a standalone deletion for withdrawing a version that no longer
belongs in the local support set (e.g. one removed upstream). Unlike
:meth:`update`, it performs no GitHub fetch — it only deletes local
content and keeps in-memory state consistent with the database.
Args:
version (str): The OSCAL version tag to remove (e.g. "v1.2.3"). The
leading "v" is required and the tag is matched case-insensitively.
Returns:
bool: True if the version was found and deleted, False if the tag was
invalid, not present, or the deletion failed.
method
def resolve_index_version(self) -> 'str'#
Select the metaschema-index-schema version this instance will use.
Reads the distinct ``index_version`` values recorded in ``oscal_versions`` and keeps
those in the compatible range ``[METASCHEMA_INDEX_VERSION, next-major)``. When at
least one qualifies, the **lowest** in range is chosen (the most conservative
compatible schema) and assigned to :attr:`active_index_version`. When none qualify,
the library's bundled database is merged into the local one (which supplies indexes
built at this library's index version) and the resolution is retried once. Returns
the resolved value.
method
def set_version_index_version(self, version: 'str', index_version: 'str' = '1.0.0') -> 'bool'#
Record the metaschema-index-schema version used to build *version*'s indexes.
Writes ``index_version`` into the version's ``oscal_versions`` row and updates the
in-memory registry. Called after a version's ``"processed"`` indexes are (re)built
so the database records which index schema they conform to.
Args:
version (str, required): OSCAL version tag (e.g. ``"v1.2.3"``).
index_version (str, optional): Index-schema version; defaults to the library's
current :data:`METASCHEMA_INDEX_VERSION`.
Returns:
bool: True on success.
method
def startup(self, check_for_updates=False, refresh_all=False)#
Perform startup tasks required to provide OSCAL support.
Ensures the support database has the required tables and data, populating
it from NIST's GitHub releases when empty, and sets ``self.ready``.
Args:
check_for_updates (bool, optional): Reserved flag to check for newer
OSCAL versions during startup. Defaults to False.
refresh_all (bool, optional): Reserved flag to force a full refresh of
all support content. Defaults to False.
Returns:
bool: True if the support capability is ready, False otherwise.
Process:
1 Check for tables
- If tables do not exist:
- create tables
- set state to "empty"
- If tables exist, check for data
- If no data, set state to "empty"
- If data exists, set state to "populated"
2 If state is "empty", check for connection to GitHub
- If cannot connect to GitHub, EXIT (cannot proceed)
- If connected to GitHub, update database
- If update fails, EXIT (cannot proceed)
- If update succeeds, set state to "populated"
3 If state is "populated" set self.ready to True
method
def supported(self, oscal_version, assets)#
Check whether the specified OSCAL version and assets are supported.
Note:
Currently not implemented; always returns False.
Args:
oscal_version (str, required): The OSCAL version to check (e.g. "v1.0.0").
assets (list, required): The asset types to check for.
Returns:
bool: True if the version and assets are supported, False otherwise.
method
def update(self, mode='new', fetch=None, save_to_fs=False)#
Update OSCAL support content based on a fetch directive.
Args:
mode (str, optional): The fetch directive. Defaults to "new".
- "all": Clear and re-fetch all OSCAL versions and support files.
- "latest"/"new": Check for new OSCAL versions and fetch any found.
- "vX.Y.Z": Clear and re-fetch a specific OSCAL version.
fetch (str, optional): Legacy alias for ``mode``; when provided it
overrides ``mode``. Defaults to None.
save_to_fs (bool, optional): When True, also emit the parsed
metaschema index files to the local file system in addition to
updating the database. When False (default), only the database
is updated. Defaults to False.
Returns:
bool: True if the update was successful, False otherwise.
method
def vacuum(self) -> 'None'#
Reclaim free space in the support database (SQLite ``VACUUM``).
A public wrapper over the internal VACUUM; a no-op on non-SQLite backends. Useful
after a bulk :meth:`remove_asset` to shrink the database file on disk.
method
def view_detail(self, version: 'str', model: 'str', format: 'str', reference_uuid: 'str') -> 'str'#
Return an HTML ``<div>`` detail view of a single metaschema node.
Given a node's reference id (as produced by :meth:`view_outline`), returns its
formal name and description, a format-appropriate representation, data type and
regex (where available), constraints, and its immediate parent and children —
each parent/child clickable by its own reference id.
Args:
version (str, required): OSCAL version, e.g. ``"v1.1.3"``.
model (str, required): OSCAL model name, e.g. ``"catalog"``.
format (str, required): ``"xml"``, ``"json"``, or ``"yaml"``.
reference_uuid (str, required): The node reference id to describe.
Returns:
str: A detail ``<div>`` fragment, or a ``<div class="ms-error">`` when the
model/version/format or reference is unknown.
method
def view_outline(self, version: 'str', model: 'str', format: 'str') -> 'str'#
Return an HTML ``<div>`` outline of a model's metaschema structure.
The outline is a clickable tree rendered in the requested format's syntax
(``"xml"``, ``"json"``, or ``"yaml"``), annotated with data types and
cardinality. Each element links to its node by a stable reference id, for use
with :meth:`view_detail`. Intended for a front-end: the HTML is a fragment
(wrapped in a ``<div>``), never a full page.
Args:
version (str, required): OSCAL version, e.g. ``"v1.1.3"``.
model (str, required): OSCAL model name, e.g. ``"catalog"``.
format (str, required): ``"xml"``, ``"json"``, or ``"yaml"``.
Returns:
str: An outline ``<div>`` fragment, or a ``<div class="ms-error">`` when
the model/version/format is unknown or the index is unavailable.
function
def configure_support(support_file='./support/oscal_support.db', db_init_mode='auto', *, db_path: 'Optional[str]' = None, init_mode: 'Optional[str]' = None)#
Configure and create the shared OSCAL support instance.
Call this before ``get_support()`` and before any OSCAL content is loaded when
non-default settings are needed. If the shared instance already exists, it is
returned unchanged.
Args:
support_file (str, optional): Path to the support database file.
Defaults to ``SUPPORT_DATABASE_DEFAULT_FILE``.
db_init_mode (str, optional): Database initialization mode — ``"auto"``,
``"extract"``, or ``"create"``. Defaults to ``"auto"``.
db_path (str, optional): Keyword-only alias for ``support_file``; overrides
it when provided.
init_mode (str, optional): Keyword-only alias for ``db_init_mode``;
overrides it when provided.
Returns:
OSCALSupport: The shared support instance.
function
def get_support()#
Return the shared OSCAL support instance, creating it if necessary.
Creates the instance with default settings (via ``configure_support()``) if it
does not already exist.
Returns:
OSCALSupport: The shared support instance.
function
def setup_support(support_file='./support/oscal_support.db', db_init_mode='auto')#
Compatibility wrapper around ``configure_support()`` for update utility scripts.
Args:
support_file (str, optional): Path to the support database file.
Defaults to ``SUPPORT_DATABASE_DEFAULT_FILE``.
db_init_mode (str, optional): Database initialization mode
(``"auto"``, ``"extract"``, or ``"create"``). Defaults to ``"auto"``.
Returns:
OSCALSupport: The shared support instance.
oscal.oscal_helpers#
oscal_helpers — model-agnostic helper functions for OSCAL JSON content.
Pure, stateless helpers that operate on plain OSCAL JSON dicts (and markup
text) without needing an ``OSCAL`` instance. Extracted from ``oscal_content``
to keep that module focused on the ``OSCAL`` base class and its content
lifecycle. Nothing here imports ``oscal_content``; ``oscal_content`` imports and
re-exports these names, so existing ``from .oscal_content import ...`` call sites
continue to work unchanged.
Contents:
new_uuid / _is_valid_uuid — UUID generation and validation.
prune_tree_copy — depth-limited safe copy of node subtrees.
_collect_ids / _find_part_by_id / _find_model_element
— id lookups over catalog-shaped dicts.
append_prop(s) / get_props — prop read/write helpers.
append_link(s) — link write helpers.
oscal_markdown_to_html_tree / _format_table_helper
— OSCAL markup → HTML helpers.
function
def append_link(parent_obj: 'dict', link: 'dict') -> 'dict'#
Append a single link dict to ``parent_obj["links"]``.
Args:
parent_obj (dict, required): OSCAL JSON object that will receive the link.
link (dict, required): Link dict. Required key: "href".
Optional keys: "rel", "media-type", "resource-fragment", "text".
Returns:
dict: The appended link entry (filtered to recognized keys).
function
def append_links(parent_obj: 'dict', links: 'list') -> 'None'#
Append multiple link dicts to ``parent_obj["links"]``.
Args:
parent_obj (dict, required): OSCAL JSON object that will receive the links.
links (list, required): Link dicts, each with at minimum an "href" key.
Returns:
None
function
def append_prop(parent_obj: 'dict', prop: 'dict') -> 'dict'#
Append a single prop dict to ``parent_obj["props"]``.
Args:
parent_obj (dict, required): OSCAL JSON object that will receive the prop.
prop (dict, required): Property dict. Required keys: "name", "value".
Optional keys: "uuid", "ns", "class", "group", "remarks".
Returns:
dict: The appended prop entry (filtered to recognized keys).
function
def append_props(parent_obj: 'dict', props: 'list') -> 'None'#
Append multiple prop dicts to ``parent_obj["props"]``.
Args:
parent_obj (dict, required): OSCAL JSON object that will receive the props.
props (list, required): Property dicts, each with at minimum "name" and "value".
Returns:
None
function
def get_props(parent_obj: 'dict', name: 'str | None' = None, uuid: 'str | None' = None, ns: 'str' = 'http://csrc.nist.gov/ns/oscal', class_: 'str | None' = None, group: 'str | None' = None) -> 'list'#
Retrieve matching prop dicts from ``parent_obj["props"]``.
Either ``name`` or ``uuid`` must be supplied. The return value is always a
list — empty when nothing matches (or when the required parameters are
missing) — never ``None``.
An absent ``ns`` on a prop is treated as the OSCAL default namespace
(``_OSCAL_NS``), matching the way ``ns`` defaults on this function's own
``ns`` parameter. Correct default-``ns`` handling is essential: a prop with
no ``ns`` is considered to be in the OSCAL namespace.
Matching behaviour:
* ``uuid`` supplied: every prop whose ``uuid`` equals ``uuid`` is
returned. If any descriptor parameter (``name``, a non-default ``ns``,
``class_`` or ``group``) is also supplied and does not match a returned
prop, a warning is logged, but the prop is still returned.
* ``uuid`` not supplied: ``name`` is required. Props are matched on
``name`` **and** effective ``ns``. When ``class_`` and/or ``group`` are
supplied they must also match. When ``class_``/``group`` are *not*
supplied, all name+ns matches are returned, ordered best match first:
props carrying fewer of the un-queried qualifiers (``class``/``group``)
sort ahead of more-specific props. Document order is preserved among
equally specific matches.
Args:
parent_obj (dict, required): OSCAL JSON object holding a ``props`` list.
name (str, optional): Prop ``name`` to match. Required if ``uuid`` is
not given.
uuid (str, optional): Prop ``uuid`` to match. Required if ``name`` is
not given.
ns (str, optional): Namespace to match; defaults to ``_OSCAL_NS``.
class_ (str, optional): Prop ``class`` to match. Maps to the ``"class"``
key (``class`` is a reserved word in Python).
group (str, optional): Prop ``group`` to match.
Returns:
list: Matching prop dicts (possibly empty), ordered best match first.
function
def new_uuid() -> 'str'#
Generate a new random (version 4) UUID string.
Returns:
str: A newly generated UUID in canonical string form.
function
def oscal_markdown_to_html_tree(markdown_text: 'str', multiline: 'bool' = True) -> 'Optional[ElementTree.Element]'#
Convert OSCAL markdown text to an HTML ElementTree element.
Calls ``oscal_markdown_to_html`` to format the markdown into HTML consistent
with the OSCAL XML specification for markup-line / markup-multiline, then parses
the resulting string into an XML element suitable for appending into a parent
XML object.
Args:
markdown_text (str, required): The markdown text to convert.
multiline (bool, optional): If True, handle markup-multiline (block elements);
if False, handle markup-line (inline elements only). Defaults to True.
Returns:
Optional[ElementTree.Element]: The parsed XML element, or None if conversion fails.
function
def prune_tree_copy(node: 'dict | None', depth: 'int | None' = None, child_keys: 'tuple' = ('groups', 'controls')) -> 'dict | None'#
Return a SAFE COPY of *node* with nested structural children limited to *depth*.
Shared, model-agnostic helper for the node getters (catalog/profile groups and
controls; assessment ``tasks`` once implemented). The returned value shares no
references with *node*, so callers may read, mutate, or serialize it without
affecting the source document — mutation of live content must go through the
OSCAL-standard-enforcing methods, never through a getter's return value.
Only the collections named in *child_keys* are treated as structural children
subject to depth pruning. The node's own intrinsic content (e.g. ``props``,
``links``, ``params``, ``parts``, ``title``) is always copied in full.
depth = None -> unlimited: a full deep copy of the entire subtree (the
default; mirrors the historical getter behavior).
depth = 0 -> node only: the *child_keys* collections are omitted.
depth = N -> N levels of structural children retained, each recursively
pruned at ``depth - 1``.
Args:
node (dict | None, required): The group/control/task dict to copy, or None.
depth (int | None, optional): Structural-child depth limit. Defaults to None.
child_keys (tuple, optional): Keys treated as structural children. Defaults
to ("groups", "controls"). Use ("tasks",) for assessment tasks.
Returns:
dict | None: A detached copy, or None when *node* is None.
Raises:
ValueError: If *depth* is a negative integer.
oscal.oscal_source#
oscal_source — OSCAL source acquisition and reference/import resolution.
Standalone machinery for turning a source reference (path, URI, ref dict, or
``OscalRef``) into loaded content, plus the data types used by import
resolution. None of this depends on the OSCAL model classes: the dependency runs
one way — ``OSCAL`` methods in ``oscal_content`` call into here. ``oscal_content``
imports and re-exports these names, so existing
``from .oscal_content import load_content`` (etc.) call sites keep working.
Contents:
OscalRef / _normalize_refs — source reference model + normalization.
ImportState / ImportFailureCode / ImportLoadError / ImportFailure
— import-resolution status/failure types.
load_content / load_source — fetch/read content from a reference.
classify_source — classify a reference by path/URI type.
_resolve_href / _canonicalize_ref / _oscal_format_variants
— href resolution/canonicalization helpers.
_find_import_candidates / _pick_import_target / _remove_import_from_dict
— import_list lookup and statement removal.
_hrefs_from_dict_spec / _backmatter_resource
— document href/back-matter extraction.
class ImportFailure#
Structured record of a failed import, carrying enough context for a retry attempt.
Retry sources the calling module may supply:
- A URI fragment (#uuid) pointing to a back-matter resource
- A full URI identifying an alternate location for the content
- The content itself as an XML, JSON, or YAML string
property
property is_fragment_ref#
True when the original import href is a back-matter fragment reference.
class ImportFailureCode#
Typed reason codes describing why an OSCAL import could not be resolved.
Grouped by failure category — fragment/back-matter, full-URI/file,
content, and duplicate/retry. Members:
FRAGMENT_INVALID_UUID (str): Fragment reference is not a valid UUID.
RESOURCE_NOT_FOUND (str): No back-matter resource matches the UUID.
RESOURCE_NO_VIABLE_CONTENT (str): Resource has neither rlinks nor base64 content.
LOCAL_NOT_FOUND (str): Local file was not found.
REMOTE_UNREACHABLE (str): Remote host could not be reached.
REMOTE_AUTH_REQUIRED (str): Remote resource requires authentication.
REMOTE_UNSUPPORTED (str): URI scheme is not supported.
CONTENT_EMPTY (str): Source returned no content.
CONTENT_INVALID (str): Content is not valid OSCAL.
ALREADY_IMPORTED (str): Retry href resolves to a file already loaded elsewhere.
No public members.
class ImportLoadError#
Exception carrying a typed import failure code from ``load_source()`` to ``resolve_imports()``.
Attributes:
code (ImportFailureCode): The typed reason the import failed.
uri (str): The URI that failed to load.
method
def __init__(self, code: 'ImportFailureCode', uri: 'str', message: 'str' = '')#
Initialize the error.
Args:
code (ImportFailureCode, required): The typed import failure reason.
uri (str, required): The URI that failed to load.
message (str, optional): Human-readable detail; a default is derived from
``code`` and ``uri`` when omitted.
class ImportState#
Resolution state of a single import entry in an OSCAL document's import_list.
Members:
READY (str): "ready" — content is valid and loaded.
NOT_LOADED (str): "not-loaded" — content has not been loaded.
INVALID (str): "invalid" — content could not be loaded or failed validation.
EXPIRED (str): "expired" — content is valid but the cached copy has expired.
DUPLICATE (str): "duplicate" — the resolved href is already loaded by an earlier import.
IGNORED (str): "ignored" — the caller explicitly chose to ignore this import.
CYCLIC (str): "cyclic" — this import resolves to one of its own ancestors; the
ancestor stays valid and recursion stops here to prevent an infinite loop.
No public members.
class OscalRef#
A single OSCAL source reference: an href with optional media type and hashes.
Attributes:
href (str): The reference target (URI or path).
media_type (str | None): Optional media type of the target.
hashes (list[dict] | None): Optional integrity hashes for the target.
source_type (str): Classified source type (set by classification; not an init arg).
source_scheme (str): URI scheme of the source (not an init arg).
source_supported (bool): Whether the source scheme can be fetched (not an init arg).
No public members.
function
def classify_source(ref: 'OscalRef', only_oscal: 'bool' = False) -> 'bool'#
Classify a source reference by path/URI type and Python stdlib accessibility.
Sets the ``source_type``, ``source_scheme``, and ``source_supported`` fields on
``ref`` in place. Classification intentionally does not use file extensions,
because many valid content endpoints (e.g. APIs) lack predictable suffixes.
Args:
ref (OscalRef, required): The reference to classify; mutated in place.
only_oscal (bool, optional): Reserved for future content-shape validation;
currently does not affect classification. Defaults to False.
Returns:
bool: True if the reference was classified (even if unsupported), False only
when the href is empty.
function
def load_content(source: 'str | dict | OscalRef | list', media_type: 'str' = '', only_oscal: 'bool' = False, cache_directive: "'CacheDirective | None'" = None) -> 'str'#
Load content from one or more sources and return the first successful payload.
Args:
source (str | dict | OscalRef | list, required): The source(s) to load, as a
URI/path string, reference dict, ``OscalRef``, or a fallback list of these.
media_type (str, optional): Expected media type hint. Defaults to "".
only_oscal (bool, optional): When True, restrict acceptance to OSCAL content.
Defaults to False.
cache_directive (CacheDirective | None, optional): Caching directive applied
to remote fetches. Defaults to the standard 24h behavior.
Returns:
str: The first successfully loaded content payload, or "" if none load and no
typed error was raised.
Raises:
ImportLoadError: When a source fails with a typed reason (the last error is
re-raised when every source in a list fails).
function
def load_source(ref: 'OscalRef', cache_directive: "'CacheDirective | None'" = None) -> 'str'#
Fetch or read content from a classified ``OscalRef``.
Args:
ref (OscalRef, required): A reference that has already been classified
(via :func:`classify_source`) to set its source type/scheme.
cache_directive (CacheDirective | None, optional): Caching directive applied
to remote (http/https) fetches. Defaults to the standard 24h behavior.
Returns:
str: The raw content as a string on success.
Raises:
ImportLoadError: With a typed ``ImportFailureCode`` on any load failure.
oscal.oscal_content#
oscal_content — OSCAL base class and shared content operations.
Defines the ``OSCAL`` base class used by all eight model classes for creating,
loading, manipulating, validating, and format-converting OSCAL content. All
published OSCAL versions, formats, and models can be validated and converted;
newly published versions can be "learned" by updating the OSCAL Support
database. This module also drives import resolution and Metapath/JSON query
support, and defines the library exception hierarchy (``OSCALError`` and its
subclasses, e.g. ``UnsupportedModelOperation``).
Two supporting modules are imported and re-exported here so existing
``from .oscal_content import ...`` call sites keep working:
* ``oscal_helpers`` — model-agnostic dict/markup helpers (props/links, UUID
generation/validation, id lookups, depth-limited safe copies).
* ``oscal_source`` — source acquisition and reference/import resolution
(``OscalRef``, ``load_content``/``load_source``, ``classify_source``, href
resolution, and the ``ImportState``/``ImportFailureCode``/``ImportLoadError``/
``ImportFailure`` types).
See https://github.com/brian-ruf/oscal-class for more details.
Module constants:
INDENT (int): Number of spaces used for indentation in pretty-printed output.
OSCAL_DEFAULT_XML_NAMESPACE (str): The NIST OSCAL XML namespace URI
(re-exported from ``oscal_support``).
OSCAL_FORMATS (list): Supported serialization formats
(re-exported from ``oscal_support``).
OSCAL_DATATYPES (dict): OSCAL Metaschema data type definitions
(re-exported from ``oscal_datatypes``).
class ContentState#
Progressive content-processing state; each level implies all prior levels passed.
Members (ordered by increasing progress):
NONE (int): -1 — no content / uninitialized.
NOT_AVAILABLE (int): 0 — content could not be acquired.
ACQUIRED (int): 1 — content was acquired (non-empty string).
WELL_FORMED (int): 2 — content is well-formed XML, JSON, or YAML.
VALID (int): 3 — content passes OSCAL schema validation (minimum for view/edit).
IMPORTS_RESOLVED (int): 4 — all imported OSCAL documents resolved successfully.
No public members.
class ImportResult#
Outcome of an :meth:`OSCAL.add_import` or :meth:`OSCAL.update_import` call.
Attributes:
status (str): One of "added", "replaced", "updated", "duplicate", "invalid", or
"error". "added"/"replaced"/"updated" are the success cases (``ok`` is True):
"added"/"replaced" come from :meth:`add_import` (new entry vs placeholder
filled), while :meth:`update_import` returns "replaced" when it repointed the
import at a new/other resource and "updated" when it modified the existing
resource in place. "duplicate" means the href already appears among this
document's own imports. "invalid" means the operation is not permitted by this
model's import cardinality (e.g. a catalog has no imports). "error" is a
bad-input or read-only failure.
entry (dict | None): The import entry — the newly added/replaced/updated entry, or
the conflicting existing import for "duplicate".
resource (dict | None): The back-matter resource referenced by the import
(created, reused, or updated). None for "duplicate"/"invalid"/"error".
message (str): Human-readable detail, primarily for the non-success statuses.
property
property is_duplicate#
bool: True when the href already matched one of this document's imports.
property
property is_updated#
bool: True when an existing resource was modified in place (update_import).
class OSCAL#
Base class for all OSCAL model documents.
Provides loading, saving, validation, format conversion (XML/JSON/YAML),
import resolution, and query support shared by every OSCAL model. Content is
held internally as a JSON-primary dict (``self._dict``); the XML tree
(``self._tree``) is a transient, derived view built on demand for XML
serialization and released afterward (it persists only in the degraded case
where XML was loaded but could not be converted to a dict). Do not instantiate
directly; use the factory classmethods ``load``, ``loads``, or ``new``, or a
model subclass.
Attributes (Content Location):
href_original: The original href as provided (e.g., in an import statement)
is_valid_href: True if the href is accessible and the content was loaded successfully
href : Working href (may differ from href_original after redirect/retry)
Attributes (Class States):
is_valid : True if the content passed OSCAL validation, False otherwise
is_local : True if the source is a local file, False if it's remote (http/https)
is_cached : True if remote content has a local cache copy, False otherwise
is_canonical: True if this is canonical/published content; forces read-only
is_read_only: True if the content may not be mutated (property; True whenever
is_canonical is set, else reflects the loader/caller flag)
is_unsaved : True if there are unsaved modifications, False otherwise
Attributes (Caching and Expiration):
loaded: Timestamp of when the content was loaded (datetime object)
ttl: Time to live for cached content in seconds (0 or less means never expire)
Attributes (Content and Summary):
content : The raw content as a string in its original format
original_format: The original format of the content (xml, json, yaml)
model : The identified OSCAL model (e.g., "catalog", "profile")
oscal_version : The OSCAL version from the metadata (if available)
last_modified : The last modified date from the metadata (if available)
title : The title from the metadata (if available)
published : The publication date from the metadata (if available)
version : The version from the metadata (if available)
remarks : Any remarks from the metadata (if available)
Attributes (Processing Objects):
self.import_list = [] # An array of dictionaries representing imported OSCAL content
Properties:
is_editable: True if the content can be modified, False otherwise
Cross-model operation guard:
Many mutation/query methods are only valid for certain models (e.g.
``resolve`` on a Profile, ``append_component`` on an SSP). Calling one on a
model that does not define it does not raise a bare ``AttributeError``:
``__getattr__`` raises :class:`UnsupportedModelOperation` (an ``OSCALError``
and ``AttributeError``) reporting which models — if any — define the name,
logs that detail, and records it on ``self.errors``. Use :meth:`supports`
to check before calling. Applications catch :class:`OSCALError` at their
boundary and show the user ``err.user_message`` instead of internals.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: 'str' = '') -> 'bool'#
Validate OSCAL content against the metaschema index in sequenced phases.
Phases (each recorded in ``validation_status``):
structure – all required fields and hierarchy are present
data-types – every leaf value matches its declared OSCAL datatype
allowed-values – every constrained value is within its enumerated set
cardinality – every array satisfies its min-occurs/max-occurs bounds
choice – every choice is mutually exclusive (at most one member present) and has a member when one is required
``validation_status["well-formed"]`` is set by ``initial_validation()``, not here.
All phases always run regardless of earlier failures, giving a complete picture
of issues in a single call. The format argument is accepted for API
compatibility but does not alter the validation path — ``_dict`` is always the
authoritative representation.
Returns True only when every phase passes (content_state reaches VALID).
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
class OSCALError#
Base class for every error intentionally raised by the oscal library.
Catch this at an application boundary to handle any library-originated
failure uniformly — typically to log the developer-facing detail and show
the end user a friendly message — without also swallowing unrelated Python
errors. Instances expose two messages:
* ``developer_message`` — precise, actionable detail for logs/telemetry.
* ``user_message`` — a safe, generic sentence suitable for end users.
``str(err)`` returns the developer message.
property
property developer_message#
str: Precise, actionable detail for developers (the default ``str``).
class OriginState#
Origin/freshness state of a document's source (not progressive).
Freshness is time-based and computed on demand rather than stored.
Members:
LOCAL (str): "local" — local file system source; always accessible.
REMOTE_UNCACHED (str): "remote-uncached" — remote source with no local cache copy.
REMOTE_FRESH (str): "remote-fresh" — remote source cached and within its TTL.
REMOTE_STALE (str): "remote-stale" — remote source cached but past its TTL.
No public members.
class UnsupportedModelOperation#
Raised when a method/attribute valid for *some* OSCAL model is accessed on
a model that does not define it (e.g. calling ``add_control`` on an SSP).
Also raised — with an empty ``valid_on`` — for names no OSCAL model defines,
which almost always indicates a typo.
This subclasses :class:`AttributeError` as well as :class:`OSCALError` so that
``hasattr(obj, name)`` and ``getattr(obj, name, default)`` keep their normal
semantics (return ``False`` / the default rather than propagating), while the
error remains catchable as an ``OSCALError``.
Attributes:
method (str): The attribute/method name that was accessed.
model (str): The model name of the instance it was accessed on.
valid_on (list[str]): Model names that *do* define the name (may be empty).
method
def __init__(self, method: 'str', model: 'str', valid_on: "'list[str] | None'" = None)#
Initialize the error.
Args:
method (str, required): The attribute/method name that was accessed.
model (str, required): The model name of the instance it was used on.
valid_on (list[str], optional): Model names that define ``method``.
property
property developer_message#
str: Which model was called, the missing operation, and where it's valid.
class VersionSupport#
Whether the content's declared OSCAL version was supported as-is.
Set during initial validation once the model/version are identified. A
``CLOSEST_MATCH`` or ``UNSUPPORTED`` result means the requested version was not
available locally and could not be acquired from the bundled database or NIST.
Members:
EXACT (str): "exact" — the declared OSCAL version's support was available (or
was successfully acquired); validation/conversion used that exact version.
CLOSEST_MATCH (str): "closest-match" — the declared version was unavailable;
the closest available version within the same OSCAL major was substituted.
``requested_oscal_version`` and ``resolved_oscal_version`` differ.
UNSUPPORTED (str): "unsupported" — the declared version/model could not be
supported at all; the document cannot advance past ``ACQUIRED``.
No public members.
class _ReadableSource#
Protocol for file-like objects that provide read().
method
def read(self, size: 'int' = -1) -> 'Any'#
Read up to ``size`` bytes/characters from the source (``-1`` reads all).
function
def append_resource(oscal_obj: 'OSCAL', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Appends a resource to the back-matter section of the OSCAL JSON content.
Args:
oscal_obj: The OSCAL document to modify.
uuid: Resource UUID; generated if not supplied.
title: Optional resource title.
description: Optional resource description.
props: Optional list of prop dicts (see append_prop).
rlinks: Optional list of rlink dicts with "href" and optional "media-type"/"hashes".
base64: Not yet implemented; a warning is logged if supplied.
remarks: Optional remarks (OSCAL markup-multiline / markdown string).
Returns:
dict | None: The appended resource dict, or None on error.
function
def create_new_oscal_content(model_name: 'str', title: 'str', version: 'str' = '', published: 'str' = '', format: 'str' = 'xml') -> 'Optional[OSCAL]'#
Returns a validated base OSCAL instance loaded from a template.
Currently this is based on loading a template file from package data.
In the future, this should be generated based on the latest metaschema definition.
The supplied ``title`` (and ``version``/``published`` when given) overwrite the
template's placeholder metadata, so ``Catalog.new("X")`` / ``Profile.new("X")`` set
the document title as expected.
The returned instance is always a base OSCAL object. Callers that need a
specific model subclass (e.g. Catalog, Profile) are responsible for
reassigning __class__ and calling _init_common() afterward.
Args:
model_name (str): The OSCAL model name (e.g., "catalog", "system-security-plan").
title (str): The title for the new OSCAL content.
version (str): Optional content version.
published (str): Optional publication date.
format (str): The desired format for the new content ("xml", "json", "yaml"). Defaults to "xml".
Returns:
Optional[OSCAL]: A base OSCAL instance loaded from template, or None on failure.
function
def current_actor() -> "'str | None'"#
Return the current actor (view/session) id, or None when unset.
Returns:
str | None: The actor id activated by :func:`use_actor`, else None.
function
def if_update_successful(fn)#
Decorator marking content dirty after a successful mutation.
Wraps a mutation method; when it returns a non-None result, sets
``self.is_unsaved = True`` and updates ``self.last_modified``.
Args:
fn (Callable, required): The mutation method to wrap.
Returns:
Callable: The wrapped method.
function
def register_model(model_name: 'str', cls: 'type') -> 'None'#
Register an OSCAL model subclass so factory methods return typed instances.
Args:
model_name (str, required): The OSCAL model name (e.g. "catalog").
cls (type, required): The ``OSCAL`` subclass implementing that model.
function
def requires(**conditions)#
Decorator factory gating a method on instance attribute/property values.
The wrapped method runs only when every ``attr == expected`` condition holds
on ``self``; otherwise it logs an error and returns None.
Args:
**conditions: Mapping of instance attribute/property name to its required
value (e.g. ``writable=True``, ``is_remote=True``).
Returns:
Callable: A decorator that wraps the target method with the guard.
Example:
>>> @requires(is_read_only=False)
... def mutate(self): ...
function
def use_actor(actor: "'str | None'")#
Set the current actor for the duration of the ``with`` block.
Mutations performed inside the block are attributed to ``actor``; a document
write-locked by a *different* actor is read-only within the block.
Args:
actor (str | None, required): The actor (view/session) id.
Yields:
str | None: The activated actor id.
oscal.oscal_datatypes#
oscal_datatypes — OSCAL Metaschema data type definitions and helpers.
Defines the OSCAL Metaschema primitive data types and their validation
patterns, and provides a helper for producing OSCAL-conformant timezone-aware
date-time strings.
Module constants:
OSCAL_DATATYPES (dict): Mapping of OSCAL Metaschema data type name (str) to
a definition dict. Each definition contains the keys ``base-type`` (str),
``xml-pattern`` (str regex), ``json-pattern`` (str regex),
``recommended-pattern`` (str regex), ``documentation`` (str),
``remarks`` (str), and ``links`` (list of {"title", "url"} dicts).
Covers types such as ``string``, ``token``, ``uuid``, ``uri``,
``date-time-with-timezone``, ``integer``, ``boolean``, ``markup-line``,
and ``markup-multiline``.
function
def normalize_uri_reference(value: str) -> str#
Best-effort conversion of a non-conformant value into a valid URI-reference.
Defensive coding for URI values that arrive with characters RFC 3986 does not permit
unescaped — e.g. a Windows path with backslashes (``R:\a\b.json``), a raw space, or a
non-ASCII (IRI) character. Backslashes are converted to forward slashes (the usual
Windows-path intent) and every remaining disallowed character is percent-encoded using
UTF-8, while URI-structural characters and existing ``%XX`` escapes are preserved. A
value that is already conformant (and any non-string/empty input) is returned unchanged.
Note this cannot invent a missing scheme, so it does not, on its own, make a bare
``#fragment`` a valid *absolute* ``uri`` — it only repairs the character set.
Args:
value (str): The candidate URI-reference.
Returns:
str: A character-set-conformant URI-reference.
function
def oscal_date_time_with_timezone(date_time=None, format='%Y-%m-%dT%H:%M:%SZ') -> str#
Convert a date/time to UTC and format it as an OSCAL date-time-with-timezone string.
Args:
date_time (datetime | str, optional): The date and time to convert. May be a
``datetime`` object or an ISO-8601 string parseable into one. Naive values
are assumed to be UTC. Defaults to the current date and time.
format (str, optional): The ``strftime`` format string to apply.
Defaults to ``"%Y-%m-%dT%H:%M:%SZ"`` (the OSCAL standard format).
Returns:
str: The formatted date-time string, or an empty string if parsing or
formatting fails.
oscal.oscal_controls#
oscal_controls — OSCAL control-layer model classes. Provides the editable model classes for the OSCAL control models: ``Catalog`` (defines controls), ``Profile`` (selects and tailors controls into baselines), and ``Mapping`` (relates controls across frameworks). Each class subclasses ``OSCAL`` from ``oscal_content`` and adds model-specific navigation and mutation helpers. ``ImportResult`` (the structured return value of :meth:`OSCAL.add_import`) and the ``MEDIA_TYPES`` / ``_infer_media_type`` media-type helpers are defined in ``oscal_content`` / ``oscal_helpers`` and re-exported here for backward compatibility.
class Catalog#
Editable OSCAL Catalog model.
Subclasses ``OSCAL`` and adds methods for creating, editing, navigating, and
removing controls and control groups. Read-only guards apply to mutation methods
when the instance state is not editable.
Attributes:
controls_tree (list[dict]): A lightweight, nested view of the catalog's
group/control hierarchy for UI tree navigation. Each node is
``{"id", "label", "title", "group", "children"}`` where ``group`` is True
for groups and False for controls, and ``children`` holds nested groups
and controls (control enhancements included). Built when the catalog is
found to be valid OSCAL and refreshed on every structural or
title/label change. See :meth:`_build_controls_tree`.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def add_part(self, parent_id: str, name: str, title: str = '', prose: str = '', ns: str = '', part_class: str = '', part_id: str = '', props: list = [], links: list = [], parts: list = []) -> Optional[dict]#
Add a part to an existing control, group, or part.
Parts are valid on controls and groups (and nest inside other parts), so
``parent_id`` may identify any of those by id. There is no limit on how many
parts of a given ``name`` a level may hold (e.g. multiple ``guidance`` parts).
Args:
parent_id (str, required): ID of the control, group, or part to add to.
name (str, required): The part ``name`` token (e.g. "overview",
"guidance", "example", "assessment-objective").
title (str, optional): Part title (markup-line).
prose (str, optional): Part prose (markup-multiline / markdown).
ns (str, optional): Part namespace URI.
part_class (str, optional): Part ``class`` value.
part_id (str, optional): ID for the new part (needed to target it later,
e.g. to nest a child part or set its title).
props (list, optional): Property dicts to add.
links (list, optional): Link dicts to add.
parts (list, optional): Pre-built child part dicts to nest.
Returns:
Optional[dict]: The newly created part dict, or None on failure — including
when the part would violate a "leaf part" rule (e.g. a ``guidance``
part cannot contain child parts).
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def create_control(self, parent_id: str, id: str, title: str = '', params: list = [], props: list = [], links: list = [], label: str = '', sort_id: str = '', alt_identifier: str = '', overview: str = '', statements: list = [], guidance: str = '', example: str = '', objectives: list = [], objects: list = [], methods: list = [], remarks: str = '') -> Optional[dict]#
Create a new control under the specified parent group or control.
Args:
parent_id (str, required): ID of the parent to add the control to —
``'[root]'`` (or an empty string) for the catalog top level, a group
id, or a control id. Nesting a control under a control models a
control enhancement (e.g. ``ac-2.1`` under ``ac-2``). The add fails if
it would mix controls and groups at the same level (not allowed in OSCAL).
id (str, required): ID of the new control.
title (str, optional): Title of the new control. Defaults to the label,
or the id, when empty.
params (list, optional): Parameters to add. Items may be parameter id
strings or full parameter dicts.
props (list, optional): Additional property dicts to add.
links (list, optional): Link dicts to add.
label (str, optional): Value for the inline ``label`` property.
sort_id (str, optional): Value for the inline ``sort-id`` property.
alt_identifier (str, optional): Value for the inline ``alt-identifier`` property.
overview (str, optional): Prose (markdown) for the ``overview`` part.
statements (list, optional): Statement items — strings or
``{'id':..., 'prose':...}`` dicts.
guidance (str, optional): Prose (markdown) for the ``guidance`` part.
example (str, optional): Prose (markdown) for the ``example`` part.
objectives (list, optional): Assessment objective items.
objects (list, optional): Assessment object items.
methods (list, optional): Assessment method items.
remarks (str, optional): Remarks prose (markdown).
Returns:
Optional[dict]: The newly created control dict, or None on failure.
Note:
Refreshes ``controls_tree`` when a control is successfully added.
method
def create_control_group(self, parent_id: str, id: str, title: str = '', params: list = [], props: list = [], links: list = [], label: str = '', sort_id: str = '', alt_identifier: str = '', overview: str = '', instruction: str = '', remarks: str = '') -> Optional[dict]#
Create a new catalog group.
Args:
parent_id (str, required): ID of the parent group, or ``'[root]'`` (or an
empty string) for the catalog top level. The add fails if it would mix
controls and groups at the same level (not allowed in OSCAL).
id (str, required): ID of the new group.
title (str, optional): Title of the new group.
params (list, optional): Parameters to add to the group.
props (list, optional): Additional property dicts to add.
links (list, optional): Link dicts to add.
label (str, optional): Value for the inline ``label`` property.
sort_id (str, optional): Value for the inline ``sort-id`` property.
alt_identifier (str, optional): Value for the inline ``alt-identifier`` property.
overview (str, optional): Prose (markdown) for the ``overview`` part.
instruction (str, optional): Prose (markdown) for the ``instruction`` part.
remarks (str, optional): Remarks prose (markdown).
Returns:
Optional[dict]: The newly created group dict, or None on failure.
Note:
Refreshes ``controls_tree`` when a group is successfully added.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_control_by_id(self, control_id: str, depth: Optional[int] = None) -> Optional[dict]#
Retrieve a control by its ID as a safe copy, searching all groups recursively.
The returned dict is a detached copy — mutating it does NOT change the catalog;
use the catalog's mutation methods to make persistent changes. The control's own
content (``parts``, ``props``, ``links``, ``params`` …) is always returned in full;
``depth`` limits only nested child controls (enhancements).
Args:
control_id (str, required): The ``id`` of the control to find.
depth (int | None, optional): Nested-enhancement depth. ``None`` (default)
returns the full subtree; ``0`` omits enhancements; ``N`` keeps N levels.
Returns:
Optional[dict]: A safe copy of the matching control, or None if not found.
method
def get_control_list(self) -> list#
Return a flat list of every control in the catalog, at all levels, as safe copies.
The returned list is detached from the document: each control is a copy, so
mutating any element does NOT change the catalog. A single deep copy of the whole
list preserves internal identity relationships (an enhancement nested inside its
parent is the same object as its own standalone entry). Use the catalog's mutation
methods to make persistent changes.
Returns:
list: Safe copies of all controls found across the catalog and its groups.
method
def get_group_by_id(self, group_id: str, depth: Optional[int] = None) -> Optional[dict]#
Retrieve a group by its ID as a safe copy, searching nested groups recursively.
The returned dict is a detached copy — mutating it does NOT change the catalog;
use the catalog's mutation methods to make persistent changes. The group's own
content (``props``, ``links`` …) is always returned in full; ``depth`` limits only
nested child groups and controls.
Args:
group_id (str, required): The ``id`` of the group to find.
depth (int | None, optional): Nested group/control depth. ``None`` (default)
returns the full subtree; ``0`` omits child groups/controls; ``N`` keeps
N levels.
Returns:
Optional[dict]: A safe copy of the matching group, or None if not found.
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
method
def insert_control(self, parent_id: str, control: dict, validate: bool = True) -> Optional[dict]#
Insert a pre-formed control subtree whole under a parent, as a safe copy.
Unlike :meth:`create_control` (which authors a control from discrete parts),
this inserts an already-formed control dict — including its nested enhancements,
parts, props, params, and links — without reshaping it. It is the faithful-copy
path used by profile resolution (and, later, alter directives) to move a control
from a source catalog into a resolved one.
The incoming dict is deep-copied before insertion, so the caller's object is not
aliased into the catalog (getters still return detached copies).
Args:
parent_id (str, required): ID of the parent to add the control to —
``'[root]'`` (or an empty string) for the catalog top level, a group id,
or a control id (control-under-control models an enhancement). The add
fails if it would mix controls and groups at the same level.
control (dict, required): The control subtree to insert. Must be a dict with
a non-empty ``id``.
validate (bool, optional): When True (default), the control is validated
against the ``control`` metaschema node first; on any error the insert is
rejected and the catalog is left unchanged.
Returns:
Optional[dict]: A safe copy of the inserted control, or None on failure —
bad input, parent not found, an id collision with an existing control, a
controls/groups mix, or failed validation.
method
def insert_group(self, parent_id: str, group: dict, shallow: bool = True, validate: bool = True) -> Optional[dict]#
Insert a group node under a parent, as a safe copy.
Companion to :meth:`insert_control` for faithful-copy workflows. By default the
insert is *shallow*: the group's intrinsic content (title, params, props, links,
parts) is inserted but its child ``groups``/``controls`` are dropped, to be filled
in afterward via :meth:`insert_control`/:meth:`insert_group`. This lets callers
(e.g. profile resolution) build a group hierarchy incrementally while keeping
empty-group pruning and duplicate handling under their own control.
Args:
parent_id (str, required): ID of the parent group, or ``'[root]'`` (or an
empty string) for the catalog top level. The add fails if it would mix
controls and groups at the same level.
group (dict, required): The group node to insert. Must be a dict with a
non-empty ``id``.
shallow (bool, optional): When True (default), drop the group's child
``groups`` and ``controls`` before insertion. When False, insert the
group subtree whole.
validate (bool, optional): When True (default), validate the (possibly
shallow) group against the ``group`` metaschema node first; on any error
the insert is rejected and the catalog is left unchanged.
Returns:
Optional[dict]: A safe copy of the inserted group, or None on failure — bad
input, parent not found, an id collision with an existing group, a
controls/groups mix, or failed validation.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove(self, id: str, cascade: bool = False, ignore_references: bool = False) -> Optional[dict]#
Remove a control or group (found by id) from the catalog.
Two independent locks guard the delete; either can block it, and the return
value makes the reason explicit:
* **cascade** — when ``cascade`` is False, a node with *immediate* children
(direct groups, controls, or parts) is not removed. The block report
lists those immediate child ids under ``"children"``. Set ``cascade=True``
to remove the node together with everything beneath it.
* **referential integrity** — when ``ignore_references`` is False, the node
is not removed if any id in its subtree (the node, nested groups/controls,
or any part) is referenced by a link elsewhere in the catalog. The block
report lists those referenced ids under ``"referenced_ids"``. Set
``ignore_references=True`` to delete anyway; the now-dangling references
are then returned under ``"dangling_refs"``.
Both conditions are evaluated; if both block, ``"blocked_by"`` contains both
reasons and both detail lists are present. Nothing is modified when blocked.
References in *other* documents (profiles, SSPs, mappings) cannot be seen or
fixed from here; on a successful delete a warning notes they may now break.
Refreshes ``controls_tree`` on success.
Args:
id (str, required): The id of the control or group to remove.
cascade (bool, optional): Permit removing a node that has immediate
children. Defaults to False.
ignore_references (bool, optional): Permit removing a node that is
referenced elsewhere in the catalog. Defaults to False.
Returns:
Optional[dict]:
* ``None`` when no control/group has that id.
* On block: ``{"removed": False, "blocked_by": [...],
"children": [...]?, "referenced_ids": [...]?}``.
* On success: ``{"removed": True, "removed_ids": [...],
"dangling_refs": [...]}``.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_label(self, id: str, label: str, class_: str = '', group: str = '') -> Optional[dict]#
Set (or clear) the ``label`` property of a control or group, found by id.
The targeted property is the ``label`` prop in the default OSCAL namespace
whose ``class``/``group`` qualifiers match the arguments: by default the one
with **no** class and **no** group — the same property the navigation tree
reads. Supplying ``class_`` and/or ``group`` targets (or creates) the label
carrying exactly those qualifiers instead.
Behaviour:
* A matching prop exists → its value is updated (the first, if several).
* No matching prop exists → one is created with the given qualifiers.
* ``label`` is empty → every matching prop is removed.
Refreshes ``controls_tree`` on success.
Args:
id (str, required): The id of the control or group to modify.
label (str, required): The new label value; an empty string removes the
matching label property.
class_ (str, optional): The prop ``class`` to target/create.
group (str, optional): The prop ``group`` to target/create.
Returns:
Optional[dict]: The modified control/group dict, or None if no such id
exists.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def set_part_title(self, part_id: str, title: str = '') -> Optional[dict]#
Set or remove the title of an existing part.
Args:
part_id (str, required): ID of the part to modify. The part must carry an
``id`` to be targetable.
title (str, optional): The new title. When empty, the part's ``title`` is
removed.
Returns:
Optional[dict]: The modified part dict, or None if no part with that id
is found.
method
def set_title(self, id: str, title: str) -> Optional[dict]#
Set the title of a control or group, found by id.
Refreshes ``controls_tree`` on success, since a node's ``title`` is drawn
from the object's title.
Args:
id (str, required): The id of the control or group to modify.
title (str, required): The new title. Must be non-empty — a control's
title is required by OSCAL, so blanking it is rejected.
Returns:
Optional[dict]: The modified control/group dict, or None if no such id
exists or ``title`` is empty.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: str = '') -> bool#
Validate the catalog, then (re)build ``controls_tree`` on success.
Extends :meth:`OSCAL.validate` so the navigation tree is refreshed the
moment the catalog is converted and found to be valid OSCAL. When the
content is not valid the tree is emptied — an invalid catalog exposes no
navigable hierarchy.
Args:
format (str, optional): Accepted for API compatibility with the base
method; does not alter the validation path.
Returns:
bool: True when every validation phase passes.
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
class Mapping#
Class representing an OSCAL Mapping object.
Inherits common OSCAL functionality and adds mapping-specific methods
for managing mappings between controls and other objects.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: 'str' = '') -> 'bool'#
Validate OSCAL content against the metaschema index in sequenced phases.
Phases (each recorded in ``validation_status``):
structure – all required fields and hierarchy are present
data-types – every leaf value matches its declared OSCAL datatype
allowed-values – every constrained value is within its enumerated set
cardinality – every array satisfies its min-occurs/max-occurs bounds
choice – every choice is mutually exclusive (at most one member present) and has a member when one is required
``validation_status["well-formed"]`` is set by ``initial_validation()``, not here.
All phases always run regardless of earlier failures, giving a complete picture
of issues in a single call. The format argument is accepted for API
compatibility but does not alter the validation path — ``_dict`` is always the
authoritative representation.
Returns True only when every phase passes (content_state reaches VALID).
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
class MergeStrategy#
How a Profile reconstructs control hierarchy under an ``as-is`` merge directive.
This is **not** the OSCAL merge directive. The directive itself — ``as-is``,
``flat``, or ``custom`` — is authored on the profile's ``merge`` element and is
surfaced by :attr:`Profile.merge_directive`. ``MergeStrategy`` is a separate,
library-only processing option (not serialized) that refines a *single* aspect of
resolution and **applies only when that directive is ``as-is``**. Under a ``flat``
or ``custom`` directive it is inert — flat has no nesting to reconstruct, and custom
defines its own structure — so setting it then has no effect.
Members:
REFERENTIAL (str): "referential" — the primary/default logic. Reconstructs
control containment from source provenance so a selected enhancement
nests under its selected parent (e.g. ``ac-3.14`` under ``ac-3``) even
when an intermediate profile selected the child without its parent. The
NIST profile-resolution spec is silent on this; REFERENTIAL honors the
hierarchy defined at the end of each import branch (the source catalog).
POSITIONAL (str): "positional" — the secondary/legacy logic. Preserves each
immediate import's presented structure; a child selected without its
parent is promoted to its group and remains a peer of its parent.
No public members.
class Profile#
Editable OSCAL Profile model with tree-driven, lazy resolution.
A profile selects and tailors controls from one or more imported catalogs/profiles.
Resolution is split into a cheap load-time step and an on-demand heavy step:
* **controls_tree (source of truth).** On load — and after any import/directive
change — :meth:`_build_controls_tree` reads each imported object's own
``controls_tree`` and applies the profile's directives to produce this profile's
``controls_tree``: the authoritative scope and organization. It is lightweight
(ids + hierarchy + an ``origin`` back to each node's immediate source); no control
content is copied. Directives applied here: ``import``/``include``/``exclude``
selection, ``merge`` (``as-is`` or ``flat``; ``custom`` is deferred and falls back
to ``as-is``), and ``combine`` duplicate handling (``keep`` renames the node id,
``use-first`` drops later duplicates). When an ``as-is`` merge would place controls
and groups together at the root, root controls are wrapped in a synthetic
"ROOT CONTROLS" group.
* **resolve() (heavy, cacheable).** :meth:`resolve` walks the tree and materializes a
brand-new ``Catalog`` in :attr:`catalog`, fetching real content per node, applying
``modify`` directives (removes → adds → set-parameters) and full internal id
renaming for duplicates, hoisting externally-defined cited parameters to the root,
carrying forward referenced back-matter resources, and rewriting out-of-scope
references to their source URIs. ``catalog`` is ``None`` until ``resolve`` is called.
* **Read-only Catalog surface.** :meth:`get_control_by_id`, :meth:`get_group_by_id`,
:meth:`get_control_list`, and :meth:`get_parameter_by_id` return safe copies from
:attr:`catalog` when resolved, or materialize them on demand from the source
(through the same code path, so the two agree) when unresolved. Content is changed
via the profile's own directive methods and re-resolved, not by editing returned
copies.
* **Serialization.** :meth:`dumps_catalog`/:meth:`dump_catalog` serialize the resolved
catalog to a string / file; :meth:`resolve_and_dumps_catalog`/
:meth:`resolve_and_dump_catalog` run the full resolve-then-serialize in one call.
Key attributes:
catalog (Catalog | None): The resolved catalog, or None until :meth:`resolve`.
controls_tree (list[dict]): Scope/organization nodes
``{id, label, title, group, origin, children}``.
duplicates (dict): Controls/groups renamed or dropped by ``combine``, keyed by
original id (see :meth:`_record_duplicate`).
resolution_status (ResolutionStatus): UNRESOLVED / RESOLVING / RESOLVED / BLOCKED.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_alter(self, control_id: str) -> Optional[dict]#
Ensure a ``modify.alters`` entry exists for *control_id* and return a safe copy.
Idempotent: creates the (initially empty) alter when absent, otherwise returns the
existing one unchanged. The document is only marked unsaved when an alter is
actually created. Used by :meth:`add_alter_adds` / :meth:`add_alter_removes`.
Args:
control_id (str, required): The control the alter targets.
Returns:
Optional[dict]: A safe copy of the alter, or None (read-only, or blank id).
method
def add_alter_adds(self, control_id: str, position: Optional[str] = None, by_id: Optional[str] = None, title: Optional[str] = None, params: Optional[list] = None, props: Optional[list] = None, links: Optional[list] = None, parts: Optional[list] = None) -> Optional[dict]#
Add an ``adds`` (addition) to *control_id*'s alter, creating the alter if needed.
The addition is built from the supplied fields, pruned/validated through the
metaschema staging gate, then appended. At least one content field
(``title``/``params``/``props``/``links``/``parts``) must be supplied. The alter is
only created once the addition validates, so a rejected call leaves nothing behind.
Args:
control_id (str, required): The control the alter targets.
position (str, optional): ``before``/``after``/``starting``/``ending``.
by_id (str, optional): The id the addition is positioned relative to.
title (str, optional): A title to add.
params (list, optional): Parameter dicts to add.
props (list, optional): Property dicts to add.
links (list, optional): Link dicts to add.
parts (list, optional): Part dicts to add.
Returns:
Optional[dict]: A safe copy of the addition, or None on failure.
method
def add_alter_removes(self, control_id: str, by_name: Optional[str] = None, by_class: Optional[str] = None, by_id: Optional[str] = None, by_item_name: Optional[str] = None, by_ns: Optional[str] = None) -> Optional[dict]#
Add a ``removes`` (removal) to *control_id*'s alter, creating the alter if needed.
The removal is built from the supplied ``by-*`` selectors (at least one required),
validated through the metaschema staging gate, then appended. The alter is only
created once the removal validates, so a rejected call leaves nothing behind.
Args:
control_id (str, required): The control the alter targets.
by_name (str, optional): Match the named flag/field/part to remove.
by_class (str, optional): Match by ``class``.
by_id (str, optional): Match by ``id``.
by_item_name (str, optional): Match by item (element) name.
by_ns (str, optional): Match by namespace.
Returns:
Optional[dict]: A safe copy of the removal, or None on failure.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def control(self, control_id: str, with_history: bool = False, depth: Optional[int] = None) -> Optional[dict]#
Retrieve a control by its ID, as a safe copy (thin alias of
:meth:`get_control_by_id`).
Works whether or not the profile is resolved: resolved fetches from
``self.catalog``; unresolved materializes on demand from the source via the
profile's controls_tree.
Args:
control_id (str, required): The ``id`` of the control to retrieve.
with_history (bool, optional): Reserved for including tailoring history.
Defaults to False.
depth (int | None, optional): Nested-enhancement depth. ``None`` (default)
returns the full subtree; ``0`` omits enhancements; ``N`` keeps N levels.
Returns:
Optional[dict]: A safe copy of the control, or None if not found.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dump_catalog(self, filename: str = '', format: str = '', pretty_print: bool = False) -> bool#
Write the resolved catalog to a file — wrapper over ``self.catalog.dump``.
Requires the profile to be resolved (``self.catalog`` is None until
:meth:`resolve`); returns False with a warning otherwise.
Args:
filename (str, optional): Path to write to; defaults to the catalog's original
location when empty.
format (str, optional): Output format ("xml", "json", "yaml"); defaults to the
catalog's original format when empty.
pretty_print (bool, optional): Whether to pretty-print. Defaults to False.
Returns:
bool: True on success, False when unresolved or the write fails.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
method
def dumps_catalog(self, format: str = '', pretty_print: bool = False) -> str#
Serialize the resolved catalog to a string — wrapper over ``self.catalog.dumps``.
Requires the profile to be resolved (``self.catalog`` is None until
:meth:`resolve`); returns ``""`` with a warning otherwise.
Args:
format (str, optional): Target format ("xml", "json", "yaml"); defaults to the
catalog's original format.
pretty_print (bool, optional): Whether to pretty-print. Defaults to False.
Returns:
str: The serialized resolved catalog, or ``""`` when unresolved.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_alter(self, control_id: str) -> Optional[dict]#
Return a safe copy of the ``modify.alters`` entry for *control_id*.
Args:
control_id (str, required): The ``control-id`` of the alter to fetch.
Returns:
Optional[dict]: A deep copy of the alter (with its ``adds``/``removes``), or
None if no alter targets *control_id*.
method
def get_control_by_id(self, control_id: str, depth: Optional[int] = None) -> Optional[dict]#
Retrieve a control as a safe copy — from the resolved catalog if resolved,
otherwise materialized on demand from the source via the profile's controls_tree.
Both paths return the same shape (the control with its in-scope enhancements
nested per ``depth``, this profile's ``modify`` directives applied). When
unresolved, the control is materialized from its origin source on each call.
Args:
control_id (str, required): The control id (as it appears in this profile's
scope — a duplicate's suffixed id is valid).
depth (int | None, optional): Enhancement depth (``None`` full).
Returns:
Optional[dict]: A safe copy of the control, or None when absent.
method
def get_control_list(self) -> list#
Return safe copies of every in-scope control, at all levels.
Resolved: pass-through to :meth:`Catalog.get_control_list`. Unresolved: each
control node in the profile's controls_tree is materialized standalone (depth 0),
mirroring the flat, enhancement-inclusive list a catalog returns.
method
def get_directives(self) -> dict#
Return the profile's merge directives as a simple, normalized dict.
A library-level view of the ``merge`` element that does not pass the raw OSCAL
through. Always includes:
* ``combine`` — ``"use-first"`` when that method is set; ``"keep"`` when ``keep``
is set *or* no ``combine`` is present (the effective default); ``"invalid"`` for
any other stored method (e.g. the OSCAL ``merge`` method, which this library does
not model).
* ``hierarchy`` — ``"flat"`` / ``"as-is"`` / ``"custom"`` from whichever directive
is present (an ``as-is`` directive maps to ``"as-is"`` regardless of its boolean
value). Defaults to ``"as-is"`` — OSCAL's default organization — when no
directive (or no ``merge``) is present.
And, only when ``hierarchy == "custom"``:
* ``custom`` — a safe copy of the ``custom`` object (with its ``groups`` /
``insert-controls`` children).
Returns:
dict: ``{"combine": ..., "hierarchy": ...[, "custom": {...}]}``.
method
def get_group_by_id(self, group_id: str, depth: Optional[int] = None) -> Optional[dict]#
Retrieve a group as a safe copy — from the resolved catalog if resolved,
otherwise materialized on demand from the source via the profile's controls_tree.
Args:
group_id (str, required): The group id (as it appears in this profile's scope).
depth (int | None, optional): Nested group/control depth (``None`` full).
Returns:
Optional[dict]: A safe copy of the group, or None when absent.
method
def get_import_selection(self, href: str) -> Optional[dict]#
Return an import statement's control-selection structures as a safe copy.
Fetches the ``include-all``, ``include-controls``, and ``exclude-controls``
structures for the import identified by *href* (any tracked href form — see
:meth:`_locate_import_statement`). Only the keys actually present are returned, so
an import that selects via ``include-controls`` yields no ``include-all`` key.
Args:
href (str, required): Any href that identifies the import statement.
Returns:
Optional[dict]: A deep-copied dict containing whichever of ``include-all`` /
``include-controls`` / ``exclude-controls`` are present (``{}`` when the
import carries none), or None when no import matches *href*.
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: str) -> Optional[dict]#
Return a parameter as a safe copy — from the resolved catalog if resolved,
otherwise located in the import tree (its source, unmutated).
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_set_parameter(self, param_id: str) -> Optional[dict]#
Return a safe copy of the ``modify.set-parameters`` entry for *param_id*.
Args:
param_id (str, required): The ``param-id`` of the set-parameter to fetch.
Returns:
Optional[dict]: A deep copy of the set-parameter, or None if none matches.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
property
property merge_strategy#
The control-tree assembly strategy (:class:`MergeStrategy`).
Defaults to ``REFERENTIAL`` (reconstruct source hierarchy). Assigning a new
value marks the controls_tree stale, drops any resolved catalog, and rebuilds
the tree so subsequent reads reflect the chosen strategy.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_alter_adds(self, control_id: str, by_id: str, position: Optional[str] = None) -> bool#
Remove ``adds`` from *control_id*'s alter that match the given selectors.
Scope is the alter for *control_id*. ``by_id`` is required; ``position`` is
optional. Every addition whose supplied selectors **all** match is removed (so
``by_id`` alone removes all additions with that ``by-id`` regardless of position;
adding ``position`` narrows it). An emptied ``adds`` array, an alter left with no
``adds``/``removes``, an emptied ``alters`` array, and an emptied ``modify`` are all
pruned.
Args:
control_id (str, required): The control whose alter is edited.
by_id (str, required): Match additions with this ``by-id``.
position (str, optional): Also require this ``position``.
Returns:
bool: True if at least one addition was removed, else False.
method
def remove_alter_removes(self, control_id: str, by_name: Optional[str] = None, by_class: Optional[str] = None, by_id: Optional[str] = None, by_item_name: Optional[str] = None, by_ns: Optional[str] = None) -> bool#
Remove ``removes`` from *control_id*'s alter that match the given selectors.
Scope is the alter for *control_id*. Every removal whose **supplied** ``by-*``
selectors all match is deleted (unspecified selectors are ignored). With no
selector supplied, every removal for the control is deleted. An emptied ``removes``
array, an alter left with no ``adds``/``removes``, an emptied ``alters`` array, and
an emptied ``modify`` are all pruned.
Args:
control_id (str, required): The control whose alter is edited.
by_name, by_class, by_id, by_item_name, by_ns (str, optional): Selectors that
must match (all supplied ones) for a removal to be deleted.
Returns:
bool: True if at least one removal was removed, else False.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def remove_set_parameter(self, param_id: str) -> bool#
Remove the ``modify.set-parameters`` entry for *param_id*.
Prunes an emptied ``set-parameters`` array and an emptied ``modify`` object.
Args:
param_id (str, required): The ``param-id`` of the set-parameter to remove.
Returns:
bool: True if a set-parameter was removed, else False (no match, blank
``param_id``, or read-only content).
method
def resolve(self) -> 'ResolutionStatus'#
Materialize the profile's controls_tree into a fresh ``self.catalog``.
Resolution is the cacheable heavy step: it walks the profile's
controls_tree — the authoritative scope/organization built at load — and for each
node fetches the real control/group content from its origin source, applies this
profile's ``modify`` directives (removes → adds → set-parameters), applies full
internal ``__<uuid>`` id renaming for duplicates, then inserts it into a brand-new
``Catalog``. Any previously resolved catalog is discarded and replaced. After
placement it: hoists cited-but-externally-defined parameters to the catalog root
(:meth:`_insert_shared_params`), assembles metadata, carries forward referenced
back-matter resources (:meth:`_carry_backmatter`), rewrites references to
out-of-scope ids to their source URIs (:meth:`_rewrite_out_of_scope_refs`), and
validates.
Because content is fetched through each source's own getters, imported *profiles*
need not be pre-resolved — their load-time controls_tree and lazy getters suffice.
Returns:
ResolutionStatus: ``RESOLVED`` on success, or ``BLOCKED`` when content is
missing or an import could not be resolved.
method
def resolve_and_dump_catalog(self, filename: str = '', format: str = '', pretty_print: bool = False) -> bool#
Resolve the profile, then write the resolved catalog to a file.
Convenience for the common one-shot case: it runs the (potentially expensive)
:meth:`resolve` and then :meth:`dump_catalog`. For finer control — e.g. to resolve
once and write several outputs, or to manage *when* the heavy resolution runs — call
:meth:`resolve` and :meth:`dump_catalog` separately instead.
Note: each call performs a full resolution (rebuilds ``self.catalog`` from scratch).
Args:
filename (str, optional): Path to write to; defaults to the catalog's original
location when empty.
format (str, optional): Output format ("xml", "json", "yaml").
pretty_print (bool, optional): Whether to pretty-print. Defaults to False.
Returns:
bool: True on success, False if resolution was blocked or the write failed.
method
def resolve_and_dumps_catalog(self, format: str = '', pretty_print: bool = False) -> str#
Resolve the profile, then serialize the resolved catalog to a string.
Convenience for the common one-shot case: it runs the (potentially expensive)
:meth:`resolve` and then :meth:`dumps_catalog`. For finer control — e.g. to resolve
once and serialize many times, or to manage *when* the heavy resolution runs — call
:meth:`resolve` and :meth:`dumps_catalog` separately instead.
Note: each call performs a full resolution (rebuilds ``self.catalog`` from scratch).
Args:
format (str, optional): Target format ("xml", "json", "yaml").
pretty_print (bool, optional): Whether to pretty-print. Defaults to False.
Returns:
str: The serialized resolved catalog, or ``""`` if resolution was blocked.
method
def resolve_duplicate(self, control_id: str, keep: Optional[str] = None, parent_id: Optional[str] = None, replacement: Optional[dict] = None) -> Optional[dict]#
Manually collapse duplicate instances of a control in the resolved catalog.
Requires a resolved catalog. Merging logic is out of scope (handled by the
caller, e.g. a GUI); this keeps, relocates, or wholesale-replaces:
* ``replacement`` given — remove every live variant (the original id and each
tracked ``new_id``) and insert ``replacement`` (validated) under ``parent_id``
(or the original variant's current parent).
* ``keep`` given (or defaulted to ``control_id``) — remove every variant except
``keep``; if ``parent_id`` is given, relocate the kept control there.
Args:
control_id (str, required): The ORIGINAL (unsuffixed) control id.
keep (str, optional): Which variant id to retain. Defaults to ``control_id``.
parent_id (str, optional): Where to place the survivor. Defaults to in place.
replacement (dict, optional): A full control dict superseding all variants.
Returns:
Optional[dict]: A safe copy of the surviving control, or None on failure.
method
def resolve_duplicate_group(self, group_id: str, keep: Optional[str] = None, parent_id: Optional[str] = None, replacement: Optional[dict] = None) -> Optional[dict]#
Manually collapse duplicate instances of a group in the resolved catalog.
The group analogue of :meth:`resolve_duplicate`. NOTE: removing a group removes
its contained controls too (cascade), so prefer resolving duplicate *controls*
first when both are tracked.
Args:
group_id (str, required): The ORIGINAL (unsuffixed) group id.
keep (str, optional): Which variant group id to retain. Defaults to ``group_id``.
parent_id (str, optional): Where to place the survivor. Defaults to in place.
replacement (dict, optional): A full group dict superseding all variants.
Returns:
Optional[dict]: A safe copy of the surviving group, or None on failure.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_directives(self, combine: Optional[str] = None, hierarchy: Optional[str] = None, custom: Optional[dict] = None) -> bool#
Set the profile's merge directives without raw pass-through to OSCAL.
Edits ``combine`` and/or the hierarchy directive independently; at least one of
``combine`` / ``hierarchy`` must be given. Changes are assembled in a **staging**
copy and committed only when the whole result is valid — a rejected call changes
nothing (roll back). The counterpart reader is :meth:`get_directives`.
Args:
combine (str, optional): ``"use-first"`` or ``"keep"``. Replaces
``merge.combine.method`` with that value. Any other value is rejected.
hierarchy (str, optional): ``"flat"``, ``"as-is"``, or ``"custom"``.
* ``"flat"`` — remove any ``as-is`` / ``custom``; add ``flat`` if absent.
* ``"as-is"`` — remove any ``flat`` / ``custom``; set ``as-is`` to ``true``.
* ``"custom"`` — requires the ``custom`` argument (below); remove any
``as-is`` / ``flat``; set ``custom`` to the provided object.
custom (dict, optional): Required when ``hierarchy == "custom"`` (ignored
otherwise). Must be a dict following the OSCAL ``merge.custom`` syntax; it
is pruned/validated through the shared metaschema staging gate
(:meth:`_stage_against_index`). If it is not valid the hierarchy is left
unchanged and the method returns ``False``.
Returns:
bool: ``True`` on success; ``False`` on any failure (read-only content, no
directive supplied, an invalid ``combine``/``hierarchy`` value, a missing
or invalid ``custom``, or a result with no hierarchy directive).
method
def set_import_selection(self, href: str, include_all: Optional[dict] = None, include_controls: Optional[list] = None, exclude_controls: Optional[list] = None) -> Optional[dict]#
Replace an import statement's control-selection structures.
Wholesale-replaces the ``include-all`` / ``include-controls`` / ``exclude-controls``
of the import identified by *href* (any tracked href form). The resulting selection
is exactly what is supplied: an argument left as ``None`` is *omitted* from the
rewritten import (pass ``exclude_controls=[]`` to clear an exclusion while keeping
it present as an empty array). ``href`` and any other schema-valid keys on the
statement are carried through unchanged.
Supplied structures are not stored blindly. The whole candidate import is assembled
and checked in a **staging** copy via :meth:`_stage_against_index`, and the live
import is only overwritten once every check passes — a rejected replacement **rolls
back** to the prior content, changing nothing. The candidate is *pruned* to exactly
the keys and nesting the OSCAL ``import`` schema permits (the metaschema represents
OSCAL syntax in full and is authoritative, so anything it does not permit — at any
depth — is dropped with a warning; a wrong array/scalar shape is rejected) and
validated for *required* content and correctness: exactly one of ``include-all`` /
``include-controls`` must be present (the mutually-exclusive OSCAL choice —
supplying neither fails), and datatypes, allowed values, required fields, and
cardinality are enforced. On success the ``controls_tree`` is rebuilt and any
resolved catalog is dropped, since scope has changed.
Args:
href (str, required): Any href that identifies the import statement.
include_all (dict, optional): The ``include-all`` object (typically ``{}``).
Mutually exclusive with ``include_controls``.
include_controls (list, optional): A list of ``select-control-by-id`` dicts
(e.g. ``[{"with-ids": ["ac-1"]}]``). Mutually exclusive with
``include_all``.
exclude_controls (list, optional): A list of ``select-control-by-id`` dicts to
exclude.
Returns:
Optional[dict]: A safe copy of the rewritten import statement, or None on
failure — no matching import, a wrong argument type, or a replacement that
fails metaschema validation (including supplying both/neither include form).
method
def set_merge(self, flat: bool = False, as_is: Optional[bool] = None, custom: Optional[dict] = None, combine: Optional[str] = None) -> Optional[dict]#
Set the profile's ``merge`` directives (``combine`` plus flat/as-is/custom).
.. deprecated::
Prefer :meth:`set_directives` (paired with :meth:`get_directives`), which
edits ``combine`` and the hierarchy directive independently and validates
``custom`` through the shared metaschema staging gate. This method writes the
whole ``merge`` element as a direct pass-through and is retained for
compatibility.
The ``merge`` assembly instructs how imported controls are organized after
profile resolution. Exactly one of ``flat``, ``as_is``, or ``custom`` must be
chosen — they are mutually exclusive — while ``combine`` is optional and may
accompany any of those choices.
The ``custom`` object is accepted whole and validated against the ``custom``
portion of the profile metaschema index; if it fails validation the profile is
left unchanged. Fine-grained management of ``custom`` internals (its ``groups``
and ``insert-controls``) is intentionally deferred to future methods, as custom
merges are uncommon.
Args:
flat (bool, optional): When True, select the ``flat`` directive (resolved
controls are flattened, without groups). Defaults to False.
as_is (bool, optional): When set, select the ``as-is`` directive with this
boolean value (True keeps the source organization). Defaults to None
(not selected).
custom (dict, optional): When set, select the ``custom`` directive using
this object. Validated against the metaschema index. Defaults to None
(not selected).
combine (str, optional): The ``combine`` method — one of ``"use-first"``,
``"merge"``, or ``"keep"``. When None, no ``combine`` is written.
Returns:
Optional[dict]: The ``merge`` dict written to the profile, or None on
failure — when not exactly one of flat/as-is/custom is given, an
argument has the wrong type, ``combine`` is not a valid method, or the
``custom`` object fails metaschema validation.
method
def set_merge_directive(self, directive: str, custom: Optional[dict] = None, combine: Optional[str] = None) -> Optional[dict]#
Change just the profile's merge *directive* (``as-is`` / ``flat`` / ``custom``).
.. deprecated::
Prefer :meth:`set_directives`, which changes the hierarchy directive (and/or
``combine``) independently and validates ``custom`` through the shared
metaschema staging gate. Retained for compatibility.
A focused convenience wrapper over :meth:`set_merge` for the common case of
switching the organization directive without restating the other arguments. The
existing ``combine`` method is preserved unless a new one is supplied, and when
switching to ``custom`` the profile's existing ``custom`` object is reused unless
one is provided. On success :attr:`merge_directive` and the ``controls_tree`` are
refreshed (both handled by :meth:`set_merge`).
Args:
directive (str, required): One of ``"as-is"``, ``"flat"``, or ``"custom"``.
custom (dict, optional): The ``custom`` object to use when
``directive == "custom"``. Defaults to the profile's current ``custom``
object; required (here or already present) for a custom directive.
combine (str, optional): A ``combine`` method to set. Defaults to the
profile's current ``combine`` method (preserved).
Returns:
Optional[dict]: The ``merge`` dict written (a safe copy), or None on failure —
an unknown ``directive``, a ``custom`` directive with no object available,
or any failure surfaced by :meth:`set_merge`.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def set_parameter(self, param_id: str, class_: Optional[str] = None, props: Optional[list] = None, links: Optional[list] = None, label: Optional[str] = None, usage: Optional[str] = None, constraints: Optional[list] = None, guidelines: Optional[list] = None, values: Optional[list] = None, select_cardinality: Optional[str] = None, select_choices: Optional[list] = None) -> Optional[dict]#
Create or update a ``modify.set-parameters`` entry (upsert, per-field merge).
When no ``set-parameter`` with *param_id* exists one is created from the supplied
fields. When one exists, each supplied field **overwrites** the corresponding
content; a field left as ``None`` leaves any existing content untouched. Most
commonly used to set a ``value`` or a ``constraint``.
``values`` and the ``select`` form (``select_cardinality`` / ``select_choices``)
are the OSCAL mutually-exclusive choice: supplying both is rejected. Supplying
``values`` clears any existing ``select`` (and vice versa). The assembled entry is
pruned/validated through the metaschema staging gate before it is committed, so an
invalid input leaves the profile unchanged.
Args:
param_id (str, required): The ``param-id`` this setting targets.
class_ (str, optional): The parameter ``class``.
props (list, optional): Property dicts.
links (list, optional): Link dicts.
label (str, optional): Parameter label (markup-line).
usage (str, optional): Usage prose (markup-multiline).
constraints (list, optional): Constraint dicts.
guidelines (list, optional): Guideline dicts.
values (list, optional): Parameter value strings. Mutually exclusive with the
``select_*`` arguments.
select_cardinality (str, optional): ``select.how-many`` (e.g. "one",
"one-or-more").
select_choices (list, optional): ``select.choice`` value strings.
Returns:
Optional[dict]: A safe copy of the written ``set-parameter``, or None on
failure (missing/blank ``param_id``, both value and select supplied, a
wrong argument type, or a result that fails metaschema validation).
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: str = '') -> bool#
Validate the profile, then (re)build ``controls_tree`` on success.
Mirrors :meth:`Catalog.validate`: the profile's authoritative scope and
organization tree is refreshed the moment the content is converted and
found to be valid OSCAL — no call to :meth:`resolve` is required. Building
the tree resolves the profile's imports if they are not already resolved.
When the content is not valid the tree is emptied and left marked stale so a
later access rebuilds it.
Args:
format (str, optional): Accepted for API compatibility with the base
method; does not alter the validation path.
Returns:
bool: True when every validation phase passes.
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
class ResolutionStatus#
Lifecycle state of a Profile's control resolution.
Members:
UNRESOLVED (str): "unresolved" — imports have not yet been resolved.
RESOLVING (str): "resolving" — resolution is in progress.
RESOLVED (str): "resolved" — the resolved catalog is available.
BLOCKED (str): "blocked" — resolution could not complete (e.g. missing import).
EXPIRED (str): "expired" — a previously resolved catalog is stale.
No public members.
function
def format_index_errors(errors: list) -> str#
Render metaschema-walk errors (from ``_walk_instance``) as a compact one-liner.
Args:
errors (list, required): The structured error dicts collected by a walk.
Returns:
str: A ``"; "``-joined summary, one clause per error.
oscal.oscal_implementation#
oscal_implementation — OSCAL implementation-layer model classes and helpers.
Provides the model classes for the OSCAL implementation models:
``ComponentDefinition`` (reusable control implementations for components) and
``SSP`` (System Security Plan). Both subclass ``OSCAL`` from ``oscal_content``.
Module-level helper functions build the nested SSP assemblies (components,
implemented requirements, by-component statements, responsible roles) and are
also exposed as ``SSP`` methods where appropriate.
Module constants:
(none exported)
class ComponentDefinition#
OSCAL Component Definition (cDef) model.
Represents reusable component definitions that describe how components
satisfy controls. Subclasses ``OSCAL``.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: 'str' = '') -> 'bool'#
Validate OSCAL content against the metaschema index in sequenced phases.
Phases (each recorded in ``validation_status``):
structure – all required fields and hierarchy are present
data-types – every leaf value matches its declared OSCAL datatype
allowed-values – every constrained value is within its enumerated set
cardinality – every array satisfies its min-occurs/max-occurs bounds
choice – every choice is mutually exclusive (at most one member present) and has a member when one is required
``validation_status["well-formed"]`` is set by ``initial_validation()``, not here.
All phases always run regardless of earlier failures, giving a complete picture
of issues in a single call. The format argument is accepted for API
compatibility but does not alter the validation path — ``_dict`` is always the
authoritative representation.
Returns True only when every phase passes (content_state reaches VALID).
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
class SSP#
OSCAL System Security Plan (SSP) model.
Subclasses ``OSCAL`` and adds SSP-specific methods for managing system
components, implemented requirements, and by-component statements.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_by_component(self, implemented_requirement_uuid: 'str', component_uuid: 'str', description: 'str', by_component_uuid: 'str' = '', implementation_status: 'str' = 'implemented', remarks: 'str' = '') -> 'Optional[dict]'#
Add a by-component statement to one of the SSP's implemented-requirements.
The by-component is **built from the supplied scalar fields** — no caller-provided
dict is stored verbatim — so it is schema-aligned by construction. It is appended
to the implemented-requirement identified by *implemented_requirement_uuid* and
returned as a safe copy (the live node stays in ``_dict``; further edits go through
methods). Through the method decorators this also enforces the read-only guard and
marks the document unsaved.
Args:
implemented_requirement_uuid (str, required): ``uuid`` of the target
implemented-requirement under ``control-implementation``.
component_uuid (str, required): UUID of the referenced system component.
description (str, required): How the component satisfies the requirement.
by_component_uuid (str, optional): UUID for the by-component; a new one is
generated when empty.
implementation_status (str, optional): ``implementation-status.state`` value.
Defaults to "implemented".
remarks (str, optional): Remarks prose (markdown).
Returns:
Optional[dict]: A safe copy of the new by-component, or None when the SSP has
no implemented-requirement with that uuid.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_component(self, component_type: 'str', component_title: 'str', component_description: 'str', op_status: 'str' = 'operational', component_uuid: 'str' = '', props: 'list' = [], links: 'list' = [], remarks: 'str' = '') -> 'Optional[dict]'#
Add a component to the SSP's ``system-implementation`` section.
Args:
component_type (str, required): The component ``type`` (e.g. "software").
component_title (str, required): The component title.
component_description (str, required): The component description.
op_status (str, optional): Operational ``status.state`` value.
Defaults to "operational".
component_uuid (str, optional): UUID for the component. A new UUID is
generated when empty.
props (list, optional): Property dicts to add.
links (list, optional): Link dicts to add.
remarks (str, optional): Remarks prose (markdown).
Returns:
Optional[dict]: The newly created component dict, or None on failure.
method
def append_impl_requirement(self, control_id: 'str', props: 'list' = [], links: 'list' = [], remarks: 'str' = '') -> 'Optional[dict]'#
Add an implemented-requirement to the SSP's ``control-implementation`` section.
Args:
control_id (str, required): The ID of the control being implemented.
props (list, optional): Property dicts to add.
links (list, optional): Link dicts to add.
remarks (str, optional): Remarks prose (markdown).
Returns:
Optional[dict]: The newly created implemented-requirement dict (with a
generated UUID), or None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: 'str' = '') -> 'bool'#
Validate OSCAL content against the metaschema index in sequenced phases.
Phases (each recorded in ``validation_status``):
structure – all required fields and hierarchy are present
data-types – every leaf value matches its declared OSCAL datatype
allowed-values – every constrained value is within its enumerated set
cardinality – every array satisfies its min-occurs/max-occurs bounds
choice – every choice is mutually exclusive (at most one member present) and has a member when one is required
``validation_status["well-formed"]`` is set by ``initial_validation()``, not here.
All phases always run regardless of earlier failures, giving a complete picture
of issues in a single call. The format argument is accepted for API
compatibility but does not alter the validation path — ``_dict`` is always the
authoritative representation.
Returns True only when every phase passes (content_state reaches VALID).
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
function
def append_component(ssp_obj: 'OSCAL', component_type: 'str', component_title: 'str', component_description: 'str', op_status: 'str' = 'operational', component_uuid: 'str' = '', props: 'list' = [], links: 'list' = [], remarks: 'str' = '') -> 'Optional[dict]'#
Add a component to an SSP's ``system-implementation`` section.
Args:
ssp_obj (OSCAL, required): The SSP instance to modify.
component_type (str, required): The component ``type`` (e.g. "software").
component_title (str, required): The component title.
component_description (str, required): The component description.
op_status (str, optional): Operational ``status.state`` value.
Defaults to "operational".
component_uuid (str, optional): UUID for the component. A new UUID is
generated when empty.
props (list, optional): Property dicts to add.
links (list, optional): Link dicts to add.
remarks (str, optional): Remarks prose (markdown).
Returns:
Optional[dict]: The newly created component dict, or None on failure.
function
def append_impl_requirement(ssp_obj: 'OSCAL', control_id: 'str', props: 'list' = [], links: 'list' = [], remarks: 'str' = '') -> 'Optional[dict]'#
Add an implemented-requirement to an SSP's ``control-implementation`` section.
Args:
ssp_obj (OSCAL, required): The SSP instance to modify.
control_id (str, required): The ID of the control being implemented.
props (list, optional): Property dicts to add.
links (list, optional): Link dicts to add.
remarks (str, optional): Remarks prose (markdown).
Returns:
Optional[dict]: The newly created implemented-requirement dict (with a
generated UUID), or None on failure.
oscal.oscal_assessment#
oscal_assessment — OSCAL assessment-layer model classes.
Provides the model classes for the OSCAL assessment models: ``AssessmentPlan``
(Security Assessment Plan / SAP), ``AssessmentResults`` (Security Assessment
Results / SAR), and ``POAM`` (Plan of Action and Milestones). Each subclasses
``OSCAL`` from ``oscal_content`` and inherits its common load/save/validate and
query behavior.
Module constants:
(none exported)
class AssessmentPlan#
OSCAL Assessment Plan (AP / SAP) model.
Represents an assessment plan that defines the scope, assets, activities,
and tasks for a security assessment. Subclasses ``OSCAL``.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: 'str' = '') -> 'bool'#
Validate OSCAL content against the metaschema index in sequenced phases.
Phases (each recorded in ``validation_status``):
structure – all required fields and hierarchy are present
data-types – every leaf value matches its declared OSCAL datatype
allowed-values – every constrained value is within its enumerated set
cardinality – every array satisfies its min-occurs/max-occurs bounds
choice – every choice is mutually exclusive (at most one member present) and has a member when one is required
``validation_status["well-formed"]`` is set by ``initial_validation()``, not here.
All phases always run regardless of earlier failures, giving a complete picture
of issues in a single call. The format argument is accepted for API
compatibility but does not alter the validation path — ``_dict`` is always the
authoritative representation.
Returns True only when every phase passes (content_state reaches VALID).
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
class AssessmentResults#
OSCAL Assessment Results (AR / SAR) model.
Represents the findings, observations, and risks produced by executing an
assessment plan. Subclasses ``OSCAL``.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: 'str' = '') -> 'bool'#
Validate OSCAL content against the metaschema index in sequenced phases.
Phases (each recorded in ``validation_status``):
structure – all required fields and hierarchy are present
data-types – every leaf value matches its declared OSCAL datatype
allowed-values – every constrained value is within its enumerated set
cardinality – every array satisfies its min-occurs/max-occurs bounds
choice – every choice is mutually exclusive (at most one member present) and has a member when one is required
``validation_status["well-formed"]`` is set by ``initial_validation()``, not here.
All phases always run regardless of earlier failures, giving a complete picture
of issues in a single call. The format argument is accepted for API
compatibility but does not alter the validation path — ``_dict`` is always the
authoritative representation.
Returns True only when every phase passes (content_state reaches VALID).
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
class POAM#
OSCAL Plan of Action and Milestones (POA&M) model.
Represents tracked security findings and their planned remediation
milestones. Subclasses ``OSCAL``.
classmethod
classmethod acquire(cls, source: 'str | dict | OscalRef | list', *, cache: "'CacheDirective | None'" = None)#
Acquire OSCAL content from one or more URI/reference sources.
The sources are treated as an ordered fallback list; the first that
resolves successfully is used.
Args:
source (str | dict | OscalRef | list, required): The reference(s) to
acquire. May be a URI/path string, an ``OscalRef``, a reference dict
containing at least ``"href"``, or a list mixing any of these.
cache (CacheDirective | None, optional): Caching directive applied to
remote fetches (e.g. ``CacheDirective.never()``,
``CacheDirective.refresh_now()``). Keyword-only. Defaults to the
standard 24h behavior.
Returns:
OSCAL: A new instance populated from the first resolvable source.
method
def add_import(self, href: 'str', uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], version: 'str' = '', remarks: 'str' = '', *, include_all: 'bool' = False) -> 'ImportResult'#
Add a first-level import to this document, backed by a back-matter resource.
Uniform across every model; legality is governed by :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — ``invalid``.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR) — ``invalid``; set
their single import with :meth:`retry_import` instead of add/remove.
* Otherwise an import is added while the count of *real* imports is below the
model's ``max`` (unbounded when ``max`` is None).
The import references a back-matter ``resource`` by UUID fragment
(``href="#<uuid>"``): if a resource whose ``rlink`` already targets ``href``
exists it is reused, otherwise one is created via :meth:`append_resource` with
which this method shares its resource parameters (``uuid``/``title``/
``description``/``props``/``remarks``). The ``href`` becomes the resource's
single ``rlink`` (with a best-effort ``media-type`` inferred from it), and
``version`` is appended to ``props`` as a ``prop`` named ``"version"``. An empty
placeholder import (href ``""``/``"#"``) is filled in place; otherwise the entry
is appended (list models) or set (single-import models). After placement the
import tree and any derived state are refreshed via :meth:`_after_imports_changed`.
Args:
href (str, required): Reference to the imported OSCAL file (XML/JSON/YAML);
becomes the created resource's ``rlink`` href.
uuid (str, optional): UUID for the created resource; generated when empty.
Ignored when an existing resource is reused. Mirrors :meth:`append_resource`.
title (str, optional): Title for the created resource.
description (str, optional): Description for the created resource.
props (list, optional): Property dicts for the created resource; ``version``
(below) is appended to these. Mirrors :meth:`append_resource`.
version (str, optional): Convenience — appended to ``props`` as a ``prop``
named ``"version"`` (resources have no native version field).
remarks (str, optional): Remarks (markdown) for the created resource.
include_all (bool, optional): Keyword-only, profile-only. When True a new
profile import selects all controls via ``include-all`` instead of the
default empty ``include-controls``/``with-ids`` placeholder. Ignored by
models whose imports carry no selection. Defaults to False.
Returns:
ImportResult: ``status`` of "added", "replaced", "duplicate", "invalid",
or "error", with the relevant ``entry`` and ``resource``.
method
def append_child(self, path: 'str', child: 'dict') -> 'dict | None'#
Appends a child dict to the list at the given JSON path.
Path segments are '/' separated, relative to the model root. The leaf
segment names the list key; it is created as an empty list if absent.
Args:
path (str): Slash-separated path to the target list relative to the
model root, e.g. "metadata/props" or "back-matter/resources".
child (dict): Dict to append to the list.
Returns:
dict | None: The appended child on success, None on failure.
method
def append_resource(self, uuid: 'str' = '', title: 'str' = '', description: 'str' = '', props: 'list' = [], rlinks: 'list' = [], base64: 'str' = '', remarks: 'str' = '') -> 'dict | None'#
Append a resource to the document's ``back-matter`` section.
Args:
uuid (str, optional): Resource UUID. A new UUID is generated when empty.
title (str, optional): Resource title.
description (str, optional): Resource description.
props (list, optional): Property dicts to add.
rlinks (list, optional): Resource-link (``rlink``) dicts to add.
base64 (str, optional): Base64-encoded inline content.
remarks (str, optional): Remarks prose (markdown).
Returns:
dict | None: The newly created resource dict, or None on failure.
method
def dump(self, filename: 'str' = '', format: 'str' = '', pretty_print: 'bool' = False) -> 'bool'#
Write the current OSCAL content to a file.
With no parameters, saves to the original location in the original format.
This will save to any valid filename, even if the file extension does not match the format.
Output keys/elements are emitted in canonical metaschema order (see :meth:`dumps`).
Args:
filename (str, optional): Path to write to. Defaults to the original
source location when empty.
format (str, optional): Output format — one of ``OSCAL_FORMATS``
("xml", "json", "yaml", "yml"). Defaults to the original format when empty.
pretty_print (bool, optional): Whether to pretty-print the output.
Defaults to False.
Returns:
bool: True if the write succeeded, False otherwise.
method
def dumps(self, format: 'str' = '', pretty_print: 'bool' = False) -> 'str'#
Serialize the current content to a string in the specified format.
Keys/elements are emitted in canonical NIST metaschema order: XML element
order is schema-required and always canonical; JSON/YAML key order is
canonical on a best-effort basis (see :meth:`_ordered_dict`).
Parameters:
- format (str): The target format for serialization ("xml", "json", or "yaml")
Defaults to the original format of the content if not specified.
- pretty_print (bool): Whether to pretty-print the output. Defaults to False.
Returns:
- str: The serialized content as a string.
property
property duplicate_imports#
Return import_list entries detected as duplicates of an earlier import.
Duplicates are non-blocking — they do NOT prevent imports_resolved from
becoming True — but they remain available for the caller to act on via
retry_import (supply a different source), ignore_import, or remove_import.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
duplicate entry holds no document of its own (the original READY entry carries
it), so for every entry here ``object_uuid`` is always ``None`` and the six
summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are always ``""``.
property
property failed_imports#
Return import_list entries that failed, each carrying a populated 'failure' field.
These are blocking: while any failed import remains, content_state stays
at VALID and imports_resolved is False.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception: a
failed import never acquires its document, so for every entry here ``object_uuid``
is always ``None`` and the six summary fields (``model``, ``title``,
``oscal_version``, ``version``, ``published``, ``last_modified``) are always ``""``.
method
def find_in_import_tree(self, fragment_id: 'str', kinds=None, _seen=None) -> 'Optional[dict]'#
Resolve an id/uuid by searching this document and its import tree.
OSCAL cross-references (``href="#..."``) can point at content that lives in an
imported document — a back-matter ``resource`` (by uuid), a metadata ``role`` (by
id), ``party`` (by uuid), ``location`` (by uuid), or ``responsible-party`` (by
role-id), or a ``control``/``group``/``param``/``part`` (by id). This walks
``self`` first, then each imported document depth-first (de-duplicated,
cycle-safe), and returns the first match together with the document that owns it.
Args:
fragment_id (str, required): The bare id/uuid to resolve (no leading ``#``).
kinds (Iterable[str] | None, optional): Restrict the search to these element
kinds (subset of :attr:`_RESOLVE_KINDS`); ``None`` searches all.
_seen (set | None, optional): Internal cycle-guard.
Returns:
Optional[dict]: ``{"element", "kind", "id", "object_uuid", "href"}`` — a safe
copy of the found element, its kind, the owning document's root uuid and
resolved href — or None when not found anywhere in the tree.
classmethod
classmethod from_string(cls, content: 'str', *, href: 'str | None' = None)#
Explicit constructor for in-memory OSCAL string content.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): URI identifying the source. Keyword-only.
Defaults to None.
Returns:
OSCAL: A new instance (delegates to :meth:`loads`).
method
def get_location_by_uuid(self, location_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``location`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.locations`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a location with the given uuid
is found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_oscal_object(self, uuid, _seen=None)#
Return the LIVE imported OSCAL document whose root UUID matches ``uuid``.
Searches this document and its resolved imports depth-first, de-duplicating
objects shared across multiple import paths (the same large catalog reached
two ways is visited once). This underpins the import mechanism's object reuse
and is the companion to :attr:`import_tree`: the tree carries each node's
``object_uuid``; pass one here to obtain the corresponding live instance.
Unlike the model getters, this returns the LIVE object (not a copy) — it is a
document handle meant for working with that instance through its own methods.
Args:
uuid (str, required): The root UUID of the document to locate.
_seen (set | None, optional): Object ids already visited; used internally
for cycle-safety. Defaults to None.
Returns:
OSCAL | None: The matching live document, or None if not found.
method
def get_parameter_by_id(self, param_id: 'str', with_source: 'bool' = False) -> 'Optional[dict]'#
Return a parameter defined anywhere in scope, or None.
Searches this document and its import tree for a ``param`` with the given id —
covering parameters defined at control, group, or catalog level (and reached
through imported catalogs/profiles). Subclasses may override to prefer resolved
content. See :meth:`_lookup_in_scope` for the ``with_source`` locator form.
method
def get_party_by_uuid(self, party_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``party`` (by uuid) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a party with the given uuid is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def get_resource_by_uuid(self, resource_uuid: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a back-matter resource defined anywhere in scope, or None.
Looks in THIS document's ``back-matter`` first; on a local miss the search
cascades out through the immediately imported documents and continues depth-first
along every branch (de-duplicated and cycle-safe) until a ``resource`` whose
``uuid`` matches is found or every branch is exhausted. This lets a cross-reference
(``href="#uuid"``) resolve even when the resource is defined in an imported
document rather than locally. Subclasses may override to prefer resolved content.
Args:
resource_uuid (str, required): The bare resource UUID to resolve (no ``#``).
with_source (bool, optional): Return the full locator (element + owning
``object_uuid``/``href``) instead of the bare resource; useful for
resolving the resource's relative rlink hrefs. Defaults to False.
local_only (bool, optional): Search only THIS document, never imports.
Defaults to False.
Returns:
Optional[dict]: A safe copy of the matching resource (or locator), or None.
method
def get_responsible_party_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``responsible-party`` (by role-id) in scope, or None.
A ``responsible-party`` is keyed by the ``role-id`` it fulfills. Looks in THIS
document's ``metadata.responsible-parties`` first, then (unless ``local_only``)
cascades depth-first through the import tree until one with the given role-id is
found or every branch is exhausted. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
Note: this targets metadata-level ``responsible-parties`` only. The
``responsible-role`` assemblies embedded throughout implementation/assessment
models are model-specific and handled by a separate, later cascade.
method
def get_role_by_id(self, role_id: 'str', with_source: 'bool' = False, local_only: 'bool' = False) -> 'Optional[dict]'#
Return a metadata ``role`` (by id) defined anywhere in scope, or None.
Looks in THIS document's ``metadata.roles`` first, then (unless ``local_only``)
cascades depth-first through the import tree until a role with the given id is
found or every branch is exhausted — so a ``role-id`` reference resolves even when
the role is defined in an imported document. See :meth:`_lookup_in_scope` for
``with_source`` and ``local_only``.
method
def ignore_import(self, href: 'str') -> 'bool'#
Mark an import as intentionally ignored.
The entry remains in import_list with status IGNORED. Like DUPLICATE,
IGNORED entries are treated as non-blocking: once all remaining entries
are READY, DUPLICATE, or IGNORED, content_state advances to
IMPORTS_RESOLVED.
Typical use: the caller presents a DUPLICATE entry to the user and the
user explicitly chooses to ignore it rather than supply a replacement.
The same priority ordering used by retry_import applies when multiple
entries share the same href — DUPLICATE and INVALID are preferred over
READY.
Args:
href: Any href that identifies the entry (href_original, href_valid,
failure.uri, or an href_list item href).
Returns:
True if an entry was found and updated, False if no match was found.
property
property import_tree#
Recursive import tree built lazily on first access and cached.
Returns a root node dict representing this document, with an ``imports`` key
holding the first-level imports; each import is a node of the same shape,
recursively. The tree is a SAFE COPY of the cached structure — mutating it does
not affect the cache; use :meth:`rebuild_import_tree` to force a fresh traversal.
The tree carries no live OSCAL objects, so it stays small and safe to
serialize/transmit. Instead, each node identifies its document by UUID and
summary metadata; call :meth:`get_oscal_object` with ``object_uuid`` to obtain
the live instance when one is actually needed.
Each node (root and every import) has these keys:
* ``href_original`` (str): the import href as written in the source document.
* ``href_valid`` (str): the resolved href actually loaded, if any.
* ``href_list`` (list[dict]): every href attempted, with per-attempt status.
* ``status`` (ImportState): READY, INVALID, DUPLICATE, or IGNORED.
* ``is_valid`` / ``is_local`` / ``is_remote`` / ``is_cached`` (bool): provenance.
* ``object_uuid`` (str | None): the imported document's root UUID, or None when
it was not acquired. Pass to :meth:`get_oscal_object` for the live object.
* ``model`` (str): the imported document's model type (e.g. "catalog").
* ``title`` (str): the imported document's metadata title.
* ``oscal_version`` (str): the OSCAL version (no ``v`` prefix, e.g. "1.1.3").
* ``version`` (str): the document's own metadata version.
* ``published`` (str): the metadata publication timestamp (RFC-3339), if present.
* ``last_modified`` (str): the metadata last-modified timestamp (RFC-3339).
* ``failure`` (ImportFailure | None): the failure record when ``status`` is
INVALID, else None.
* ``imports`` (list[dict]): child import nodes (empty when none or unacquired).
The six summary fields (``model``, ``title``, ``oscal_version``, ``version``,
``published``, ``last_modified``) are populated only when the object was
successfully acquired; otherwise each is an empty string ``""``.
This is the single source of truth for the node/entry field schema. The import
getters :attr:`failed_imports`, :attr:`duplicate_imports`, and
:attr:`unresolved_imports` return these same per-entry fields as flat lists
(without the recursive ``imports`` key). Note that those getters only ever hold
failed or duplicate entries, which carry no loaded document — so in their results
``object_uuid`` is always ``None`` and the six summary fields are always ``""``.
property
property imports_resolved#
bool: True when all imports resolved (``content_state >= IMPORTS_RESOLVED``).
method
def initial_validation(self, content: 'str') -> 'bool'#
Perform initial validation of content and advance the content state.
Detects the format, checks that the content is a recognized, well-formed
OSCAL format (XML, JSON, or YAML), identifies the model/version and extracts
summary metadata, then invokes full OSCAL schema validation. Updates
``self.content_state`` progressively as each stage passes.
Args:
content (str, required): The raw OSCAL content to validate.
Returns:
bool: True if initial validation is successful, False otherwise.
property
property is_acquired#
bool: True once content has been acquired (``content_state >= ACQUIRED``).
property
property is_read_only#
bool: True when the content may not be mutated (most-restrictive-wins).
Read-only when any of these hold: the underlying writable flag is set,
the content is canonical/published (``is_canonical``), or the document is
write-locked by a *different* actor in its workspace (see
:meth:`_locked_by_other`). Because every mutation gate checks this property,
canonical status and workspace locks are enforced uniformly.
property
property is_remote#
bool: True when the content originates from a remote source (not a local file).
property
property is_valid#
bool: True when content passes OSCAL validation (``content_state >= VALID``).
property
property is_well_formed#
bool: True when content is well-formed (``content_state >= WELL_FORMED``).
method
def json_query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using JSON key name syntax (via :class:`NativePath`).
Steps use the actual JSON key names (``controls``, ``props``, ``parts``, …)
with no metaschema translation required. Arrays are iterated
transparently, so ``//controls[id='ac-2.2']`` navigates directly into
any ``controls`` array at any depth.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits.
Parameters
----------
path : str
Path expression using JSON key names, e.g.
``"//controls[id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def json_query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`json_query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using JSON key names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
classmethod
classmethod load(cls, source: 'str | os.PathLike | _ReadableSource', *, href: 'str | None' = None)#
Initialize an instance from a local file path or file-like object.
Aligns with Python's conventional ``load(...)`` behavior (cf. ``json.load`` /
``pickle.load``): the source is a **local** path or a file-like object. Use
``loads(...)`` for in-memory strings/dicts, and ``acquire(...)`` for URI/reference
sources (``http``/``https``/``file``/``ftp``/…) — ``load`` does not fetch remotely.
Args:
source (str | os.PathLike | file-like, required): A filesystem path or an
object with a ``read()`` method.
href (str | None, optional): URI label identifying the source; defaults to
the path or the object's ``name``. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the loaded content.
Raises:
TypeError: If ``source`` is neither path-like nor file-like.
classmethod
classmethod loads(cls, content: 'str | dict', *, href: 'str | None' = None)#
Initialize an instance from in-memory OSCAL content.
Args:
content (str | dict, required): OSCAL content already in memory, as a
serialized string or a dict.
href (str | None, optional): URI identifying the original content
source. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance populated from the content.
classmethod
classmethod new(cls, title: 'str', version: 'str' = '', published: 'str' = '')#
Create a new OSCAL document from a template.
Must be called on a specific model class (``Catalog.new()``,
``Profile.new()``, etc.), not on ``OSCAL`` directly.
Args:
title (str, required): Document title (stored in metadata).
version (str, optional): Document version (stored in metadata).
Defaults to "".
published (str, optional): Publication date (stored in metadata).
Defaults to "".
Returns:
OSCAL: A new editable instance of the model subclass.
Raises:
TypeError: If called on the ``OSCAL`` base class instead of a subclass.
classmethod
classmethod open(cls, source: 'str | os.PathLike | dict | OscalRef | list | _ReadableSource', *, href: 'str | None' = None)#
Universal constructor — inspects the source type and delegates to
the appropriate loader.
Delegates to:
load() — file-like objects (anything with .read()), PathLike
objects, and bare string paths (no URI scheme)
acquire() — URI strings (http/https/file/ftp/...), OscalRef,
reference dicts, and fallback lists
Args:
source (str | os.PathLike | dict | OscalRef | list | file-like, required):
Any supported OSCAL source. String values with a URI scheme are
acquired; bare paths and file-like objects are loaded.
href (str | None, optional): URI label passed through to ``load()`` when
applicable. Keyword-only. Defaults to None.
Returns:
OSCAL: A new instance from the appropriate loader.
property
property origin_state#
Computed from is_local, is_cached, and TTL. Changes over time for cached remote content.
method
def put(self, path: 'str', value, mode: "Literal['replace', 'insert']" = 'replace', *, validate: 'bool' = False, check_refs: 'bool' = False) -> 'bool'#
Write a value into the JSON content at a slash-separated path.
This is the shared, guarded entry point for JSON mutations. It centralizes the
defensive behavior that would otherwise be repeated at every call site:
the read-only / content guard (:meth:`_can_mutate`), auto-creation of missing
intermediate objects and optional OSCAL arrays, and dirty-state bookkeeping
(``is_unsaved`` / ``last_modified``).
Path segments are ``'/'`` separated and relative to the model root (e.g.
``"metadata/title"`` or ``"metadata/roles/0/title"``). A numeric segment
indexes a list; any other segment names a dict key. Missing intermediate dict
keys are created automatically.
Args:
path (str, required): Slash-separated path relative to the model root.
value (Any, required): The JSON-compatible value to write.
mode (str, optional): ``"replace"`` (default) sets the value at ``path``;
``"insert"`` treats the leaf as an optional array — creating it if
absent — and appends ``value`` to it.
validate (bool, optional): When True, run metaschema-driven datatype/regex
and allowed-value checks before writing (see :meth:`_validate_write`).
Currently a permissive extension point. Defaults to False.
check_refs (bool, optional): When True, run referential-integrity checks
before writing (see :meth:`_check_referential_integrity`). Currently a
permissive extension point. Defaults to False.
Returns:
bool: True on success, False on any failure (guard, bad path/index,
validation, or type mismatch). No mutation occurs on failure.
method
def query(self, path: 'str', context: 'dict | None' = None) -> 'list'#
Query the JSON content using XML element name syntax (via :class:`OSCALPath`).
Steps use OSCAL XML element names (``control``, ``prop``, ``part``, …)
and the metaschema index translates them to the correct JSON keys
(``controls``, ``props``, ``parts``, …) including array/BY_KEY grouping.
The returned list contains SAFE COPIES — mutating a result does not change the
document; use the model's mutation methods for persistent edits. A single deep
copy of the whole result set preserves internal identity between overlapping
matches.
Parameters
----------
path : str
Path expression using XML element names, e.g.
``"//control[@id='ac-2.2']"`` or ``"/*/metadata/title"``.
context : dict, optional
Sub-dict to query within. Defaults to the full document dict
(``self._dict``).
Returns a list of matching JSON values (as copies), or ``[]`` on error / no match.
method
def query_one(self, path: 'str', context: 'dict | None' = None, default=None)#
Return the first result of :meth:`query` as a safe copy, or ``default``.
Args:
path (str, required): Path expression using OSCAL XML element names.
context (dict | None, optional): Sub-dict to query within. Defaults to the
full document dict.
default (Any, optional): Value to return when there is no match. Returned
as-is (not copied). Defaults to None.
Returns:
Any: A safe copy of the first matching JSON value, or ``default``.
method
def reachable_ids(self, _seen=None) -> 'set'#
Return every ``id``/``uuid`` value in this document and its import tree.
Used to decide whether a cross-reference resolves somewhere in scope. The walk
is de-duplicated and cycle-safe across the import graph.
method
def rebuild_import_tree(self) -> 'dict'#
Discard the cached import tree and rebuild it from the current import_list.
Returns:
dict: The freshly built root node of the recursive import tree.
method
def remove_import(self, href: 'str') -> 'bool'#
Remove a first-level import statement from this document.
Operates only on this document's own imports, never on descendants. The
import *statement* is deleted from ``self._dict``; any back-matter resource
it referenced via a URI fragment (``href="#uuid"``) is intentionally
preserved. The affected part of the import tree is refreshed and any
model-specific derived state (e.g. a Profile's resolved catalog) is reset
via :meth:`_after_imports_changed`.
Cardinality is enforced from :data:`_IMPORT_SPEC`:
* Models with no top-level import (catalog, mapping-collection) — invalid.
* Fixed-cardinality models where ``min == max`` (SSP/AP/AR require exactly
one) — invalid; change that import with :meth:`retry_import` instead.
* Otherwise the removal is rejected when it would drop the count of *real*
imports (non-empty, non-``"#"`` href) below the model's minimum. Removing
an empty placeholder is always allowed for variable-cardinality models.
Args:
href: Any href that identifies the import — its literal href (including
a ``"#uuid"`` fragment or an empty ``""``/``"#"`` placeholder), or
the resolved target href of the back-matter resource it references.
Returns:
True if an import was found and removed; False if not found, the
cardinality forbids removal, or the content is read-only.
method
def resolve_imports(self, base_path: 'str' = '', *, cache_directive: "'CacheDirective | None'" = None) -> 'list'#
Discover and load every OSCAL document referenced by this document's
import declarations. Populates (and returns) self.import_list.
Because ``validate()`` resolves imports, loading a document cascades this
depth-first down the whole import tree. Two guards prevent runaway on shared
or circular graphs: the object registry ensures a file loaded via multiple
branches (a diamond) is held once, and an import that resolves back to an
ancestor still being resolved (a cycle) is marked ``ImportState.CYCLIC`` and
not loaded — the ancestor stays valid and recursion stops there.
A ``cache_directive`` is applied to this document's direct imports; a
``refresh`` or ``CACHE_NEVER`` directive bypasses the in-memory registry so
the imported content is genuinely reloaded rather than reused.
Recognised import locations by model:
profile → import/@href
component-definition → import-component-definition/@href,
component/control-implementation/@source,
capability/control-implementation/@source
system-security-plan → import-profile/@href
assessment-plan → import-ssp/@href
plan-of-action-and-milestones → import-ssp/@href
assessment-results → import-ap/@href
mapping-collection → mapping/source/@href,
mapping/target/@href
Args:
base_path (str, optional): Directory used to resolve relative hrefs.
Defaults to the directory of this document's own href.
cache_directive (CacheDirective | None, optional): Caching directive
applied to this document's direct import fetches. Keyword-only.
Defaults to the standard 24h behavior.
Returns:
list[dict]: self.import_list, one entry per discovered reference.
method
def retry_import(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Retry a failed import identified by href, using a replacement source.
The failed import is matched by href (original or previously resolved),
then re-attempted using ``replacement_href`` (resolved relative to this
document's location).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def retry_imports(self, failed_href: 'str', replacement_href: 'str') -> 'bool'#
Compatibility alias for :meth:`retry_import` (plural method name).
Args:
failed_href (str, required): The href of the failed import to retry.
replacement_href (str, required): The replacement href to attempt.
Returns:
bool: True if the import was successfully resolved on retry, False otherwise.
method
def set_metadata(self, content: 'dict' = {}) -> 'bool'#
Set simple metadata fields on the OSCAL content's ``metadata`` section.
Complex metadata collections (revisions, roles, parties, links, props, etc.)
are not yet supported and are skipped with a warning.
Args:
content (dict, optional): Mapping of metadata field name to value to set.
Defaults to an empty dict.
Returns:
bool: True on success, or None when the content cannot be mutated.
method
def supports(self, name: 'str') -> 'bool'#
Return True if this model exposes ``name`` as a method or attribute.
Lets callers look before they leap when an operation is only valid for
some OSCAL models::
if doc.supports("add_control"):
doc.add_control(...)
Only class-level (shared, model-defined) members count; per-instance
attributes set at runtime are ignored, so the check reflects the model's
capabilities rather than incidental state.
Args:
name (str, required): The method/attribute name to test.
Returns:
bool: True if the model defines ``name``.
property
property unresolved_imports#
Return import_list entries that still warrant user attention.
Includes failed imports (INVALID) and duplicates (DUPLICATE). Excludes
READY (resolved) and IGNORED (explicitly dismissed by the caller).
This is the signal a UI should use to decide whether to keep showing
import-resolution affordances. It stays non-empty while there is still
something the user can act on — even when ``imports_resolved`` is already
True because the only remaining items are non-blocking duplicates.
Once every entry is READY or IGNORED, this list is empty and the
resolution UI can close.
Each entry is a safe copy shaped like an :attr:`import_tree` node (same field
schema, but a flat list without the recursive ``imports`` key). Exception:
unresolved entries (failed or duplicate) never carry a loaded document, so for
every entry here ``object_uuid`` is always ``None`` and the six summary fields
(``model``, ``title``, ``oscal_version``, ``version``, ``published``,
``last_modified``) are always ``""``.
method
def update_import(self, *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None, new_resource: 'bool' = True) -> 'ImportResult'#
Modify the single import of a one-import model (SSP, AP, AR, or POA&M).
These models carry exactly one import (POA&M: at most one), so :meth:`add_import`
and :meth:`remove_import` do not apply — this is how their import is changed. It
takes the same resource fields as :meth:`update_resource` (``title``,
``description``, ``props``, ``rlinks``, ``remarks`` — ``None`` leaves a field
unchanged; arrays replace wholesale) plus ``new_resource``. The behavior depends
on what the existing import points at:
* **No import yet** (only possible for POA&M): the call is forwarded to
:meth:`add_import` (``new_resource`` does not apply and is omitted); the import
target's href is taken from the first supplied ``rlink``.
* **Import is a direct URI** (or an empty ``""``/``"#"`` placeholder): a new
back-matter resource is created (via :meth:`append_resource`) — its ``rlink`` is
the supplied ``rlinks`` when given, otherwise the existing URI — and the import's
href is repointed to that resource's ``#uuid``. (``new_resource`` does not apply:
there is no backing resource to update.) Status: "replaced".
* **Import is a ``#uuid`` fragment**:
- ``new_resource=True`` (default): a brand-new resource with a new UUID is
created via :meth:`append_resource` from the supplied fields, and the import's
href is repointed to it. The prior resource is **left in place** — it is not
deleted, because other content may reference it (including OSCAL documents not
currently loaded that import this one and cite that resource by UUID). Because
the new resource is built only from the fields you pass, supply ``rlinks`` (and
any props) for the new target; read the old resource first with
:meth:`get_resource_by_uuid` if you want to carry values forward. Status:
"replaced".
- ``new_resource=False``: the existing resource is edited in place via
:meth:`update_resource` (same wholesale array-replacement semantics and
data-loss caveats — see that method). The import's href is unchanged. Status:
"updated".
Args:
title (str | None, optional): Resource title; ``""`` removes it.
description (str | None, optional): Resource description; ``""`` removes it.
props (list | None, optional): Replacement property dicts.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). Also the source of the import target's
href when creating a resource or bootstrapping a POA&M import.
remarks (str | None, optional): Resource remarks (markdown); ``""`` removes it.
new_resource (bool, optional): When the import already references a ``#uuid``
resource, True (default) creates a new resource and repoints; False edits
the existing resource in place. Ignored for the direct-URI/placeholder and
no-import cases. Defaults to True.
Returns:
ImportResult: ``status`` "added"/"replaced"/"updated" on success (``ok`` True),
or "invalid"/"error" otherwise, carrying the import ``entry`` and the
created/updated ``resource``.
method
def update_resource(self, uuid: 'str', *, title: 'Optional[str]' = None, description: 'Optional[str]' = None, props: 'Optional[list]' = None, rlinks: 'Optional[list]' = None, remarks: 'Optional[str]' = None) -> 'dict | None'#
Update fields of an existing ``back-matter`` resource, selected by ``uuid``.
Only a resource defined in THIS document's own ``back-matter`` is editable
(imported resources are not). Each field is optional and independent:
* ``None`` (the default) leaves that field untouched.
* A scalar (``title``/``description``/``remarks``) replaces the current value;
an empty string removes the field entirely.
* An array (``props``/``rlinks``) **replaces the existing array wholesale** — the
old list is discarded and the supplied list becomes the new one. Passing an
empty list removes the field.
.. warning::
Array replacement is destructive and total, not a merge. Whatever you pass
for ``props`` or ``rlinks`` becomes the *complete* new list; every entry not
present in your list is permanently dropped. A resource frequently carries
entries you did not author and may not be aware of — for example multiple
``rlinks`` pointing at format variants (``.xml``/``.json``) of the same file,
``rlinks`` bearing ``hashes`` for integrity, a ``base64`` payload, or ``props``
added by other tools or pipelines. Supplying a partial list here silently
deletes all of those. There is no undo.
**Recommended pattern:** read the current resource first with
:meth:`get_resource_by_uuid`, mutate the copy it returns (append to / edit the
existing ``props``/``rlinks`` rather than rebuilding them from scratch), then
pass those full arrays back to this method. That way you extend the resource
instead of overwriting it, and nothing you did not intend to touch is lost::
res = doc.get_resource_by_uuid(uuid) # safe copy of the whole resource
res["rlinks"].append({"href": "catalog.json",
"media-type": "application/json"})
doc.update_resource(uuid, rlinks=res["rlinks"]) # full list, nothing dropped
Args:
uuid (str, required): UUID of the local back-matter resource to update.
title (str | None, optional): New title; ``""`` removes it. ``None`` = unchanged.
description (str | None, optional): New description; ``""`` removes it.
props (list | None, optional): Replacement property dicts (see
:func:`append_props`). ``[]`` removes all props. ``None`` = unchanged.
rlinks (list | None, optional): Replacement ``rlink`` dicts (``href`` plus
optional ``media-type``/``hashes``). ``[]`` removes all rlinks.
remarks (str | None, optional): New remarks (markdown); ``""`` removes them.
Returns:
dict | None: A safe copy of the updated resource, or None when the content is
read-only, ``uuid`` is empty, or no local resource with that UUID exists.
method
def validate(self, format: 'str' = '') -> 'bool'#
Validate OSCAL content against the metaschema index in sequenced phases.
Phases (each recorded in ``validation_status``):
structure – all required fields and hierarchy are present
data-types – every leaf value matches its declared OSCAL datatype
allowed-values – every constrained value is within its enumerated set
cardinality – every array satisfies its min-occurs/max-occurs bounds
choice – every choice is mutually exclusive (at most one member present) and has a member when one is required
``validation_status["well-formed"]`` is set by ``initial_validation()``, not here.
All phases always run regardless of earlier failures, giving a complete picture
of issues in a single call. The format argument is accepted for API
compatibility but does not alter the validation path — ``_dict`` is always the
authoritative representation.
Returns True only when every phase passes (content_state reaches VALID).
method
def walk_imports(self, visitor_fn, depth=0, _seen=None, *, scope='successful')#
Walk the import tree depth-first, calling ``visitor_fn(entry, depth)`` for each entry.
Args:
visitor_fn (Callable, required): Callable receiving ``(entry_dict, depth_int)``.
depth (int, optional): Current recursion depth; used internally. Defaults to 0.
_seen (set | None, optional): Object ids already visited; used internally to
prevent cycles. Defaults to None.
scope (str, optional): Keyword-only. Which entries to visit — "successful"
(default) visits only READY imports and recurses into them; "failed" visits
only INVALID/NOT_LOADED imports without recursion; "all" visits every entry,
recursing only into READY imports.
Returns:
None
property
property xml#
Return the content as an XML string in canonical element order.
Rebuilds from the current dict via the metaschema converter (reflecting
the latest edits, in schema-required element order) and retains no tree.
oscal.oscal_registry#
oscal_registry — process-shared identity map for loaded OSCAL objects.
Ensures a given OSCAL document is held in memory once and reused across branches
of an import tree (and across separate resolves in the same process), so two
references to the same file share a single object instead of loading it twice.
Objects are keyed by a composite **content identity** — ``(root-uuid,
last-modified, published)`` — which treats the same content as identical
regardless of format or location, with a **canonicalized href** as a pre-fetch
fast path. Values are held via weak references (``WeakValueDictionary``), so an
object stays registered only while some importer still holds it and is dropped
automatically once no longer referenced.
The default registry is a process-global singleton (``get_registry()``). The
``ObjectRegistry`` class is injectable so a future Workspace/session can own an
isolated instance.
Module constants:
(none exported)
class ObjectRegistry#
An identity map of loaded OSCAL objects, keyed by content identity and href.
Lookups check the canonical href first (cheap, pre-fetch), then the composite
content-identity key. Stale entries — objects whose own TTL has expired
(``is_cache_expired``) — are treated as misses and dropped so the caller
reloads. Thread-safe via an internal lock.
method
def __init__(self) -> None#
Initialize an empty registry (weak identity/href maps and a resolution stack).
method
def alias_href(self, href: str, obj: Any) -> None#
Point an additional canonical href at an already-registered object.
Args:
href (str, required): The canonical href to alias.
obj (Any, required): The object the href should resolve to.
method
def enter_resolving(self, href: str) -> None#
Mark a canonical href as currently being resolved (push onto the DFS stack).
method
def exit_resolving(self, href: str) -> None#
Unmark a canonical href once its resolution completes (pop from the stack).
method
def forget(self, obj: Any) -> None#
Remove every reference to ``obj`` from the registry (thread-safe).
Used to drop a document that was only transiently registered (e.g. a root
registered by identity key for the duration of its import resolution).
method
def get(self, *, key: Optional[tuple] = None, href: str = '') -> Optional[Any]#
Return a live, fresh object matching ``href`` (checked first) or ``key``.
Args:
key (tuple | None, optional): Composite content-identity key.
href (str, optional): Canonicalized href.
Returns:
Any | None: The registered object, or None on miss or when the match is
stale (its ``is_cache_expired`` is True), in which case it is dropped.
method
def is_resolving(self, href: str) -> bool#
Return True when ``href`` is an ancestor currently being resolved (a cycle).
method
def register(self, obj: Any, *, key: Optional[tuple] = None, href: str = '') -> Any#
Register ``obj`` under its content-identity key and/or canonical href.
Args:
obj (Any, required): The object to register.
key (tuple | None, optional): Composite content-identity key.
href (str, optional): Canonicalized href.
Returns:
Any: The registered object (``obj``).
function
def get_registry() -> oscal.oscal_registry.ObjectRegistry#
Return the currently active object registry.
Returns the registry activated by :func:`use_registry` (e.g. a Workspace's own
registry) when one is in effect on the current context, otherwise the
process-global default. Because a document load cascades synchronously, every
object created during the load picks up whichever registry is active.
Returns:
ObjectRegistry: The active registry, or the process-global default.
function
def use_registry(registry: oscal.oscal_registry.ObjectRegistry)#
Activate ``registry`` for the duration of the ``with`` block.
Objects created while this context is active (including transitively-loaded
imports) use ``registry`` instead of the process-global default.
Args:
registry (ObjectRegistry, required): The registry to activate.
Yields:
ObjectRegistry: The activated registry.
oscal.oscal_cache#
oscal_cache — on-disk cache of remote OSCAL content.
Provides a persistent, cross-session cache of content fetched from remote URLs so
the same remote document is not downloaded repeatedly. It reuses the shared
``filecache`` file-store schema (the same table the support database uses) in a
separate ``local_cache.db`` located alongside the support database. The database
is created lazily on first use, not at startup.
Cached content is keyed by its (canonicalized) remote URL via the ``filecache``
``original_location`` column, with the fetch time stored in ``acquired``; an entry
is served only while it is within ``LOCAL_CACHE_TTL`` seconds of that time,
otherwise it is refetched and the entry refreshed.
This complements the in-memory object registry (``oscal_registry``): the registry
avoids re-loading/parsing a live object, while this cache avoids the network round
trip across process runs.
Caching is controlled per fetch by a :class:`CacheDirective`. The directive is
applied first, then the fetch is evaluated for local reuse vs. refresh. Because
the directive's TTL is compared against the entry's last-fetch time, changing the
TTL re-evaluates freshness against that time (e.g. an entry fetched 6h ago is
still fresh under a new 12h TTL). ``CACHE_NEVER`` purges any copy and always
fetches remotely; ``CACHE_FOREVER`` reuses a copy of any age; ``refresh`` forces a
refetch now.
Module constants:
LOCAL_CACHE_TTL (int): Default seconds a cached item stays fresh (86400 = 24h).
CACHE_FOREVER (int): TTL sentinel — never expires (reuse a copy of any age).
CACHE_NEVER (int): TTL sentinel — do not cache (purge and always fetch remotely).
LOCAL_CACHE_FILENAME (str): Filename of the cache database ("local_cache.db").
class CacheDirective#
A per-fetch instruction for how the remote-content cache should behave.
The directive is applied first, then the fetch is evaluated: the (possibly
overridden) TTL is compared against the cached entry's last-fetch time to decide
whether the local copy is reused or the content is refetched.
Attributes:
ttl (int): Freshness window in seconds, or a sentinel — ``CACHE_FOREVER``
(reuse a copy of any age) or ``CACHE_NEVER`` (purge and always fetch).
Defaults to ``LOCAL_CACHE_TTL`` (24h).
refresh (bool): When True, force a refetch now regardless of freshness
(the refreshed content replaces the cached copy). Defaults to False.
classmethod
classmethod default(cls) -> 'CacheDirective'#
Default behavior: 24h TTL, no forced refresh.
Returns:
CacheDirective: A directive with the default TTL and no refresh.
classmethod
classmethod forever(cls) -> 'CacheDirective'#
Keep the cached copy until manually purged or refreshed.
Returns:
CacheDirective: A directive with ``ttl=CACHE_FOREVER``.
classmethod
classmethod never(cls) -> 'CacheDirective'#
Never cache: purge any existing copy and always fetch remotely.
Returns:
CacheDirective: A directive with ``ttl=CACHE_NEVER``.
classmethod
classmethod of(cls, seconds: int) -> 'CacheDirective'#
Cache with a specific TTL.
Args:
seconds (int, required): Freshness window in seconds.
Returns:
CacheDirective: A directive with ``ttl=seconds``.
classmethod
classmethod refresh_now(cls, ttl: int = 86400) -> 'CacheDirective'#
Force a refetch now, then cache the result.
Args:
ttl (int, optional): TTL to apply to the refreshed copy. Defaults to
``LOCAL_CACHE_TTL`` (24h).
Returns:
CacheDirective: A directive with ``refresh=True`` and the given ``ttl``.
class LocalCache#
Persistent cache of remote content, backed by a ``filecache`` table.
The backing ``local_cache.db`` is opened/created lazily on first access. Entries
are keyed by remote URL and expire ``LOCAL_CACHE_TTL`` seconds after they were
fetched.
method
def __init__(self, db_path: str = '') -> None#
Initialize the cache.
Args:
db_path (str, optional): Explicit path to the cache database. When empty,
the path is resolved lazily to ``local_cache.db`` beside the support
database.
method
def get(self, url: str, directive: Optional[oscal.oscal_cache.CacheDirective] = None) -> Optional[str]#
Apply ``directive``, then return cached content for ``url`` if reusable.
The directive is applied first: ``CACHE_NEVER`` purges any copy; ``refresh``
forces a miss. Freshness is then evaluated by comparing the directive's TTL
against the entry's last-fetch time (``CACHE_FOREVER`` reuses any age).
Args:
url (str, required): The (canonicalized) remote URL key.
directive (CacheDirective | None, optional): Caching directive; defaults
to :meth:`CacheDirective.default` (24h, no refresh).
Returns:
Optional[str]: The cached content to reuse, or None to fetch remotely.
method
def purge(self, url: str) -> None#
Remove the cached entry for a single ``url`` (manual deletion).
Args:
url (str, required): The (canonicalized) remote URL key.
method
def put(self, url: str, content, directive: Optional[oscal.oscal_cache.CacheDirective] = None) -> bool#
Store or refresh cached content for ``url``, resetting its last-fetch time.
A ``CACHE_NEVER`` directive stores nothing (the content is used but not cached).
Args:
url (str, required): The (canonicalized) remote URL key.
content (str | bytes, required): The fetched content to cache.
directive (CacheDirective | None, optional): Caching directive; defaults
to :meth:`CacheDirective.default`.
Returns:
bool: True when stored, False when skipped or on error.
function
def get_local_cache() -> oscal.oscal_cache.LocalCache#
Return the process-global default remote-content cache.
Returns:
LocalCache: The shared cache instance (its database is created on first use).
oscal.oscal_workspace#
oscal_workspace — a Workspace that owns a set of related OSCAL documents.
A ``Workspace`` is the entry point for opening/creating OSCAL content as a project.
It owns an isolated in-memory object registry (so two workspaces are independent
object graphs) and injects that registry into every document it loads — including
transitively-loaded imports — via :func:`oscal.oscal_registry.use_registry`.
Within one workspace, opening the same file twice returns the **same** object
(root documents are shared, keyed by their source path/href), which is the basis
for multi-view editing. The remote-content disk cache remains process-global
(shared across workspaces).
A workspace can be **saved to a single SQLite project file** (content + state,
reusing the shared ``filecache`` schema) and reloaded self-contained, without
refetching. The project file also carries project-level metadata (title, path,
last-modified, remarks, and an extensible attributes bag) and is the intended
substrate for future multi-view / multi-user (locking, sync) support.
Module constants:
WORKSPACE_META_TABLE (dict): Schema for the ``workspace_meta`` key/value table.
WORKSPACE_DOCS_TABLE (dict): Schema for the ``workspace_documents`` table.
class Workspace#
A named set of related OSCAL documents with an isolated object registry.
Documents opened through the workspace share one registry (imports dedup within
the workspace) and one document identity map (opening the same source twice
returns the same object). Carries project metadata and can be persisted to a
single SQLite project file.
method
def __init__(self, title: str = '', path: str = '', registry: Optional[oscal.oscal_registry.ObjectRegistry] = None) -> None#
Create a workspace.
Args:
title (str, optional): Project title.
path (str, optional): Default path for the workspace's project file.
registry (ObjectRegistry | None, optional): Registry to use; a fresh
isolated one is created when omitted.
method
def as_actor(self, actor: str)#
Context manager that attributes mutations in the block to ``actor``.
Args:
actor (str, required): The actor (view/session) id.
Returns:
A context manager activating ``actor`` as the current actor.
method
def close(self, doc: oscal.oscal_content.OSCAL) -> None#
Stop tracking a document (releasing the workspace's strong reference and lock).
method
def is_locked(self, doc: oscal.oscal_content.OSCAL) -> bool#
Return True when ``doc`` is write-locked by any actor.
classmethod
classmethod load(cls, path: str) -> 'Workspace'#
Load a workspace from its SQLite project file (self-contained; no refetch).
Args:
path (str, required): The workspace project file.
Returns:
Workspace: The reconstructed workspace, with documents rehydrated and
their import trees rewired from the persisted content and state.
method
def loads(self, content: str, *, href: Optional[str] = None) -> oscal.oscal_content.OSCAL#
Open in-memory content into the workspace.
Args:
content (str, required): Serialized OSCAL content.
href (str | None, optional): Source URI to key/track the document by.
Returns:
OSCAL: The opened document.
method
def lock(self, doc: oscal.oscal_content.OSCAL, actor: Optional[str] = None) -> bool#
Acquire the write lock on ``doc`` for ``actor`` (exclusive editing).
While held, the document is read-only to every other actor. Re-locking by
the same actor succeeds (idempotent).
Args:
doc (OSCAL, required): The document to lock.
actor (str | None, optional): The actor; defaults to the current actor.
Returns:
bool: True if the lock is held by ``actor`` afterward, False if another
actor already holds it.
Raises:
ValueError: When no actor is given and none is active.
method
def lock_holder(self, doc: oscal.oscal_content.OSCAL) -> Optional[str]#
Return the actor holding the write lock on ``doc``, or None.
Args:
doc (OSCAL, required): The document.
Returns:
Optional[str]: The lock-holding actor, or None when unlocked.
method
def new(self, model_cls, title: str, **kwargs) -> oscal.oscal_content.OSCAL#
Create a new document in the workspace.
Args:
model_cls (type, required): A model class (e.g. ``Catalog``).
title (str, required): Document title.
**kwargs: Passed through to ``model_cls.new``.
Returns:
OSCAL: The new document, tracked by the workspace.
method
def open(self, source) -> oscal.oscal_content.OSCAL#
Open a document into the workspace (loading it under the workspace registry).
Re-opening the same source returns the already-open document (shared root).
Args:
source (str, required): A path or URI to load.
Returns:
OSCAL: The (possibly already-open) document.
method
def save(self, path: str = '') -> bool#
Save the workspace (content + state + project metadata) to a SQLite file.
Every reachable document (roots and their resolved imports) is serialized as
JSON into the shared ``filecache`` table, with its state and import edges
recorded in ``workspace_documents``; project metadata goes in
``workspace_meta``. Reusing ``filecache`` means no schema change to the
support database.
Args:
path (str, optional): Destination path. Defaults to ``self.path``.
Returns:
bool: True on success.
method
def unlock(self, doc: oscal.oscal_content.OSCAL, actor: Optional[str] = None) -> bool#
Release the write lock on ``doc``.
Args:
doc (OSCAL, required): The document to unlock.
actor (str | None, optional): The actor; defaults to the current actor.
A caller may only release its own lock (unless ``actor`` is None-held).
Returns:
bool: True when the document is unlocked afterward; False when the lock
is held by a different actor and cannot be released.
oscal.metaschema_parser#
metaschema_parser — parse NIST resolved-metaschema XML into a structural index.
Parses OSCAL resolved-metaschema XML files into a dictionary representation of the
metaschema structure (assemblies, fields, flags, attributes, child elements, and
allowed-value constraints). The resulting index drives XML↔JSON conversion and
validation elsewhere in the library.
While there is some defensive coding, this module assumes metaschema files are
valid; it does not validate metaschema structure or content. It ignores unexpected
structures and logs a WARNING when it encounters expected but unhandled structures.
Module constants:
SUPPRESS_XPATH_NOT_FOUND_WARNINGS (bool): Suppress warnings when an XPath yields
no match.
RUNAWAY_LIMIT (int): Maximum recursion/iteration count before aborting as a
runaway.
DEBUG_OBJECT (str): Name of a definition to trace for debugging ("" disables).
PRUNE_JSON (bool): Remove None values and empty arrays from the resolved JSON output.
OSCAL_DEFAULT_NAMESPACE (str): The NIST OSCAL namespace URI.
METASCHEMA_DEFAULT_NAMESPACE (str): The NIST Metaschema namespace URI.
METASCHEMA_TOP_IGNNORE (list): Top-level metaschema elements to ignore.
METASCHEMA_TOP_KEEP (list): Top-level metaschema elements to process.
METASCHEMA_PROPS_HANDLED (list): Metaschema ``prop`` names handled on definitions.
METASCHEMA_RULE_PROPS_HANDLED (list): Metaschema ``prop`` names handled on rules.
METASCHEMA_INDEX_PROPS_HANDLED (list): Metaschema ``prop`` names handled on indexes.
METASCHEMA_ROP_NAMESPACE (list): Recognized metaschema property namespace URIs.
METASCHEMA_ROOT_ELEMENT (str): Root element name of a metaschema document
("METASCHEMA").
CONSTRAINT_ROOT_ELEMENT (str): Root element name of a meta-constraints document.
CONSTRAINT_TOP_IGNORE (list): Top-level constraint elements to ignore.
CONSTRAINT_TOP_KEEP (list): Top-level constraint elements to process.
GREEN, BLUE, YELLOW, RED, ORANGE, MAGENTA, CYAN, PURPLE, BOLD, RESET (str):
ANSI terminal escape codes used for colorized diagnostic output.
class MetaschemaParser#
Parses a single OSCAL resolved-metaschema XML document into a structural index.
Holds the parsed metaschema tree and namespace/model context, resolves imported
metaschemas, and walks assemblies, fields, and flags to build the nested index
(nodes, attributes, allowed-value constraints) consumed by the converter and
validator. Prefer the :meth:`create` classmethod to construct instances.
method
def __init__(self, metaschema, support, oscal_version='', import_registry=None)#
Initialize a parser for one metaschema document.
Args:
metaschema (str, required): The resolved-metaschema XML content to parse.
support (OSCALSupport, required): The OSCAL support object used to fetch
imported metaschemas and store results.
oscal_version (str, optional): The OSCAL version this metaschema belongs
to. Defaults to "".
import_registry (dict, optional): Shared registry of already-parsed imports
(``href -> MetaschemaParser``), so a metaschema imported by more than one
model is parsed once and reused. Endures across all models of one OSCAL
version and is cleared when that version finishes. Defaults to ``None``
(a fresh, isolated registry) — never a mutable default.
method
def build_metaschema_tree(self)#
Build the full structural index for this metaschema's model.
Recursively walks the root assembly to produce the node tree, applies
constraints against a synthesized XML skeleton, prunes empty values, and
annotates namespace conditions and JSON paths.
Returns:
dict: The metaschema index — model metadata plus a ``nodes`` tree — or an
empty dict on error or when the root assembly cannot be found.
classmethod
classmethod create(cls, metaschema, support, oscal_version='', import_registry=None)#
Construct a ``MetaschemaParser`` (preferred factory over direct instantiation).
Args:
metaschema (str, required): The resolved-metaschema XML content to parse.
support (OSCALSupport, required): The OSCAL support object.
oscal_version (str, optional): The OSCAL version. Defaults to "".
import_registry (dict, optional): Shared per-version import registry (see
:meth:`__init__`). Defaults to ``None`` — a fresh registry per top-level
parse. Recursive import parsing threads the parent's registry down.
Returns:
MetaschemaParser: A new parser instance.
method
def get_markup_content(self, xExpr, context=None)#
Run an XPath query and return its markup content as a string.
Handles results that are either plain strings or nodes containing HTML
(markup) formatting, returning a string in either case.
Args:
xExpr (str, required): An XPath expression.
context (Element, optional): Node to evaluate the expression against.
Defaults to None (whole document).
Returns:
str: The matched content as a string (markup preserved as HTML).
method
def graceful_accumulate(self, current_value, xExpr, context=None)#
Prepend a resolved markup value onto an accumulating list of values.
Used where a field/assembly reference's values must be added to (rather than
replace) any values already defined on the referenced define-field/assembly.
Args:
current_value (list, required): The existing accumulated values; wrapped in
a list if not already one.
xExpr (str, required): XPath expression yielding the markup value to add.
context (Element, optional): Node to evaluate against. Defaults to None.
Returns:
list: ``current_value`` with the resolved value inserted at the front (when
non-empty).
method
def graceful_override(self, current_value, xExpr, context=None)#
Return an overriding value when present, otherwise keep the current value.
Used where a field/assembly reference's value must replace any value already
defined on the referenced define-field/assembly.
Args:
current_value (Any, required): The existing value to keep if no override
is found.
xExpr (str, required): XPath expression yielding the overriding value.
context (Element, optional): Node to evaluate against. Defaults to None.
Returns:
Any: The resolved override value if non-empty, otherwise ``current_value``.
method
def handle_attributes(self, metaschema_node, definition_obj: 'ET.Element', structure_type, name, parent)#
Map an XML definition's attributes onto a metaschema node.
Translates attributes such as ``as-type`` (datatype), ``required``,
``min-occurs``/``max-occurs`` (cardinality), ``collapsible``, ``deprecated``,
``default``, and ``in-xml`` (XML wrapping) into node fields. Unhandled
attributes are logged as warnings.
Args:
metaschema_node (dict, required): The node being built; updated in place.
definition_obj (ET.Element, required): The XML definition element.
structure_type (str, required): The definition's structure type.
name (str, required): The definition name.
parent (str, required): The parent path.
Returns:
dict: The updated ``metaschema_node``.
method
def handle_children(self, name, structure_type, metaschema_node, context, handle_choice=0)#
Resolve the child model of an assembly (fields, assemblies, choices, any).
Walks the assembly's ``model`` (or a specific ``choice`` group), recursing to
build each child node and constructing synthetic nodes for ``choice``/``any``.
Args:
name (str, required): The definition name being processed.
structure_type (str, required): "define-assembly" or "choice".
metaschema_node (dict, required): The parent node (provides path/source).
context (Element, required): The XML context to search within.
handle_choice (int, optional): 1-based index of the choice group to process
when ``structure_type`` is "choice". Defaults to 0.
Returns:
list: The resolved child node dicts.
method
def handle_constraints(self, metaschema_node, definition_obj, structure_type, name, parent)#
Process ``<constraint><allowed-values>`` elements from a definition object.
Targets are handled as follows: ``.`` or absent applies to the current node;
``@flag-name`` applies to the named flag child; complex Metapath targets are
resolved against the XML skeleton (or stored with the unresolved target
preserved). Multiple allowed-values sets for the same target are cumulative;
``allow-other`` conflicts resolve with 'yes' winning and emit a warning.
Args:
metaschema_node (dict, required): The node being built; updated in place.
definition_obj (ET.Element, required): The XML definition element.
structure_type (str, required): The definition's structure type.
name (str, required): The definition name.
parent (str, required): The parent path.
Returns:
dict: The updated ``metaschema_node``.
method
def handle_flags(self, metaschema_node, definition_obj, structure_type, name, parent)#
Resolve the flags defined or referenced by a field or assembly.
Finds each ``define-flag``/``flag`` child, recurses to build its node, and
collects the results.
Args:
metaschema_node (dict, required): The parent node being built (used for
path context).
definition_obj (ET.Element, required): The field/assembly XML definition.
structure_type (str, required): The parent's structure type.
name (str, required): The parent definition name.
parent (str, required): The parent path.
Returns:
list: The resolved flag node dicts (empty when none are present).
method
def handle_group_as(self, metaschema_node, definition_obj: 'ET.Element', structure_type, name, parent)#
Apply a definition's ``group-as`` element to a metaschema node.
Reads the ``group-as`` name and its ``in-xml``/``in-json`` grouping
attributes and records them (and XML wrapping) on the node.
Args:
metaschema_node (dict, required): The node being built; updated in place.
definition_obj (ET.Element, required): The XML definition element.
structure_type (str, required): The definition's structure type.
name (str, required): The definition name (for logging).
parent (str, required): The parent path (used to build wrapped paths).
Returns:
dict: The updated ``metaschema_node``.
method
def handle_props(self, metaschema_node, definition_obj, structure_type, name, parent)#
Map a definition's ``prop`` elements onto a metaschema node.
Recognized props (``METASCHEMA_PROPS_HANDLED`` in the OSCAL namespace) are
promoted to dedicated node keys; any other prop is appended to the node's
``props`` list as ``{"name", "value", "namespace"}``.
Args:
metaschema_node (dict, required): The node being built; updated in place.
definition_obj (ET.Element, required): The XML definition element.
structure_type (str, required): The definition's structure type.
name (str, required): The definition name.
parent (str, required): The parent path.
Returns:
dict: The updated ``metaschema_node``.
method
def initialize_metaschema_index(self)#
Create a new, fully-keyed metaschema index-constraint dict with default values.
Called before each index constraint is populated, to guarantee a consistent
key set (id, level, name, target, handled props, etc.).
Returns:
dict: A new index dict with all expected keys initialized.
method
def initialize_metaschema_node(self)#
Create a new, fully-keyed metaschema index node with default (empty) values.
Called as each node is created, including the top-level node, to guarantee a
consistent key set (path, name, datatype, cardinality, children, constraints,
handled props, etc.).
Returns:
dict: A new node dict with all expected keys initialized.
method
def initialize_metaschema_rule(self)#
Create a new, fully-keyed metaschema rule with default (empty) values.
Called before each rule (e.g. an allowed-values constraint) is populated, to
guarantee a consistent key set (id, level, datatype, allowed-values,
allow-other, test, message, cardinality, etc.).
Returns:
dict: A new rule dict with all expected keys initialized.
method
def look_in_imports(self, name, structure_type, parent='', ignore_local=False, already_searched=None)#
Search imported metaschemas for a definition by name and structure type.
Args:
name (str, required): The definition name to find.
structure_type (str, required): The structure type to match
(e.g. "define-assembly", "define-field", "define-flag").
parent (str, optional): Parent path for the resolved node. Defaults to "".
ignore_local (bool, optional): Passed through to recursion; ignore local
definitions in the imported metaschema. Defaults to False.
already_searched (list | None, optional): Definition names already visited,
to prevent cycles. Defaults to None.
Returns:
dict | None: The resolved node from the imported metaschema, or None if not
found.
method
def recurse_metaschema(self, name, structure_type='define-assembly', parent='', ignore_local=False, already_searched=None, context=None, skip_children=False, use_name=None)#
Recursively build a metaschema index node and its descendants.
Processes the XML definition for ``name`` and extracts a node dict describing
its attributes, flags, and child elements, recursing into referenced
definitions.
Args:
name (str, required): The definition/element name to process (e.g. a model
or field name).
structure_type (str, optional): The kind of definition — "define-assembly",
"define-field", "define-flag", or an inline assembly/field/flag.
Defaults to "define-assembly".
parent (str, optional): Name of the parent definition, for logging/paths.
Defaults to "".
ignore_local (bool, optional): When True, ignore local (non-exported)
definitions; set True when recursing into an imported metaschema so its
private locals are not exposed. Defaults to False.
already_searched (list | None, optional): Definition names already visited,
to prevent infinite recursion. Defaults to None.
context (Element, optional): XML context node to search within.
Defaults to None.
skip_children (bool, optional): When True, do not recurse into child
elements. Defaults to False.
use_name (str | None, optional): Override for the node's effective
(use-)name. Defaults to None.
Returns:
dict: The metaschema index node for ``name`` (with nested children).
method
def set_default_values(self, metaschema_node, definition_obj, structure_type, name, parent)#
Fill in default node values required by the metaschema specification.
Applies spec defaults for any unset attributes — datatype ("string"),
cardinality (0..1, or 1..1 for the root), ``json-collapsible``,
``deprecated``, ``default``, and XML wrapping for fields/assemblies.
Args:
metaschema_node (dict, required): The node being built; updated in place.
definition_obj (ET.Element, required): The XML definition element.
structure_type (str, required): The definition's structure type.
name (str, required): The definition name.
parent (str, required): The parent path; an empty value marks the root node.
Returns:
dict: The updated ``metaschema_node``.
method
def setup_imports(self)#
Identify ``import`` elements and load each as a nested ``MetaschemaParser``.
Imported metaschemas are fetched from the support database and stored in
``self.imports`` keyed by model name for later cross-metaschema lookups. Each
import is also recorded as a child of this parser's ``import_inventory`` tree
node, so the full import-relationship graph is captured.
Duplicate imports (the same metaschema reached from more than one model, or via
more than one path) are parsed only once: an :attr:`import_registry`, shared for
the whole OSCAL version, is consulted first and its parsed object reused.
Returns:
None
method
def str_node(self, node)#
Build a human-readable summary of a parsed metaschema index node.
Args:
node (dict, required): An index node produced by the parser, carrying
keys such as ``formal-name``, ``use-name``, ``min-occurs``,
``max-occurs``, ``datatype``, ``children``, and ``constraints``.
Returns:
str: A multi-line, human-readable description of the node.
method
def top_pass(self)#
Perform the first parsing pass: deserialize XML and read top-level metadata.
Parses the metaschema content, then extracts the model name, schema name,
OSCAL version, namespace, and JSON base URI, and sets up imports.
Returns:
bool: True if the XML was well-formed and parsed, False otherwise.
method
def xpath(self, xExpr, context=None) -> 'ET.Element | list[ET.Element] | None'#
Run an XPath query and return the matching element(s).
Args:
xExpr (str, required): An XPath expression.
context (Element, optional): Node to evaluate the expression against.
When None, the expression runs against the whole document.
Defaults to None.
Returns:
ET.Element | list[ET.Element] | None: A single element, a list of
elements, or None on error / no match.
method
def xpath_atomic(self, xExpr, context=None)#
Run an XPath query and return the first result as a string.
Args:
xExpr (str, required): An XPath expression.
context (Element, optional): Node to evaluate the expression against.
When None, the expression runs against the whole document.
Defaults to None.
Returns:
str: The first matching result as a string, or "" on error / no match.
function
def clean_none_values_recursive(dictionary)#
Recursively drop None values and empty containers from a dict.
Removes key/value pairs whose value is None, and prunes empty nested dicts and
lists (including dicts nested inside lists), returning a new cleaned dict.
Args:
dictionary (dict, required): The dictionary to clean.
Returns:
dict: A new dictionary with None values and empty containers removed.
function
def parse_metaschema(support=None, oscal_version=None, save_to_fs=False) -> 'int'#
Parse and store the OSCAL metaschema index for one or all supported versions.
Args:
support (OSCALSupport, optional): The OSCAL support object. Currently the
shared instance is fetched internally via ``get_support()`` regardless
of this argument. Defaults to None.
oscal_version (str, optional): The OSCAL version to parse. When None, all
supported versions are processed. Defaults to None.
save_to_fs (bool, optional): When True, also write each model index (and the
parse report) to the local file system. Defaults to False (database only).
Returns:
int: 0 on success, 1 on error (process-style exit code).
function
def parse_metaschema_specific(support, oscal_version, save_to_fs=False)#
Parse and store every model index for a specific OSCAL version.
Each model index is stored in the support database as
``(version, model, "processed")``. When ``save_to_fs`` is True it is also written
to ``support/<version>/<model>.json`` alongside the support database.
Args:
support (OSCALSupport, required): The OSCAL support object providing
metaschema assets and asset storage.
oscal_version (str, required): The OSCAL version to parse.
save_to_fs (bool, optional): When True, also write each model index (and the
parse report) to the local file system. Defaults to False (database only).
Returns:
bool: True if all models parsed and stored successfully, False otherwise.