openlineage-python 1.35.0__tar.gz → 1.37.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.
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/PKG-INFO +1 -1
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/client.py +16 -15
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/constants.py +1 -1
- openlineage_python-1.37.0/openlineage/client/naming/dataset.py +512 -0
- openlineage_python-1.37.0/openlineage/client/tags.py +11 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/__init__.py +4 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/async_http.py +67 -4
- openlineage_python-1.37.0/openlineage/client/transport/datadog.py +164 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/http.py +3 -1
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/pyproject.toml +1 -1
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_client.py +285 -1
- openlineage_python-1.37.0/tests/test_datadog.py +1014 -0
- openlineage_python-1.37.0/tests/test_dataset.py +276 -0
- openlineage_python-1.37.0/tests/transform/transformers/__init__.py +2 -0
- openlineage_python-1.35.0/openlineage/client/tags.py +0 -17
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/.gitignore +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/README.md +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/Makefile +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/conf.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/convert.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/index.rst +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/make.bat +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/source/openlineage.client.rst +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/__init__.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/event_v2.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/facet.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/facet_v2.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/facets.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/filter.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/base.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/catalog_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/dataset_type_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/datasource_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/documentation_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/documentation_job.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/environment_variables_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/error_message_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/external_query_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/extraction_error_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/job_type_job.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/nominal_time_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/ownership_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/ownership_job.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/parent_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/processing_engine_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/schema_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/source_code_job.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/source_code_location_job.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/sql_job.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/storage_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_dataset.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_job.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/base.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/cli.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/generate.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/header.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/Enum.jinja2 +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/dataclass.jinja2 +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/facet_v2.jinja2 +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/root.jinja2 +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/validators.jinja2 +0 -0
- {openlineage_python-1.35.0/openlineage/client/transport/transform/transformers → openlineage_python-1.37.0/openlineage/client/naming}/__init__.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/py.typed +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/run.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/serde.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/amazon_datazone.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/composite.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/console.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/factory.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/file.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/kafka.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/msk_iam.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/noop.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/__init__.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/transform.py +0 -0
- {openlineage_python-1.35.0/tests → openlineage_python-1.37.0/openlineage/client/transport/transform/transformers}/__init__.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/transformers/job_namespace_replace_transformer.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transport.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/utils.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/uuid.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/redact_fields.yml +0 -0
- {openlineage_python-1.35.0/tests/generator → openlineage_python-1.37.0/tests}/__init__.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/config.yml +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/exact_filter.yml +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/http.yml +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/openlineage.yml +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/regex_filter.yml +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/test-transport.yaml +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/conftest.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/example_full_event.json +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/fixtures/events.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/fixtures/mock_responses.py +0 -0
- {openlineage_python-1.35.0/tests/transform → openlineage_python-1.37.0/tests/generator}/__init__.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/generator/test_base.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/generator/test_cli.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/docker-compose.yml +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/test_server/Dockerfile +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/test_server/requirements.txt +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/test_server/test_server.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/nominal_time_without_end.json +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/serde_example_dataset_event.json +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/serde_example_job_event.json +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/serde_example_run_event.json +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_amazon_datazone.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_async_http.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_composite.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_events.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_facet.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_facet_v2.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_factory.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_file.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_http.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_http_integration.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_kafka.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_msk_iam.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_utils.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_uuid.py +0 -0
- {openlineage_python-1.35.0/tests/transform/transformers → openlineage_python-1.37.0/tests/transform}/__init__.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/transform/test_transform.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/transform/transformers/test_job_namespace_replace_transformer.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/transport.py +0 -0
- {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tox.ini +0 -0
|
@@ -67,11 +67,9 @@ class OpenLineageConfig:
|
|
|
67
67
|
if "filters" in params:
|
|
68
68
|
config.filters = [FilterConfig(**filter_config) for filter_config in params["filters"]]
|
|
69
69
|
if "tags" in params:
|
|
70
|
-
job_tags = params["tags"].get("job", {})
|
|
71
|
-
run_tags = params["tags"].get("run", {})
|
|
72
70
|
config.tags = TagsConfig(
|
|
73
|
-
job=[
|
|
74
|
-
run=[
|
|
71
|
+
job=params["tags"].get("job", {}),
|
|
72
|
+
run=params["tags"].get("run", {}),
|
|
75
73
|
)
|
|
76
74
|
return config
|
|
77
75
|
|
|
@@ -232,9 +230,9 @@ class OpenLineageClient:
|
|
|
232
230
|
This method determines the appropriate transport by executing a sequence of checks:
|
|
233
231
|
1. Verifies if OpenLineage is disabled through environment variable.
|
|
234
232
|
2. Looks for a transport object provided in the arguments.
|
|
235
|
-
3. Attempts to configure the transport from
|
|
233
|
+
3. Attempts to configure the transport from user config, config file or env vars.
|
|
236
234
|
4. Tries to initialize HTTP transport with an url argument (deprecated).
|
|
237
|
-
5. Tries to set up HTTP transport using environment variables.
|
|
235
|
+
5. Tries to set up HTTP transport using simple environment variables.
|
|
238
236
|
6. If no configuration is found, defaults to a console transport and logs a warning message.
|
|
239
237
|
|
|
240
238
|
Returns:
|
|
@@ -245,11 +243,11 @@ class OpenLineageClient:
|
|
|
245
243
|
log.info("OpenLineage is disabled. No events will be emitted.")
|
|
246
244
|
return NoopTransport(NoopConfig())
|
|
247
245
|
|
|
248
|
-
# 2. Check if transport is provided explicitly
|
|
246
|
+
# 2. Check if transport is provided explicitly as argument
|
|
249
247
|
if kwargs.get("transport"):
|
|
250
248
|
return cast("Transport", kwargs["transport"])
|
|
251
249
|
|
|
252
|
-
# 3. Check if transport configuration is provided
|
|
250
|
+
# 3. Check if transport configuration is provided as config (explicit, file or env vars)
|
|
253
251
|
if self.config.transport and self.config.transport.get("type"):
|
|
254
252
|
factory = kwargs.get("factory") or get_default_factory()
|
|
255
253
|
return factory.create(self.config.transport)
|
|
@@ -260,7 +258,7 @@ class OpenLineageClient:
|
|
|
260
258
|
url=kwargs["url"], options=kwargs.get("options"), session=kwargs.get("session")
|
|
261
259
|
)
|
|
262
260
|
|
|
263
|
-
# 5. Check HTTP transport initialization with env variables
|
|
261
|
+
# 5. Check HTTP transport initialization with simple env variables
|
|
264
262
|
if os.environ.get("OPENLINEAGE_URL"):
|
|
265
263
|
return self._http_transport_from_env_variables()
|
|
266
264
|
|
|
@@ -307,7 +305,8 @@ class OpenLineageClient:
|
|
|
307
305
|
log.exception("Couldn't check if OpenLineage config file is readable: `%s`", path)
|
|
308
306
|
return None
|
|
309
307
|
|
|
310
|
-
|
|
308
|
+
@staticmethod
|
|
309
|
+
def _http_transport_from_env_variables() -> HttpTransport:
|
|
311
310
|
"""
|
|
312
311
|
Create HTTP transport from legacy environment variables
|
|
313
312
|
"""
|
|
@@ -345,7 +344,7 @@ class OpenLineageClient:
|
|
|
345
344
|
k.startswith(f"OPENLINEAGE__TRANSPORT__TRANSPORTS__{default_transport_name}")
|
|
346
345
|
for k in os.environ
|
|
347
346
|
):
|
|
348
|
-
log.
|
|
347
|
+
log.info(
|
|
349
348
|
"%s already found in environment variables, skipping aliasing OPENLINEAGE_URL",
|
|
350
349
|
default_transport_name,
|
|
351
350
|
)
|
|
@@ -388,7 +387,7 @@ class OpenLineageClient:
|
|
|
388
387
|
def _load_config_from_env_variables(cls) -> dict[str, Any] | None:
|
|
389
388
|
config: dict[str, Any] = {}
|
|
390
389
|
|
|
391
|
-
# get os.environ.items only starting with
|
|
390
|
+
# get os.environ.items only starting with OPENLINEAGE__ prefix and reverse sort
|
|
392
391
|
# to make sure that top-level keys have precedence
|
|
393
392
|
env_vars = sorted(
|
|
394
393
|
filter(lambda k: k[0].startswith(cls.DYNAMIC_ENV_VARS_PREFIX), os.environ.items()), reverse=True
|
|
@@ -451,14 +450,16 @@ class OpenLineageClient:
|
|
|
451
450
|
"""
|
|
452
451
|
run_event_types = (RunEvent, event_v2.RunEvent)
|
|
453
452
|
run_and_job_event_types = (RunEvent, event_v2.RunEvent, JobEvent, event_v2.JobEvent)
|
|
454
|
-
tags_job = self.config.tags.job
|
|
453
|
+
# tags_job = self.config.tags.job
|
|
454
|
+
tags_job = [TagsJobFacetFields(key, value, "USER") for (key, value) in self.config.tags.job.items()]
|
|
455
455
|
if isinstance(event, run_and_job_event_types) and tags_job:
|
|
456
456
|
# Ensure facets exists
|
|
457
457
|
event.job.facets = {} if not event.job.facets else event.job.facets
|
|
458
458
|
tags_facet = event.job.facets.get("tags", TagsJobFacet())
|
|
459
459
|
event.job.facets["tags"] = self._update_tag_facet(tags_facet, tags_job) # type: ignore [arg-type, assignment]
|
|
460
460
|
|
|
461
|
-
tags_run = self.config.tags.run
|
|
461
|
+
# tags_run = self.config.tags.run
|
|
462
|
+
tags_run = [TagsRunFacetFields(key, value, "USER") for (key, value) in self.config.tags.run.items()]
|
|
462
463
|
if isinstance(event, run_event_types) and tags_run:
|
|
463
464
|
# Ensure facets exists
|
|
464
465
|
event.run.facets = {} if not event.run.facets else event.run.facets
|
|
@@ -467,8 +468,8 @@ class OpenLineageClient:
|
|
|
467
468
|
|
|
468
469
|
return event
|
|
469
470
|
|
|
471
|
+
@staticmethod
|
|
470
472
|
def _update_tag_facet(
|
|
471
|
-
self,
|
|
472
473
|
tags_facet: TagsJobFacet | TagsRunFacet,
|
|
473
474
|
user_tags: list[TagsJobFacetFields | TagsRunFacetFields],
|
|
474
475
|
) -> TagsJobFacet | TagsRunFacet:
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# Copyright 2018-2025 contributors to the OpenLineage project
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Utility module containing dataset naming helpers based on OpenLineage's dataset naming
|
|
6
|
+
specification.
|
|
7
|
+
|
|
8
|
+
Each class represents naming logic for a specific platform (e.g., BigQuery, AWS Glue),
|
|
9
|
+
and provides methods to construct valid dataset names and namespaces.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Protocol
|
|
16
|
+
|
|
17
|
+
import attr
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _check_not_empty(instance: object, attribute: attr.Attribute[str], value: str) -> str: # noqa: ARG001
|
|
21
|
+
"""Validator to check if a string is not None and not empty."""
|
|
22
|
+
if value is None:
|
|
23
|
+
msg = f"{attribute.name} cannot be None"
|
|
24
|
+
raise ValueError(msg)
|
|
25
|
+
if not value.strip():
|
|
26
|
+
msg = f"{attribute.name} cannot be empty"
|
|
27
|
+
raise ValueError(msg)
|
|
28
|
+
return value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _check_enum_not_none(
|
|
32
|
+
_: object, attribute: attr.Attribute[PubSubResourceType], value: PubSubResourceType
|
|
33
|
+
) -> PubSubResourceType:
|
|
34
|
+
"""Validator to check if an enum value is not None."""
|
|
35
|
+
if value is None:
|
|
36
|
+
msg = f"{attribute.name} cannot be None"
|
|
37
|
+
raise ValueError(msg)
|
|
38
|
+
return PubSubResourceType(value)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DatasetNaming(Protocol):
|
|
42
|
+
"""Interface representing dataset naming logic for a specific platform."""
|
|
43
|
+
|
|
44
|
+
def get_namespace(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Returns the dataset namespace (e.g., URI or ARN identifying the data source).
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The namespace string
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def get_name(self) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Returns the dataset name specific to the platform format.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The dataset name string
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@attr.define
|
|
62
|
+
class Athena(DatasetNaming):
|
|
63
|
+
"""Naming implementation for Athena."""
|
|
64
|
+
|
|
65
|
+
region_name: str = attr.field(validator=_check_not_empty)
|
|
66
|
+
catalog: str = attr.field(validator=_check_not_empty)
|
|
67
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
68
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
69
|
+
|
|
70
|
+
def get_namespace(self) -> str:
|
|
71
|
+
return f"awsathena://athena.{self.region_name}.amazonaws.com"
|
|
72
|
+
|
|
73
|
+
def get_name(self) -> str:
|
|
74
|
+
return f"{self.catalog}.{self.database}.{self.table}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@attr.define
|
|
78
|
+
class AWSGlue(DatasetNaming):
|
|
79
|
+
"""Naming implementation for AWS Glue."""
|
|
80
|
+
|
|
81
|
+
region_name: str = attr.field(validator=_check_not_empty)
|
|
82
|
+
account_id: str = attr.field(validator=_check_not_empty)
|
|
83
|
+
database_name: str = attr.field(validator=_check_not_empty)
|
|
84
|
+
table_name: str = attr.field(validator=_check_not_empty)
|
|
85
|
+
|
|
86
|
+
def get_namespace(self) -> str:
|
|
87
|
+
return f"arn:aws:glue:{self.region_name}:{self.account_id}"
|
|
88
|
+
|
|
89
|
+
def get_name(self) -> str:
|
|
90
|
+
return f"table/{self.database_name}/{self.table_name}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@attr.define
|
|
94
|
+
class AzureCosmosDB(DatasetNaming):
|
|
95
|
+
"""Naming implementation for Azure Cosmos DB."""
|
|
96
|
+
|
|
97
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
98
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
99
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
100
|
+
|
|
101
|
+
def get_namespace(self) -> str:
|
|
102
|
+
return f"azurecosmos://{self.host}/dbs/{self.database}"
|
|
103
|
+
|
|
104
|
+
def get_name(self) -> str:
|
|
105
|
+
return f"colls/{self.table}"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@attr.define
|
|
109
|
+
class AzureDataExplorer(DatasetNaming):
|
|
110
|
+
"""Naming implementation for Azure Data Explorer."""
|
|
111
|
+
|
|
112
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
113
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
114
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
115
|
+
|
|
116
|
+
def get_namespace(self) -> str:
|
|
117
|
+
return f"azurekusto://{self.host}.kusto.windows.net"
|
|
118
|
+
|
|
119
|
+
def get_name(self) -> str:
|
|
120
|
+
return f"{self.database}/{self.table}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@attr.define
|
|
124
|
+
class AzureSynapse(DatasetNaming):
|
|
125
|
+
"""Naming implementation for AzureSynapse."""
|
|
126
|
+
|
|
127
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
128
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
129
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
130
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
131
|
+
|
|
132
|
+
def get_namespace(self) -> str:
|
|
133
|
+
return f"sqlserver://{self.host}:{self.port}"
|
|
134
|
+
|
|
135
|
+
def get_name(self) -> str:
|
|
136
|
+
return f"{self.schema}.{self.table}"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@attr.define
|
|
140
|
+
class BigQuery(DatasetNaming):
|
|
141
|
+
"""
|
|
142
|
+
Naming implementation for BigQuery.
|
|
143
|
+
|
|
144
|
+
Namespace is always "bigquery".
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
project_id: str = attr.field(validator=_check_not_empty)
|
|
148
|
+
dataset_name: str = attr.field(validator=_check_not_empty)
|
|
149
|
+
table_name: str = attr.field(validator=_check_not_empty)
|
|
150
|
+
|
|
151
|
+
def get_namespace(self) -> str:
|
|
152
|
+
return "bigquery"
|
|
153
|
+
|
|
154
|
+
def get_name(self) -> str:
|
|
155
|
+
return f"{self.project_id}.{self.dataset_name}.{self.table_name}"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@attr.define
|
|
159
|
+
class Cassandra(DatasetNaming):
|
|
160
|
+
"""Naming implementation for Cassandra."""
|
|
161
|
+
|
|
162
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
163
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
164
|
+
keyspace: str = attr.field(validator=_check_not_empty)
|
|
165
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
166
|
+
|
|
167
|
+
def get_namespace(self) -> str:
|
|
168
|
+
return f"cassandra://{self.host}:{self.port}"
|
|
169
|
+
|
|
170
|
+
def get_name(self) -> str:
|
|
171
|
+
return f"{self.keyspace}.{self.table}"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@attr.define
|
|
175
|
+
class MySQL(DatasetNaming):
|
|
176
|
+
"""Naming implementation for MySQL."""
|
|
177
|
+
|
|
178
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
179
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
180
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
181
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
182
|
+
|
|
183
|
+
def get_namespace(self) -> str:
|
|
184
|
+
return f"mysql://{self.host}:{self.port}"
|
|
185
|
+
|
|
186
|
+
def get_name(self) -> str:
|
|
187
|
+
return f"{self.database}.{self.table}"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@attr.define
|
|
191
|
+
class CrateDB(DatasetNaming):
|
|
192
|
+
"""Naming implementation for CrateDB."""
|
|
193
|
+
|
|
194
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
195
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
196
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
197
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
198
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
199
|
+
|
|
200
|
+
def get_namespace(self) -> str:
|
|
201
|
+
return f"crate://{self.host}:{self.port}"
|
|
202
|
+
|
|
203
|
+
def get_name(self) -> str:
|
|
204
|
+
return f"{self.database}.{self.schema}.{self.table}"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@attr.define
|
|
208
|
+
class DB2(DatasetNaming):
|
|
209
|
+
"""Naming implementation for DB2."""
|
|
210
|
+
|
|
211
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
212
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
213
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
214
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
215
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
216
|
+
|
|
217
|
+
def get_namespace(self) -> str:
|
|
218
|
+
return f"db2://{self.host}:{self.port}"
|
|
219
|
+
|
|
220
|
+
def get_name(self) -> str:
|
|
221
|
+
return f"{self.database}.{self.schema}.{self.table}"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@attr.define
|
|
225
|
+
class OceanBase(DatasetNaming):
|
|
226
|
+
"""Naming implementation for OceanBase."""
|
|
227
|
+
|
|
228
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
229
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
230
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
231
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
232
|
+
|
|
233
|
+
def get_namespace(self) -> str:
|
|
234
|
+
return f"oceanbase://{self.host}:{self.port}"
|
|
235
|
+
|
|
236
|
+
def get_name(self) -> str:
|
|
237
|
+
return f"{self.database}.{self.table}"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@attr.define
|
|
241
|
+
class Oracle(DatasetNaming):
|
|
242
|
+
"""Naming implementation for Oracle."""
|
|
243
|
+
|
|
244
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
245
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
246
|
+
service_name: str = attr.field(validator=_check_not_empty)
|
|
247
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
248
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
249
|
+
|
|
250
|
+
def get_namespace(self) -> str:
|
|
251
|
+
return f"oracle://{self.host}:{self.port}"
|
|
252
|
+
|
|
253
|
+
def get_name(self) -> str:
|
|
254
|
+
return f"{self.service_name}.{self.schema}.{self.table}"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@attr.define
|
|
258
|
+
class Postgres(DatasetNaming):
|
|
259
|
+
"""Naming implementation for Postgres."""
|
|
260
|
+
|
|
261
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
262
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
263
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
264
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
265
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
266
|
+
|
|
267
|
+
def get_namespace(self) -> str:
|
|
268
|
+
return f"postgres://{self.host}:{self.port}"
|
|
269
|
+
|
|
270
|
+
def get_name(self) -> str:
|
|
271
|
+
return f"{self.database}.{self.schema}.{self.table}"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@attr.define
|
|
275
|
+
class Teradata(DatasetNaming):
|
|
276
|
+
"""Naming implementation for Teradata."""
|
|
277
|
+
|
|
278
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
279
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
280
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
281
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
282
|
+
|
|
283
|
+
def get_namespace(self) -> str:
|
|
284
|
+
return f"teradata://{self.host}:{self.port}"
|
|
285
|
+
|
|
286
|
+
def get_name(self) -> str:
|
|
287
|
+
return f"{self.database}.{self.table}"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@attr.define
|
|
291
|
+
class Redshift(DatasetNaming):
|
|
292
|
+
"""Naming implementation for Redshift."""
|
|
293
|
+
|
|
294
|
+
cluster_identifier: str = attr.field(validator=_check_not_empty)
|
|
295
|
+
region: str = attr.field(validator=_check_not_empty)
|
|
296
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
297
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
298
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
299
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
300
|
+
|
|
301
|
+
def get_namespace(self) -> str:
|
|
302
|
+
return f"redshift://{self.cluster_identifier}.{self.region}:{self.port}"
|
|
303
|
+
|
|
304
|
+
def get_name(self) -> str:
|
|
305
|
+
return f"{self.database}.{self.schema}.{self.table}"
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@attr.define
|
|
309
|
+
class Snowflake(DatasetNaming):
|
|
310
|
+
"""Naming implementation for Snowflake."""
|
|
311
|
+
|
|
312
|
+
organization_name: str = attr.field(validator=_check_not_empty)
|
|
313
|
+
account_name: str = attr.field(validator=_check_not_empty)
|
|
314
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
315
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
316
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
317
|
+
|
|
318
|
+
def get_namespace(self) -> str:
|
|
319
|
+
return f"snowflake://{self.organization_name}-{self.account_name}"
|
|
320
|
+
|
|
321
|
+
def get_name(self) -> str:
|
|
322
|
+
return f"{self.database}.{self.schema}.{self.table}"
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@attr.define
|
|
326
|
+
class Trino(DatasetNaming):
|
|
327
|
+
"""Naming implementation for Trino."""
|
|
328
|
+
|
|
329
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
330
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
331
|
+
catalog: str = attr.field(validator=_check_not_empty)
|
|
332
|
+
schema: str = attr.field(validator=_check_not_empty)
|
|
333
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
334
|
+
|
|
335
|
+
def get_namespace(self) -> str:
|
|
336
|
+
return f"trino://{self.host}:{self.port}"
|
|
337
|
+
|
|
338
|
+
def get_name(self) -> str:
|
|
339
|
+
return f"{self.catalog}.{self.schema}.{self.table}"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@attr.define
|
|
343
|
+
class ABFSS(DatasetNaming):
|
|
344
|
+
"""Naming implementation for ABFSS."""
|
|
345
|
+
|
|
346
|
+
container: str = attr.field(validator=_check_not_empty)
|
|
347
|
+
service: str = attr.field(validator=_check_not_empty)
|
|
348
|
+
path: str = attr.field(validator=_check_not_empty)
|
|
349
|
+
|
|
350
|
+
def get_namespace(self) -> str:
|
|
351
|
+
return f"abfss://{self.container}@{self.service}.dfs.core.windows.net"
|
|
352
|
+
|
|
353
|
+
def get_name(self) -> str:
|
|
354
|
+
return self.path
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@attr.define
|
|
358
|
+
class DBFS(DatasetNaming):
|
|
359
|
+
"""Naming implementation for DBFS."""
|
|
360
|
+
|
|
361
|
+
workspace: str = attr.field(validator=_check_not_empty)
|
|
362
|
+
path: str = attr.field(validator=_check_not_empty)
|
|
363
|
+
|
|
364
|
+
def get_namespace(self) -> str:
|
|
365
|
+
return f"dbfs://{self.workspace}"
|
|
366
|
+
|
|
367
|
+
def get_name(self) -> str:
|
|
368
|
+
return self.path
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@attr.define
|
|
372
|
+
class GCS(DatasetNaming):
|
|
373
|
+
"""Naming implementation for GCS."""
|
|
374
|
+
|
|
375
|
+
bucket_name: str = attr.field(validator=_check_not_empty)
|
|
376
|
+
object_key: str = attr.field(validator=_check_not_empty)
|
|
377
|
+
|
|
378
|
+
def get_namespace(self) -> str:
|
|
379
|
+
return f"gs://{self.bucket_name}"
|
|
380
|
+
|
|
381
|
+
def get_name(self) -> str:
|
|
382
|
+
return self.object_key
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@attr.define
|
|
386
|
+
class HDFS(DatasetNaming):
|
|
387
|
+
"""Naming implementation for HDFS."""
|
|
388
|
+
|
|
389
|
+
namenode_host: str = attr.field(validator=_check_not_empty)
|
|
390
|
+
namenode_port: str = attr.field(validator=_check_not_empty)
|
|
391
|
+
path: str = attr.field(validator=_check_not_empty)
|
|
392
|
+
|
|
393
|
+
def get_namespace(self) -> str:
|
|
394
|
+
return f"hdfs://{self.namenode_host}:{self.namenode_port}"
|
|
395
|
+
|
|
396
|
+
def get_name(self) -> str:
|
|
397
|
+
return self.path
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@attr.define
|
|
401
|
+
class Hive(DatasetNaming):
|
|
402
|
+
"""Naming implementation for Hive."""
|
|
403
|
+
|
|
404
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
405
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
406
|
+
database: str = attr.field(validator=_check_not_empty)
|
|
407
|
+
table: str = attr.field(validator=_check_not_empty)
|
|
408
|
+
|
|
409
|
+
def get_namespace(self) -> str:
|
|
410
|
+
return f"hive://{self.host}:{self.port}"
|
|
411
|
+
|
|
412
|
+
def get_name(self) -> str:
|
|
413
|
+
return f"{self.database}.{self.table}"
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@attr.define
|
|
417
|
+
class Kafka(DatasetNaming):
|
|
418
|
+
"""Naming implementation for Kafka."""
|
|
419
|
+
|
|
420
|
+
bootstrap_server_host: str = attr.field(validator=_check_not_empty)
|
|
421
|
+
port: str = attr.field(validator=_check_not_empty)
|
|
422
|
+
topic: str = attr.field(validator=_check_not_empty)
|
|
423
|
+
|
|
424
|
+
def get_namespace(self) -> str:
|
|
425
|
+
return f"kafka://{self.bootstrap_server_host}:{self.port}"
|
|
426
|
+
|
|
427
|
+
def get_name(self) -> str:
|
|
428
|
+
return self.topic
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@attr.define
|
|
432
|
+
class LocalFileSystem(DatasetNaming):
|
|
433
|
+
"""Naming implementation for Local File System."""
|
|
434
|
+
|
|
435
|
+
path: str = attr.field(validator=_check_not_empty)
|
|
436
|
+
|
|
437
|
+
def get_namespace(self) -> str:
|
|
438
|
+
return "file"
|
|
439
|
+
|
|
440
|
+
def get_name(self) -> str:
|
|
441
|
+
return self.path
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@attr.define
|
|
445
|
+
class RemoteFileSystem(DatasetNaming):
|
|
446
|
+
"""Naming implementation for Remote File System."""
|
|
447
|
+
|
|
448
|
+
host: str = attr.field(validator=_check_not_empty)
|
|
449
|
+
path: str = attr.field(validator=_check_not_empty)
|
|
450
|
+
|
|
451
|
+
def get_namespace(self) -> str:
|
|
452
|
+
return f"file://{self.host}"
|
|
453
|
+
|
|
454
|
+
def get_name(self) -> str:
|
|
455
|
+
return self.path
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@attr.define
|
|
459
|
+
class S3(DatasetNaming):
|
|
460
|
+
"""Naming implementation for S3."""
|
|
461
|
+
|
|
462
|
+
bucket_name: str = attr.field(validator=_check_not_empty)
|
|
463
|
+
object_key: str = attr.field(validator=_check_not_empty)
|
|
464
|
+
|
|
465
|
+
def get_namespace(self) -> str:
|
|
466
|
+
return f"s3://{self.bucket_name}"
|
|
467
|
+
|
|
468
|
+
def get_name(self) -> str:
|
|
469
|
+
return self.object_key
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@attr.define
|
|
473
|
+
class WASBS(DatasetNaming):
|
|
474
|
+
"""Naming implementation for WASBS."""
|
|
475
|
+
|
|
476
|
+
container: str = attr.field(validator=_check_not_empty)
|
|
477
|
+
service_name: str = attr.field(validator=_check_not_empty)
|
|
478
|
+
object_key: str = attr.field(validator=_check_not_empty)
|
|
479
|
+
|
|
480
|
+
def get_namespace(self) -> str:
|
|
481
|
+
return f"wasbs://{self.container}@{self.service_name}.dfs.core.windows.net"
|
|
482
|
+
|
|
483
|
+
def get_name(self) -> str:
|
|
484
|
+
return self.object_key
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class PubSubResourceType(Enum):
|
|
488
|
+
"""Enum representing supported Pub/Sub resource types."""
|
|
489
|
+
|
|
490
|
+
TOPIC = "topic"
|
|
491
|
+
SUBSCRIPTION = "subscription"
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@attr.define
|
|
495
|
+
class PubSubNaming(DatasetNaming):
|
|
496
|
+
"""
|
|
497
|
+
Naming implementation for Google Pub/Sub.
|
|
498
|
+
|
|
499
|
+
Namespace is fixed as "pubsub". Name format depends on the resource type:
|
|
500
|
+
- topic:{project_id}:{topic_id}
|
|
501
|
+
- subscription:{project_id}:{subscription_id}
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
resource_type: PubSubResourceType = attr.field(validator=_check_enum_not_none)
|
|
505
|
+
project_id: str = attr.field(validator=_check_not_empty)
|
|
506
|
+
resource_id: str = attr.field(validator=_check_not_empty)
|
|
507
|
+
|
|
508
|
+
def get_namespace(self) -> str:
|
|
509
|
+
return "pubsub"
|
|
510
|
+
|
|
511
|
+
def get_name(self) -> str:
|
|
512
|
+
return f"{self.resource_type.value}:{self.project_id}:{self.resource_id}"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright 2018-2025 contributors to the OpenLineage project
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import attr
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@attr.define
|
|
9
|
+
class TagsConfig:
|
|
10
|
+
job: dict[str, str] = attr.field(factory=dict)
|
|
11
|
+
run: dict[str, str] = attr.field(factory=dict)
|
{openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/__init__.py
RENAMED
|
@@ -6,6 +6,7 @@ from openlineage.client.transport.amazon_datazone import AmazonDataZoneConfig, A
|
|
|
6
6
|
from openlineage.client.transport.async_http import AsyncHttpConfig, AsyncHttpTransport
|
|
7
7
|
from openlineage.client.transport.composite import CompositeTransport
|
|
8
8
|
from openlineage.client.transport.console import ConsoleTransport
|
|
9
|
+
from openlineage.client.transport.datadog import DatadogConfig, DatadogTransport
|
|
9
10
|
from openlineage.client.transport.factory import DefaultTransportFactory
|
|
10
11
|
from openlineage.client.transport.file import FileTransport
|
|
11
12
|
from openlineage.client.transport.http import HttpConfig, HttpTransport
|
|
@@ -29,6 +30,7 @@ _factory.register_transport(NoopTransport.kind, NoopTransport)
|
|
|
29
30
|
_factory.register_transport(FileTransport.kind, FileTransport)
|
|
30
31
|
_factory.register_transport(TransformTransport.kind, TransformTransport)
|
|
31
32
|
_factory.register_transport(AmazonDataZoneTransport.kind, AmazonDataZoneTransport)
|
|
33
|
+
_factory.register_transport(DatadogTransport.kind, DatadogTransport)
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
def get_default_factory() -> DefaultTransportFactory:
|
|
@@ -50,6 +52,8 @@ __all__ = [
|
|
|
50
52
|
"CompositeTransport",
|
|
51
53
|
"Config",
|
|
52
54
|
"ConsoleTransport",
|
|
55
|
+
"DatadogConfig",
|
|
56
|
+
"DatadogTransport",
|
|
53
57
|
"FileTransport",
|
|
54
58
|
"HttpConfig",
|
|
55
59
|
"HttpTransport",
|