culvert 0.1.0__py3-none-any.whl
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.
- culvert/__init__.py +27 -0
- culvert-0.1.0.dist-info/METADATA +112 -0
- culvert-0.1.0.dist-info/RECORD +134 -0
- culvert-0.1.0.dist-info/WHEEL +4 -0
- culvert-0.1.0.dist-info/entry_points.txt +10 -0
- data_pipeline_contract_tests/__init__.py +28 -0
- data_pipeline_contract_tests/blob_store.py +34 -0
- data_pipeline_contract_tests/secrets.py +41 -0
- data_pipeline_contract_tests/stage_metrics_hook.py +109 -0
- data_pipeline_contract_tests/warehouse.py +36 -0
- data_pipeline_core/__init__.py +60 -0
- data_pipeline_core/audit/__init__.py +5 -0
- data_pipeline_core/audit/records.py +34 -0
- data_pipeline_core/autoconfig.py +144 -0
- data_pipeline_core/contracts/__init__.py +41 -0
- data_pipeline_core/contracts/audit.py +37 -0
- data_pipeline_core/contracts/blob_store.py +81 -0
- data_pipeline_core/contracts/finops.py +33 -0
- data_pipeline_core/contracts/governance.py +46 -0
- data_pipeline_core/contracts/job_control.py +125 -0
- data_pipeline_core/contracts/lineage.py +28 -0
- data_pipeline_core/contracts/observability.py +78 -0
- data_pipeline_core/contracts/pipeline.py +46 -0
- data_pipeline_core/contracts/runtime.py +97 -0
- data_pipeline_core/contracts/secrets.py +31 -0
- data_pipeline_core/contracts/source.py +60 -0
- data_pipeline_core/contracts/stage_metrics.py +100 -0
- data_pipeline_core/contracts/warehouse.py +94 -0
- data_pipeline_core/dataquality/__init__.py +44 -0
- data_pipeline_core/dataquality/data_quality_transform.py +368 -0
- data_pipeline_core/dataquality/field_violation.py +48 -0
- data_pipeline_core/dataquality/numeric_range.py +54 -0
- data_pipeline_core/dataquality/validation_result.py +90 -0
- data_pipeline_core/dataquality/violation_kind.py +35 -0
- data_pipeline_core/decorators.py +77 -0
- data_pipeline_core/finops_api/__init__.py +27 -0
- data_pipeline_core/finops_api/budget.py +235 -0
- data_pipeline_core/finops_api/labels.py +30 -0
- data_pipeline_core/finops_api/models.py +40 -0
- data_pipeline_core/governance_api/__init__.py +21 -0
- data_pipeline_core/governance_api/classification.py +18 -0
- data_pipeline_core/governance_api/masker.py +102 -0
- data_pipeline_core/governance_api/pii_masking_governance_policy.py +173 -0
- data_pipeline_core/governance_api/policies.py +56 -0
- data_pipeline_core/job_control_api/__init__.py +26 -0
- data_pipeline_core/job_control_api/models.py +106 -0
- data_pipeline_core/job_control_api/types.py +44 -0
- data_pipeline_core/lineage/__init__.py +5 -0
- data_pipeline_core/lineage/events.py +67 -0
- data_pipeline_core/py.typed +0 -0
- data_pipeline_core/runtime.py +414 -0
- data_pipeline_core/schema/__init__.py +5 -0
- data_pipeline_core/schema/entity.py +70 -0
- data_pipeline_gcp_bigquery/__init__.py +18 -0
- data_pipeline_gcp_bigquery/cost_tracker.py +240 -0
- data_pipeline_gcp_bigquery/finops_sink.py +158 -0
- data_pipeline_gcp_bigquery/warehouse.py +133 -0
- data_pipeline_gcp_gcs/__init__.py +17 -0
- data_pipeline_gcp_gcs/blob_store.py +108 -0
- data_pipeline_gcp_gcs/cost_tracker.py +249 -0
- data_pipeline_gcp_observability/__init__.py +53 -0
- data_pipeline_gcp_observability/cloud_monitoring_metrics_hook.py +287 -0
- data_pipeline_gcp_observability/cloud_trace_observability_hook.py +236 -0
- data_pipeline_gcp_observability/culvert_mdc_populator.py +186 -0
- data_pipeline_gcp_observability/data_catalog_lineage_emitter.py +189 -0
- data_pipeline_gcp_pubsub/__init__.py +18 -0
- data_pipeline_gcp_pubsub/cost_tracker.py +205 -0
- data_pipeline_gcp_pubsub/io.py +93 -0
- data_pipeline_gcp_secrets/__init__.py +13 -0
- data_pipeline_gcp_secrets/secret_manager_provider.py +164 -0
- data_pipeline_orchestration/__init__.py +105 -0
- data_pipeline_orchestration/_job_control.py +91 -0
- data_pipeline_orchestration/callbacks/__init__.py +54 -0
- data_pipeline_orchestration/callbacks/dlq.py +174 -0
- data_pipeline_orchestration/callbacks/factory.py +128 -0
- data_pipeline_orchestration/callbacks/handlers.py +223 -0
- data_pipeline_orchestration/callbacks/quarantine.py +100 -0
- data_pipeline_orchestration/callbacks/types.py +66 -0
- data_pipeline_orchestration/dependency.py +250 -0
- data_pipeline_orchestration/factories/__init__.py +91 -0
- data_pipeline_orchestration/factories/_dag_builders.py +917 -0
- data_pipeline_orchestration/factories/base_dag_factory.py +206 -0
- data_pipeline_orchestration/factories/config.py +372 -0
- data_pipeline_orchestration/factories/dag_factory.py +146 -0
- data_pipeline_orchestration/factories/dag_factory_alias.py +154 -0
- data_pipeline_orchestration/factories/validators.py +366 -0
- data_pipeline_orchestration/hooks/secrets.py +84 -0
- data_pipeline_orchestration/operators/__init__.py +24 -0
- data_pipeline_orchestration/operators/dataflow.py +592 -0
- data_pipeline_orchestration/routing/__init__.py +54 -0
- data_pipeline_orchestration/routing/config.py +70 -0
- data_pipeline_orchestration/routing/router.py +167 -0
- data_pipeline_orchestration/routing/yaml_selector.py +228 -0
- data_pipeline_orchestration/sensors/__init__.py +15 -0
- data_pipeline_orchestration/sensors/dataflow.py +90 -0
- data_pipeline_orchestration/sensors/pubsub.py +311 -0
- data_pipeline_tester/__init__.py +41 -0
- data_pipeline_tester/assertions/__init__.py +79 -0
- data_pipeline_tester/assertions/beam_assertions.py +88 -0
- data_pipeline_tester/assertions/pipeline_assertions.py +106 -0
- data_pipeline_tester/assertions/record_assertions.py +109 -0
- data_pipeline_tester/base/__init__.py +37 -0
- data_pipeline_tester/base/beam_test.py +152 -0
- data_pipeline_tester/base/gdw_test.py +1 -0
- data_pipeline_tester/base/pipeline_test.py +134 -0
- data_pipeline_tester/base/result.py +70 -0
- data_pipeline_tester/base/scenario_test.py +107 -0
- data_pipeline_tester/base/validation_test.py +143 -0
- data_pipeline_tester/bdd/__init__.py +6 -0
- data_pipeline_tester/bdd/base.py +38 -0
- data_pipeline_tester/bdd/steps/__init__.py +0 -0
- data_pipeline_tester/bdd/steps/common_steps.py +18 -0
- data_pipeline_tester/bdd/steps/dq_steps.py +51 -0
- data_pipeline_tester/bdd/steps/pipeline_steps.py +54 -0
- data_pipeline_tester/builders/__init__.py +41 -0
- data_pipeline_tester/builders/config_builder.py +128 -0
- data_pipeline_tester/builders/pipeline_builder.py +102 -0
- data_pipeline_tester/builders/record_builder.py +166 -0
- data_pipeline_tester/comparison/__init__.py +16 -0
- data_pipeline_tester/comparison/dual_run.py +373 -0
- data_pipeline_tester/fixtures/__init__.py +88 -0
- data_pipeline_tester/fixtures/beam.py +64 -0
- data_pipeline_tester/fixtures/bigquery.py +83 -0
- data_pipeline_tester/fixtures/common.py +123 -0
- data_pipeline_tester/fixtures/gcs.py +79 -0
- data_pipeline_tester/mocks/__init__.py +39 -0
- data_pipeline_tester/mocks/bigquery_mock.py +99 -0
- data_pipeline_tester/mocks/gcs_mock.py +95 -0
- data_pipeline_tester/mocks/pubsub_mock.py +384 -0
- data_pipeline_transform/__init__.py +4 -0
- data_pipeline_transform/dbt_shared/macros/audit_columns.sql +50 -0
- data_pipeline_transform/dbt_shared/macros/data_quality_check.sql +121 -0
- data_pipeline_transform/dbt_shared/macros/enrichment.sql +48 -0
- data_pipeline_transform/dbt_shared/macros/pii_masking.sql +181 -0
culvert/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Culvert — a cloud-agnostic, polyglot data-pipeline framework.
|
|
2
|
+
|
|
3
|
+
One language-neutral contract set (Source, Sink, Transform, Pipeline,
|
|
4
|
+
RuntimeContext, BlobStore, Warehouse, JobControlRepository, ...), realised in
|
|
5
|
+
Java and Python, with cloud specifics behind adapters. This distribution is the
|
|
6
|
+
Python side; the Java twin ships as ``com.enrichmeai.culvert:*`` on Maven
|
|
7
|
+
Central.
|
|
8
|
+
|
|
9
|
+
For 0.1.0 the import packages keep their library names — start at
|
|
10
|
+
``data_pipeline_core`` for the contracts::
|
|
11
|
+
|
|
12
|
+
from data_pipeline_core import autoconfig
|
|
13
|
+
from data_pipeline_core.contracts.blob_store import BlobStore
|
|
14
|
+
|
|
15
|
+
Install extras for the adapters you need::
|
|
16
|
+
|
|
17
|
+
pip install culvert[gcp] # BigQuery, GCS, Pub/Sub, Secret Manager, observability
|
|
18
|
+
pip install culvert[orchestration] # Airflow-side DAG factory, operators, sensors
|
|
19
|
+
pip install culvert[transform] # dbt runner assets
|
|
20
|
+
pip install culvert[all]
|
|
21
|
+
|
|
22
|
+
Docs and worked examples: https://github.com/enrichmeai/culvert
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: culvert
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cloud-agnostic, polyglot data-pipeline framework: one contract set, adapters per cloud. Python side of Culvert (Java twin on Maven Central: com.enrichmeai.culvert).
|
|
5
|
+
Project-URL: Homepage, https://github.com/enrichmeai/culvert
|
|
6
|
+
Project-URL: Repository, https://github.com/enrichmeai/culvert
|
|
7
|
+
Project-URL: Issues, https://github.com/enrichmeai/culvert/issues
|
|
8
|
+
Author: Joseph Aruja
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: airflow,bigquery,cloud-agnostic,contracts,data-pipeline,dbt,framework,gcp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: typing-extensions>=4.7
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
Requires-Dist: dbt-bigquery>=1.5.0; extra == 'all'
|
|
22
|
+
Requires-Dist: google-cloud-bigquery>=3.11.0; extra == 'all'
|
|
23
|
+
Requires-Dist: google-cloud-datacatalog>=3.14.0; extra == 'all'
|
|
24
|
+
Requires-Dist: google-cloud-monitoring>=2.14.0; extra == 'all'
|
|
25
|
+
Requires-Dist: google-cloud-pubsub>=2.18.0; extra == 'all'
|
|
26
|
+
Requires-Dist: google-cloud-secret-manager>=2.16.0; extra == 'all'
|
|
27
|
+
Requires-Dist: google-cloud-storage>=2.10.0; extra == 'all'
|
|
28
|
+
Requires-Dist: opentelemetry-api>=1.20.0; extra == 'all'
|
|
29
|
+
Requires-Dist: pyyaml>=6.0; extra == 'all'
|
|
30
|
+
Provides-Extra: contract-tests
|
|
31
|
+
Requires-Dist: pytest>=7.4; extra == 'contract-tests'
|
|
32
|
+
Provides-Extra: gcp
|
|
33
|
+
Requires-Dist: google-cloud-bigquery>=3.11.0; extra == 'gcp'
|
|
34
|
+
Requires-Dist: google-cloud-datacatalog>=3.14.0; extra == 'gcp'
|
|
35
|
+
Requires-Dist: google-cloud-monitoring>=2.14.0; extra == 'gcp'
|
|
36
|
+
Requires-Dist: google-cloud-pubsub>=2.18.0; extra == 'gcp'
|
|
37
|
+
Requires-Dist: google-cloud-secret-manager>=2.16.0; extra == 'gcp'
|
|
38
|
+
Requires-Dist: google-cloud-storage>=2.10.0; extra == 'gcp'
|
|
39
|
+
Requires-Dist: opentelemetry-api>=1.20.0; extra == 'gcp'
|
|
40
|
+
Provides-Extra: orchestration
|
|
41
|
+
Requires-Dist: google-cloud-bigquery>=3.11.0; extra == 'orchestration'
|
|
42
|
+
Requires-Dist: google-cloud-pubsub>=2.18.0; extra == 'orchestration'
|
|
43
|
+
Requires-Dist: google-cloud-storage>=2.10.0; extra == 'orchestration'
|
|
44
|
+
Requires-Dist: pyyaml>=6.0; extra == 'orchestration'
|
|
45
|
+
Provides-Extra: tester
|
|
46
|
+
Requires-Dist: apache-beam[gcp]==2.56.0; extra == 'tester'
|
|
47
|
+
Requires-Dist: google-cloud-bigquery>=3.11.0; extra == 'tester'
|
|
48
|
+
Requires-Dist: google-cloud-pubsub>=2.18.0; extra == 'tester'
|
|
49
|
+
Requires-Dist: google-cloud-storage>=2.10.0; extra == 'tester'
|
|
50
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'tester'
|
|
51
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == 'tester'
|
|
52
|
+
Requires-Dist: pytest>=7.0.0; extra == 'tester'
|
|
53
|
+
Provides-Extra: transform
|
|
54
|
+
Requires-Dist: dbt-bigquery>=1.5.0; extra == 'transform'
|
|
55
|
+
Description-Content-Type: text/markdown
|
|
56
|
+
|
|
57
|
+
# Culvert
|
|
58
|
+
|
|
59
|
+
**A cloud-agnostic, polyglot data-pipeline framework.** One contract set —
|
|
60
|
+
`Source`, `Sink`, `Transform`, `Pipeline`, `RuntimeContext`, `BlobStore`,
|
|
61
|
+
`Warehouse`, `JobControlRepository`, and friends — realised in **Java and
|
|
62
|
+
Python**, with cloud specifics behind adapters. Define the pipeline once
|
|
63
|
+
against contracts; point it at an emulator on your laptop, a dev project, or
|
|
64
|
+
production by wiring, not rewriting.
|
|
65
|
+
|
|
66
|
+
- **Contract-driven** — the [language-neutral spec](https://github.com/enrichmeai/culvert/blob/main/docs/CONTRACT.md)
|
|
67
|
+
is the portability boundary. Business logic depends only on contracts.
|
|
68
|
+
- **Two clouds today** — GCP is the first full implementation; AWS is a real
|
|
69
|
+
(Java) adapter family with the same pipeline proven on both. Azure is on the
|
|
70
|
+
roadmap.
|
|
71
|
+
- **No application framework in the core** — plain Python Protocols +
|
|
72
|
+
entry-point auto-discovery; composes into Beam pipelines, Airflow DAGs, or a
|
|
73
|
+
script on your laptop.
|
|
74
|
+
- **Local-first** — the whole stack runs against emulators with zero cloud
|
|
75
|
+
account; cloud is where you prove and ship, not where you develop.
|
|
76
|
+
|
|
77
|
+
## Install
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install culvert # core contracts only (no cloud SDKs)
|
|
81
|
+
pip install culvert[gcp] # + BigQuery, GCS, Pub/Sub, Secret Manager, observability
|
|
82
|
+
pip install culvert[orchestration] # + Airflow-side DAG factory, operators, sensors
|
|
83
|
+
pip install culvert[transform] # + dbt integration
|
|
84
|
+
pip install culvert[all]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Python ≥ 3.10. For 0.1.0 the import packages keep their library names — the
|
|
88
|
+
contracts live in `data_pipeline_core`:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from data_pipeline_core import autoconfig
|
|
92
|
+
|
|
93
|
+
config = autoconfig.discover() # entry-point adapter discovery
|
|
94
|
+
blob_store = config.blob_store() # GcsBlobStore if culvert[gcp] installed
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The Java twin ships as `com.enrichmeai.culvert:*` on Maven Central — same
|
|
98
|
+
contracts, Java owns the Beam/Dataflow execution layer; Python owns the
|
|
99
|
+
Airflow runtime and dbt packaging.
|
|
100
|
+
|
|
101
|
+
## Status
|
|
102
|
+
|
|
103
|
+
`0.1.0` — first public release. Built and validated against a real GCP
|
|
104
|
+
project (Cloud Run, BigQuery, Pub/Sub, event-driven end-to-end) before
|
|
105
|
+
publishing. GCP adapters are production-shaped; the AWS family is Java-side;
|
|
106
|
+
Azure is a roadmap skeleton. Honest limitations are documented per adapter in
|
|
107
|
+
the source.
|
|
108
|
+
|
|
109
|
+
Docs, architecture, worked example deployments, and the engineering story:
|
|
110
|
+
**https://github.com/enrichmeai/culvert**
|
|
111
|
+
|
|
112
|
+
MIT licensed.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
culvert/__init__.py,sha256=I0wbpJgTo-c0xEif_5i_tfNqklyXf13KH-NSnDNMM2s,1040
|
|
2
|
+
data_pipeline_contract_tests/__init__.py,sha256=VcJjW6mrWX0HO64gozESfO076CZijWI52zTJeHt1sn8,986
|
|
3
|
+
data_pipeline_contract_tests/blob_store.py,sha256=JZUcZs1gt0WULzNUEIHZdCdYyv06rDlx3x1r6ayh9Oo,1073
|
|
4
|
+
data_pipeline_contract_tests/secrets.py,sha256=pzlJnl_-x6VfcHpcAtCJ9OmrkhEmzj46-loNaE46SwI,1354
|
|
5
|
+
data_pipeline_contract_tests/stage_metrics_hook.py,sha256=tCFxrD5BT8nq-NhtNkI9MujVHKSnX0gjwHV1uaKyOVc,4372
|
|
6
|
+
data_pipeline_contract_tests/warehouse.py,sha256=5B10dA9y46oI_qI7-pfch24oBiimVnAx6vjRr0AHDtc,1323
|
|
7
|
+
data_pipeline_core/__init__.py,sha256=ScNCKd2CwkaDL6YOim2BCDylwTaP-jtn9HeOgyRKkYw,2329
|
|
8
|
+
data_pipeline_core/autoconfig.py,sha256=IOmwu11AhuFsy8yn3SOiqDCDBa8h_k3x_JsNV_dOf7A,5406
|
|
9
|
+
data_pipeline_core/decorators.py,sha256=2yvjDcnDWH4w2PxFVecBURpp8kQgfI0BTV5cc9FBv3k,2512
|
|
10
|
+
data_pipeline_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
data_pipeline_core/runtime.py,sha256=-u7ncxGAE2ibh8nJ9mmXMcJ13sBfnJ4dz7XsXSb2GcY,15728
|
|
12
|
+
data_pipeline_core/audit/__init__.py,sha256=OHQCTUlOGMer4IYyr5TRNTV7NR6alPIIodnHKe7BiSo,168
|
|
13
|
+
data_pipeline_core/audit/records.py,sha256=cKwd4x8pgNze7Q9U1Czao2Mu59OsMK-m0srWYRHCkek,997
|
|
14
|
+
data_pipeline_core/contracts/__init__.py,sha256=qs6RUsA95Vdy6vstNiR891-BCquw_22Scy_IjJ3gcLg,1575
|
|
15
|
+
data_pipeline_core/contracts/audit.py,sha256=XCx-HeI1BgGy082gT0lhmPAboaPChSjwMrW-rdD9hlo,1237
|
|
16
|
+
data_pipeline_core/contracts/blob_store.py,sha256=JV_YAxE_WDZihGU95zY-m3CtE-F9xAF-5tpZLcYy9_s,2641
|
|
17
|
+
data_pipeline_core/contracts/finops.py,sha256=ym8QDiI9EA012p5M_sOngSITcdVz-hITJfo1T9Jco5c,1233
|
|
18
|
+
data_pipeline_core/contracts/governance.py,sha256=EkXpuJfOOWqCU1zpCs7ma_bZKAErF2KlW3xQI0F4Dpk,1633
|
|
19
|
+
data_pipeline_core/contracts/job_control.py,sha256=WouoYykydGfjrxDdbRZAfNinQZXhQkPWCam89Di8lTc,4254
|
|
20
|
+
data_pipeline_core/contracts/lineage.py,sha256=994hyvoO_LJZSzEFZD35cmCXAN6gRrZAHuoSq28lcJg,946
|
|
21
|
+
data_pipeline_core/contracts/observability.py,sha256=CWZzaj1BdNPRK-9m91HY-vabIDuRGXkJs7MfNG_Xy80,2481
|
|
22
|
+
data_pipeline_core/contracts/pipeline.py,sha256=3bhCvi9Luw5LspFzVoxNfE6s4hDWmAN5CEP3SQJ0x8s,1369
|
|
23
|
+
data_pipeline_core/contracts/runtime.py,sha256=PVYaOYHClOZdEdyjUAOZS7iKyzdz61EXgwkl78efvxk,4317
|
|
24
|
+
data_pipeline_core/contracts/secrets.py,sha256=ZQkU-leLqGAqPgkKFOuBrb1M7jXBBZLhejBPtXG4jJQ,1143
|
|
25
|
+
data_pipeline_core/contracts/source.py,sha256=1-Ngdd0iyPqHh73cIweP4MErtSWS74JAzujRUZv5xaQ,2037
|
|
26
|
+
data_pipeline_core/contracts/stage_metrics.py,sha256=OXbF5Ie2vgr8eGAhJqgEkwJjgpy1p-OX0C_MEZhm6kY,3900
|
|
27
|
+
data_pipeline_core/contracts/warehouse.py,sha256=StJiDm6ySK1vVxVT9hKTJiN5qEbcADs-eUeq2M5rUdk,3144
|
|
28
|
+
data_pipeline_core/dataquality/__init__.py,sha256=igfv9RvTQJ_nkl5c_Sx4Aw4GEXhVw3clEDU304su400,1507
|
|
29
|
+
data_pipeline_core/dataquality/data_quality_transform.py,sha256=3iookCH7oI-l7_D1Nnb27zxJZ8A0XNZTvPaqWbueqRU,15370
|
|
30
|
+
data_pipeline_core/dataquality/field_violation.py,sha256=eeaRkxsknJ-VuZLX5e9n9K8ri-B30Fq9YNi-fYnYDcQ,1672
|
|
31
|
+
data_pipeline_core/dataquality/numeric_range.py,sha256=cLocX6GvWteo-jU76lEv7080yqufgv8_yjpBdu-4QXY,1862
|
|
32
|
+
data_pipeline_core/dataquality/validation_result.py,sha256=H2m2M3I0w6mspCPTjD4lOGmcXVJUpdAIFeNT60KtQ6s,3124
|
|
33
|
+
data_pipeline_core/dataquality/violation_kind.py,sha256=mzbhQI-igbML5ErF5doR4fhHjk8lxyhfU1Rl4_HQVyY,1220
|
|
34
|
+
data_pipeline_core/finops_api/__init__.py,sha256=LF7h5aDW5mL_E2nPXoxaunId5aGGvKHFfaNPUABIVaw,855
|
|
35
|
+
data_pipeline_core/finops_api/budget.py,sha256=yDrkG4IxOXqFJ8kaG2AkQ_8sR9hSI-HLfy-S5DBe9fw,9170
|
|
36
|
+
data_pipeline_core/finops_api/labels.py,sha256=4TGX-Z4M0vuu3VxtSmaupmhuUSSbGm5PHJf5ERHhsc8,961
|
|
37
|
+
data_pipeline_core/finops_api/models.py,sha256=WUoZvycA3TvIuq6dhTQrjN3KP8pvdC_3H_oJCqGfX4Q,1374
|
|
38
|
+
data_pipeline_core/governance_api/__init__.py,sha256=z-GSVht8drj1ppuIx8qP0KmfwADKkrTKtobwvhnQtnQ,596
|
|
39
|
+
data_pipeline_core/governance_api/classification.py,sha256=R4bXcad0NXTq0MEmE78U7f6OXJuaNwSQbioaEdcNMfA,572
|
|
40
|
+
data_pipeline_core/governance_api/masker.py,sha256=RZurFdESsc2s3cUzT9zHwSkzJEt5jtZQqmszpUn7s3k,3522
|
|
41
|
+
data_pipeline_core/governance_api/pii_masking_governance_policy.py,sha256=epuJ7W7nvvzjGbHy31qqy2GqY11xXWN7QFzF_RRKb4c,6910
|
|
42
|
+
data_pipeline_core/governance_api/policies.py,sha256=qgDvXKB95G3fV1YdX__5YUzT-ZN97tBWKITgtN7jlno,1686
|
|
43
|
+
data_pipeline_core/job_control_api/__init__.py,sha256=0iYej02zSMa4_-1Rr-gbm8II2MfyJesp_9nP8F0m4fQ,561
|
|
44
|
+
data_pipeline_core/job_control_api/models.py,sha256=kMgwW9F9lZswgAdDf9IfbAsHSCiYgrvZsRIxdLBoqTs,3219
|
|
45
|
+
data_pipeline_core/job_control_api/types.py,sha256=sttpoVR3A2r3HcGsU1qPQFse-eGnY36kc9FNmW-HsE4,1086
|
|
46
|
+
data_pipeline_core/lineage/__init__.py,sha256=shMojrPXu2hgJCnmfGlOVTJhMFD2sHEM1CmbEqeimzI,171
|
|
47
|
+
data_pipeline_core/lineage/events.py,sha256=6BZ2bJB_nfCSGIYwdJCpjGukq-3KZL36yDVhVQdzHGc,1982
|
|
48
|
+
data_pipeline_core/schema/__init__.py,sha256=FDFlB6K1yw4VsJ-XjfSwo91b4wiTmI7aNREZ1BPgiPo,178
|
|
49
|
+
data_pipeline_core/schema/entity.py,sha256=Na-fS5GaQC0k5Nz5jKdfYk22cEtVQNdZ3t1Hwh_ZZf0,2866
|
|
50
|
+
data_pipeline_gcp_bigquery/__init__.py,sha256=-fOkVAvRw7Lcy9h999qRR6-K-tRCahhyKE_Gkif4RYE,526
|
|
51
|
+
data_pipeline_gcp_bigquery/cost_tracker.py,sha256=9CuxpNVpsxDJPXMe0jNlJYubOGtijHTdVTecJeLQUno,9366
|
|
52
|
+
data_pipeline_gcp_bigquery/finops_sink.py,sha256=3Z1yt6s6yjOM-vsa5n9ndMjfKS-Zz-9kLVIyVDQCz7I,6327
|
|
53
|
+
data_pipeline_gcp_bigquery/warehouse.py,sha256=3uXuB5wfitpdgcOe3j4osuQ03-0Kk9aWW4Ou1kAyrD4,5076
|
|
54
|
+
data_pipeline_gcp_gcs/__init__.py,sha256=Yzj81NMiv8CslB2qT8DHGSzh1koWKQksyJQ2ccUAx4g,426
|
|
55
|
+
data_pipeline_gcp_gcs/blob_store.py,sha256=Fn5cCf_zQ1heSsK6og2lT5H5WUMUTY_9tHUtlZSOWKA,3918
|
|
56
|
+
data_pipeline_gcp_gcs/cost_tracker.py,sha256=15w6jXJdD6rhGJ8f2YDZONgWNxoSOErFwEGb-Irgyl0,9246
|
|
57
|
+
data_pipeline_gcp_observability/__init__.py,sha256=y4HMRDeGhncSEoJvJxbb6cpxYUFeehFTjOOdX0DaIiA,1783
|
|
58
|
+
data_pipeline_gcp_observability/cloud_monitoring_metrics_hook.py,sha256=ScjYnAESrelqA7TR3J4lqcDu4hli81RMWGzrzyCRmoY,10937
|
|
59
|
+
data_pipeline_gcp_observability/cloud_trace_observability_hook.py,sha256=Tw1RPV4UczeCmsCje185PF3zzXivbpplVXdLEbJ4fvA,8967
|
|
60
|
+
data_pipeline_gcp_observability/culvert_mdc_populator.py,sha256=7M2lv8AuXGujMaKmF_1UvRN8A1hmBiV5jXfud1Gc9Wo,6003
|
|
61
|
+
data_pipeline_gcp_observability/data_catalog_lineage_emitter.py,sha256=8mU583_MC-x0OjysLeEEqB3DyE3UVpSK_joHVeT5aVU,7015
|
|
62
|
+
data_pipeline_gcp_pubsub/__init__.py,sha256=GPtmBp4bo0MEkOjLjJ-1gtYbwCJpzfmH0wYPD-JhZzk,470
|
|
63
|
+
data_pipeline_gcp_pubsub/cost_tracker.py,sha256=NRDJIGp52ToZK47vVDarCL3cVFA-nUUf19KEToL9ME4,7573
|
|
64
|
+
data_pipeline_gcp_pubsub/io.py,sha256=yfndM1HvACzyEhHxURKifPtZjRjVaphOi55TgmdKbw8,3462
|
|
65
|
+
data_pipeline_gcp_secrets/__init__.py,sha256=qFAge9_PFFmmG7671E6pj0WYcoXlhK4V9tresL-CAZQ,389
|
|
66
|
+
data_pipeline_gcp_secrets/secret_manager_provider.py,sha256=6syIkm9rwykJ_DvxtaKMffGtRJledOjKLJyBGNqj6eg,6444
|
|
67
|
+
data_pipeline_orchestration/__init__.py,sha256=JPG-vKX9vN5p27FC6tLPoHImouLwxc96Ukf4edEdcWE,2496
|
|
68
|
+
data_pipeline_orchestration/_job_control.py,sha256=KEG0yES5SXvy7zVnWt6MncS7IXWjRalvvKYCK_1WLv0,3696
|
|
69
|
+
data_pipeline_orchestration/dependency.py,sha256=lH-wyMIlO9FNHE7gRn-YH36ZPcLZdWyRJo25xl_WxqQ,9231
|
|
70
|
+
data_pipeline_orchestration/callbacks/__init__.py,sha256=B6ec6OSY1ovhXZQ5hnjBSgSgH16DiJNZaCnHak4HnPo,961
|
|
71
|
+
data_pipeline_orchestration/callbacks/dlq.py,sha256=VUGq7V_8wKhEieDz3CksJN7ivsKPrEFvq2CbjEw8gXs,5529
|
|
72
|
+
data_pipeline_orchestration/callbacks/factory.py,sha256=pSmoevqtTCABpvQseQLQWVQGCP_kAv43m8wzqlunBrY,3676
|
|
73
|
+
data_pipeline_orchestration/callbacks/handlers.py,sha256=d6m-Pn5j03DBMWpuOHnU39XEvmjt09-4VKNv5OJIjNU,6582
|
|
74
|
+
data_pipeline_orchestration/callbacks/quarantine.py,sha256=CM5xoFzF7iP-qYDCSb2VObqkcfK0LU_linDyaYlaNao,2972
|
|
75
|
+
data_pipeline_orchestration/callbacks/types.py,sha256=6qByCaLPIibhFY3yyYRkcpWNt7HvekG_npBU00QFrKU,1791
|
|
76
|
+
data_pipeline_orchestration/factories/__init__.py,sha256=Cx585Uz0N5dug_ML25K4S1UCbh6PRryHlV2tDnqwPQc,2213
|
|
77
|
+
data_pipeline_orchestration/factories/_dag_builders.py,sha256=4AsspjaWOWhB3_ThSrNFiLoYUgpsAfSoSUYorzNyOGg,38663
|
|
78
|
+
data_pipeline_orchestration/factories/base_dag_factory.py,sha256=XciK6QffNdH6UK5MQ8ZrcEK3tFHIxtaeJh2KOoku1U8,5830
|
|
79
|
+
data_pipeline_orchestration/factories/config.py,sha256=LJHqbEQcI_MmNVwwDhGSUCyMUayWD_uJ6cI2irJjaPg,12737
|
|
80
|
+
data_pipeline_orchestration/factories/dag_factory.py,sha256=NUoK7X5aKsg_6BJxAKhwO9xFVPmF-cTUjyPpxsOXSXU,5987
|
|
81
|
+
data_pipeline_orchestration/factories/dag_factory_alias.py,sha256=ec220Ai0fIdkgwZ0Wukx58ahhR9P7F9iPxaRZLTZEls,5938
|
|
82
|
+
data_pipeline_orchestration/factories/validators.py,sha256=0pOKRXHpVVsNEQ8SfUNMEfpid2Lwn8OOGRLVB-Gy-Rg,12316
|
|
83
|
+
data_pipeline_orchestration/hooks/secrets.py,sha256=UVAfJucwHxs7zfmFll5DbRQ6Sn3SmD8jQteD-4RWtgA,2652
|
|
84
|
+
data_pipeline_orchestration/operators/__init__.py,sha256=51zUKPKdJIqJ_ZVqbERhD8Tcn_vd_ASuNWXPEBOFqoA,454
|
|
85
|
+
data_pipeline_orchestration/operators/dataflow.py,sha256=u4pl0ff7D0WEkWlKmiQGd5crnXxRDxslqxP91jyqtVs,20927
|
|
86
|
+
data_pipeline_orchestration/routing/__init__.py,sha256=SzahekNMWT5xuYlTcjQUbk9zHvqx7afGD-UXvLwsNjE,1393
|
|
87
|
+
data_pipeline_orchestration/routing/config.py,sha256=cWu7W84oXcxYWlDu5GbXo9-FhDamx1q8ip8poBuC32Q,1710
|
|
88
|
+
data_pipeline_orchestration/routing/router.py,sha256=y16wVUCJvQ94th6diqa8ZXBRUeK7R8cp94Gh1RgtG6c,4633
|
|
89
|
+
data_pipeline_orchestration/routing/yaml_selector.py,sha256=xMhuA__MLYVYDNM-jKSWVV7cPll7EjVPPxYZ2gJduFQ,7354
|
|
90
|
+
data_pipeline_orchestration/sensors/__init__.py,sha256=xf91EHczJouGtbskWIZZvhDEq7bNnLTvFlP1GtP3miQ,284
|
|
91
|
+
data_pipeline_orchestration/sensors/dataflow.py,sha256=AVN_F16dNxjtuOY0HVYKBDsYc4OzmCM6UHjCFFMzz4s,3091
|
|
92
|
+
data_pipeline_orchestration/sensors/pubsub.py,sha256=RJVEmCFfh05hXdE6vrlyqa1xJ1bO2R2vkHC5sPy0C8k,11470
|
|
93
|
+
data_pipeline_tester/__init__.py,sha256=NaopYycE680DfS3M3qeJD7AS6lx-bwo2K6KJagZnsmA,966
|
|
94
|
+
data_pipeline_tester/assertions/__init__.py,sha256=tDUPUiQJ3_mSPsFkkaIf6khiBMkzgUFz0ZLi2IWcHeU,1975
|
|
95
|
+
data_pipeline_tester/assertions/beam_assertions.py,sha256=UkHrizI07kt2yCiHcujyM33fviKMtTRwQ6WGyQAPtyc,2443
|
|
96
|
+
data_pipeline_tester/assertions/pipeline_assertions.py,sha256=zjkAaLqqXZDj7FxbmPOJStzyZajp_YcdLEq_yYIn-b0,3016
|
|
97
|
+
data_pipeline_tester/assertions/record_assertions.py,sha256=YgTOeXIg0Cl7rSVafQumCs4i2Aixi6LBYFv1HDGXYks,2886
|
|
98
|
+
data_pipeline_tester/base/__init__.py,sha256=rCkw2DUZKT5dLPP61fjiMWbFDTX60LJ7AiVve6OGH5g,1207
|
|
99
|
+
data_pipeline_tester/base/beam_test.py,sha256=G5f7XuHFawQiodas1cYWYPmBQqHJY5CUqT_jDSge9js,4999
|
|
100
|
+
data_pipeline_tester/base/gdw_test.py,sha256=DILKFeGoVm_G5gOsN5bOvlI9ILjiwfcGKHp_X2_FHCk,66
|
|
101
|
+
data_pipeline_tester/base/pipeline_test.py,sha256=IqCCqsgmEVRYZruYKsZAylQYiRcf7mF4zFs8A2rCwdM,4119
|
|
102
|
+
data_pipeline_tester/base/result.py,sha256=O2kbl9k--V3mF6vWqYoCU-1HLAV0guddo4Nxbb8T--M,1695
|
|
103
|
+
data_pipeline_tester/base/scenario_test.py,sha256=vKKjw9RhMwpa8ERjNtGeQ3Ldak6nnnN8vQAOWOVPrvw,3175
|
|
104
|
+
data_pipeline_tester/base/validation_test.py,sha256=DsP7-rm-KEEdXUzEu4HrZ5UBB2Hjpkp5SXXNdthANp4,4706
|
|
105
|
+
data_pipeline_tester/bdd/__init__.py,sha256=rT6bDPHNEAR2uPO802KCZ3efQNtCFii6f-GMRsUQveg,124
|
|
106
|
+
data_pipeline_tester/bdd/base.py,sha256=7gp82365icc1qqSisV367Ht_4u6SIx_xtTWJbp3od50,1042
|
|
107
|
+
data_pipeline_tester/bdd/steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
108
|
+
data_pipeline_tester/bdd/steps/common_steps.py,sha256=aPXY3VcHaSm4EIRGxPNoO8bYPNf8zHzr--O8tuHUJDk,642
|
|
109
|
+
data_pipeline_tester/bdd/steps/dq_steps.py,sha256=T12OnBj_uQrojAMq4DjLN2n2n_Y12Nblp0eNt2fEvvg,1936
|
|
110
|
+
data_pipeline_tester/bdd/steps/pipeline_steps.py,sha256=BR1d950ydJwGQOnmGxZFc10U6hxKlnt4wJkiMYlPksM,2113
|
|
111
|
+
data_pipeline_tester/builders/__init__.py,sha256=WQiGMw41iH2S1nIUles4-CATqSs3Aj6qwlnM2pBecng,1141
|
|
112
|
+
data_pipeline_tester/builders/config_builder.py,sha256=CIVrGodzrQuUiAp9kWvn-1ti9D0Rwj_j946L16ronhc,3804
|
|
113
|
+
data_pipeline_tester/builders/pipeline_builder.py,sha256=ohVzeOXiNQeZTsrwuZP3LYWIgjRbfVcfl5XZ0zOIFHU,2579
|
|
114
|
+
data_pipeline_tester/builders/record_builder.py,sha256=LWy_klIox7ggAlL9ZKJiJCZULVqOdYvlt-69d8Kfjds,4000
|
|
115
|
+
data_pipeline_tester/comparison/__init__.py,sha256=HWQawx5sIpW8DstAsk-tIJ2O2BowAVjAYpq58LTAE0g,238
|
|
116
|
+
data_pipeline_tester/comparison/dual_run.py,sha256=R_lvryr3RFu5R3FxklHZo7X7fkVzV6900Avp8g6la7E,12155
|
|
117
|
+
data_pipeline_tester/fixtures/__init__.py,sha256=Z_jUwDI3JEeBWQcHDDkzheBYYhOVajAvJcbByzJ2e3Q,2194
|
|
118
|
+
data_pipeline_tester/fixtures/beam.py,sha256=5ySAIl2ONwgJSooR7D-31INswDLucMeF4rg28jPvzQc,1438
|
|
119
|
+
data_pipeline_tester/fixtures/bigquery.py,sha256=3ef6mC2rsCyIfHGMZj1_EzGIHC3AzjkxWziTNP0MSfw,2127
|
|
120
|
+
data_pipeline_tester/fixtures/common.py,sha256=FeVIWbdu6tI6Mry02uYIG8NFAljNG81F5GYUY6bhAio,3125
|
|
121
|
+
data_pipeline_tester/fixtures/gcs.py,sha256=oZnSXDz_5D035YcOt6j1fUn8F3FTO8AVnKQh-K6De2Q,1650
|
|
122
|
+
data_pipeline_tester/mocks/__init__.py,sha256=jM9LwxLCTB4vyMOZ7gMr2zL0bJ-TMen1mUuglZ7v1tw,1097
|
|
123
|
+
data_pipeline_tester/mocks/bigquery_mock.py,sha256=9i4LtDnkdVU3OGCEAQwNJO66AgEC1J61xIMcvV76iiE,2639
|
|
124
|
+
data_pipeline_tester/mocks/gcs_mock.py,sha256=bOG2EUXlvHUBYDMfdVDqa3vPW8i6VCEM_4A7kSywZso,2558
|
|
125
|
+
data_pipeline_tester/mocks/pubsub_mock.py,sha256=mAfZ08ypGlRJWch6rwva5IKgCgqcLMFDA3YmbgsnOQk,11839
|
|
126
|
+
data_pipeline_transform/__init__.py,sha256=ltAvCHCTw0ylJorp_yI363VF7xsxr6fNyGFSm-Nx87g,61
|
|
127
|
+
data_pipeline_transform/dbt_shared/macros/audit_columns.sql,sha256=KilvtCY65KmQV_me4qKPV5-591pjt1S7ZKGmAz1anAg,1468
|
|
128
|
+
data_pipeline_transform/dbt_shared/macros/data_quality_check.sql,sha256=7_uO9kNfIFc4ocM0OzT-gGdTLU55Qw0N-MZndx1LVAg,4456
|
|
129
|
+
data_pipeline_transform/dbt_shared/macros/enrichment.sql,sha256=EjXAnM0SSkSs2RqCR1FP4g7YE3hFhsiVgxiD7pJxS8s,2414
|
|
130
|
+
data_pipeline_transform/dbt_shared/macros/pii_masking.sql,sha256=MGmtrLdYiVfZJFdBx69dkeubapmOUMoxYEJEkMzxX9c,6155
|
|
131
|
+
culvert-0.1.0.dist-info/METADATA,sha256=WAB1KLIcu0DDS0zLbkKhfkSi2KiymWi3zTdbAnXd0Fg,5331
|
|
132
|
+
culvert-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
133
|
+
culvert-0.1.0.dist-info/entry_points.txt,sha256=BRGRVTNRy5mXOv8VdZckmGKMm4W34s9E0lw_5n8kvIM,557
|
|
134
|
+
culvert-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[data_pipeline_core.adapters]
|
|
2
|
+
blob_store = data_pipeline_gcp_gcs:GcsBlobStore
|
|
3
|
+
finops = data_pipeline_gcp_bigquery:BigQueryFinOpsSink
|
|
4
|
+
lineage = data_pipeline_gcp_observability:DataCatalogLineageEmitter
|
|
5
|
+
observability = data_pipeline_gcp_observability:CloudTraceObservabilityHook
|
|
6
|
+
secrets = data_pipeline_gcp_secrets:SecretManagerProvider
|
|
7
|
+
sink = data_pipeline_gcp_pubsub:PubSubSink
|
|
8
|
+
source = data_pipeline_gcp_pubsub:PubSubSource
|
|
9
|
+
stage_metrics = data_pipeline_gcp_observability:CloudMonitoringMetricsHook
|
|
10
|
+
warehouse = data_pipeline_gcp_bigquery:BigQueryWarehouse
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Abstract pytest contract tests for the Culvert data pipeline framework.
|
|
2
|
+
|
|
3
|
+
Each cloud adapter library inherits the relevant mixin and supplies the
|
|
4
|
+
adapter under test. The mixin tests prove the adapter honours the
|
|
5
|
+
Protocol's documented behaviour.
|
|
6
|
+
|
|
7
|
+
Mirror of the Java ``data-pipeline-contract-tests`` library.
|
|
8
|
+
|
|
9
|
+
Sprint-5 deliverable. Sprint-17 (T17.3): added
|
|
10
|
+
:class:`StageMetricsHookContract` and brought existing mixins to 1:1
|
|
11
|
+
method-level parity with their Java counterparts (null-rejection tests).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from data_pipeline_contract_tests.blob_store import BlobStoreContract
|
|
17
|
+
from data_pipeline_contract_tests.secrets import SecretProviderContract
|
|
18
|
+
from data_pipeline_contract_tests.stage_metrics_hook import StageMetricsHookContract
|
|
19
|
+
from data_pipeline_contract_tests.warehouse import WarehouseContract
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"BlobStoreContract",
|
|
25
|
+
"SecretProviderContract",
|
|
26
|
+
"StageMetricsHookContract",
|
|
27
|
+
"WarehouseContract",
|
|
28
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""BlobStore contract test mixin.
|
|
2
|
+
|
|
3
|
+
Java mirror: ``com.enrichmeai.culvert.contracttests.BlobStoreContractTest``
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BlobStoreContract:
|
|
12
|
+
"""Mixin — subclasses provide ``store``, ``known_uri``, ``missing_uri``
|
|
13
|
+
fixtures. ``known_uri`` resolves to bytes equal to ``b"hello"``.
|
|
14
|
+
|
|
15
|
+
Java mirror: ``BlobStoreContractTest`` (Sprint-5 deliverable).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def test_get_known_returns_bytes(self, store, known_uri):
|
|
19
|
+
assert store.get(known_uri) == b"hello"
|
|
20
|
+
|
|
21
|
+
def test_exists_known_true(self, store, known_uri):
|
|
22
|
+
assert store.exists(known_uri) is True
|
|
23
|
+
|
|
24
|
+
def test_exists_missing_false(self, store, missing_uri):
|
|
25
|
+
assert store.exists(missing_uri) is False
|
|
26
|
+
|
|
27
|
+
def test_delete_missing_idempotent(self, store, missing_uri):
|
|
28
|
+
# Should not raise.
|
|
29
|
+
store.delete(missing_uri)
|
|
30
|
+
|
|
31
|
+
def test_null_arguments_rejected(self, store):
|
|
32
|
+
# Java: ``nullArgumentsRejected`` — get(null) must raise.
|
|
33
|
+
with pytest.raises((TypeError, ValueError)):
|
|
34
|
+
store.get(None)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""SecretProvider contract test mixin.
|
|
2
|
+
|
|
3
|
+
Java mirror: ``com.enrichmeai.culvert.contracttests.SecretProviderContractTest``
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SecretProviderContract:
|
|
12
|
+
"""Mixin — subclasses provide ``self.provider`` via a pytest fixture
|
|
13
|
+
named ``provider`` (and configure it so ``get("known")`` returns
|
|
14
|
+
``"the-secret"`` and ``get("missing")`` raises ``KeyError``).
|
|
15
|
+
|
|
16
|
+
Java mirror: ``SecretProviderContractTest`` (Sprint-5 deliverable).
|
|
17
|
+
|
|
18
|
+
Example wiring in a cloud adapter's test sources:
|
|
19
|
+
|
|
20
|
+
.. code-block:: python
|
|
21
|
+
|
|
22
|
+
from data_pipeline_contract_tests import SecretProviderContract
|
|
23
|
+
|
|
24
|
+
class TestMySecretProvider(SecretProviderContract):
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def provider(self):
|
|
27
|
+
client = make_mock_client({"known": "the-secret"})
|
|
28
|
+
return MySecretProvider("my-project", client)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def test_get_known_returns_value(self, provider):
|
|
32
|
+
assert provider.get("known") == "the-secret"
|
|
33
|
+
|
|
34
|
+
def test_get_missing_raises(self, provider):
|
|
35
|
+
with pytest.raises((KeyError, LookupError)):
|
|
36
|
+
provider.get("missing")
|
|
37
|
+
|
|
38
|
+
def test_null_name_rejected(self, provider):
|
|
39
|
+
# Java: ``nullNameRejected`` — get(null, ...) must raise.
|
|
40
|
+
with pytest.raises((TypeError, ValueError)):
|
|
41
|
+
provider.get(None)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""StageMetricsHook contract test mixin.
|
|
2
|
+
|
|
3
|
+
Java mirror: no ``Abstract*ContractTest`` class exists on the Java side —
|
|
4
|
+
this mixin is derived directly from the ``StageMetricsHook`` interface contract
|
|
5
|
+
and its accompanying ``StageMetrics`` record
|
|
6
|
+
(``com.enrichmeai.culvert.contracts.StageMetricsHook``,
|
|
7
|
+
``com.enrichmeai.culvert.contracts.StageMetrics``, Sprint-12 / issue #65).
|
|
8
|
+
|
|
9
|
+
The defining guarantee of ``StageMetricsHook`` is:
|
|
10
|
+
"Implementations must not propagate monitoring-backend failures to the
|
|
11
|
+
caller. If the backend is unavailable the implementation logs and
|
|
12
|
+
swallows the exception so the pipeline continues uninterrupted."
|
|
13
|
+
|
|
14
|
+
That guarantee is the primary behavioral assertion this mixin checks.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from data_pipeline_core.contracts.stage_metrics import StageMetrics
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _valid_metrics(**overrides) -> StageMetrics:
|
|
25
|
+
"""Build a minimal valid :class:`StageMetrics` snapshot."""
|
|
26
|
+
defaults = dict(
|
|
27
|
+
pipeline_id="pipe-1",
|
|
28
|
+
run_id="run-abc",
|
|
29
|
+
stage_name="my-stage",
|
|
30
|
+
rows_processed=100,
|
|
31
|
+
stage_latency_ms=42.5,
|
|
32
|
+
error_count=0,
|
|
33
|
+
)
|
|
34
|
+
defaults.update(overrides)
|
|
35
|
+
return StageMetrics(**defaults)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StageMetricsHookContract:
|
|
39
|
+
"""Mixin — subclasses provide two pytest fixtures:
|
|
40
|
+
|
|
41
|
+
- ``hook`` — the :class:`~data_pipeline_core.contracts.stage_metrics.StageMetricsHook`
|
|
42
|
+
implementation under test (happy-path instance).
|
|
43
|
+
- ``failing_hook`` — the same implementation wired to a backend that
|
|
44
|
+
raises an exception on every ``record_stage_metrics`` call, used to
|
|
45
|
+
verify the swallow-and-continue guarantee.
|
|
46
|
+
|
|
47
|
+
Java mirror: no ``AbstractStageMetricsHookContractTest`` exists in
|
|
48
|
+
``data-pipeline-contract-tests-java``; guarantees are derived directly
|
|
49
|
+
from the interface contract in ``StageMetricsHook.java`` (Sprint-12
|
|
50
|
+
/ issue #65).
|
|
51
|
+
|
|
52
|
+
Example wiring:
|
|
53
|
+
|
|
54
|
+
.. code-block:: python
|
|
55
|
+
|
|
56
|
+
from data_pipeline_contract_tests import StageMetricsHookContract
|
|
57
|
+
from data_pipeline_core.contracts.stage_metrics import StageMetrics
|
|
58
|
+
from unittest.mock import MagicMock
|
|
59
|
+
|
|
60
|
+
class TestMyHook(StageMetricsHookContract):
|
|
61
|
+
@pytest.fixture
|
|
62
|
+
def hook(self):
|
|
63
|
+
client = make_mock_monitoring_client()
|
|
64
|
+
return MyCloudMetricsHook(client)
|
|
65
|
+
|
|
66
|
+
@pytest.fixture
|
|
67
|
+
def failing_hook(self):
|
|
68
|
+
client = MagicMock()
|
|
69
|
+
client.create_time_series.side_effect = RuntimeError("backend down")
|
|
70
|
+
return MyCloudMetricsHook(client)
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def test_record_stage_metrics_returns_none(self, hook):
|
|
74
|
+
"""Happy-path: ``record_stage_metrics`` with a valid snapshot returns
|
|
75
|
+
``None`` (i.e. does not raise and has no meaningful return value).
|
|
76
|
+
"""
|
|
77
|
+
result = hook.record_stage_metrics(_valid_metrics())
|
|
78
|
+
assert result is None
|
|
79
|
+
|
|
80
|
+
def test_record_stage_metrics_all_fields(self, hook):
|
|
81
|
+
"""Happy-path: all three label fields and all three metric fields are
|
|
82
|
+
accepted, including edge values (zero rows, zero errors, non-zero latency).
|
|
83
|
+
"""
|
|
84
|
+
hook.record_stage_metrics(
|
|
85
|
+
_valid_metrics(rows_processed=0, stage_latency_ms=0.0, error_count=0)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def test_record_stage_metrics_with_errors(self, hook):
|
|
89
|
+
"""Happy-path: non-zero ``error_count`` is accepted without raising."""
|
|
90
|
+
hook.record_stage_metrics(_valid_metrics(error_count=5))
|
|
91
|
+
|
|
92
|
+
def test_backend_failure_is_swallowed(self, failing_hook):
|
|
93
|
+
"""Core contract guarantee: if the monitoring backend is unavailable
|
|
94
|
+
the implementation must NOT propagate the exception to the caller.
|
|
95
|
+
The pipeline continues uninterrupted.
|
|
96
|
+
|
|
97
|
+
This is the primary behavioral invariant stated in both
|
|
98
|
+
``StageMetricsHook.java`` and the Python ``StageMetricsHook`` Protocol.
|
|
99
|
+
"""
|
|
100
|
+
# Must not raise even though the backend raises internally.
|
|
101
|
+
failing_hook.record_stage_metrics(_valid_metrics())
|
|
102
|
+
|
|
103
|
+
def test_null_metrics_rejected(self, hook):
|
|
104
|
+
"""``record_stage_metrics(None)`` must raise ``TypeError`` or
|
|
105
|
+
``ValueError`` — implementations should not silently accept ``None``
|
|
106
|
+
in place of a ``StageMetrics`` snapshot.
|
|
107
|
+
"""
|
|
108
|
+
with pytest.raises((TypeError, ValueError)):
|
|
109
|
+
hook.record_stage_metrics(None)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Warehouse contract test mixin.
|
|
2
|
+
|
|
3
|
+
Java mirror: ``com.enrichmeai.culvert.contracttests.WarehouseContractTest``
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WarehouseContract:
|
|
12
|
+
"""Mixin — subclasses provide ``warehouse`` fixture configured so that
|
|
13
|
+
``query("SELECT id FROM contract_test_table")`` yields ``{"id": 1}``,
|
|
14
|
+
``table_exists("contract_test_table")`` is True, and
|
|
15
|
+
``table_exists("contract_missing_table")`` is False.
|
|
16
|
+
|
|
17
|
+
Java mirror: ``WarehouseContractTest`` (Sprint-5 deliverable; T15.4
|
|
18
|
+
added ``known_table``/``missing_table`` hook — Python subclasses
|
|
19
|
+
override the fixture to supply qualified names if needed).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def test_query_streams_rows(self, warehouse):
|
|
23
|
+
rows = list(warehouse.query("SELECT id FROM contract_test_table"))
|
|
24
|
+
assert len(rows) >= 1
|
|
25
|
+
assert "id" in rows[0]
|
|
26
|
+
|
|
27
|
+
def test_table_exists_known_true(self, warehouse):
|
|
28
|
+
assert warehouse.table_exists("contract_test_table") is True
|
|
29
|
+
|
|
30
|
+
def test_table_exists_missing_false(self, warehouse):
|
|
31
|
+
assert warehouse.table_exists("contract_missing_table") is False
|
|
32
|
+
|
|
33
|
+
def test_null_sql_rejected(self, warehouse):
|
|
34
|
+
# Java: ``nullSqlRejected`` — query(null, ...) must raise.
|
|
35
|
+
with pytest.raises((TypeError, ValueError)):
|
|
36
|
+
warehouse.query(None)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""data-pipeline-core: the cloud-neutral contracts for the Culvert framework.
|
|
2
|
+
|
|
3
|
+
This package contains only Protocols, dataclasses, enums, and TypedDicts.
|
|
4
|
+
Zero dependencies on google.cloud, boto3, or azure. Cloud-specific
|
|
5
|
+
implementations of the Protocols live in sibling distributions
|
|
6
|
+
(`data-pipeline-gcp-*`, future `data-pipeline-aws-*`, `data-pipeline-azure-*`).
|
|
7
|
+
|
|
8
|
+
The public surface is the `contracts` package plus the supporting types.
|
|
9
|
+
Most users will import the Protocols from the top level:
|
|
10
|
+
|
|
11
|
+
from data_pipeline_core import (
|
|
12
|
+
Source, Sink, Transform, Pipeline, PipelineStage, RuntimeContext,
|
|
13
|
+
JobControlRepository, BlobStore, Warehouse,
|
|
14
|
+
AuditEventPublisher, GovernancePolicy, LineageEmitter,
|
|
15
|
+
ObservabilityHook, FinOpsSink, SecretProvider,
|
|
16
|
+
StageMetrics, StageMetricsHook,
|
|
17
|
+
)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from data_pipeline_core.contracts.audit import AuditEventPublisher
|
|
21
|
+
from data_pipeline_core.contracts.blob_store import BlobStore
|
|
22
|
+
from data_pipeline_core.contracts.finops import FinOpsSink
|
|
23
|
+
from data_pipeline_core.contracts.governance import GovernancePolicy
|
|
24
|
+
from data_pipeline_core.contracts.job_control import JobControlRepository
|
|
25
|
+
from data_pipeline_core.contracts.lineage import LineageEmitter
|
|
26
|
+
from data_pipeline_core.contracts.observability import ObservabilityHook
|
|
27
|
+
from data_pipeline_core.contracts.pipeline import Pipeline, PipelineStage
|
|
28
|
+
from data_pipeline_core.contracts.runtime import RuntimeContext
|
|
29
|
+
from data_pipeline_core.contracts.secrets import SecretProvider
|
|
30
|
+
from data_pipeline_core.contracts.source import Sink, Source, Transform
|
|
31
|
+
from data_pipeline_core.contracts.stage_metrics import StageMetrics, StageMetricsHook
|
|
32
|
+
from data_pipeline_core.contracts.warehouse import Warehouse
|
|
33
|
+
|
|
34
|
+
__version__ = "0.1.0"
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"__version__",
|
|
38
|
+
# Source / Sink / Transform — the I/O primitives
|
|
39
|
+
"Source",
|
|
40
|
+
"Sink",
|
|
41
|
+
"Transform",
|
|
42
|
+
# Pipeline composition
|
|
43
|
+
"Pipeline",
|
|
44
|
+
"PipelineStage",
|
|
45
|
+
# The DI container / run metadata carrier
|
|
46
|
+
"RuntimeContext",
|
|
47
|
+
# Cloud-pluggable contracts
|
|
48
|
+
"JobControlRepository",
|
|
49
|
+
"BlobStore",
|
|
50
|
+
"Warehouse",
|
|
51
|
+
"AuditEventPublisher",
|
|
52
|
+
"GovernancePolicy",
|
|
53
|
+
"LineageEmitter",
|
|
54
|
+
"ObservabilityHook",
|
|
55
|
+
"FinOpsSink",
|
|
56
|
+
"SecretProvider",
|
|
57
|
+
# Typed per-stage metrics (Sprint-12 / T17.1)
|
|
58
|
+
"StageMetrics",
|
|
59
|
+
"StageMetricsHook",
|
|
60
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""The AuditRecord dataclass — the unit of audit emission.
|
|
2
|
+
|
|
3
|
+
Mirrors the existing `gcp_pipeline_core.audit.records.AuditRecord` shape
|
|
4
|
+
verbatim (eleven fields). Stage 1 will collapse the two into one.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AuditRecord:
|
|
16
|
+
"""A single audit event emitted at a pipeline boundary (e.g. an
|
|
17
|
+
ingestion stage completing).
|
|
18
|
+
|
|
19
|
+
`audit_hash` is a deterministic content hash that downstream
|
|
20
|
+
reconciliation uses to dedupe replays. `metadata` is the catch-all
|
|
21
|
+
for stage-specific context (table identifiers, partition keys, etc.)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
run_id: str
|
|
25
|
+
pipeline_name: str
|
|
26
|
+
entity_type: str
|
|
27
|
+
source_file: str
|
|
28
|
+
record_count: int
|
|
29
|
+
processed_timestamp: datetime
|
|
30
|
+
processing_duration_seconds: float
|
|
31
|
+
success: bool
|
|
32
|
+
error_count: int = 0
|
|
33
|
+
audit_hash: str = ""
|
|
34
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|