openframe-core 3.2.0__tar.gz → 3.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.github/CHANGELOG.md +13 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/PKG-INFO +1 -1
- openframe_core-3.2.1/openframe/core/schemas/__init__.py +128 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/pyproject.toml +1 -1
- openframe_core-3.2.1/tests/test_schemas.py +91 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.github/requirements/docs.txt +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.github/requirements/test.txt +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.github/workflows/app-test.yml +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.github/workflows/auto-docs.yml +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.github/workflows/python-build.yml +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.gitignore +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/.vscode/settings.json +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/LICENSE +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/README.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/assets/images/banner.png +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/assets/images/favicon.png +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/assets/images/logo-dark.png +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/assets/images/logo-light.png +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/assets/javascripts/extra.js +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/assets/stylesheets/extra.css +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/cicd/pipeline.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/getting-started/index.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/config.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/exceptions.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/health.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/inbound.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/index.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/middleware.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/plugins.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/ports.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/runtime.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/telemetry.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/testing.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/code/modules/tracing.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/developer-guide/debugging.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/developer-guide/explore.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/developer-guide/first-change.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/developer-guide/how-it-works.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/developer-guide/local-dev.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/developer-guide/quick-start/index.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/index.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-001-namespace.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-002-hexagonal.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-003-async.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-004-pydantic.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-005-middleware.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-006-unified-port-lifecycle.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/index.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/capability-taxonomy.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/component-lifecycle.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/data-flows.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/design-decisions.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/error-taxonomy.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/package-journey/adapter-lifecycle.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/package-journey/error-propagation.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/package-journey/index.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/package-journey/template-wiring.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/package-journey/tracing-flow.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/system-architecture.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/deployment/cicd.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/deployment/environments.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/deployment/secrets-and-config.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/infrastructure/networking.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/infrastructure/platform.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/infrastructure/storage.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/index.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/monitoring.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/runbooks/adapter-connection-failure.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/runbooks/modal-cold-start-spike.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/runbooks/postgres-pool-exhausted.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/runbooks/queue-consumer-stuck.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/runbooks/redis-connection-lost.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/operations/runbooks/telemetry-export-failing.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/overview/glossary.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/overview/roadmap.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/overview/system-overview.md +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/mkdocs.yml +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/config/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/config/settings.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/exceptions/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/exceptions/adapter.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/exceptions/base.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/exceptions/codes.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/exceptions/plugin.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/inbound/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/inbound/command.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/inbound/context.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/inbound/query.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/inbound/usecase.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/middleware/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/middleware/telemetry.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/middleware/types.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/plugins/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/plugins/registry.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/capability.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/context.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/health.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/identity.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/lifecycle.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/outbound/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/outbound/consumer.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/outbound/producer.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/outbound/repository.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/ports/port.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/runtime/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/runtime/bootstrap.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/telemetry/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/telemetry/setup.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/consumer.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/inbound.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/lifecycle.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/port.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/producer.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/repository.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/fakes/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/fakes/consumer.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/fakes/inbound.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/fakes/producer.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/fakes/repository.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/fixtures.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/tracing/__init__.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/tracing/propagation.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/tracing/proxy.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/py.typed +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/conftest.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_config.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_exceptions.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_inbound.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_inbound_contracts.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_middleware.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_middleware_types.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_plugins.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_ports.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_ports_primitives.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_record_error.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_runtime.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_shutdown_telemetry.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_telemetry_sidecar.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_testing_fakes.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_tracing.py +0 -0
- {openframe_core-3.2.0 → openframe_core-3.2.1}/tests/test_tracing_propagation.py +0 -0
|
@@ -58,6 +58,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
+
## v3.2.1
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- `openframe.core.schemas` — new experimental module providing
|
|
66
|
+
the `@contract(name, version)` decorator, `ContractMeta` dataclass,
|
|
67
|
+
and `get_contract_meta()` helper. Marks Pydantic models as
|
|
68
|
+
published cross-service contracts. The schema export, registry,
|
|
69
|
+
breaking-change diff, and codegen machinery lives in the
|
|
70
|
+
separate `openframe-schemas` package (openframe-tooling repo).
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
61
74
|
## v3.2.0
|
|
62
75
|
|
|
63
76
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openframe-core
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.1
|
|
4
4
|
Summary: OpenFrame Microservice Suite - platform kernel. Port contracts, testing utilities, plugin registry, and composition root.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Furious-Meteors/openframe-core
|
|
6
6
|
Project-URL: Documentation, https://furious-meteors.github.io/openframe-core/
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe.core.schemas
|
|
3
|
+
========================
|
|
4
|
+
Cross-service data shape governance (ADR-007).
|
|
5
|
+
|
|
6
|
+
The @contract decorator marks a Pydantic model as a published
|
|
7
|
+
cross-service contract — a shape that crosses a service boundary
|
|
8
|
+
(via REST, Kafka, gRPC, or any other transport) and must be
|
|
9
|
+
governed for breaking changes.
|
|
10
|
+
|
|
11
|
+
This module owns only the *marker*. The machinery that acts on
|
|
12
|
+
the marker — schema export, version registry, breaking-change diff,
|
|
13
|
+
codegen — lives in the separate openframe-schemas package, which
|
|
14
|
+
has its own dependencies and release cadence.
|
|
15
|
+
|
|
16
|
+
Dependency order:
|
|
17
|
+
core/schemas → stdlib only (no pydantic, no openframe imports)
|
|
18
|
+
|
|
19
|
+
Usage::
|
|
20
|
+
|
|
21
|
+
from openframe.core.schemas import contract
|
|
22
|
+
|
|
23
|
+
@contract(name="item", version="1.0")
|
|
24
|
+
class Item(BaseModel):
|
|
25
|
+
id: str
|
|
26
|
+
name: str
|
|
27
|
+
description: str | None = None
|
|
28
|
+
status: str = "active"
|
|
29
|
+
|
|
30
|
+
The @contract decorator does not change the class in any way that
|
|
31
|
+
affects Pydantic validation, serialization, or runtime behaviour.
|
|
32
|
+
It attaches __contract__ as a class attribute that openframe-schemas
|
|
33
|
+
reads at schema-export time.
|
|
34
|
+
|
|
35
|
+
.. stability: experimental
|
|
36
|
+
Experimental — the decorator signature (name, version) is stable;
|
|
37
|
+
the ContractMeta attributes may gain additional fields in future
|
|
38
|
+
minor versions.
|
|
39
|
+
"""
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
from dataclasses import dataclass
|
|
43
|
+
|
|
44
|
+
__all__ = ["ContractMeta", "contract", "get_contract_meta"]
|
|
45
|
+
|
|
46
|
+
__stability__ = "experimental"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class ContractMeta:
|
|
51
|
+
"""
|
|
52
|
+
Metadata attached to a @contract-decorated model.
|
|
53
|
+
|
|
54
|
+
.. stability: experimental
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
name: The contract's stable name (e.g. ``"item"``). Identifies
|
|
58
|
+
the contract independently of the Python class name.
|
|
59
|
+
version: The contract's version string (e.g. ``"1.0"``). Governed
|
|
60
|
+
by openframe-schemas' breaking-change diff tooling.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
name: str
|
|
64
|
+
version: str
|
|
65
|
+
|
|
66
|
+
__stability__ = "experimental"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def contract(name: str, version: str):
|
|
70
|
+
"""
|
|
71
|
+
Mark a class as a published cross-service contract.
|
|
72
|
+
|
|
73
|
+
Attaches a :class:`ContractMeta` instance to the decorated class as
|
|
74
|
+
``cls.__contract__``. Returns the class unchanged — pure decoration,
|
|
75
|
+
no wrapping — so Pydantic validation, serialization, and any other
|
|
76
|
+
runtime behaviour are unaffected.
|
|
77
|
+
|
|
78
|
+
Works with any class, not just Pydantic ``BaseModel`` subclasses, but
|
|
79
|
+
the intended use is always Pydantic models.
|
|
80
|
+
|
|
81
|
+
.. stability: experimental
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
name: The contract's stable name.
|
|
85
|
+
version: The contract's version string.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
A decorator that attaches ``__contract__`` to the class and
|
|
89
|
+
returns it unchanged.
|
|
90
|
+
|
|
91
|
+
Usage::
|
|
92
|
+
|
|
93
|
+
@contract(name="item", version="1.0")
|
|
94
|
+
class Item(BaseModel):
|
|
95
|
+
id: str
|
|
96
|
+
name: str
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def _decorate(cls: type) -> type:
|
|
100
|
+
cls.__contract__ = ContractMeta(name=name, version=version)
|
|
101
|
+
return cls
|
|
102
|
+
|
|
103
|
+
return _decorate
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
contract.__stability__ = "experimental"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_contract_meta(cls: type) -> ContractMeta | None:
|
|
110
|
+
"""
|
|
111
|
+
Return the ContractMeta attached to a class, if any.
|
|
112
|
+
|
|
113
|
+
Safe on any class — undecorated classes simply return ``None``
|
|
114
|
+
rather than raising.
|
|
115
|
+
|
|
116
|
+
.. stability: experimental
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
cls: The class to inspect.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The :class:`ContractMeta` attached via :func:`contract`, or
|
|
123
|
+
``None`` if the class is not @contract-decorated.
|
|
124
|
+
"""
|
|
125
|
+
return getattr(cls, "__contract__", None)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
get_contract_meta.__stability__ = "experimental"
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openframe-core"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.1"
|
|
8
8
|
description = "OpenFrame Microservice Suite - platform kernel. Port contracts, testing utilities, plugin registry, and composition root."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests/test_schemas.py
|
|
3
|
+
========================
|
|
4
|
+
Tests for openframe.core.schemas — the @contract marker (ADR-007).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from openframe.core.schemas import ContractMeta, contract, get_contract_meta
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_contract_decorator_attaches_meta() -> None:
|
|
15
|
+
@contract(name="item", version="1.0")
|
|
16
|
+
class Item(BaseModel):
|
|
17
|
+
id: str
|
|
18
|
+
name: str
|
|
19
|
+
|
|
20
|
+
assert Item.__contract__.name == "item"
|
|
21
|
+
assert Item.__contract__.version == "1.0"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_contract_decorator_returns_class_unchanged() -> None:
|
|
25
|
+
class RawItem(BaseModel):
|
|
26
|
+
id: str
|
|
27
|
+
name: str
|
|
28
|
+
|
|
29
|
+
DecoratedItem = contract(name="item", version="1.0")(RawItem)
|
|
30
|
+
|
|
31
|
+
assert DecoratedItem is RawItem
|
|
32
|
+
instance = DecoratedItem(id="1", name="widget")
|
|
33
|
+
assert isinstance(instance, RawItem)
|
|
34
|
+
assert DecoratedItem.model_json_schema() == RawItem.model_json_schema()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_get_contract_meta_returns_meta_for_decorated_class() -> None:
|
|
38
|
+
@contract(name="order", version="2.1")
|
|
39
|
+
class Order(BaseModel):
|
|
40
|
+
id: str
|
|
41
|
+
|
|
42
|
+
meta = get_contract_meta(Order)
|
|
43
|
+
assert meta is not None
|
|
44
|
+
assert meta.name == "order"
|
|
45
|
+
assert meta.version == "2.1"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_get_contract_meta_returns_none_for_undecorated_class() -> None:
|
|
49
|
+
class PlainModel(BaseModel):
|
|
50
|
+
id: str
|
|
51
|
+
|
|
52
|
+
assert get_contract_meta(PlainModel) is None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_contract_decorator_works_on_nested_model() -> None:
|
|
56
|
+
class Address(BaseModel):
|
|
57
|
+
street: str
|
|
58
|
+
city: str
|
|
59
|
+
|
|
60
|
+
@contract(name="customer", version="1.0")
|
|
61
|
+
class Customer(BaseModel):
|
|
62
|
+
id: str
|
|
63
|
+
address: Address
|
|
64
|
+
|
|
65
|
+
instance = Customer(id="1", address=Address(street="Main St", city="Springfield"))
|
|
66
|
+
assert instance.address.city == "Springfield"
|
|
67
|
+
assert get_contract_meta(Customer).name == "customer"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_contract_meta_repr() -> None:
|
|
71
|
+
assert repr(ContractMeta("item", "1.0")) == "ContractMeta(name='item', version='1.0')"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_contract_marker_does_not_affect_pydantic_validation() -> None:
|
|
75
|
+
class PlainModel(BaseModel):
|
|
76
|
+
id: str
|
|
77
|
+
count: int
|
|
78
|
+
|
|
79
|
+
@contract(name="counted", version="1.0")
|
|
80
|
+
class DecoratedModel(BaseModel):
|
|
81
|
+
id: str
|
|
82
|
+
count: int
|
|
83
|
+
|
|
84
|
+
plain = PlainModel(id="1", count=5)
|
|
85
|
+
decorated = DecoratedModel(id="1", count=5)
|
|
86
|
+
assert plain.model_dump() == decorated.model_dump()
|
|
87
|
+
|
|
88
|
+
with pytest.raises(Exception):
|
|
89
|
+
PlainModel(id="1", count="not-an-int")
|
|
90
|
+
with pytest.raises(Exception):
|
|
91
|
+
DecoratedModel(id="1", count="not-an-int")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-001-namespace.md
RENAMED
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-002-hexagonal.md
RENAMED
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-003-async.md
RENAMED
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-004-pydantic.md
RENAMED
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/adrs/adr-005-middleware.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/capability-taxonomy.md
RENAMED
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/component-lifecycle.md
RENAMED
|
File without changes
|
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/design-decisions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/package-journey/index.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/architecture/system-architecture.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/docs/technical/deployment/secrets-and-config.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openframe_core-3.2.0 → openframe_core-3.2.1}/openframe/core/testing/contracts/repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|