openlineage-python 1.36.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.36.0 → openlineage_python-1.37.0}/PKG-INFO +1 -1
  2. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/client.py +6 -6
  3. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/constants.py +1 -1
  4. openlineage_python-1.37.0/openlineage/client/tags.py +11 -0
  5. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/__init__.py +4 -0
  6. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/async_http.py +3 -1
  7. openlineage_python-1.37.0/openlineage/client/transport/datadog.py +164 -0
  8. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/http.py +3 -1
  9. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/pyproject.toml +1 -1
  10. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_client.py +11 -0
  11. openlineage_python-1.37.0/tests/test_datadog.py +1014 -0
  12. openlineage_python-1.36.0/openlineage/client/tags.py +0 -17
  13. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/.gitignore +0 -0
  14. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/README.md +0 -0
  15. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/docs/Makefile +0 -0
  16. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/docs/conf.py +0 -0
  17. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/docs/convert.py +0 -0
  18. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/docs/index.rst +0 -0
  19. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/docs/make.bat +0 -0
  20. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/docs/source/openlineage.client.rst +0 -0
  21. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/__init__.py +0 -0
  22. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/event_v2.py +0 -0
  23. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/facet.py +0 -0
  24. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/facet_v2.py +0 -0
  25. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/facets.py +0 -0
  26. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/filter.py +0 -0
  27. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/base.py +0 -0
  28. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/catalog_dataset.py +0 -0
  29. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
  30. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
  31. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
  32. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/dataset_type_dataset.py +0 -0
  33. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
  34. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/datasource_dataset.py +0 -0
  35. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/documentation_dataset.py +0 -0
  36. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/documentation_job.py +0 -0
  37. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/environment_variables_run.py +0 -0
  38. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/error_message_run.py +0 -0
  39. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/external_query_run.py +0 -0
  40. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/extraction_error_run.py +0 -0
  41. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
  42. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/job_type_job.py +0 -0
  43. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
  44. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/nominal_time_run.py +0 -0
  45. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
  46. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/ownership_dataset.py +0 -0
  47. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/ownership_job.py +0 -0
  48. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/parent_run.py +0 -0
  49. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/processing_engine_run.py +0 -0
  50. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/schema_dataset.py +0 -0
  51. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/source_code_job.py +0 -0
  52. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/source_code_location_job.py +0 -0
  53. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/sql_job.py +0 -0
  54. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/storage_dataset.py +0 -0
  55. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
  56. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_dataset.py +0 -0
  57. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_job.py +0 -0
  58. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generated/tags_run.py +0 -0
  59. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/base.py +0 -0
  60. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/cli.py +0 -0
  61. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/generate.py +0 -0
  62. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/header.py +0 -0
  63. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/Enum.jinja2 +0 -0
  64. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/dataclass.jinja2 +0 -0
  65. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/facet_v2.jinja2 +0 -0
  66. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/root.jinja2 +0 -0
  67. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/generator/templates/validators.jinja2 +0 -0
  68. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/naming/__init__.py +0 -0
  69. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/naming/dataset.py +0 -0
  70. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/py.typed +0 -0
  71. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/run.py +0 -0
  72. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/serde.py +0 -0
  73. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/amazon_datazone.py +0 -0
  74. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/composite.py +0 -0
  75. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/console.py +0 -0
  76. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/factory.py +0 -0
  77. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/file.py +0 -0
  78. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/kafka.py +0 -0
  79. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/msk_iam.py +0 -0
  80. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/noop.py +0 -0
  81. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/__init__.py +0 -0
  82. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/transform.py +0 -0
  83. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/transformers/__init__.py +0 -0
  84. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/transform/transformers/job_namespace_replace_transformer.py +0 -0
  85. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/transport/transport.py +0 -0
  86. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/utils.py +0 -0
  87. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/openlineage/client/uuid.py +0 -0
  88. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/redact_fields.yml +0 -0
  89. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/__init__.py +0 -0
  90. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/config/config.yml +0 -0
  91. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/config/exact_filter.yml +0 -0
  92. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/config/http.yml +0 -0
  93. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/config/openlineage.yml +0 -0
  94. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/config/regex_filter.yml +0 -0
  95. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/config/test-transport.yaml +0 -0
  96. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/conftest.py +0 -0
  97. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/example_full_event.json +0 -0
  98. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/fixtures/events.py +0 -0
  99. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/fixtures/mock_responses.py +0 -0
  100. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/generator/__init__.py +0 -0
  101. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/generator/test_base.py +0 -0
  102. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/generator/test_cli.py +0 -0
  103. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/integration/docker-compose.yml +0 -0
  104. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/integration/test_server/Dockerfile +0 -0
  105. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/integration/test_server/requirements.txt +0 -0
  106. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/integration/test_server/test_server.py +0 -0
  107. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/nominal_time_without_end.json +0 -0
  108. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/serde_example_dataset_event.json +0 -0
  109. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/serde_example_job_event.json +0 -0
  110. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/serde_example_run_event.json +0 -0
  111. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_amazon_datazone.py +0 -0
  112. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_async_http.py +0 -0
  113. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_composite.py +0 -0
  114. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_dataset.py +0 -0
  115. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_events.py +0 -0
  116. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_facet.py +0 -0
  117. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_facet_v2.py +0 -0
  118. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_factory.py +0 -0
  119. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_file.py +0 -0
  120. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_http.py +0 -0
  121. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_http_integration.py +0 -0
  122. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_kafka.py +0 -0
  123. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_msk_iam.py +0 -0
  124. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_utils.py +0 -0
  125. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/test_uuid.py +0 -0
  126. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/transform/__init__.py +0 -0
  127. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/transform/test_transform.py +0 -0
  128. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/transform/transformers/__init__.py +0 -0
  129. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/transform/transformers/test_job_namespace_replace_transformer.py +0 -0
  130. {openlineage_python-1.36.0 → openlineage_python-1.37.0}/tests/transport.py +0 -0
  131. {openlineage_python-1.36.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.36.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
 
@@ -452,14 +450,16 @@ class OpenLineageClient:
452
450
  """
453
451
  run_event_types = (RunEvent, event_v2.RunEvent)
454
452
  run_and_job_event_types = (RunEvent, event_v2.RunEvent, JobEvent, event_v2.JobEvent)
455
- 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()]
456
455
  if isinstance(event, run_and_job_event_types) and tags_job:
457
456
  # Ensure facets exists
458
457
  event.job.facets = {} if not event.job.facets else event.job.facets
459
458
  tags_facet = event.job.facets.get("tags", TagsJobFacet())
460
459
  event.job.facets["tags"] = self._update_tag_facet(tags_facet, tags_job) # type: ignore [arg-type, assignment]
461
460
 
462
- 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()]
463
463
  if isinstance(event, run_event_types) and tags_run:
464
464
  # Ensure facets exists
465
465
  event.run.facets = {} if not event.run.facets else event.run.facets
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "1.36.0"
5
+ __version__ = "1.37.0"
6
6
 
7
7
  DEFAULT_TIMEOUT_MS = 5000
8
8
  DEFAULT_NAMESPACE_NAME = "default"
@@ -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",
@@ -606,7 +606,9 @@ class AsyncHttpTransport(Transport):
606
606
  }
607
607
  if self.config.compression == HttpCompression.GZIP:
608
608
  headers["Content-Encoding"] = "gzip"
609
- return gzip.compress(event.encode("utf-8")), headers
609
+ # levels higher than 3 are twice as slow:
610
+ # https://github.com/python/cpython/issues/91349#issuecomment-2737161048
611
+ return gzip.compress(event.encode("utf-8"), compresslevel=3), headers
610
612
 
611
613
  return event, headers
612
614
 
@@ -0,0 +1,164 @@
1
+ # Copyright 2018-2025 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import TYPE_CHECKING, Any
7
+ from urllib.parse import urlparse
8
+
9
+ import attr
10
+ from openlineage.client.transport.async_http import AsyncHttpConfig, AsyncHttpTransport
11
+ from openlineage.client.transport.http import HttpConfig, HttpTransport
12
+ from openlineage.client.transport.transport import Config, Transport
13
+ from openlineage.client.utils import get_only_specified_fields
14
+
15
+ if TYPE_CHECKING:
16
+ from openlineage.client.client import Event
17
+
18
+
19
+ # A mapping from https://docs.datadoghq.com/getting_started/site/ to
20
+ # Datadog endpoint that accepts OL events.
21
+ SITE_MAPPING = {
22
+ "datadoghq.com": "https://data-obs-intake.datadoghq.com",
23
+ "us3.datadoghq.com": "https://data-obs-intake.us3.datadoghq.com",
24
+ "us5.datadoghq.com": "https://data-obs-intake.us5.datadoghq.com",
25
+ "datadoghq.eu": "https://data-obs-intake.datadoghq.eu",
26
+ "ddog-gov.com": "https://data-obs-intake.ddog-gov.com",
27
+ "ap1.datadoghq.com": "https://data-obs-intake.ap1.datadoghq.com",
28
+ "ap2.datadoghq.com": "https://data-obs-intake.ap2.datadoghq.com",
29
+ "datad0g.com": "https://data-obs-intake.datad0g.com",
30
+ }
31
+
32
+
33
+ def _is_valid_url(url: str) -> bool:
34
+ try:
35
+ parsed = urlparse(url)
36
+ return parsed.scheme in ("http", "https") and bool(parsed.netloc)
37
+ except Exception:
38
+ return False
39
+
40
+
41
+ @attr.define
42
+ class DatadogConfig(Config):
43
+ apiKey: str = attr.field() # noqa: N815
44
+ site: str = attr.field(default="datadoghq.com")
45
+ timeout: float = attr.field(default=5.0)
46
+ retry: dict[str, Any] = attr.field(
47
+ default={
48
+ "total": 5,
49
+ "read": 5,
50
+ "connect": 5,
51
+ "backoff_factor": 0.3,
52
+ "status_forcelist": [500, 502, 503, 504],
53
+ "allowed_methods": ["HEAD", "POST"],
54
+ }
55
+ )
56
+ max_queue_size: int = attr.field(default=10000)
57
+ max_concurrent_requests: int = attr.field(default=100)
58
+ async_transport_rules: dict[str, dict[str, bool]] = attr.field(default={"dbt": {"*": True}})
59
+
60
+ @classmethod
61
+ def from_dict(cls, params: dict[str, Any]) -> DatadogConfig:
62
+ api_key = params.get("apiKey") or params.get("apikey") or os.getenv("DD_API_KEY")
63
+ if not api_key:
64
+ msg = (
65
+ "apiKey is required for DatadogTransport. "
66
+ "Provide it in config or set DD_API_KEY environment variable."
67
+ )
68
+ raise ValueError(msg)
69
+
70
+ site = params.get("site") or os.getenv("DD_SITE", "datadoghq.com")
71
+
72
+ if site not in SITE_MAPPING and not _is_valid_url(site): # type: ignore
73
+ msg = f"Invalid site '{site}'. Must be one of: {list(SITE_MAPPING.keys())} or a valid URL"
74
+ raise ValueError(msg)
75
+
76
+ config_params = params.copy()
77
+ config_params["apiKey"] = api_key
78
+ config_params["site"] = site
79
+
80
+ specified_dict = get_only_specified_fields(cls, config_params)
81
+ return cls(**specified_dict)
82
+
83
+
84
+ class DatadogTransport(Transport):
85
+ kind = "datadog"
86
+ config_class = DatadogConfig
87
+
88
+ def __init__(self, config: DatadogConfig) -> None:
89
+ self.config = config
90
+
91
+ if _is_valid_url(config.site):
92
+ intake_url = config.site
93
+ else:
94
+ intake_url = SITE_MAPPING[config.site]
95
+
96
+ shared_config = {
97
+ "url": intake_url,
98
+ "timeout": config.timeout,
99
+ "retry": config.retry,
100
+ "compression": "gzip",
101
+ "auth": {"type": "api_key", "apiKey": config.apiKey},
102
+ }
103
+
104
+ http_config = HttpConfig.from_dict(shared_config)
105
+ self.http = HttpTransport(http_config)
106
+
107
+ async_config = shared_config.copy()
108
+ async_config.update(
109
+ {
110
+ "max_queue_size": config.max_queue_size,
111
+ "max_concurrent_requests": config.max_concurrent_requests,
112
+ }
113
+ )
114
+ async_http_config = AsyncHttpConfig.from_dict(async_config)
115
+ self.async_http = AsyncHttpTransport(async_http_config)
116
+
117
+ def emit(self, event: Event) -> Any:
118
+ # Check if event has JobTypeJobFacet with integration = "dbt"
119
+ should_use_async = self._should_use_async_transport(event)
120
+
121
+ if should_use_async:
122
+ return self.async_http.emit(event)
123
+ else:
124
+ return self.http.emit(event)
125
+
126
+ def close(self, timeout: float = -1) -> bool:
127
+ http_result = self.http.close(timeout)
128
+ async_result = self.async_http.close(timeout)
129
+ return http_result and async_result
130
+
131
+ def _should_use_async_transport(self, event: Event) -> bool:
132
+ """
133
+ Determine if event should use async transport based on async_transport_rules.
134
+ Returns True if the event matches the configured rules for async transport.
135
+ """
136
+
137
+ # Check for double wildcard first - if present, use async for all events
138
+ wildcard_rules = self.config.async_transport_rules.get("*", {})
139
+ if wildcard_rules.get("*"):
140
+ return True
141
+
142
+ if not hasattr(event, "job") or not hasattr(event.job, "facets"):
143
+ return False
144
+
145
+ job_facets = event.job.facets
146
+ if not job_facets:
147
+ return False
148
+
149
+ job_type_facet = job_facets.get("jobType")
150
+ if not job_type_facet:
151
+ return False
152
+
153
+ integration = getattr(job_type_facet, "integration", "").lower()
154
+ job_type = getattr(job_type_facet, "jobType", "").lower()
155
+
156
+ if not integration:
157
+ return False
158
+
159
+ for rule_integration, job_type_rules in self.config.async_transport_rules.items():
160
+ if rule_integration == "*" or rule_integration.lower() == integration:
161
+ for rule_job_type, use_async in job_type_rules.items():
162
+ if rule_job_type == "*" or rule_job_type.lower() == job_type:
163
+ return use_async
164
+ return False
@@ -226,6 +226,8 @@ class HttpTransport(Transport):
226
226
  }
227
227
  if self.compression == HttpCompression.GZIP:
228
228
  headers["Content-Encoding"] = "gzip"
229
- return gzip.compress(event_str.encode("utf-8")), headers
229
+ # levels higher than 3 are twice as slow:
230
+ # https://github.com/python/cpython/issues/91349#issuecomment-2737161048
231
+ return gzip.compress(event_str.encode("utf-8"), compresslevel=3), headers
230
232
 
231
233
  return event_str, headers
@@ -6,7 +6,7 @@ requires = [
6
6
 
7
7
  [project]
8
8
  name = "openlineage-python"
9
- version = "1.36.0"
9
+ version = "1.37.0"
10
10
  description = "OpenLineage Python Client"
11
11
  readme = "README.md"
12
12
  keywords = [
@@ -8,6 +8,7 @@ import re
8
8
  from typing import TYPE_CHECKING
9
9
  from unittest.mock import MagicMock, patch
10
10
 
11
+ import attr
11
12
  import pytest
12
13
  from openlineage.client import event_v2
13
14
  from openlineage.client.client import OpenLineageClient, OpenLineageClientOptions, OpenLineageConfig
@@ -417,6 +418,16 @@ def test_ol_config_from_dict():
417
418
  OpenLineageConfig.from_dict({"facets": "invalid_data"})
418
419
 
419
420
 
421
+ def test_ol_config_from_the_same_dict():
422
+ config_dict = {
423
+ "transport": {"url": "http://localhost:5050"},
424
+ "facets": {"environment_variables": ["VAR1", "VAR2"]},
425
+ "filters": [{"type": "exact", "match": "job_name"}],
426
+ }
427
+ config = OpenLineageConfig.from_dict(config_dict)
428
+ assert OpenLineageConfig.from_dict(attr.asdict(config)) == config
429
+
430
+
420
431
  @patch("yaml.safe_load", return_value=None)
421
432
  def test_config_file_content_empty_file(mock_yaml) -> None: # noqa: ARG001
422
433
  assert OpenLineageClient().config == OpenLineageConfig()