Source code for syside.reqif

  1"""
  2Syside Automator ReqIF import/export.
  3
  4This module converts between ReqIF (Requirements Interchange Format) files
  5and SysMLv2 workspaces. It can be driven from the command line or from
  6Python scripts; both surfaces share the same underlying entry points.
  7
  8Installation
  9------------
 10
 11The public functions and CLI both require the ``[reqif]`` extra which pulls in
 12additional third-party dependencies. Install it with:
 13
 14.. code-block:: shell
 15
 16    pip install 'syside[reqif]'
 17    # or, for uv-managed projects:
 18    uv add 'syside[reqif]'
 19
 20When the extra is missing, the captured ``ImportError`` is exposed as
 21``_missing_extra_error`` for callers building their own CLI.
 22
 23Command-line usage
 24------------------
 25
 26The CLI is reachable both through the top-level ``syside`` console script
 27and as a runnable module. The three are equivalent:
 28
 29.. code-block:: shell
 30
 31    syside reqif <subcommand> [options]
 32    python -m syside reqif <subcommand> [options]
 33    python -m syside.reqif <subcommand> [options]
 34
 35All subcommands operate on the current working directory as the workspace
 36root. The available subcommands are:
 37
 38- ``init`` — write the canonical ``SysideReqIF`` SysMLv2 library into the
 39  workspace so subsequent imports have something to reference. Use
 40  ``--lib-dir`` to override the default :data:`LIBRARY_DIR` location.
 41- ``import <file>`` — convert a ``.reqif`` or ``.reqifz`` file into
 42  SysMLv2 packages under the workspace, performing a fresh build or a
 43  delta update depending on existing content. Attachments are extracted
 44  into ``--attachments-dir`` (default: :data:`ATTACHMENTS_DIR`).
 45- ``lock`` — assign stable UUIDs and last-change timestamps to any
 46  ReqIF-tagged elements that don't yet have them, so subsequent
 47  round-trips preserve identity. Takes no options.
 48- ``export`` — convert the SysMLv2 models from the workspace into a
 49  ``.reqif`` or ``.reqifz`` file.
 50- ``check reqif <file>`` — validate that a ReqIF file satisfies the
 51  invariants required for round-trippable conversion (relation-group
 52  ownership, no hierarchy cycles, no name shadowing, etc.).
 53- ``check sysml`` — validate the workspace SysMLv2 model (currently
 54  enforces at most one top-level package per ``.sysml`` document, and
 55  that all ReqIF tags are locked).
 56
 57Each subcommand exits ``0`` on success and raises on failure.
 58
 59Library usage
 60-------------
 61
 62Every CLI subcommand has a public Python counterpart: an options
 63dataclass plus a function named after the subcommand. The functions
 64return the CLI exit code and raise ``ValueError`` on validation
 65failures, and operate on the current working directory (use
 66``os.chdir`` to target a different workspace).
 67
 68.. code-block:: python
 69
 70    from pathlib import Path
 71
 72    from syside.reqif import (
 73        ImportOptions,
 74        CheckReqifOptions,
 75        InitOptions,
 76        check_reqif,
 77        import_,
 78        init,
 79    )
 80
 81    # Initialise the workspace once.
 82    init(InitOptions(lib_dir=None))
 83
 84    # Validate a ReqIF file before importing it.
 85    reqif_file = Path("requirements.reqif")
 86    check_reqif(CheckReqifOptions(file=reqif_file))
 87
 88    # Import the file; attachments land in ./attachments_reqif/.
 89    import_(ImportOptions(file=reqif_file, attachments_dir=None))
 90
 91The default-path constants (:data:`LIBRARY_PKG`, :data:`LIBRARY_DIR`,
 92:data:`ATTACHMENTS_DIR`) are re-exported here so callers can reference
 93the same defaults the CLI uses without reaching into private modules.
 94"""
 95
 96__unstable__ = True
 97
 98_missing_extra_error: BaseException | None
 99try:
100    # Import the chain that transitively loads the [reqif]-extra deps
101    # (reqif, markdown-it-py, markdownify) first, so that a missing extra
102    # bails before any partial bindings can land in this namespace.
103    from syside.reqif._cmd_check import run_reqif as check_reqif
104    from syside.reqif._cmd_check import run_sysml as check_sysml
105    from syside.reqif._cmd_import import run as import_
106    from syside.reqif._cmd_init import run as init
107    from syside.reqif._cmd_lock import run as lock
108    from syside.reqif._cmd_export import run as export
109    from syside.reqif._cli import (
110        CheckReqifOptions,
111        CheckSysmlOptions,
112        ImportOptions,
113        InitOptions,
114        ExportOptions,
115        _OptionsBase as OptionsBase,
116    )
117    from syside.reqif._config import ATTACHMENTS_DIR, LIBRARY_DIR, LIBRARY_PKG
118except ImportError as e:
119    # Anything rooted in ``syside`` is an internal regression (typo, missing
120    # submodule, etc.) and must surface untouched — the friendly install-hint
121    # path is reserved for failures rooted in the third-party deps the
122    # ``[reqif]`` extra pulls in (reqif, markdown-it-py, markdownify, and
123    # their transitive dependencies). This avoids having to maintain a
124    # hand-rolled list of extra deps in lockstep with pyproject.toml.
125    if (e.name or "").split(".", 1)[0] == "syside":
126        raise
127    __all__: list[str] = []
128    _missing_extra_error = e
129else:
130    __all__ = [
131        "ATTACHMENTS_DIR",
132        "CheckReqifOptions",
133        "CheckSysmlOptions",
134        "ImportOptions",
135        "InitOptions",
136        "ExportOptions",
137        "OptionsBase",
138        "LIBRARY_DIR",
139        "LIBRARY_PKG",
140        "check_reqif",
141        "check_sysml",
142        "import_",
143        "init",
144        "lock",
145        "export",
146    ]
147    _missing_extra_error = None