pipelantic 0.2.0__tar.gz → 0.3.0__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.
Files changed (87) hide show
  1. {pipelantic-0.2.0 → pipelantic-0.3.0}/CHANGELOG.md +39 -0
  2. {pipelantic-0.2.0 → pipelantic-0.3.0}/PKG-INFO +12 -11
  3. {pipelantic-0.2.0 → pipelantic-0.3.0}/README.md +9 -10
  4. {pipelantic-0.2.0 → pipelantic-0.3.0}/pyproject.toml +6 -1
  5. pipelantic-0.3.0/src/pipelantic/__init__.py +189 -0
  6. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/_version.py +1 -1
  7. pipelantic-0.3.0/src/pipelantic/capabilities.py +134 -0
  8. pipelantic-0.3.0/src/pipelantic/cli/__init__.py +285 -0
  9. pipelantic-0.3.0/src/pipelantic/cli/__main__.py +6 -0
  10. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/contracts.py +13 -10
  11. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/diagnostics.py +39 -4
  12. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/diff.py +78 -11
  13. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/dtcs.py +17 -1
  14. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/model.py +1 -0
  15. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/pipeline.py +33 -2
  16. pipelantic-0.3.0/src/pipelantic/plan/__init__.py +34 -0
  17. pipelantic-0.3.0/src/pipelantic/plan/artifacts.py +77 -0
  18. pipelantic-0.3.0/src/pipelantic/plan/explain.py +52 -0
  19. pipelantic-0.3.0/src/pipelantic/plan/model.py +315 -0
  20. pipelantic-0.3.0/src/pipelantic/plan/planner.py +570 -0
  21. pipelantic-0.3.0/src/pipelantic/plan/regions.py +73 -0
  22. pipelantic-0.3.0/src/pipelantic/plan/serialize.py +73 -0
  23. pipelantic-0.3.0/src/pipelantic/plan/slicing.py +81 -0
  24. pipelantic-0.3.0/src/pipelantic/policy.py +76 -0
  25. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/ports.py +23 -3
  26. pipelantic-0.3.0/src/pipelantic/profile.py +157 -0
  27. pipelantic-0.3.0/src/pipelantic/registry.py +215 -0
  28. pipelantic-0.3.0/src/pipelantic/reliability.py +277 -0
  29. pipelantic-0.3.0/src/pipelantic/schema_drift.py +345 -0
  30. pipelantic-0.3.0/src/pipelantic/schemas/__init__.py +32 -0
  31. pipelantic-0.3.0/src/pipelantic/schemas/pipeline-plan.schema.json +40 -0
  32. pipelantic-0.3.0/src/pipelantic/schemas/profile.schema.json +33 -0
  33. pipelantic-0.3.0/src/pipelantic/schemas/project-config.schema.json +16 -0
  34. pipelantic-0.3.0/src/pipelantic/secrets.py +40 -0
  35. pipelantic-0.3.0/src/pipelantic/symbols.py +117 -0
  36. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/transformation.py +9 -0
  37. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/validation.py +334 -10
  38. pipelantic-0.3.0/tests/cli/test_cli.py +68 -0
  39. pipelantic-0.3.0/tests/fixtures/sample_pipeline.py +18 -0
  40. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/interchange/test_regression_p0_2.py +4 -3
  41. pipelantic-0.3.0/tests/interchange/test_regression_p0_3.py +103 -0
  42. pipelantic-0.3.0/tests/model/__init__.py +0 -0
  43. pipelantic-0.3.0/tests/plan/__init__.py +0 -0
  44. pipelantic-0.3.0/tests/plan/test_planner.py +231 -0
  45. pipelantic-0.3.0/tests/plan/test_serialize.py +69 -0
  46. pipelantic-0.3.0/tests/profile/__init__.py +0 -0
  47. pipelantic-0.3.0/tests/profile/test_profile.py +41 -0
  48. pipelantic-0.3.0/tests/reliability/__init__.py +0 -0
  49. pipelantic-0.3.0/tests/reliability/test_reliability.py +27 -0
  50. pipelantic-0.3.0/tests/schema_drift/__init__.py +0 -0
  51. pipelantic-0.3.0/tests/schema_drift/test_schema_drift.py +53 -0
  52. pipelantic-0.3.0/tests/unit/__init__.py +0 -0
  53. pipelantic-0.3.0/tests/unit/test_data_facade.py +44 -0
  54. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/unit/test_package.py +5 -2
  55. pipelantic-0.3.0/tests/validation/__init__.py +0 -0
  56. pipelantic-0.3.0/tests/validation/test_phases.py +139 -0
  57. pipelantic-0.2.0/src/pipelantic/__init__.py +0 -81
  58. {pipelantic-0.2.0 → pipelantic-0.3.0}/.gitignore +0 -0
  59. {pipelantic-0.2.0 → pipelantic-0.3.0}/LICENSE +0 -0
  60. {pipelantic-0.2.0 → pipelantic-0.3.0}/SECURITY.md +0 -0
  61. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/exceptions.py +0 -0
  62. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/identity.py +0 -0
  63. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/inspection.py +0 -0
  64. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/__init__.py +0 -0
  65. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/bundle.py +0 -0
  66. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/diagnostics.py +0 -0
  67. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/dpcs.py +0 -0
  68. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/normalize.py +0 -0
  69. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/odcs.py +0 -0
  70. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/policy.py +0 -0
  71. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/provenance.py +0 -0
  72. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/interchange/security.py +0 -0
  73. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/mermaid.py +0 -0
  74. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/py.typed +0 -0
  75. {pipelantic-0.2.0 → pipelantic-0.3.0}/src/pipelantic/refs.py +0 -0
  76. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/__init__.py +0 -0
  77. {pipelantic-0.2.0/tests/model → pipelantic-0.3.0/tests/cli}/__init__.py +0 -0
  78. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/conftest.py +0 -0
  79. {pipelantic-0.2.0/tests/unit → pipelantic-0.3.0/tests/fixtures}/__init__.py +0 -0
  80. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/interchange/test_acceptance_0_2.py +0 -0
  81. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/model/test_acceptance.py +0 -0
  82. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/unit/test_diagnostics.py +0 -0
  83. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/unit/test_graph_validation.py +0 -0
  84. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/unit/test_identity.py +0 -0
  85. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/unit/test_regression_p0.py +0 -0
  86. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/unit/test_subpipeline.py +0 -0
  87. {pipelantic-0.2.0 → pipelantic-0.3.0}/tests/unit/test_transformation.py +0 -0
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-07-16
9
+
10
+ ### Added
11
+
12
+ - `Data` as the preferred thin facade over ContractModel (`DataContractModel`
13
+ remains as a deprecated alias)
14
+ - Multi-phase validation (structural, reference, semantic, policy, capability)
15
+ with diagnostic actions and source symbols
16
+ - `Profile` templates, `SecretRef`, scoped registries, and capability negotiation
17
+ - Immutable secret-free `PipelinePlan` IR (`pipelantic.plan/1`) with slicing,
18
+ explain output, and canonical fingerprints
19
+ - Schema-drift models (`NormalizedSchema`, observations, changes, `DriftImpact`)
20
+ - Portable reliability/intent models (freshness, write/materialization intents,
21
+ idempotency, evidence schemas)
22
+ - JSON Schemas for profiles, project config, and PipelinePlan
23
+ - `pipelantic` CLI: `validate`, `inspect`, `plan`, and `plan explain`
24
+ (plus `plan --explain` alias, `--nodes`, and mutual exclusion for
25
+ `--run-one` / `--run-until`)
26
+ - `Pipeline.plan()` / `Pipeline.explain_plan()`
27
+
28
+ ### Fixed
29
+
30
+ - `run_until` selects declaration-order prefix (not only upstream closure)
31
+ - Unknown plan selections fail closed with `PMPLAN501`
32
+ - Multi-engine regions and cross-engine durable artifact strategies
33
+ - Profile snapshot included in plan fingerprint
34
+ - Strict policy requires registered transformation implementations
35
+ - Subpipeline validation inherits parent planning context
36
+ - Plan JSON round-trip restores `secret_ref` and verifies fingerprints
37
+ - Schema-only DTCS ports map `datetime`/`decimal`/`binary` correctly
38
+ - Diff APIs return diagnostics for malformed toolkit input
39
+
40
+ ### Changed
41
+
42
+ - Package version and public status advance to 0.3 (Validation and Pipeline Plan IR)
43
+ - Explicit `click` dependency for the CLI plan command group (Typer 0.15+ no
44
+ longer pulls it in transitively)
45
+
8
46
  ## [0.2.0] - 2026-07-16
9
47
 
10
48
  ### Added
@@ -48,5 +86,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
48
86
  - uv + ruff toolchain, MkDocs documentation site, shared GitHub Actions
49
87
  checks, and tag-triggered PyPI release
50
88
 
89
+ [0.3.0]: https://github.com/eddiethedean/pipelantic/releases/tag/v0.3.0
51
90
  [0.2.0]: https://github.com/eddiethedean/pipelantic/releases/tag/v0.2.0
52
91
  [0.1.0]: https://github.com/eddiethedean/pipelantic/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipelantic
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Typed, contract-driven data pipeline modeling for Python.
5
5
  Project-URL: Homepage, https://github.com/eddiethedean/pipelantic
6
6
  Project-URL: Documentation, https://github.com/eddiethedean/pipelantic/tree/main/docs
@@ -20,11 +20,13 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.11
23
+ Requires-Dist: click<9,>=8
23
24
  Requires-Dist: contractmodel>=0.1.2
24
25
  Requires-Dist: dpcs<1,>=0.13
25
26
  Requires-Dist: dtcs<1,>=0.11
26
27
  Requires-Dist: packaging>=24
27
28
  Requires-Dist: pydantic<3,>=2.12
29
+ Requires-Dist: typer<1,>=0.15
28
30
  Description-Content-Type: text/markdown
29
31
 
30
32
  ![Pipelantic banner](https://raw.githubusercontent.com/eddiethedean/pipelantic/main/docs/theme/assets/pipelantic-banner.png)
@@ -40,11 +42,11 @@ Typed, contract-driven data pipeline modeling for Python.
40
42
 
41
43
  ## Status
42
44
 
43
- **0.2.0 — Contract Interoperability**
45
+ **0.3.0 — Validation and Pipeline Plan IR**
44
46
 
45
- Pipelantic provides the typed modeling kernel plus ODCS/DTCS/DPCS generate and
46
- load, deterministic contract bundles, and compatibility hooks. Planning and
47
- execution plugins arrive in later milestones.
47
+ Pipelantic provides the typed modeling kernel, contract interoperability, and
48
+ an immutable secret-free `PipelinePlan`. Planning is pure (no secret
49
+ resolution or execution). Local runtime arrives in later milestones.
48
50
 
49
51
  See the [hosted documentation](https://pipelantic.readthedocs.io/) for the
50
52
  full design,
@@ -78,8 +80,8 @@ the `dev` dependency group (pytest, ruff, mkdocs) by default.
78
80
  Tag a version that matches `src/pipelantic/_version.py`, then push the tag:
79
81
 
80
82
  ```bash
81
- git tag v0.2.0
82
- git push origin v0.2.0
83
+ git tag v0.3.0
84
+ git push origin v0.3.0
83
85
  ```
84
86
 
85
87
  GitHub Actions runs checks and publishes to PyPI using the `PYPI_API_TOKEN`
@@ -88,17 +90,16 @@ repository secret.
88
90
  ## Quick example
89
91
 
90
92
  ```python
91
- from contractmodel import ContractModel as DataContractModel
92
- from pipelantic import Input, Output, Pipeline, Sink, Source, Transformation
93
+ from pipelantic import Data, Input, Output, Pipeline, Sink, Source, Transformation
93
94
 
94
95
 
95
- class RawCustomer(DataContractModel):
96
+ class RawCustomer(Data):
96
97
  customer_id: int
97
98
  first_name: str
98
99
  last_name: str
99
100
 
100
101
 
101
- class Customer(DataContractModel):
102
+ class Customer(Data):
102
103
  customer_id: int
103
104
  full_name: str
104
105
 
@@ -11,11 +11,11 @@ Typed, contract-driven data pipeline modeling for Python.
11
11
 
12
12
  ## Status
13
13
 
14
- **0.2.0 — Contract Interoperability**
14
+ **0.3.0 — Validation and Pipeline Plan IR**
15
15
 
16
- Pipelantic provides the typed modeling kernel plus ODCS/DTCS/DPCS generate and
17
- load, deterministic contract bundles, and compatibility hooks. Planning and
18
- execution plugins arrive in later milestones.
16
+ Pipelantic provides the typed modeling kernel, contract interoperability, and
17
+ an immutable secret-free `PipelinePlan`. Planning is pure (no secret
18
+ resolution or execution). Local runtime arrives in later milestones.
19
19
 
20
20
  See the [hosted documentation](https://pipelantic.readthedocs.io/) for the
21
21
  full design,
@@ -49,8 +49,8 @@ the `dev` dependency group (pytest, ruff, mkdocs) by default.
49
49
  Tag a version that matches `src/pipelantic/_version.py`, then push the tag:
50
50
 
51
51
  ```bash
52
- git tag v0.2.0
53
- git push origin v0.2.0
52
+ git tag v0.3.0
53
+ git push origin v0.3.0
54
54
  ```
55
55
 
56
56
  GitHub Actions runs checks and publishes to PyPI using the `PYPI_API_TOKEN`
@@ -59,17 +59,16 @@ repository secret.
59
59
  ## Quick example
60
60
 
61
61
  ```python
62
- from contractmodel import ContractModel as DataContractModel
63
- from pipelantic import Input, Output, Pipeline, Sink, Source, Transformation
62
+ from pipelantic import Data, Input, Output, Pipeline, Sink, Source, Transformation
64
63
 
65
64
 
66
- class RawCustomer(DataContractModel):
65
+ class RawCustomer(Data):
67
66
  customer_id: int
68
67
  first_name: str
69
68
  last_name: str
70
69
 
71
70
 
72
- class Customer(DataContractModel):
71
+ class Customer(Data):
73
72
  customer_id: int
74
73
  full_name: str
75
74
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pipelantic"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Typed, contract-driven data pipeline modeling for Python."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -23,8 +23,13 @@ dependencies = [
23
23
  "packaging>=24",
24
24
  "dtcs>=0.11,<1",
25
25
  "dpcs>=0.13,<1",
26
+ "typer>=0.15,<1",
27
+ "click>=8,<9",
26
28
  ]
27
29
 
30
+ [project.scripts]
31
+ pipelantic = "pipelantic.cli:run"
32
+
28
33
  [project.urls]
29
34
  Homepage = "https://github.com/eddiethedean/pipelantic"
30
35
  Documentation = "https://github.com/eddiethedean/pipelantic/tree/main/docs"
@@ -0,0 +1,189 @@
1
+ """Pipelantic — typed, contract-driven data pipeline modeling.
2
+
3
+ 0.3 adds multi-phase validation, profiles, SecretRef, scoped registries,
4
+ schema-drift and reliability models, and an immutable PipelinePlan IR.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import warnings
10
+ from typing import Any
11
+
12
+ from pipelantic._version import __version__
13
+ from pipelantic.capabilities import CapabilityDecision, PluginCapabilities
14
+ from pipelantic.contracts import Data, load_data_contract, write_odcs
15
+ from pipelantic.diagnostics import (
16
+ Diagnostic,
17
+ DiagnosticAction,
18
+ Severity,
19
+ SourceLocation,
20
+ ValidationReport,
21
+ )
22
+ from pipelantic.exceptions import (
23
+ ModelDefinitionError,
24
+ PipelanticError,
25
+ PipelineValidationError,
26
+ )
27
+ from pipelantic.interchange import (
28
+ ArtifactProvenance,
29
+ ContractBundle,
30
+ ProvenanceKind,
31
+ diff_data_contracts,
32
+ diff_pipelines,
33
+ diff_transformations,
34
+ generate_contracts,
35
+ graphs_equivalent,
36
+ load_bundle,
37
+ normalize_pipeline,
38
+ write_contracts,
39
+ )
40
+ from pipelantic.model import Edge, LogicalGraph, Node, NodeKind
41
+ from pipelantic.pipeline import Pipeline, Sink, Source, SubpipelineInstance
42
+ from pipelantic.plan import (
43
+ ArtifactRef,
44
+ ArtifactStrategy,
45
+ PipelinePlan,
46
+ explain_plan,
47
+ plan_pipeline,
48
+ )
49
+ from pipelantic.policy import ValidationPolicy
50
+ from pipelantic.ports import Input, Output, Parameter
51
+ from pipelantic.profile import (
52
+ Profile,
53
+ development_profile,
54
+ load_profile,
55
+ production_profile,
56
+ resolve_profile,
57
+ test_profile,
58
+ write_profile,
59
+ )
60
+ from pipelantic.refs import OutputRef
61
+ from pipelantic.registry import (
62
+ BindingDescriptor,
63
+ ImplementationDescriptor,
64
+ PlanningContext,
65
+ PluginDescriptor,
66
+ RegistryBundle,
67
+ builtin_stub_registry,
68
+ )
69
+ from pipelantic.reliability import (
70
+ BackfillDeclaration,
71
+ FreshnessExpectation,
72
+ IdempotencyDeclaration,
73
+ MaterializationIntent,
74
+ MaterializationMode,
75
+ PartitionCompletenessExpectation,
76
+ ReconciliationDeclaration,
77
+ ReliabilityEvidence,
78
+ RepairDeclaration,
79
+ RetrySafetyDeclaration,
80
+ WriteIntent,
81
+ WriteMode,
82
+ )
83
+ from pipelantic.schema_drift import (
84
+ DriftImpact,
85
+ NormalizedSchema,
86
+ SchemaChange,
87
+ SchemaChangeSet,
88
+ SchemaObservation,
89
+ diff_contract_schemas,
90
+ diff_normalized_schemas,
91
+ normalize_schema_from_model,
92
+ )
93
+ from pipelantic.secrets import SecretRef
94
+ from pipelantic.transformation import ImplementationRecord, Step, Transformation
95
+
96
+ __all__ = [
97
+ "ArtifactProvenance",
98
+ "ArtifactRef",
99
+ "ArtifactStrategy",
100
+ "BackfillDeclaration",
101
+ "BindingDescriptor",
102
+ "CapabilityDecision",
103
+ "ContractBundle",
104
+ "Data",
105
+ "DataContractModel",
106
+ "Diagnostic",
107
+ "DiagnosticAction",
108
+ "DriftImpact",
109
+ "Edge",
110
+ "FreshnessExpectation",
111
+ "IdempotencyDeclaration",
112
+ "ImplementationDescriptor",
113
+ "ImplementationRecord",
114
+ "Input",
115
+ "LogicalGraph",
116
+ "MaterializationIntent",
117
+ "MaterializationMode",
118
+ "ModelDefinitionError",
119
+ "Node",
120
+ "NodeKind",
121
+ "NormalizedSchema",
122
+ "Output",
123
+ "OutputRef",
124
+ "Parameter",
125
+ "PartitionCompletenessExpectation",
126
+ "PipelanticError",
127
+ "Pipeline",
128
+ "PipelinePlan",
129
+ "PipelineValidationError",
130
+ "PlanningContext",
131
+ "PluginCapabilities",
132
+ "PluginDescriptor",
133
+ "Profile",
134
+ "ProvenanceKind",
135
+ "ReconciliationDeclaration",
136
+ "RegistryBundle",
137
+ "ReliabilityEvidence",
138
+ "RepairDeclaration",
139
+ "RetrySafetyDeclaration",
140
+ "SchemaChange",
141
+ "SchemaChangeSet",
142
+ "SchemaObservation",
143
+ "SecretRef",
144
+ "Severity",
145
+ "Sink",
146
+ "Source",
147
+ "SourceLocation",
148
+ "Step",
149
+ "SubpipelineInstance",
150
+ "Transformation",
151
+ "ValidationPolicy",
152
+ "ValidationReport",
153
+ "WriteIntent",
154
+ "WriteMode",
155
+ "__version__",
156
+ "builtin_stub_registry",
157
+ "development_profile",
158
+ "diff_contract_schemas",
159
+ "diff_data_contracts",
160
+ "diff_normalized_schemas",
161
+ "diff_pipelines",
162
+ "diff_transformations",
163
+ "explain_plan",
164
+ "generate_contracts",
165
+ "graphs_equivalent",
166
+ "load_bundle",
167
+ "load_data_contract",
168
+ "load_profile",
169
+ "normalize_pipeline",
170
+ "normalize_schema_from_model",
171
+ "plan_pipeline",
172
+ "production_profile",
173
+ "resolve_profile",
174
+ "test_profile",
175
+ "write_contracts",
176
+ "write_odcs",
177
+ "write_profile",
178
+ ]
179
+
180
+
181
+ def __getattr__(name: str) -> Any:
182
+ if name == "DataContractModel":
183
+ warnings.warn(
184
+ "DataContractModel is deprecated; use pipelantic.Data instead.",
185
+ DeprecationWarning,
186
+ stacklevel=2,
187
+ )
188
+ return Data
189
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -1,3 +1,3 @@
1
1
  """Package version."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
@@ -0,0 +1,134 @@
1
+ """Plugin capability declarations and negotiation results."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import StrEnum
7
+ from typing import Any
8
+
9
+
10
+ class CapabilityDecision(StrEnum):
11
+ """Outcome of comparing required vs available capabilities."""
12
+
13
+ SUPPORTED = "supported"
14
+ FALLBACK = "fallback"
15
+ UNSUPPORTED = "unsupported"
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class PluginCapabilities:
20
+ """Declared capabilities of a plugin or engine."""
21
+
22
+ engine: str
23
+ async_execution: bool = False
24
+ streaming: bool = False
25
+ transactions: bool = False
26
+ checkpoints: bool = False
27
+ sql: bool = False
28
+ spark: bool = False
29
+ dataframe: bool = True
30
+ secret_provider: bool = False
31
+ extras: frozenset[str] = field(default_factory=frozenset)
32
+
33
+ def supports(self, requirement: str) -> bool:
34
+ """Return True when this capability set covers ``requirement``."""
35
+ known = {
36
+ "async": self.async_execution,
37
+ "async_execution": self.async_execution,
38
+ "streaming": self.streaming,
39
+ "transactions": self.transactions,
40
+ "checkpoints": self.checkpoints,
41
+ "sql": self.sql,
42
+ "spark": self.spark,
43
+ "dataframe": self.dataframe,
44
+ "secret_provider": self.secret_provider,
45
+ }
46
+ if requirement in known:
47
+ return known[requirement]
48
+ return requirement in self.extras
49
+
50
+ def to_dict(self) -> dict[str, Any]:
51
+ """Serialize capabilities."""
52
+ return {
53
+ "engine": self.engine,
54
+ "async_execution": self.async_execution,
55
+ "streaming": self.streaming,
56
+ "transactions": self.transactions,
57
+ "checkpoints": self.checkpoints,
58
+ "sql": self.sql,
59
+ "spark": self.spark,
60
+ "dataframe": self.dataframe,
61
+ "secret_provider": self.secret_provider,
62
+ "extras": sorted(self.extras),
63
+ }
64
+
65
+
66
+ @dataclass(frozen=True, slots=True)
67
+ class CapabilityNegotiation:
68
+ """Record of a capability check for one requirement."""
69
+
70
+ requirement: str
71
+ engine: str
72
+ decision: CapabilityDecision
73
+ fallback_engine: str | None = None
74
+ message: str | None = None
75
+
76
+ def to_dict(self) -> dict[str, Any]:
77
+ """Serialize negotiation record."""
78
+ return {
79
+ "requirement": self.requirement,
80
+ "engine": self.engine,
81
+ "decision": self.decision.value,
82
+ "fallback_engine": self.fallback_engine,
83
+ "message": self.message,
84
+ }
85
+
86
+
87
+ def negotiate_capabilities(
88
+ *,
89
+ requirements: list[str],
90
+ available: PluginCapabilities,
91
+ fallback: PluginCapabilities | None = None,
92
+ allow_fallback: bool = False,
93
+ ) -> list[CapabilityNegotiation]:
94
+ """Negotiate required capabilities against an available engine.
95
+
96
+ Unsupported requirements fail closed unless ``allow_fallback`` is True and
97
+ a fallback engine covers the requirement.
98
+ """
99
+ results: list[CapabilityNegotiation] = []
100
+ for requirement in requirements:
101
+ if available.supports(requirement):
102
+ results.append(
103
+ CapabilityNegotiation(
104
+ requirement=requirement,
105
+ engine=available.engine,
106
+ decision=CapabilityDecision.SUPPORTED,
107
+ )
108
+ )
109
+ continue
110
+ if allow_fallback and fallback is not None and fallback.supports(requirement):
111
+ results.append(
112
+ CapabilityNegotiation(
113
+ requirement=requirement,
114
+ engine=available.engine,
115
+ decision=CapabilityDecision.FALLBACK,
116
+ fallback_engine=fallback.engine,
117
+ message=(
118
+ f"Requirement {requirement!r} unsupported by "
119
+ f"{available.engine}; using fallback {fallback.engine}."
120
+ ),
121
+ )
122
+ )
123
+ continue
124
+ results.append(
125
+ CapabilityNegotiation(
126
+ requirement=requirement,
127
+ engine=available.engine,
128
+ decision=CapabilityDecision.UNSUPPORTED,
129
+ message=(
130
+ f"Requirement {requirement!r} unsupported by {available.engine}."
131
+ ),
132
+ )
133
+ )
134
+ return results