openlineage-python 1.35.0__tar.gz → 1.36.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 (128) hide show
  1. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/PKG-INFO +1 -1
  2. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/client.py +10 -9
  3. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/constants.py +1 -1
  4. openlineage_python-1.36.0/openlineage/client/naming/dataset.py +512 -0
  5. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/async_http.py +64 -3
  6. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/pyproject.toml +1 -1
  7. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_client.py +274 -1
  8. openlineage_python-1.36.0/tests/test_dataset.py +276 -0
  9. openlineage_python-1.36.0/tests/transform/transformers/__init__.py +2 -0
  10. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/.gitignore +0 -0
  11. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/README.md +0 -0
  12. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/docs/Makefile +0 -0
  13. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/docs/conf.py +0 -0
  14. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/docs/convert.py +0 -0
  15. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/docs/index.rst +0 -0
  16. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/docs/make.bat +0 -0
  17. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/docs/source/openlineage.client.rst +0 -0
  18. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/__init__.py +0 -0
  19. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/event_v2.py +0 -0
  20. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/facet.py +0 -0
  21. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/facet_v2.py +0 -0
  22. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/facets.py +0 -0
  23. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/filter.py +0 -0
  24. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/base.py +0 -0
  25. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/catalog_dataset.py +0 -0
  26. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
  27. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
  28. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
  29. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/dataset_type_dataset.py +0 -0
  30. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
  31. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/datasource_dataset.py +0 -0
  32. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/documentation_dataset.py +0 -0
  33. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/documentation_job.py +0 -0
  34. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/environment_variables_run.py +0 -0
  35. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/error_message_run.py +0 -0
  36. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/external_query_run.py +0 -0
  37. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/extraction_error_run.py +0 -0
  38. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
  39. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/job_type_job.py +0 -0
  40. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
  41. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/nominal_time_run.py +0 -0
  42. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
  43. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/ownership_dataset.py +0 -0
  44. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/ownership_job.py +0 -0
  45. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/parent_run.py +0 -0
  46. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/processing_engine_run.py +0 -0
  47. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/schema_dataset.py +0 -0
  48. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/source_code_job.py +0 -0
  49. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/source_code_location_job.py +0 -0
  50. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/sql_job.py +0 -0
  51. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/storage_dataset.py +0 -0
  52. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
  53. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/tags_dataset.py +0 -0
  54. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/tags_job.py +0 -0
  55. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generated/tags_run.py +0 -0
  56. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/base.py +0 -0
  57. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/cli.py +0 -0
  58. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/generate.py +0 -0
  59. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/header.py +0 -0
  60. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/templates/Enum.jinja2 +0 -0
  61. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/templates/dataclass.jinja2 +0 -0
  62. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/templates/facet_v2.jinja2 +0 -0
  63. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/templates/root.jinja2 +0 -0
  64. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/generator/templates/validators.jinja2 +0 -0
  65. {openlineage_python-1.35.0/openlineage/client/transport/transform/transformers → openlineage_python-1.36.0/openlineage/client/naming}/__init__.py +0 -0
  66. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/py.typed +0 -0
  67. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/run.py +0 -0
  68. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/serde.py +0 -0
  69. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/tags.py +0 -0
  70. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/__init__.py +0 -0
  71. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/amazon_datazone.py +0 -0
  72. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/composite.py +0 -0
  73. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/console.py +0 -0
  74. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/factory.py +0 -0
  75. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/file.py +0 -0
  76. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/http.py +0 -0
  77. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/kafka.py +0 -0
  78. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/msk_iam.py +0 -0
  79. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/noop.py +0 -0
  80. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/transform/__init__.py +0 -0
  81. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/transform/transform.py +0 -0
  82. {openlineage_python-1.35.0/tests → openlineage_python-1.36.0/openlineage/client/transport/transform/transformers}/__init__.py +0 -0
  83. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/transform/transformers/job_namespace_replace_transformer.py +0 -0
  84. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/transport/transport.py +0 -0
  85. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/utils.py +0 -0
  86. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/openlineage/client/uuid.py +0 -0
  87. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/redact_fields.yml +0 -0
  88. {openlineage_python-1.35.0/tests/generator → openlineage_python-1.36.0/tests}/__init__.py +0 -0
  89. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/config/config.yml +0 -0
  90. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/config/exact_filter.yml +0 -0
  91. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/config/http.yml +0 -0
  92. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/config/openlineage.yml +0 -0
  93. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/config/regex_filter.yml +0 -0
  94. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/config/test-transport.yaml +0 -0
  95. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/conftest.py +0 -0
  96. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/example_full_event.json +0 -0
  97. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/fixtures/events.py +0 -0
  98. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/fixtures/mock_responses.py +0 -0
  99. {openlineage_python-1.35.0/tests/transform → openlineage_python-1.36.0/tests/generator}/__init__.py +0 -0
  100. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/generator/test_base.py +0 -0
  101. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/generator/test_cli.py +0 -0
  102. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/integration/docker-compose.yml +0 -0
  103. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/integration/test_server/Dockerfile +0 -0
  104. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/integration/test_server/requirements.txt +0 -0
  105. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/integration/test_server/test_server.py +0 -0
  106. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/nominal_time_without_end.json +0 -0
  107. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/serde_example_dataset_event.json +0 -0
  108. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/serde_example_job_event.json +0 -0
  109. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/serde_example_run_event.json +0 -0
  110. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_amazon_datazone.py +0 -0
  111. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_async_http.py +0 -0
  112. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_composite.py +0 -0
  113. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_events.py +0 -0
  114. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_facet.py +0 -0
  115. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_facet_v2.py +0 -0
  116. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_factory.py +0 -0
  117. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_file.py +0 -0
  118. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_http.py +0 -0
  119. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_http_integration.py +0 -0
  120. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_kafka.py +0 -0
  121. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_msk_iam.py +0 -0
  122. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_utils.py +0 -0
  123. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/test_uuid.py +0 -0
  124. {openlineage_python-1.35.0/tests/transform/transformers → openlineage_python-1.36.0/tests/transform}/__init__.py +0 -0
  125. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/transform/test_transform.py +0 -0
  126. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/transform/transformers/test_job_namespace_replace_transformer.py +0 -0
  127. {openlineage_python-1.35.0 → openlineage_python-1.36.0}/tests/transport.py +0 -0
  128. {openlineage_python-1.35.0 → openlineage_python-1.36.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.36.0
4
4
  Summary: OpenLineage Python Client
5
5
  Author-email: OpenLineage <info@openlineage.io>
6
6
  Keywords: openlineage
@@ -232,9 +232,9 @@ class OpenLineageClient:
232
232
  This method determines the appropriate transport by executing a sequence of checks:
233
233
  1. Verifies if OpenLineage is disabled through environment variable.
234
234
  2. Looks for a transport object provided in the arguments.
235
- 3. Attempts to configure the transport from a YAML config file.
235
+ 3. Attempts to configure the transport from user config, config file or env vars.
236
236
  4. Tries to initialize HTTP transport with an url argument (deprecated).
237
- 5. Tries to set up HTTP transport using environment variables.
237
+ 5. Tries to set up HTTP transport using simple environment variables.
238
238
  6. If no configuration is found, defaults to a console transport and logs a warning message.
239
239
 
240
240
  Returns:
@@ -245,11 +245,11 @@ class OpenLineageClient:
245
245
  log.info("OpenLineage is disabled. No events will be emitted.")
246
246
  return NoopTransport(NoopConfig())
247
247
 
248
- # 2. Check if transport is provided explicitly
248
+ # 2. Check if transport is provided explicitly as argument
249
249
  if kwargs.get("transport"):
250
250
  return cast("Transport", kwargs["transport"])
251
251
 
252
- # 3. Check if transport configuration is provided in YAML config file
252
+ # 3. Check if transport configuration is provided as config (explicit, file or env vars)
253
253
  if self.config.transport and self.config.transport.get("type"):
254
254
  factory = kwargs.get("factory") or get_default_factory()
255
255
  return factory.create(self.config.transport)
@@ -260,7 +260,7 @@ class OpenLineageClient:
260
260
  url=kwargs["url"], options=kwargs.get("options"), session=kwargs.get("session")
261
261
  )
262
262
 
263
- # 5. Check HTTP transport initialization with env variables
263
+ # 5. Check HTTP transport initialization with simple env variables
264
264
  if os.environ.get("OPENLINEAGE_URL"):
265
265
  return self._http_transport_from_env_variables()
266
266
 
@@ -307,7 +307,8 @@ class OpenLineageClient:
307
307
  log.exception("Couldn't check if OpenLineage config file is readable: `%s`", path)
308
308
  return None
309
309
 
310
- def _http_transport_from_env_variables(self) -> HttpTransport:
310
+ @staticmethod
311
+ def _http_transport_from_env_variables() -> HttpTransport:
311
312
  """
312
313
  Create HTTP transport from legacy environment variables
313
314
  """
@@ -345,7 +346,7 @@ class OpenLineageClient:
345
346
  k.startswith(f"OPENLINEAGE__TRANSPORT__TRANSPORTS__{default_transport_name}")
346
347
  for k in os.environ
347
348
  ):
348
- log.warning(
349
+ log.info(
349
350
  "%s already found in environment variables, skipping aliasing OPENLINEAGE_URL",
350
351
  default_transport_name,
351
352
  )
@@ -388,7 +389,7 @@ class OpenLineageClient:
388
389
  def _load_config_from_env_variables(cls) -> dict[str, Any] | None:
389
390
  config: dict[str, Any] = {}
390
391
 
391
- # get os.environ.items only starting with OPENLINEAGE_ prefix and reverse sort
392
+ # get os.environ.items only starting with OPENLINEAGE__ prefix and reverse sort
392
393
  # to make sure that top-level keys have precedence
393
394
  env_vars = sorted(
394
395
  filter(lambda k: k[0].startswith(cls.DYNAMIC_ENV_VARS_PREFIX), os.environ.items()), reverse=True
@@ -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.36.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}"