datahub-yaml-source 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.
Files changed (36) hide show
  1. datahub_yaml_source/__init__.py +11 -0
  2. datahub_yaml_source/builders/__init__.py +0 -0
  3. datahub_yaml_source/builders/application.py +68 -0
  4. datahub_yaml_source/builders/assertion.py +254 -0
  5. datahub_yaml_source/builders/chart.py +41 -0
  6. datahub_yaml_source/builders/common.py +489 -0
  7. datahub_yaml_source/builders/container.py +145 -0
  8. datahub_yaml_source/builders/dashboard.py +45 -0
  9. datahub_yaml_source/builders/data_flow_job.py +95 -0
  10. datahub_yaml_source/builders/data_process_instance.py +64 -0
  11. datahub_yaml_source/builders/data_product.py +31 -0
  12. datahub_yaml_source/builders/dataset.py +147 -0
  13. datahub_yaml_source/builders/document.py +77 -0
  14. datahub_yaml_source/builders/domain.py +73 -0
  15. datahub_yaml_source/builders/glossary.py +115 -0
  16. datahub_yaml_source/builders/incident.py +74 -0
  17. datahub_yaml_source/builders/ml.py +271 -0
  18. datahub_yaml_source/builders/platform.py +21 -0
  19. datahub_yaml_source/builders/query.py +49 -0
  20. datahub_yaml_source/builders/raw_aspect.py +236 -0
  21. datahub_yaml_source/builders/semantic.py +150 -0
  22. datahub_yaml_source/builders/software.py +253 -0
  23. datahub_yaml_source/builders/structured_property.py +54 -0
  24. datahub_yaml_source/builders/tag.py +22 -0
  25. datahub_yaml_source/loader.py +404 -0
  26. datahub_yaml_source/models.py +1812 -0
  27. datahub_yaml_source/urns.py +315 -0
  28. datahub_yaml_source/yaml_source.py +605 -0
  29. datahub_yaml_source/yaml_source_config.py +200 -0
  30. datahub_yaml_source/yaml_source_report.py +81 -0
  31. datahub_yaml_source-0.1.0.dist-info/METADATA +124 -0
  32. datahub_yaml_source-0.1.0.dist-info/RECORD +36 -0
  33. datahub_yaml_source-0.1.0.dist-info/WHEEL +5 -0
  34. datahub_yaml_source-0.1.0.dist-info/entry_points.txt +2 -0
  35. datahub_yaml_source-0.1.0.dist-info/licenses/LICENSE +190 -0
  36. datahub_yaml_source-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,11 @@
1
+ from importlib.metadata import PackageNotFoundError
2
+ from importlib.metadata import version as _version
3
+
4
+ from datahub_yaml_source.yaml_source import YamlSource
5
+
6
+ __all__ = ["YamlSource"]
7
+
8
+ try:
9
+ __version__ = _version("datahub-yaml-source")
10
+ except PackageNotFoundError: # running from a source tree with no install
11
+ __version__ = "0.0.0"
File without changes
@@ -0,0 +1,68 @@
1
+ from collections.abc import Iterable
2
+
3
+ from datahub.ingestion.api.workunit import MetadataWorkUnit
4
+ from datahub.metadata.schema_classes import (
5
+ ApplicationLineageClass,
6
+ ApplicationPropertiesClass,
7
+ EdgeClass,
8
+ )
9
+
10
+ from datahub_yaml_source.builders.common import common_aspect_mcps, mcp_workunit
11
+ from datahub_yaml_source.models import ApplicationDoc, ApplicationLineageEdgeDoc
12
+ from datahub_yaml_source.urns import ReferenceIndex, api_urn, application_urn, dataset_urn
13
+ from datahub_yaml_source.yaml_source_report import YamlSourceReport
14
+
15
+
16
+ def _application_lineage_edges(
17
+ edges: list[ApplicationLineageEdgeDoc] | None, direction: str, context: str
18
+ ) -> list[EdgeClass] | None:
19
+ if not edges:
20
+ return None
21
+ result = []
22
+ for i, edge in enumerate(edges):
23
+ if bool(edge.api) == bool(edge.dataset):
24
+ raise ValueError(
25
+ f"{context} applicationLineage.{direction}[{i}] must set "
26
+ f"exactly one of 'api' or 'dataset'"
27
+ )
28
+ if edge.api:
29
+ destination_urn = api_urn(edge.api)
30
+ else:
31
+ assert edge.dataset is not None # guaranteed by the exactly-one check above
32
+ destination_urn = dataset_urn(edge.dataset)
33
+ result.append(EdgeClass(destinationUrn=destination_urn))
34
+ return result
35
+
36
+
37
+ def build_application(
38
+ doc: ApplicationDoc, index: ReferenceIndex, report: YamlSourceReport
39
+ ) -> Iterable[MetadataWorkUnit]:
40
+ context = f"APPLICATION '{doc.id}'"
41
+ entity_urn = application_urn(doc.id)
42
+
43
+ # Resolve (and validate) applicationLineage before yielding anything --
44
+ # `get_workunits_internal` streams work units straight to the sink as they're
45
+ # yielded, so raising after an earlier yield would leave a partially-emitted
46
+ # entity instead of cleanly skipping the whole malformed document (see
47
+ # `build_document`'s equivalent native/external check for the same reason).
48
+ input_edges = output_edges = None
49
+ if doc.applicationLineage:
50
+ input_edges = _application_lineage_edges(
51
+ doc.applicationLineage.consumes, "consumes", context
52
+ )
53
+ output_edges = _application_lineage_edges(
54
+ doc.applicationLineage.produces, "produces", context
55
+ )
56
+
57
+ yield mcp_workunit(
58
+ entity_urn,
59
+ ApplicationPropertiesClass(name=doc.name, description=doc.description),
60
+ )
61
+
62
+ if input_edges or output_edges:
63
+ yield mcp_workunit(
64
+ entity_urn,
65
+ ApplicationLineageClass(inputEdges=input_edges, outputEdges=output_edges),
66
+ )
67
+
68
+ yield from common_aspect_mcps(entity_urn, doc, index, report, context)
@@ -0,0 +1,254 @@
1
+ import re
2
+ from collections.abc import Iterable
3
+ from typing import Any
4
+
5
+ from datahub.emitter.mce_builder import make_schema_field_urn
6
+ from datahub.ingestion.api.workunit import MetadataWorkUnit
7
+ from datahub.metadata.schema_classes import (
8
+ AssertionActionClass,
9
+ AssertionActionsClass,
10
+ AssertionInfoClass,
11
+ AssertionNoteClass,
12
+ AssertionSourceClass,
13
+ AssertionStdParameterClass,
14
+ AssertionStdParametersClass,
15
+ AuditStampClass,
16
+ CustomAssertionInfoClass,
17
+ FieldAssertionInfoClass,
18
+ FieldMetricAssertionClass,
19
+ FieldValuesAssertionClass,
20
+ FieldValuesFailThresholdClass,
21
+ FreshnessAssertionInfoClass,
22
+ FreshnessAssertionScheduleClass,
23
+ FreshnessCronScheduleClass,
24
+ OtherSchemaClass,
25
+ RowCountChangeClass,
26
+ RowCountTotalClass,
27
+ SchemaAssertionInfoClass,
28
+ SchemaFieldClass,
29
+ SchemaFieldSpecClass,
30
+ SchemaMetadataClass,
31
+ SqlAssertionInfoClass,
32
+ VolumeAssertionInfoClass,
33
+ )
34
+ from datahub.metadata.urns import AssertionUrn
35
+
36
+ from datahub_yaml_source.builders.common import (
37
+ DEFAULT_ACTOR_URN,
38
+ common_aspect_mcps,
39
+ mcp_workunit,
40
+ schema_field_data_type,
41
+ stringify_custom_properties,
42
+ )
43
+ from datahub_yaml_source.models import AssertionActionsDoc, AssertionDoc
44
+ from datahub_yaml_source.urns import ReferenceIndex
45
+ from datahub_yaml_source.yaml_source_report import YamlSourceReport
46
+
47
+
48
+ def _build_freshness_assertion(doc: AssertionDoc) -> FreshnessAssertionInfoClass:
49
+ a = doc.assertion
50
+ schedule = FreshnessAssertionScheduleClass(
51
+ type=a.scheduleType or "CRON",
52
+ cron=FreshnessCronScheduleClass(cron=a.cron or "", timezone=a.timezone or "UTC"),
53
+ )
54
+ return FreshnessAssertionInfoClass(
55
+ type=a.freshnessType or "DATASET_CHANGE", entity=a.entityUrn, schedule=schedule
56
+ )
57
+
58
+
59
+ def _build_sql_assertion(doc: AssertionDoc) -> SqlAssertionInfoClass:
60
+ a = doc.assertion
61
+ return SqlAssertionInfoClass(
62
+ type=a.sqlType or "METRIC",
63
+ entity=a.entityUrn,
64
+ statement=a.statement or "",
65
+ operator=a.operator or "GREATER_THAN",
66
+ parameters=AssertionStdParametersClass(
67
+ value=AssertionStdParameterClass(value=str(a.value), type="NUMBER")
68
+ ),
69
+ changeType=a.changeType,
70
+ )
71
+
72
+
73
+ def _build_field_assertion(doc: AssertionDoc) -> FieldAssertionInfoClass:
74
+ a = doc.assertion
75
+ field_spec = SchemaFieldSpecClass(
76
+ path=a.fieldPath or "",
77
+ type=a.dataType or "STRING",
78
+ nativeType=a.nativeDataType or "",
79
+ )
80
+
81
+ field_values_assertion = None
82
+ field_metric_assertion = None
83
+ if a.fieldType == "FIELD_METRIC":
84
+ field_metric_assertion = FieldMetricAssertionClass(
85
+ field=field_spec,
86
+ metric=a.metric or "UNIQUE_PERCENTAGE",
87
+ operator=a.metricOperator or "EQUAL_TO",
88
+ parameters=(
89
+ AssertionStdParametersClass(
90
+ value=AssertionStdParameterClass(value=str(a.metricValue), type="NUMBER")
91
+ )
92
+ if a.metricValue is not None
93
+ else None
94
+ ),
95
+ )
96
+ else:
97
+ field_values_assertion = FieldValuesAssertionClass(
98
+ field=field_spec,
99
+ operator=a.operator or "NOT_NULL",
100
+ failThreshold=FieldValuesFailThresholdClass(type="COUNT", value=0),
101
+ excludeNulls=a.excludeNulls,
102
+ )
103
+
104
+ return FieldAssertionInfoClass(
105
+ type=a.fieldType or "FIELD_VALUES",
106
+ entity=a.entityUrn,
107
+ fieldValuesAssertion=field_values_assertion,
108
+ fieldMetricAssertion=field_metric_assertion,
109
+ )
110
+
111
+
112
+ _DATASET_URN_PLATFORM_RE = re.compile(r"^urn:li:dataset:\((?P<platform>urn:li:dataPlatform:[^,]+),")
113
+
114
+
115
+ def _platform_urn_from_dataset_urn(entity_urn: str) -> str:
116
+ match = _DATASET_URN_PLATFORM_RE.match(entity_urn)
117
+ return match.group("platform") if match else "urn:li:dataPlatform:unknown"
118
+
119
+
120
+ def _build_volume_assertion(doc: AssertionDoc) -> VolumeAssertionInfoClass:
121
+ a = doc.assertion
122
+ if a.value is None:
123
+ raise ValueError(f"VOLUME assertion '{doc.id}' is missing a 'value' to compare against.")
124
+ # `RowCountTotalClass.parameters` / `RowCountChangeClass.parameters` are
125
+ # required in the Avro schema despite the generated stub typing them
126
+ # Optional -- passing None parses fine but fails MCP validation at emit
127
+ # time (same trap as `DeprecationClass.note`, see build_deprecation_aspect()).
128
+ parameters = AssertionStdParametersClass(
129
+ value=AssertionStdParameterClass(value=str(a.value), type="NUMBER")
130
+ )
131
+ if a.volumeType == "ROW_COUNT_CHANGE":
132
+ return VolumeAssertionInfoClass(
133
+ type="ROW_COUNT_CHANGE",
134
+ entity=a.entityUrn,
135
+ rowCountChange=RowCountChangeClass(
136
+ type=a.changeType or "ABSOLUTE",
137
+ operator=a.operator or "GREATER_THAN_OR_EQUAL_TO",
138
+ parameters=parameters,
139
+ ),
140
+ )
141
+ return VolumeAssertionInfoClass(
142
+ type="ROW_COUNT_TOTAL",
143
+ entity=a.entityUrn,
144
+ rowCountTotal=RowCountTotalClass(
145
+ operator=a.operator or "GREATER_THAN_OR_EQUAL_TO", parameters=parameters
146
+ ),
147
+ )
148
+
149
+
150
+ def _build_data_schema_assertion(doc: AssertionDoc) -> SchemaAssertionInfoClass:
151
+ a = doc.assertion
152
+ fields = [
153
+ SchemaFieldClass(
154
+ fieldPath=f.path,
155
+ type=schema_field_data_type(f.type),
156
+ nativeDataType=f.nativeType or f.type,
157
+ )
158
+ for f in (a.schemaFields or [])
159
+ ]
160
+ expected_schema = SchemaMetadataClass(
161
+ schemaName=f"{doc.id}-expected-schema",
162
+ platform=_platform_urn_from_dataset_urn(a.entityUrn),
163
+ version=0,
164
+ hash="",
165
+ platformSchema=OtherSchemaClass(rawSchema=""),
166
+ fields=fields,
167
+ )
168
+ return SchemaAssertionInfoClass(
169
+ entity=a.entityUrn, schema=expected_schema, compatibility=a.compatibility
170
+ )
171
+
172
+
173
+ def _build_custom_assertion(doc: AssertionDoc) -> CustomAssertionInfoClass:
174
+ a = doc.assertion
175
+ # `CustomAssertionInfoClass.field` is a schemaField URN string, not a
176
+ # SchemaFieldSpec -- build the URN from the dataset URN + the column path.
177
+ field_urn = make_schema_field_urn(a.entityUrn, a.fieldPath) if a.fieldPath else None
178
+ return CustomAssertionInfoClass(
179
+ type=a.customType or "CUSTOM", entity=a.entityUrn, field=field_urn, logic=a.logic or ""
180
+ )
181
+
182
+
183
+ _BUILDERS = {
184
+ "FRESHNESS": (_build_freshness_assertion, "freshnessAssertion"),
185
+ "VOLUME": (_build_volume_assertion, "volumeAssertion"),
186
+ "SQL": (_build_sql_assertion, "sqlAssertion"),
187
+ "FIELD": (_build_field_assertion, "fieldAssertion"),
188
+ "DATA_SCHEMA": (_build_data_schema_assertion, "schemaAssertion"),
189
+ "CUSTOM": (_build_custom_assertion, "customAssertion"),
190
+ }
191
+
192
+
193
+ def _build_assertion_actions(actions: AssertionActionsDoc | None) -> AssertionActionsClass | None:
194
+ if actions is None:
195
+ return None
196
+ return AssertionActionsClass(
197
+ onSuccess=[AssertionActionClass(type=a.type) for a in (actions.onSuccess or [])] or None,
198
+ onFailure=[AssertionActionClass(type=a.type) for a in (actions.onFailure or [])] or None,
199
+ )
200
+
201
+
202
+ def build_assertion(
203
+ doc: AssertionDoc, index: ReferenceIndex, report: YamlSourceReport
204
+ ) -> Iterable[MetadataWorkUnit]:
205
+ builder = _BUILDERS.get(doc.assertion.type)
206
+ if builder is None:
207
+ raise ValueError(
208
+ f"Unsupported assertion type '{doc.assertion.type}' for assertion '{doc.id}'. "
209
+ f"Supported types: {sorted(_BUILDERS)}"
210
+ )
211
+ build_fn, kwarg_name = builder
212
+ sub_assertion = build_fn(doc)
213
+
214
+ entity_urn = AssertionUrn(doc.id).urn()
215
+
216
+ custom_properties = None
217
+ if doc.properties:
218
+ custom_properties = stringify_custom_properties(
219
+ {
220
+ k: v
221
+ for k, v in {
222
+ "name": doc.properties.name,
223
+ "dbt_test": doc.properties.dbt_test,
224
+ }.items()
225
+ if v is not None
226
+ }
227
+ )
228
+
229
+ # The sub-assertion kwarg name is chosen from `_BUILDERS` at runtime; `Any`
230
+ # is the honest element type for that dispatch.
231
+ sub_assertion_kwarg: dict[str, Any] = {kwarg_name: sub_assertion}
232
+ aspect = AssertionInfoClass(
233
+ type=doc.assertion.type,
234
+ description=doc.description,
235
+ customProperties=custom_properties,
236
+ source=AssertionSourceClass(type=doc.sourceType),
237
+ **sub_assertion_kwarg,
238
+ )
239
+ yield mcp_workunit(entity_urn, aspect)
240
+
241
+ if doc.assertionNote:
242
+ yield mcp_workunit(
243
+ entity_urn,
244
+ AssertionNoteClass(
245
+ content=doc.assertionNote,
246
+ lastModified=AuditStampClass(time=0, actor=DEFAULT_ACTOR_URN),
247
+ ),
248
+ )
249
+
250
+ actions_aspect = _build_assertion_actions(doc.assertionActions)
251
+ if actions_aspect:
252
+ yield mcp_workunit(entity_urn, actions_aspect)
253
+
254
+ yield from common_aspect_mcps(entity_urn, doc, index, report, f"ASSERTION '{doc.id}'")
@@ -0,0 +1,41 @@
1
+ from collections.abc import Iterable
2
+
3
+ from datahub.ingestion.api.workunit import MetadataWorkUnit
4
+ from datahub.sdk.chart import Chart
5
+
6
+ from datahub_yaml_source.builders.common import common_sdk_kwargs, stringify_custom_properties
7
+ from datahub_yaml_source.models import ChartDoc
8
+ from datahub_yaml_source.urns import ReferenceIndex, container_key, dataset_urn
9
+ from datahub_yaml_source.yaml_source_report import YamlSourceReport
10
+
11
+
12
+ def build_chart(
13
+ doc: ChartDoc, index: ReferenceIndex, report: YamlSourceReport
14
+ ) -> Iterable[MetadataWorkUnit]:
15
+ context = f"CHART '{doc.name}'"
16
+ common = common_sdk_kwargs(doc, index, report, context)
17
+
18
+ # `parent_container=` defaults to a private `Unset` sentinel, not `None`
19
+ # -- passing `None` explicitly still triggers `_set_container(None)`,
20
+ # which emits a (harmless but unwanted-here) empty `browsePathsV2`. Only
21
+ # pass the kwarg at all when there's an actual container (same trap as
22
+ # `build_dataset()`/`build_data_flow()`).
23
+ if doc.container is not None:
24
+ if not index.has_container(doc.container):
25
+ report.report_dangling_reference(f"{context} references an undeclared container")
26
+ common["parent_container"] = container_key(doc.container)
27
+
28
+ chart = Chart(
29
+ platform=doc.platform,
30
+ name=doc.name,
31
+ platform_instance=doc.instance,
32
+ display_name=doc.displayName,
33
+ description=doc.description,
34
+ external_url=doc.externalUrl,
35
+ chart_url=doc.chartUrl,
36
+ chart_type=doc.chartType,
37
+ custom_properties=stringify_custom_properties(doc.properties),
38
+ input_datasets=[dataset_urn(ref) for ref in (doc.inputDatasets or [])] or None,
39
+ **common,
40
+ )
41
+ yield from chart.as_workunits()