diff --git a/.config/mise/tasks/build b/.config/mise/tasks/build index c56bfdd3..059c4ada 100755 --- a/.config/mise/tasks/build +++ b/.config/mise/tasks/build @@ -1,11 +1,8 @@ #!/usr/bin/env bash -#MISE description="Build all packages" +#MISE description="Build the bundled morphir-python distribution" set -euo pipefail -echo "Building morphir package..." -uv build packages/morphir --out-dir packages/morphir/dist - -echo "Building morphir-tools package..." -uv build packages/morphir-tools --out-dir packages/morphir-tools/dist +echo "Building morphir-python package..." +uv build --out-dir dist echo "Build complete!" diff --git a/.config/mise/tasks/release-check b/.config/mise/tasks/release-check index e6ec9580..3b706c50 100755 --- a/.config/mise/tasks/release-check +++ b/.config/mise/tasks/release-check @@ -15,13 +15,11 @@ import zipfile from email.parser import BytesParser from pathlib import Path -versions = set() -for name in ("morphir", "morphir-tools"): - directory = Path("packages") / name +for folder, name in ((".", "morphir-python"),): + directory = Path(folder) project = tomllib.loads((directory / "pyproject.toml").read_text())["project"] - versions.add(project["version"]) tag = os.environ.get("RELEASE_TAG", "") - if tag and tag != f"v{project['version']}": + if tag and tag not in (f"v{project['version']}", f"python-v{project['version']}"): raise SystemExit(f"{tag} does not match {name} {project['version']}") stem = name.replace("-", "_") + "-" + project["version"] expected = {f"{stem}-py3-none-any.whl", f"{stem}.tar.gz"} @@ -36,6 +34,12 @@ for name in ("morphir", "morphir-tools"): metadata = [n for n in archive.namelist() if n.endswith(".dist-info/METADATA")] raw = archive.read(metadata[0]) has_license = any(n.endswith("/licenses/LICENSE") for n in archive.namelist()) + for source in (Path("packages/morphir/src"), Path("packages/morphir-tools/src")): + expected_sources = {str(p.relative_to(source)) for p in source.rglob("*") + if p.is_file() and p.suffix in (".py", ".typed")} + missing = expected_sources - set(archive.namelist()) + if missing: + raise SystemExit(f"Missing bundled sources in {path}: {missing}") else: with tarfile.open(path) as archive: member = archive.extractfile(f"{stem}/PKG-INFO") @@ -48,39 +52,46 @@ for name in ("morphir", "morphir-tools"): raise SystemExit(f"Incorrect name or version in {path}") if metadata["License-Expression"] != "Apache-2.0" or not has_license: raise SystemExit(f"Missing Apache-2.0 license in {path}") -if len(versions) != 1: - raise SystemExit("Both packages must use the same release version") + if any(req.startswith("morphir") for req in metadata.get_all("Requires-Dist", [])): + raise SystemExit(f"{path} must not depend on a separate Morphir distribution") PY uvx --from twine==7.0.0 twine check --strict \ - packages/morphir/dist/* packages/morphir-tools/dist/* + dist/* uv export --frozen --all-packages --no-default-groups --no-emit-workspace \ --no-hashes --output-file "$release_dir/constraints.txt" uv venv --clear --python 3.14 "$release_dir/venv" release_python="$release_dir/venv/bin/python" uv pip install --python "$release_python" --constraint "$release_dir/constraints.txt" \ - packages/morphir/dist/*.whl packages/morphir-tools/dist/*.whl + dist/*.whl uv pip check --python "$release_python" "$release_python" -I - <<'PY' -from importlib.metadata import version +from importlib.metadata import PackageNotFoundError, version import morphir import morphir_tools from morphir.ir import format_version from morphir.sdk import basics -assert morphir.__version__ == version("morphir") -assert morphir_tools.__version__ == version("morphir-tools") +assert morphir.__version__ == version("morphir-python") +assert morphir_tools.__version__ == version("morphir-python") assert basics.round(2.5) == 3 assert format_version.V3.major == 3 +try: + version("morphir") +except PackageNotFoundError: + pass +else: + raise AssertionError("Bundle verification must not install a separate morphir wheel") PY -"$release_dir/venv/bin/morphir-tools" --help +"$release_dir/venv/bin/morphir-python" --help +"$release_dir/venv/bin/morphir-python" ir inspect tests/fixtures/ir/v3/elm-simpleValueTree.json -# These two first-party packages are not yet on PyPI. Audit the exact installed -# third-party versions, without querying PyPI for the unpublished packages. -uv pip freeze --python "$release_python" --exclude morphir --exclude morphir-tools \ +# Audit the exact installed third-party versions without querying PyPI for the +# first-party bundle being released. +uv pip freeze --python "$release_python" --exclude morphir-python \ > "$release_dir/requirements.txt" uvx --from pip-audit==2.10.1 pip-audit --strict --no-deps --disable-pip \ --requirement "$release_dir/requirements.txt" \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1248bdfd..b088057b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: workflow_call: inputs: release-tag: - description: Tag that must match both package versions + description: Tag that must match the bundled package version type: string default: '' @@ -97,18 +97,11 @@ jobs: env: RELEASE_TAG: ${{ inputs.release-tag }} - - name: Upload morphir distributions + - name: Upload morphir-python distributions uses: actions/upload-artifact@v6 with: - name: morphir-dist - path: packages/morphir/dist/ - if-no-files-found: error - - - name: Upload morphir-tools distributions - uses: actions/upload-artifact@v6 - with: - name: morphir-tools-dist - path: packages/morphir-tools/dist/ + name: morphir-python-dist + path: dist/ if-no-files-found: error - name: Upload release scan reports diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a6ccf7e..047c3d13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: tags: - 'v*' + - 'python-v*' # Qualification only. Publishing requires a tag push. workflow_dispatch: @@ -21,8 +22,8 @@ jobs: with: release-tag: ${{ github.ref_type == 'tag' && github.ref_name || '' }} - publish-morphir: - name: Publish morphir to PyPI + publish-morphir-python: + name: Publish morphir-python to PyPI if: github.event_name == 'push' && github.repository == 'finos/morphir-python' needs: qualify runs-on: ubuntu-latest @@ -30,31 +31,12 @@ jobs: id-token: write environment: name: pypi - url: https://pypi.org/project/morphir/ + url: https://pypi.org/project/morphir-python/ steps: - - name: Download morphir dist + - name: Download morphir-python dist uses: actions/download-artifact@v7 with: - name: morphir-dist - path: dist/ - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - publish-morphir-tools: - name: Publish morphir-tools to PyPI - needs: [qualify, publish-morphir] - runs-on: ubuntu-latest - permissions: - id-token: write - environment: - name: pypi - url: https://pypi.org/project/morphir-tools/ - steps: - - name: Download morphir-tools dist - uses: actions/download-artifact@v7 - with: - name: morphir-tools-dist + name: morphir-python-dist path: dist/ - name: Publish to PyPI @@ -62,7 +44,7 @@ jobs: github-release: name: Create GitHub Release - needs: [publish-morphir, publish-morphir-tools] + needs: publish-morphir-python runs-on: ubuntu-latest permissions: contents: write diff --git a/AGENTS.md b/AGENTS.md index d51188df..e957b365 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,16 +11,21 @@ Morphir Python is a port of Morphir to Python, providing functional domain model ``` morphir-python/ ├── packages/ -│ ├── morphir/ # Core library - IR models, types, pure functions -│ └── morphir-tools/ # CLI tools and extensions (depends on morphir) +│ ├── morphir/ # Core library workspace - IR models, types, pure functions +│ └── morphir-tools/ # CLI source; root morphir-python bundles library and CLI ├── tests/ │ ├── unit/ # Unit tests for morphir package -│ └── tools/ # Unit tests for morphir-tools package +│ └── tools/ # Unit tests for morphir-python package └── features/ # BDD feature tests (behave) ``` ### Package Responsibilities +The root `pyproject.toml` publishes one `morphir-python` distribution containing +both source components. It provides `import morphir`, `import morphir_tools`, and +the `morphir-python` command. The core remains a standalone workspace for development; +the release workflow does not publish or require a separate `morphir` PyPI package. + **`morphir` (core library)** - Morphir IR (Intermediate Representation) models, in two versions: `morphir.ir.v3` (formats 3.0.0 and 3.1.0; morphir-elm is normative for 3.0.0, @@ -36,8 +41,8 @@ morphir-python/ - Zero CLI or IO dependencies - Should be usable as a standalone library -**`morphir-tools` (CLI/tools)** -- Command-line interface (`morphir-tools`, Typer): `ir inspect`, `ir convert` +**`morphir-python` (CLI/tools, in `packages/morphir-tools/`)** +- Command-line interface (`morphir-python`, Typer): `ir inspect`, `ir convert` - File I/O operations - Code generation backends - Depends on `morphir` core library @@ -266,7 +271,7 @@ mise run coverage # Coverage report When adding dependencies: - **morphir package**: Should have minimal/zero dependencies -- **morphir-tools package**: Can have CLI and IO dependencies +- **morphir-python package**: Can have CLI and IO dependencies - Always pin minimum versions in pyproject.toml ## Error Handling diff --git a/README.md b/README.md index 4510134d..ac378271 100644 --- a/README.md +++ b/README.md @@ -7,44 +7,26 @@ Python port of [Morphir](https://morphir.finos.org) - a library of tools for bui Morphir enables you to write business logic once and use it across multiple platforms, languages, and runtimes. It provides a strongly-typed intermediate representation (IR) that captures the semantics of your domain model. -## Packages - -This monorepo contains two packages: - -| Package | Description | PyPI | -|---------|-------------|------| -| `morphir` | Core library - IR models, the Morphir SDK runtime (`morphir.sdk`), types, and functional domain modeling primitives | [![PyPI](https://img.shields.io/pypi/v/morphir)](https://pypi.org/project/morphir/) | -| `morphir-tools` | CLI tools and extensions for working with Morphir | [![PyPI](https://img.shields.io/pypi/v/morphir-tools)](https://pypi.org/project/morphir-tools/) | - ## Installation -### Library Only - -If you just need the Morphir library for your Python project: +Install the library, Morphir SDK runtime, and CLI together: ```bash -pip install morphir +pip install morphir-python ``` Or with uv: ```bash -uv add morphir -``` - -### With CLI Tools - -For the full toolset including CLI commands: - -```bash -pip install morphir-tools +uv add morphir-python ``` -Or with uv: +| PyPI distribution | Python imports | Command | +|-------------------|----------------|---------| +| [`morphir-python`](https://pypi.org/project/morphir-python/) | `morphir`, `morphir_tools` | `morphir-python` | -```bash -uv add morphir-tools -``` +The distribution includes the core library and tools. It does not require a +separate `morphir` package from PyPI. ## Quick Start @@ -65,8 +47,8 @@ v4_library = v3_to_v4(library).value Or from the command line: ```bash -morphir-tools ir inspect morphir-ir.json -morphir-tools ir convert morphir-ir.json --to v4 -o morphir-ir.v4.json +morphir-python ir inspect morphir-ir.json +morphir-python ir convert morphir-ir.json --to v4 -o morphir-ir.v4.json ``` See the [morphir package README](packages/morphir/README.md#morphir-ir-versions) @@ -166,20 +148,14 @@ mise run check ## Releases -The initial release is `0.0.1` for both PyPI distributions: - -| PyPI distribution | Python import | Command | -|-------------------|---------------|---------| -| `morphir` | `morphir` | None | -| `morphir-tools` | `morphir_tools` | `morphir-tools` | - -`morphir-python` is the repository name. The workspace itself is not published. -Both packages require Python 3.14 or later. `morphir-tools` installs `morphir` as -a dependency. +The initial PyPI release is `morphir-python==0.0.1`, containing both the `morphir` +library and the `morphir-python` CLI. Python 3.14 or later is required. The root +`pyproject.toml` defines the public distribution. The library workspace under +`packages/morphir/` remains independent of CLI dependencies for development; it +is included in the bundle rather than published separately by this workflow. -Publishing uses [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/using-a-publisher/), -without a stored PyPI token. Configure a publisher for **each** distribution on -PyPI, including a pending publisher if the project has not been created yet: +Publishing uses [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/using-a-publisher/) +without a stored PyPI token. Configure the `morphir-python` project on PyPI with: | Publisher field | Value | |-----------------|-------| @@ -188,34 +164,35 @@ PyPI, including a pending publisher if the project has not been created yet: | Workflow filename | `release.yml` | | Environment | `pypi` | -The GitHub `pypi` environment permits release tags matching `v*`. After maintainer -approval, push a tag such as `v0.0.1` whose version matches both package metadata -files and their runtime `__version__` values. Update the tools package's minimum -`morphir` dependency and refresh `uv.lock` when changing versions. +The GitHub `pypi` environment permits release tags matching `v*` or `python-v*`. +After maintainer approval, push a tag such as `python-v0.0.1` whose version matches +root package metadata and the runtime `__version__` values. Refresh `uv.lock` +when changing versions. A new tag records the corrected source without moving +the earlier failed `v0.0.1` tag. The release workflow runs the same lint, format, type, coverage and BDD checks as -PR CI. It builds each package once, validates wheel and source metadata and license -files, installs the wheels in an isolated environment, checks imports and the CLI, -then scans the locked runtime dependency versions with `pip-audit` and -`pip-licenses`. Known vulnerabilities, audit failures, unknown licenses and -licenses outside the exact Apache-2.0, MIT, BSD-2-Clause, BSD-3-Clause and ISC -allowlist block publication. The license allowlist also accepts the corresponding -legacy license classifiers. The CVE scan queries third-party dependencies; -the unpublished first-party packages are excluded from that query. These checks -use package metadata and known vulnerability advisories, not a source-code license -or security review. Scan reports are retained as workflow artifacts. - -Only the two publishing jobs receive `id-token: write`. They publish the checked -artifacts, core library first, and then create a GitHub release. The workflow's -manual **Run workflow** action runs qualification without publishing. To qualify -locally: +PR CI. It builds the bundle once, validates wheel and source metadata and license +files, verifies that every library and CLI source file is in the wheel, installs +only that wheel in an isolated environment, checks imports and the CLI, then +scans the locked runtime dependency versions with `pip-audit` and `pip-licenses`. +Known vulnerabilities, audit failures, unknown licenses and licenses outside the +Apache-2.0, MIT, BSD-2-Clause, BSD-3-Clause and ISC allowlist block publication. +The allowlist also accepts corresponding legacy license classifiers. The CVE +scan queries third-party dependencies, excluding the first-party bundle being +released. These checks use package metadata and known vulnerability advisories; +they do not replace a source-code license or security review. Scan reports are +retained as workflow artifacts. + +Only the publishing job receives `id-token: write`. It publishes the checked +artifacts, then a separate job creates the GitHub release. The workflow's manual +**Run workflow** action runs qualification without publishing. To qualify locally: ```bash mise run install mise run check mise run coverage mise run build -RELEASE_TAG=v0.0.1 mise run release-check +RELEASE_TAG=python-v0.0.1 mise run release-check ``` ## Project Principles diff --git a/openspec/specs/tools/ir-interchange/spec.md b/openspec/specs/tools/ir-interchange/spec.md index 1e3cc50c..9c8c5b1d 100644 --- a/openspec/specs/tools/ir-interchange/spec.md +++ b/openspec/specs/tools/ir-interchange/spec.md @@ -1,7 +1,7 @@ # tools/ir-interchange Specification ## Purpose -Gives morphir-tools users commands to inspect versioned `morphir-ir.json` files and convert them between format 3 (3.0.x and 3.1.x) and draft IR v4, using the same library readers, codecs, and migrations that Python callers use. +Gives morphir-python users commands to inspect versioned `morphir-ir.json` files and convert them between format 3 (3.0.x and 3.1.x) and draft IR v4, using the same library readers, codecs, and migrations that Python callers use. ## Requirements diff --git a/packages/morphir-tools/README.md b/packages/morphir-tools/README.md index 6fae6017..66b8a68d 100644 --- a/packages/morphir-tools/README.md +++ b/packages/morphir-tools/README.md @@ -1,4 +1,4 @@ -# Morphir Tools +# Morphir Python CLI tools and extensions for [Morphir Python](https://github.com/finos/morphir-python). @@ -12,30 +12,32 @@ This package provides: ## Installation ```bash -pip install morphir-tools +pip install morphir-python ``` Or with uv: ```bash -uv add morphir-tools +uv add morphir-python ``` -This will also install the `morphir` core library as a dependency. +The `morphir-python` distribution includes the `morphir` core library and SDK +runtime alongside the CLI. It does not depend on a separate `morphir` PyPI +package. Python imports remain `morphir` and `morphir_tools`. ## Usage -The `morphir-tools` command inspects and converts versioned `morphir-ir.json` +The `morphir-python` command inspects and converts versioned `morphir-ir.json` files. It reads format 3.0.x (what morphir-elm writes), format 3.1.x (which adds the v3 `Specs` distribution) and draft IR 4.0.x, reads the legacy formats 1 and 2 to upgrade them, and uses the `morphir` library for every read, write, and migration. ```bash -morphir-tools ir inspect morphir-ir.json -morphir-tools ir convert morphir-ir.json --to v4 -o out.json --package-version 1.2.0 -morphir-tools ir convert v4.json --to v3 -o out.json --drop-source-locations -morphir-tools ir convert old-format-1.json --to v3 -o out.json +morphir-python ir inspect morphir-ir.json +morphir-python ir convert morphir-ir.json --to v4 -o out.json --package-version 1.2.0 +morphir-python ir convert v4.json --to v3 -o out.json --drop-source-locations +morphir-python ir convert old-format-1.json --to v3 -o out.json ``` `inspect` prints the normalized format version, the distribution kind @@ -56,7 +58,7 @@ format-version rules and the draft v4 status. ## Requirements - Python 3.14+ -- morphir (core library) +- Core library and SDK included ## License diff --git a/packages/morphir-tools/pyproject.toml b/packages/morphir-tools/pyproject.toml deleted file mode 100644 index c390f35f..00000000 --- a/packages/morphir-tools/pyproject.toml +++ /dev/null @@ -1,45 +0,0 @@ -[project] -name = "morphir-tools" -version = "0.0.1" -description = "CLI tools and extensions for Morphir Python" -readme = "README.md" -license = "Apache-2.0" -license-files = ["LICENSE"] -requires-python = ">=3.14" -keywords = ["morphir", "cli", "tools", "code-generation", "domain-modeling"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Environment :: Console", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.14", - "Topic :: Software Development :: Code Generators", - "Topic :: Software Development :: Libraries", - "Typing :: Typed", -] -authors = [ - { name = "FINOS", email = "morphir@finos.org" } -] -dependencies = [ - "morphir>=0.0.1", - "typer>=0.15", - # Rich uses Pygments; older versions are affected by CVE-2026-4539. - "pygments>=2.20.0", -] - -[project.urls] -Homepage = "https://github.com/finos/morphir-python" -Documentation = "https://morphir.finos.org" -Repository = "https://github.com/finos/morphir-python" -Issues = "https://github.com/finos/morphir-python/issues" - -[project.scripts] -morphir-tools = "morphir_tools.cli:main" - -[build-system] -requires = ["hatchling>=1.27"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["src/morphir_tools"] diff --git a/packages/morphir-tools/src/morphir_tools/__init__.py b/packages/morphir-tools/src/morphir_tools/__init__.py index 31cfe934..7d28926c 100644 --- a/packages/morphir-tools/src/morphir_tools/__init__.py +++ b/packages/morphir-tools/src/morphir_tools/__init__.py @@ -1,4 +1,4 @@ -"""Morphir Tools - CLI tools and extensions for Morphir Python. +"""Morphir Python - CLI tools and extensions for Morphir Python. This package provides command-line tools and utilities for working with Morphir domain models, including code generation and development tools. diff --git a/packages/morphir-tools/src/morphir_tools/cli.py b/packages/morphir-tools/src/morphir_tools/cli.py index f3338177..9a3369d4 100644 --- a/packages/morphir-tools/src/morphir_tools/cli.py +++ b/packages/morphir-tools/src/morphir_tools/cli.py @@ -1,4 +1,4 @@ -"""The ``morphir-tools`` command line. +"""The ``morphir-python`` command line. Every IR operation goes through the `morphir` library: the shared format-version reader, the v3 (3.0.x and 3.1.x) and draft v4 codecs, and `morphir.ir.migrate`. @@ -31,7 +31,7 @@ from morphir.ir.v4.path import Path as Path4 app = typer.Typer( - name="morphir-tools", + name="morphir-python", help="Command-line tools for Morphir IR.", no_args_is_help=True, ) @@ -219,5 +219,5 @@ def convert( def main() -> None: - """Run the ``morphir-tools`` command line.""" + """Run the ``morphir-python`` command line.""" app() diff --git a/packages/morphir/README.md b/packages/morphir/README.md index 22aa052d..233347f0 100644 --- a/packages/morphir/README.md +++ b/packages/morphir/README.md @@ -11,14 +11,17 @@ This is the core library package containing: ## Installation +The core library is included in the `morphir-python` distribution, together with +the SDK runtime and CLI. Python code continues to use `import morphir`. + ```bash -pip install morphir +pip install morphir-python ``` Or with uv: ```bash -uv add morphir +uv add morphir-python ``` ## Usage @@ -225,7 +228,7 @@ type there, which can differ from the declared type (an alias expanded, a type variable instantiated), and the draft codec then fails with `no_v4_spelling`. Opt in to dropping them with `v3_to_v4(..., drop_input_attributes=True)`, `drop_input_attributes(dist)` for any v4 distribution, or -`morphir-tools ir convert --to v4 --drop-input-attributes`. Each input that loses +`morphir-python ir convert --to v4 --drop-input-attributes`. Each input that loses information reports `input_attributes_dropped`; without the opt-in the in-memory migration stays lossless. @@ -381,4 +384,4 @@ Apache-2.0 - see the [LICENSE](../../LICENSE) file for details. ## Related Packages -- [morphir-tools](https://pypi.org/project/morphir-tools/) - CLI tools and extensions +- [morphir-python](https://pypi.org/project/morphir-python/) - CLI tools and extensions diff --git a/packages/morphir/src/morphir/ir/v4/codec.py b/packages/morphir/src/morphir/ir/v4/codec.py index bd670de8..317ebfa4 100644 --- a/packages/morphir/src/morphir/ir/v4/codec.py +++ b/packages/morphir/src/morphir/ir/v4/codec.py @@ -1235,7 +1235,7 @@ def _encode_expression_body(defn: v.Definition, loc: Location) -> Json: f"input {encode_name(name, here)!r} carries attributes other than its " "declared type; ExpressionBody.inputTypes has no attribute slot. " "Drop them with morphir.ir.migrate.drop_input_attributes or " - "`morphir-tools ir convert --drop-input-attributes`", + "`morphir-python ir convert --drop-input-attributes`", ) return { "ExpressionBody": { diff --git a/pyproject.toml b/pyproject.toml index 77e54459..b1d35983 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,45 @@ [project] -name = "morphir-workspace" +name = "morphir-python" version = "0.0.1" -description = "Morphir Python monorepo workspace" +description = "Morphir domain modeling library, SDK runtime, and CLI for Python" requires-python = ">=3.14" readme = "README.md" license = "Apache-2.0" +license-files = ["LICENSE"] +authors = [{ name = "FINOS", email = "morphir@finos.org" }] +keywords = ["morphir", "domain-modeling", "functional", "cli", "ir"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.14", + "Typing :: Typed", +] +dependencies = [ + "typer>=0.15", + # Rich uses Pygments; older versions are affected by CVE-2026-4539. + "pygments>=2.20.0", +] + +[project.urls] +Homepage = "https://github.com/finos/morphir-python" +Documentation = "https://morphir.finos.org" +Repository = "https://github.com/finos/morphir-python" +Issues = "https://github.com/finos/morphir-python/issues" + +[project.scripts] +morphir-python = "morphir_tools.cli:main" + +[build-system] +requires = ["hatchling>=1.27"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["packages/morphir/src/morphir", "packages/morphir-tools/src/morphir_tools"] + +[tool.hatch.build.targets.sdist] +only-include = ["packages", "README.md", "LICENSE", "uv.lock"] [dependency-groups] dev = [ @@ -24,10 +59,7 @@ test = [ managed = true [tool.uv.workspace] -members = ["packages/*"] - -[tool.uv.sources] -morphir = { workspace = true } +members = ["packages/morphir"] # ============================================================================= # Ruff Configuration diff --git a/tests/tools/__init__.py b/tests/tools/__init__.py index 6b0f7375..0f95fb16 100644 --- a/tests/tools/__init__.py +++ b/tests/tools/__init__.py @@ -1 +1 @@ -"""Unit tests for the morphir-tools package.""" +"""Unit tests for the morphir-python package.""" diff --git a/tests/tools/test_cli.py b/tests/tools/test_cli.py index 9914728c..2e8be39f 100644 --- a/tests/tools/test_cli.py +++ b/tests/tools/test_cli.py @@ -1,4 +1,4 @@ -"""Tests for the morphir-tools IR interchange CLI (capability tools/ir-interchange).""" +"""Tests for the morphir-python IR interchange CLI (capability tools/ir-interchange).""" import json from pathlib import Path @@ -39,6 +39,11 @@ def v4_file(tmp_path: Path) -> Path: class TestHelp: + def test_command_name(self) -> None: + result = run("--help") + assert result.exit_code == 0 + assert "morphir-python" in result.output + def test_ir_help(self) -> None: result = run("ir", "--help") assert result.exit_code == 0 @@ -50,7 +55,7 @@ def test_main_entry_point(self, monkeypatch: pytest.MonkeyPatch) -> None: from morphir_tools import cli - monkeypatch.setattr(sys, "argv", ["morphir-tools", "ir", "--help"]) + monkeypatch.setattr(sys, "argv", ["morphir-python", "ir", "--help"]) with pytest.raises(SystemExit) as info: cli.main() assert info.value.code == 0 diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index bc03a626..c7ffdfc3 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -1,10 +1,12 @@ -"""Tests for the morphir-tools package.""" +"""Tests for the morphir-python package.""" + +from importlib.metadata import distribution, version import morphir_tools class TestMorphirToolsPackage: - """Tests for the morphir-tools package initialization.""" + """Tests for the morphir-python package initialization.""" def test_version_is_defined(self) -> None: """The package should have a version attribute.""" @@ -14,6 +16,24 @@ def test_version_matches_expected(self, sample_version: str) -> None: """The version should match the expected value.""" assert morphir_tools.__version__ == sample_version + def test_installed_distribution_name(self) -> None: + """The tools import belongs to the morphir-python distribution.""" + assert version("morphir-python") == morphir_tools.__version__ + + def test_installed_console_command(self) -> None: + """The published command is named morphir-python.""" + commands = { + entry.name: entry.value + for entry in distribution("morphir-python").entry_points + if entry.group == "console_scripts" + } + assert commands == {"morphir-python": "morphir_tools.cli:main"} + + def test_bundle_does_not_require_separate_core(self) -> None: + """Installing the bundle must not query PyPI for an unpublished core.""" + requirements = distribution("morphir-python").requires or [] + assert not any(item.startswith("morphir") for item in requirements) + def test_version_is_string(self) -> None: """The version should be a string.""" assert isinstance(morphir_tools.__version__, str) diff --git a/uv.lock b/uv.lock index 00624e74..e57b1709 100644 --- a/uv.lock +++ b/uv.lock @@ -5,8 +5,7 @@ requires-python = ">=3.14" [manifest] members = [ "morphir", - "morphir-tools", - "morphir-workspace", + "morphir-python", ] [[package]] @@ -199,27 +198,14 @@ version = "0.0.1" source = { editable = "packages/morphir" } [[package]] -name = "morphir-tools" +name = "morphir-python" version = "0.0.1" -source = { editable = "packages/morphir-tools" } +source = { editable = "." } dependencies = [ - { name = "morphir" }, { name = "pygments" }, { name = "typer" }, ] -[package.metadata] -requires-dist = [ - { name = "morphir", editable = "packages/morphir" }, - { name = "pygments", specifier = ">=2.20.0" }, - { name = "typer", specifier = ">=0.15" }, -] - -[[package]] -name = "morphir-workspace" -version = "0.0.1" -source = { virtual = "." } - [package.dev-dependencies] dev = [ { name = "mypy" }, @@ -235,6 +221,10 @@ test = [ ] [package.metadata] +requires-dist = [ + { name = "pygments", specifier = ">=2.20.0" }, + { name = "typer", specifier = ">=0.15" }, +] [package.metadata.requires-dev] dev = [