Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/python-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ available there.
| Target | `python` |
| IR versions | `3`, `4` |
| MEP version | `0.1` |
| Multiple source documents | `multiDocument: true`, from `0.4.2` |

Extension `0.4.2` pairs with `morphir-python==0.1.0` and
`morphir-python-native==0.1.1`, qualified with `morphir==0.1.0`.
The native API reports compiler version `0.4.2`.
Use `0.4.2` or later with hosts that require an explicit multi-document claim;
`0.4.1` omits that claim.

The declaration is in [`rust/extension.toml`](../rust/extension.toml).

Expand Down
2 changes: 1 addition & 1 deletion packages/morphir-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ format-version rules and the draft v4 status.
## Requirements

- Python 3.14+
- `morphir>=0.0.2` and `morphir-python-native==0.1.0` (installed as dependencies)
- `morphir>=0.1.0` and `morphir-python-native==0.1.1` (installed as dependencies)

## License

Expand Down
6 changes: 3 additions & 3 deletions packages/morphir-tools/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "morphir-python"
version = "0.0.3"
version = "0.1.0"
description = "CLI tools and extensions for Morphir Python"
readme = "README.md"
license = "Apache-2.0"
Expand All @@ -22,9 +22,9 @@ authors = [
{ name = "FINOS", email = "morphir@finos.org" }
]
dependencies = [
"morphir>=0.0.2",
"morphir>=0.1.0",
# Exact qualified native compiler; checked again when the compiler first loads.
"morphir-python-native==0.1.0",
"morphir-python-native==0.1.1",
"typer>=0.15",
# Rich uses Pygments; older versions are affected by CVE-2026-4539.
"pygments>=2.20.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/morphir-tools/src/morphir_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
Example:
>>> import morphir_tools
>>> morphir_tools.__version__
'0.0.3'
'0.1.0'
"""

__version__ = "0.0.3"
__version__ = "0.1.0"
__all__ = ["__version__"]
2 changes: 1 addition & 1 deletion packages/morphir-tools/src/morphir_tools/python/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from ._types import NativeCompilerInfo, NativeCompilerUnavailable

EXPECTED_NATIVE_VERSION = "0.1.0"
EXPECTED_NATIVE_VERSION = "0.1.1"
"""The exact ``morphir-python-native`` version this release was qualified with.

It must equal the pin in this distribution's ``pyproject.toml``.
Expand Down
2 changes: 1 addition & 1 deletion packages/morphir/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "morphir"
version = "0.0.2"
version = "0.1.0"
description = "Python port of Morphir - functional domain modeling for the enterprise"
readme = "README.md"
license = "Apache-2.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/morphir/src/morphir/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
Example:
>>> import morphir
>>> morphir.__version__
'0.0.2'
'0.1.0'
"""

__version__ = "0.0.2"
__version__ = "0.1.0"
__all__ = ["__version__"]
4 changes: 2 additions & 2 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/crates/morphir-python-binding/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "morphir-python-binding"
version = "0.4.1"
version = "0.4.2"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
Expand Down
1 change: 1 addition & 0 deletions rust/crates/morphir-python-binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl Extension for PythonExtension {
}],
ir_versions: vec!["3".into(), "4".into()],
compile: true,
multi_document: true,
..Default::default()
}),
backend: Some(BackendCapability {
Expand Down
7 changes: 7 additions & 0 deletions rust/crates/morphir-python-binding/tests/ir_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,15 @@ fn generate(ir: Value) -> GenerateResult {

#[test]
fn both_versions_preserve_imports_privacy_tuples_and_conditionals() {
assert!(
PythonExtension::capabilities()
.frontend
.unwrap()
.multi_document
);
for version in ["3", "3.0.0", "4", "4.0.0"] {
let mut request = a_request(version);
assert_eq!(request.sources.documents.len(), 2);
let ir = compile(request.clone());
if version.starts_with('3') {
let decoded: classic::Distribution = serde_json::from_value(ir.clone()).unwrap();
Expand Down
5 changes: 5 additions & 0 deletions rust/crates/morphir-python-binding/tests/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ fn rejects_unsupported_or_ambiguous_source_without_partial_ir() {
#[test]
fn advertises_and_enforces_the_initial_contract() {
let capabilities = PythonExtension::capabilities();
assert!(capabilities.frontend.as_ref().unwrap().multi_document);
assert_eq!(
serde_json::to_value(&capabilities).unwrap()["frontend"]["multiDocument"],
json!(true)
);
assert_eq!(capabilities.frontend.unwrap().ir_versions, ["3", "4"]);
assert_eq!(capabilities.backend.unwrap().targets, ["python"]);
let mut request = a_request("");
Expand Down
10 changes: 9 additions & 1 deletion rust/crates/morphir-python-binding/tests/support/native.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Native protocol fixture with a completed initialization handshake.

use morphir_extension_sdk::{
NativeExtension,
Extension, NativeExtension,
protocol::{ExtensionRequest, InitializeParams, MEP_VERSION, PeerInfo, methods},
};
use morphir_python_binding::PythonExtension;
Expand All @@ -25,5 +25,13 @@ pub fn an_initialized_extension() -> NativeExtension {
.unwrap(),
);
assert!(response.error.is_none(), "{:?}", response.error);
let expected = serde_json::to_value(PythonExtension::capabilities()).unwrap();
assert_eq!(expected["frontend"]["multiDocument"], true);
assert_eq!(response.result.unwrap()["capabilities"], expected);
let response = extension
.protocol()
.handle(ExtensionRequest::new(methods::CAPABILITIES, serde_json::json!({}), 1).unwrap());
assert!(response.error.is_none(), "{:?}", response.error);
assert_eq!(response.result.unwrap(), expected);
extension
}
2 changes: 1 addition & 1 deletion rust/crates/morphir-python-native/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "morphir-python-native"
version = "0.1.0"
version = "0.1.1"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
Expand Down
15 changes: 15 additions & 0 deletions scripts/extension_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def receipt(bundle: Path) -> dict[str, object]:
if not (
isinstance(frontend, dict)
and frontend.get("compile") is True
and frontend.get("multiDocument") is True
and frontend.get("irVersions") == extension["ir_versions"]
and isinstance(backend, dict)
and backend.get("generate") is True
Expand All @@ -212,6 +213,7 @@ def receipt(bundle: Path) -> dict[str, object]:
"git_commit": descriptor.get("gitCommit"),
"packager_rev": declaration()["packager"]["rev"],
"capabilities": sorted(k for k, v in capabilities.items() if v),
"multiDocument": frontend["multiDocument"],
}


Expand Down Expand Up @@ -258,6 +260,8 @@ def morphir_cli() -> tuple[Path, str]:

def host_check(bundle: Path) -> dict[str, object]:
"""Install the bundle with the pinned CLI and round trip offline."""
from morphir_tools.python import native_compiler

bundle = bundle.resolve()
described = receipt(bundle)
cli, cli_digest = morphir_cli()
Expand Down Expand Up @@ -335,6 +339,10 @@ def morphir(*args: str, offline: bool = False) -> str:
return {
**described,
"host": {"morphir": declaration()["host"]["version"], "sha256": cli_digest},
"python_api": {
"native_version": native_compiler().version,
"compiler_version": native_compiler().compiler_version,
},
"offline_round_trips": results,
}

Expand All @@ -348,8 +356,15 @@ def agrees_with_api(document: dict[str, Any], tag: str) -> bool:
IrVersion,
SourceDocument,
compile_sources,
native_compiler,
)

compiler = native_compiler()
if compiler.compiler_version != crate_version():
raise BundleError(
f"API compiler {compiler.compiler_version} differs from "
f"bundle {crate_version()}"
)
sources = [
SourceDocument(p.relative_to(SAMPLE).as_posix(), p.read_text())
for p in sorted(SAMPLE.rglob("*.py"))
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
@pytest.fixture
def sample_version() -> str:
"""Provide the expected version string for tests."""
return "0.0.2"
return "0.1.0"
4 changes: 2 additions & 2 deletions tests/tools/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_version_is_defined(self) -> None:

def test_version_matches_expected(self) -> None:
"""The version should match the expected value."""
assert morphir_tools.__version__ == "0.0.3"
assert morphir_tools.__version__ == "0.1.0"

def test_installed_distribution_name(self) -> None:
"""The tools import belongs to the morphir-python distribution."""
Expand All @@ -32,7 +32,7 @@ def test_installed_console_command(self) -> None:
def test_cli_requires_separate_core(self) -> None:
"""The CLI must install the independent library and SDK distribution."""
requirements = distribution("morphir-python").requires or []
assert "morphir>=0.0.2" in requirements
assert "morphir>=0.1.0" in requirements

def test_version_is_string(self) -> None:
"""The version should be a string."""
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/test_extension_bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Require explicit multi-document claims in Python release bundles."""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

import pytest

from scripts import extension_bundle as bundle

if TYPE_CHECKING:
from pathlib import Path


@pytest.mark.parametrize("multi_document", [None, False, True])
def test_bundle_requires_multi_document_claim(
tmp_path: Path, multi_document: bool | None
) -> None:
version = bundle.crate_version()
artifact = f"morphir-python-binding-{version}.wasm"
wasm = b"qualification fixture"
digest = bundle.sha256(wasm)
(tmp_path / artifact).write_bytes(wasm)
(tmp_path / f"{artifact}.sha256").write_text(f"{digest} {artifact}\n")
frontend: dict[str, object] = {"compile": True, "irVersions": ["3", "4"]}
if multi_document is not None:
frontend["multiDocument"] = multi_document
(tmp_path / "release.json").write_text(
json.dumps(
{
"extensionId": "morphir-python",
"version": version,
"artifacts": [
{
"sha256": digest,
"claims": {
"extension": {"id": "morphir-python", "version": version},
"capabilities": {
"frontend": frontend,
"backend": {"generate": True, "targets": ["python"]},
},
},
}
],
}
)
)
if multi_document is True:
assert bundle.receipt(tmp_path)["multiDocument"] is True
else:
with pytest.raises(bundle.BundleError, match="claims lack"):
bundle.receipt(tmp_path)
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading