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.
Files changed (131) hide show
  1. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/PKG-INFO +1 -1
  2. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/client.py +16 -15
  3. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/constants.py +1 -1
  4. openlineage_python-1.37.0/openlineage/client/naming/dataset.py +512 -0
  5. openlineage_python-1.37.0/openlineage/client/tags.py +11 -0
  6. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/__init__.py +4 -0
  7. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/async_http.py +67 -4
  8. openlineage_python-1.37.0/openlineage/client/transport/datadog.py +164 -0
  9. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/http.py +3 -1
  10. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/pyproject.toml +1 -1
  11. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_client.py +285 -1
  12. openlineage_python-1.37.0/tests/test_datadog.py +1014 -0
  13. openlineage_python-1.37.0/tests/test_dataset.py +276 -0
  14. openlineage_python-1.37.0/tests/transform/transformers/__init__.py +2 -0
  15. openlineage_python-1.35.0/openlineage/client/tags.py +0 -17
  16. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/.gitignore +0 -0
  17. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/README.md +0 -0
  18. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/Makefile +0 -0
  19. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/conf.py +0 -0
  20. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/convert.py +0 -0
  21. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/index.rst +0 -0
  22. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/make.bat +0 -0
  23. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/docs/source/openlineage.client.rst +0 -0
  24. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/__init__.py +0 -0
  25. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/event_v2.py +0 -0
  26. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/facet.py +0 -0
  27. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/facet_v2.py +0 -0
  28. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/facets.py +0 -0
  29. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/filter.py +0 -0
  30. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/base.py +0 -0
  31. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/catalog_dataset.py +0 -0
  32. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
  33. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
  34. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
  35. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/dataset_type_dataset.py +0 -0
  36. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
  37. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/datasource_dataset.py +0 -0
  38. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/documentation_dataset.py +0 -0
  39. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/documentation_job.py +0 -0
  40. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/environment_variables_run.py +0 -0
  41. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/error_message_run.py +0 -0
  42. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/external_query_run.py +0 -0
  43. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/extraction_error_run.py +0 -0
  44. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
  45. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/job_type_job.py +0 -0
  46. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
  47. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/nominal_time_run.py +0 -0
  48. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
  49. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/ownership_dataset.py +0 -0
  50. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/ownership_job.py +0 -0
  51. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/parent_run.py +0 -0
  52. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/processing_engine_run.py +0 -0
  53. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/schema_dataset.py +0 -0
  54. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/source_code_job.py +0 -0
  55. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/source_code_location_job.py +0 -0
  56. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/sql_job.py +0 -0
  57. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/storage_dataset.py +0 -0
  58. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
  59. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_dataset.py +0 -0
  60. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_job.py +0 -0
  61. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_run.py +0 -0
  62. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/base.py +0 -0
  63. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/cli.py +0 -0
  64. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/generate.py +0 -0
  65. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/header.py +0 -0
  66. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/Enum.jinja2 +0 -0
  67. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/dataclass.jinja2 +0 -0
  68. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/facet_v2.jinja2 +0 -0
  69. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/root.jinja2 +0 -0
  70. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/validators.jinja2 +0 -0
  71. {openlineage_python-1.35.0/openlineage/client/transport/transform/transformers → openlineage_python-1.37.0/openlineage/client/naming}/__init__.py +0 -0
  72. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/py.typed +0 -0
  73. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/run.py +0 -0
  74. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/serde.py +0 -0
  75. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/amazon_datazone.py +0 -0
  76. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/composite.py +0 -0
  77. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/console.py +0 -0
  78. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/factory.py +0 -0
  79. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/file.py +0 -0
  80. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/kafka.py +0 -0
  81. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/msk_iam.py +0 -0
  82. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/noop.py +0 -0
  83. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/__init__.py +0 -0
  84. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/transform.py +0 -0
  85. {openlineage_python-1.35.0/tests → openlineage_python-1.37.0/openlineage/client/transport/transform/transformers}/__init__.py +0 -0
  86. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/transformers/job_namespace_replace_transformer.py +0 -0
  87. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/transport/transport.py +0 -0
  88. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/utils.py +0 -0
  89. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/openlineage/client/uuid.py +0 -0
  90. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/redact_fields.yml +0 -0
  91. {openlineage_python-1.35.0/tests/generator → openlineage_python-1.37.0/tests}/__init__.py +0 -0
  92. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/config.yml +0 -0
  93. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/exact_filter.yml +0 -0
  94. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/http.yml +0 -0
  95. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/openlineage.yml +0 -0
  96. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/regex_filter.yml +0 -0
  97. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/config/test-transport.yaml +0 -0
  98. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/conftest.py +0 -0
  99. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/example_full_event.json +0 -0
  100. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/fixtures/events.py +0 -0
  101. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/fixtures/mock_responses.py +0 -0
  102. {openlineage_python-1.35.0/tests/transform → openlineage_python-1.37.0/tests/generator}/__init__.py +0 -0
  103. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/generator/test_base.py +0 -0
  104. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/generator/test_cli.py +0 -0
  105. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/docker-compose.yml +0 -0
  106. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/test_server/Dockerfile +0 -0
  107. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/test_server/requirements.txt +0 -0
  108. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/integration/test_server/test_server.py +0 -0
  109. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/nominal_time_without_end.json +0 -0
  110. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/serde_example_dataset_event.json +0 -0
  111. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/serde_example_job_event.json +0 -0
  112. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/serde_example_run_event.json +0 -0
  113. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_amazon_datazone.py +0 -0
  114. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_async_http.py +0 -0
  115. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_composite.py +0 -0
  116. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_events.py +0 -0
  117. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_facet.py +0 -0
  118. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_facet_v2.py +0 -0
  119. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_factory.py +0 -0
  120. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_file.py +0 -0
  121. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_http.py +0 -0
  122. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_http_integration.py +0 -0
  123. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_kafka.py +0 -0
  124. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_msk_iam.py +0 -0
  125. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_utils.py +0 -0
  126. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/test_uuid.py +0 -0
  127. {openlineage_python-1.35.0/tests/transform/transformers → openlineage_python-1.37.0/tests/transform}/__init__.py +0 -0
  128. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/transform/test_transform.py +0 -0
  129. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/transform/transformers/test_job_namespace_replace_transformer.py +0 -0
  130. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tests/transport.py +0 -0
  131. {openlineage_python-1.35.0 → openlineage_python-1.37.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openlineage-python
3
- Version: 1.35.0
3
+ Version: 1.37.0
4
4
  Summary: OpenLineage Python Client
5
5
  Author-email: OpenLineage <info@openlineage.io>
6
6
  Keywords: openlineage
@@ -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=[TagsJobFacetFields(key, value, "USER") for (key, value) in job_tags.items()],
74
- run=[TagsRunFacetFields(key, value, "USER") for (key, value) in run_tags.items()],
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 a YAML config file.
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 in YAML config file
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
- def _http_transport_from_env_variables(self) -> HttpTransport:
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.warning(
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 OPENLINEAGE_ prefix and reverse sort
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:
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "1.35.0"
5
+ __version__ = "1.37.0"
6
6
 
7
7
  DEFAULT_TIMEOUT_MS = 5000
8
8
  DEFAULT_NAMESPACE_NAME = "default"
@@ -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)
@@ -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",