openlineage-python 1.31.0__tar.gz → 1.32.1__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 (104) hide show
  1. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/PKG-INFO +5 -5
  2. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/client.py +5 -2
  3. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/constants.py +1 -1
  4. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/facet.py +26 -26
  5. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/filter.py +8 -1
  6. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/run.py +16 -16
  7. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/serde.py +4 -4
  8. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/composite.py +10 -5
  9. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/console.py +4 -3
  10. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/http.py +34 -20
  11. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/kafka.py +1 -6
  12. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/transport.py +3 -0
  13. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/utils.py +2 -2
  14. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/pyproject.toml +5 -5
  15. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_client.py +71 -5
  16. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_file.py +3 -3
  17. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_http.py +70 -3
  18. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_uuid.py +5 -1
  19. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tox.ini +1 -1
  20. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/.gitignore +0 -0
  21. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/README.md +0 -0
  22. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/docs/Makefile +0 -0
  23. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/docs/conf.py +0 -0
  24. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/docs/convert.py +0 -0
  25. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/docs/index.rst +0 -0
  26. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/docs/make.bat +0 -0
  27. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/docs/source/openlineage.client.rst +0 -0
  28. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/__init__.py +0 -0
  29. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/event_v2.py +0 -0
  30. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/facet_v2.py +0 -0
  31. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/facets.py +0 -0
  32. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/base.py +0 -0
  33. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/column_lineage_dataset.py +0 -0
  34. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
  35. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
  36. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/dataset_type_dataset.py +0 -0
  37. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/dataset_version_dataset.py +0 -0
  38. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/datasource_dataset.py +0 -0
  39. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/documentation_dataset.py +0 -0
  40. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/documentation_job.py +0 -0
  41. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/environment_variables_run.py +0 -0
  42. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/error_message_run.py +0 -0
  43. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/external_query_run.py +0 -0
  44. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/extraction_error_run.py +0 -0
  45. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
  46. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/job_type_job.py +0 -0
  47. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
  48. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/nominal_time_run.py +0 -0
  49. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
  50. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/ownership_dataset.py +0 -0
  51. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/ownership_job.py +0 -0
  52. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/parent_run.py +0 -0
  53. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/processing_engine_run.py +0 -0
  54. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/schema_dataset.py +0 -0
  55. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/source_code_job.py +0 -0
  56. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/source_code_location_job.py +0 -0
  57. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/sql_job.py +0 -0
  58. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/storage_dataset.py +0 -0
  59. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/symlinks_dataset.py +0 -0
  60. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/tags_dataset.py +0 -0
  61. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/tags_job.py +0 -0
  62. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generated/tags_run.py +0 -0
  63. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/base.py +0 -0
  64. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/cli.py +0 -0
  65. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/generate.py +0 -0
  66. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/header.py +0 -0
  67. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/templates/Enum.jinja2 +0 -0
  68. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/templates/dataclass.jinja2 +0 -0
  69. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/templates/facet_v2.jinja2 +0 -0
  70. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/templates/root.jinja2 +0 -0
  71. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/generator/templates/validators.jinja2 +0 -0
  72. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/py.typed +0 -0
  73. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/tags.py +0 -0
  74. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/__init__.py +0 -0
  75. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/factory.py +0 -0
  76. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/file.py +0 -0
  77. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/msk_iam.py +0 -0
  78. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/transport/noop.py +0 -0
  79. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/openlineage/client/uuid.py +0 -0
  80. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/redact_fields.yml +0 -0
  81. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/__init__.py +0 -0
  82. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/config/config.yml +0 -0
  83. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/config/exact_filter.yml +0 -0
  84. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/config/http.yml +0 -0
  85. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/config/openlineage.yml +0 -0
  86. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/config/regex_filter.yml +0 -0
  87. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/conftest.py +0 -0
  88. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/example_full_event.json +0 -0
  89. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/generator/__init__.py +0 -0
  90. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/generator/test_base.py +0 -0
  91. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/generator/test_cli.py +0 -0
  92. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/nominal_time_without_end.json +0 -0
  93. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/serde_example_dataset_event.json +0 -0
  94. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/serde_example_job_event.json +0 -0
  95. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/serde_example_run_event.json +0 -0
  96. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_composite.py +0 -0
  97. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_events.py +0 -0
  98. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_facet.py +0 -0
  99. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_facet_v2.py +0 -0
  100. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_factory.py +0 -0
  101. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_kafka.py +0 -0
  102. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_msk_iam.py +0 -0
  103. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/test_utils.py +0 -0
  104. {openlineage_python-1.31.0 → openlineage_python-1.32.1}/tests/transport.py +0 -0
@@ -1,23 +1,23 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openlineage-python
3
- Version: 1.31.0
3
+ Version: 1.32.1
4
4
  Summary: OpenLineage Python Client
5
5
  Author-email: OpenLineage <info@openlineage.io>
6
6
  Keywords: openlineage
7
7
  Classifier: Operating System :: OS Independent
8
8
  Classifier: Programming Language :: Python
9
9
  Classifier: Programming Language :: Python :: 3 :: Only
10
- Classifier: Programming Language :: Python :: 3.8
11
10
  Classifier: Programming Language :: Python :: 3.9
12
11
  Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
15
- Requires-Python: >=3.8
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.9
16
16
  Requires-Dist: attrs>=20.0
17
17
  Requires-Dist: packaging>=21.0
18
18
  Requires-Dist: python-dateutil>=2.8.2
19
19
  Requires-Dist: pyyaml>=5.4
20
- Requires-Dist: requests>=2.20.0
20
+ Requires-Dist: requests>=2.32.3
21
21
  Provides-Extra: docs
22
22
  Requires-Dist: beautifulsoup4>=4.12.0; extra == 'docs'
23
23
  Requires-Dist: pydata-sphinx-theme>=0.13; extra == 'docs'
@@ -26,7 +26,7 @@ Requires-Dist: sphinx>=7.0; extra == 'docs'
26
26
  Provides-Extra: generator
27
27
  Requires-Dist: click; extra == 'generator'
28
28
  Requires-Dist: datamodel-code-generator==0.25.4; extra == 'generator'
29
- Requires-Dist: ruff==0.3.5; extra == 'generator'
29
+ Requires-Dist: ruff; extra == 'generator'
30
30
  Provides-Extra: kafka
31
31
  Requires-Dist: confluent-kafka>=2.1.1; extra == 'kafka'
32
32
  Provides-Extra: msk-iam
@@ -153,7 +153,7 @@ class OpenLineageClient:
153
153
  ) -> Event | None:
154
154
  """Filters jobs according to config-defined events"""
155
155
  for _filter in self._filters:
156
- if isinstance(event, RunEvent) and _filter.filter_event(event) is None:
156
+ if isinstance(event, (RunEvent, event_v2.RunEvent)) and _filter.filter_event(event) is None:
157
157
  return None
158
158
  return event
159
159
 
@@ -397,7 +397,10 @@ class OpenLineageClient:
397
397
  """
398
398
  Adds environment variables as facets to the event object.
399
399
  """
400
- if isinstance(event, RunEvent) and (env_vars := self._collect_environment_variables()):
400
+ if isinstance(event, (RunEvent, event_v2.RunEvent)) and (
401
+ env_vars := self._collect_environment_variables()
402
+ ):
403
+ event.run.facets = event.run.facets or {}
401
404
  event.run.facets["environmentVariables"] = EnvironmentVariablesRunFacet(
402
405
  environmentVariables=[
403
406
  EnvironmentVariable(name=name, value=value) for name, value in env_vars.items()
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "1.31.0"
5
+ __version__ = "1.32.1"
6
6
 
7
7
  DEFAULT_TIMEOUT_MS = 5000
8
8
  DEFAULT_NAMESPACE_NAME = "default"
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  import warnings
4
4
  from enum import Enum
5
- from typing import Any, ClassVar, Dict, List, Optional
5
+ from typing import Any, ClassVar, Optional
6
6
 
7
7
  import attr
8
8
  from openlineage.client.constants import DEFAULT_PRODUCER
@@ -27,8 +27,8 @@ class BaseFacet(RedactMixin):
27
27
  _producer: str = attr.ib(init=False)
28
28
  _schemaURL: str = attr.ib(init=False) # noqa: N815
29
29
 
30
- _base_skip_redact: ClassVar[List[str]] = ["_producer", "_schemaURL"]
31
- _additional_skip_redact: ClassVar[List[str]] = []
30
+ _base_skip_redact: ClassVar[list[str]] = ["_producer", "_schemaURL"]
31
+ _additional_skip_redact: ClassVar[list[str]] = []
32
32
 
33
33
  def __attrs_post_init__(self) -> None:
34
34
  self._producer = PRODUCER
@@ -39,7 +39,7 @@ class BaseFacet(RedactMixin):
39
39
  return SCHEMA_URI + "#/definitions/BaseFacet"
40
40
 
41
41
  @property
42
- def skip_redact(self) -> List[str]:
42
+ def skip_redact(self) -> list[str]:
43
43
  return self._base_skip_redact + self._additional_skip_redact
44
44
 
45
45
 
@@ -48,7 +48,7 @@ class NominalTimeRunFacet(BaseFacet):
48
48
  nominalStartTime: str = attr.ib() # noqa: N815
49
49
  nominalEndTime: Optional[str] = attr.ib(default=None) # noqa: N815
50
50
 
51
- _additional_skip_redact: ClassVar[List[str]] = ["nominalStartTime", "nominalEndTime"]
51
+ _additional_skip_redact: ClassVar[list[str]] = ["nominalStartTime", "nominalEndTime"]
52
52
 
53
53
  @staticmethod
54
54
  def _get_schema() -> str:
@@ -57,10 +57,10 @@ class NominalTimeRunFacet(BaseFacet):
57
57
 
58
58
  @attr.s
59
59
  class ParentRunFacet(BaseFacet):
60
- run: Dict[Any, Any] = attr.ib()
61
- job: Dict[Any, Any] = attr.ib()
60
+ run: dict[Any, Any] = attr.ib()
61
+ job: dict[Any, Any] = attr.ib()
62
62
 
63
- _additional_skip_redact: ClassVar[List[str]] = ["job", "run"]
63
+ _additional_skip_redact: ClassVar[list[str]] = ["job", "run"]
64
64
 
65
65
  @classmethod
66
66
  def create(cls, runId: str, namespace: str, name: str) -> "ParentRunFacet": # noqa: N803
@@ -93,7 +93,7 @@ class SourceCodeLocationJobFacet(BaseFacet):
93
93
  type: str = attr.ib()
94
94
  url: str = attr.ib()
95
95
 
96
- _additional_skip_redact: ClassVar[List[str]] = ["type", "url"]
96
+ _additional_skip_redact: ClassVar[list[str]] = ["type", "url"]
97
97
 
98
98
  @staticmethod
99
99
  def _get_schema() -> str:
@@ -124,12 +124,12 @@ class SchemaField(RedactMixin):
124
124
  type: str = attr.ib()
125
125
  description: Optional[str] = attr.ib(default=None)
126
126
 
127
- _do_not_redact: ClassVar[List[str]] = ["name", "type"]
127
+ _do_not_redact: ClassVar[list[str]] = ["name", "type"]
128
128
 
129
129
 
130
130
  @attr.s
131
131
  class SchemaDatasetFacet(BaseFacet):
132
- fields: List[SchemaField] = attr.ib()
132
+ fields: list[SchemaField] = attr.ib()
133
133
 
134
134
  @staticmethod
135
135
  def _get_schema() -> str:
@@ -141,7 +141,7 @@ class DataSourceDatasetFacet(BaseFacet):
141
141
  name: str = attr.ib()
142
142
  uri: str = attr.ib()
143
143
 
144
- _additional_skip_redact: ClassVar[List[str]] = ["name", "uri"]
144
+ _additional_skip_redact: ClassVar[list[str]] = ["name", "uri"]
145
145
 
146
146
  @staticmethod
147
147
  def _get_schema() -> str:
@@ -154,7 +154,7 @@ class OutputStatisticsOutputDatasetFacet(BaseFacet):
154
154
  size: Optional[int] = attr.ib(default=None)
155
155
  fileCount: Optional[int] = attr.ib(default=None) # noqa: N815
156
156
 
157
- _additional_skip_redact: ClassVar[List[str]] = ["rowCount", "size", "fileCount"]
157
+ _additional_skip_redact: ClassVar[list[str]] = ["rowCount", "size", "fileCount"]
158
158
 
159
159
  @staticmethod
160
160
  def _get_schema() -> str:
@@ -169,7 +169,7 @@ class ColumnMetric:
169
169
  count: Optional[int] = attr.ib(default=None)
170
170
  min: Optional[float] = attr.ib(default=None)
171
171
  max: Optional[float] = attr.ib(default=None)
172
- quantiles: Optional[Dict[str, float]] = attr.ib(default=None)
172
+ quantiles: Optional[dict[str, float]] = attr.ib(default=None)
173
173
 
174
174
 
175
175
  @attr.s
@@ -177,7 +177,7 @@ class DataQualityMetricsInputDatasetFacet(BaseFacet):
177
177
  rowCount: Optional[int] = attr.ib(default=None) # noqa: N815
178
178
  bytes: Optional[int] = attr.ib(default=None)
179
179
  fileCount: Optional[int] = attr.ib(default=None) # noqa: N815
180
- columnMetrics: Dict[str, ColumnMetric] = attr.ib(factory=dict) # noqa: N815
180
+ columnMetrics: dict[str, ColumnMetric] = attr.ib(factory=dict) # noqa: N815
181
181
 
182
182
  @staticmethod
183
183
  def _get_schema() -> str:
@@ -190,7 +190,7 @@ class Assertion(RedactMixin):
190
190
  success: bool = attr.ib()
191
191
  column: Optional[str] = attr.ib(default=None)
192
192
 
193
- _skip_redact: ClassVar[List[str]] = ["column"]
193
+ _skip_redact: ClassVar[list[str]] = ["column"]
194
194
 
195
195
 
196
196
  @attr.s
@@ -198,7 +198,7 @@ class DataQualityAssertionsDatasetFacet(BaseFacet):
198
198
 
199
199
  """This facet represents asserted expectations on dataset or it's column."""
200
200
 
201
- assertions: List[Assertion] = attr.ib()
201
+ assertions: list[Assertion] = attr.ib()
202
202
 
203
203
  @staticmethod
204
204
  def _get_schema() -> str:
@@ -213,7 +213,7 @@ class SourceCodeJobFacet(BaseFacet):
213
213
  language: str = attr.ib() # language that the code was written in
214
214
  source: str = attr.ib() # source code text
215
215
 
216
- _additional_skip_redact: ClassVar[List[str]] = ["language"]
216
+ _additional_skip_redact: ClassVar[list[str]] = ["language"]
217
217
 
218
218
  @staticmethod
219
219
  def _get_schema() -> str:
@@ -235,7 +235,7 @@ class ErrorMessageRunFacet(BaseFacet):
235
235
  programmingLanguage: str = attr.ib() # noqa: N815
236
236
  stackTrace: Optional[str] = attr.ib(default=None) # noqa: N815
237
237
 
238
- _additional_skip_redact: ClassVar[List[str]] = ["programmingLanguage"]
238
+ _additional_skip_redact: ClassVar[list[str]] = ["programmingLanguage"]
239
239
 
240
240
  @staticmethod
241
241
  def _get_schema() -> str:
@@ -254,7 +254,7 @@ class SymlinksDatasetFacet(BaseFacet):
254
254
 
255
255
  """This facet represents dataset symlink names."""
256
256
 
257
- identifiers: List[SymlinksDatasetFacetIdentifiers] = attr.ib(factory=dict)
257
+ identifiers: list[SymlinksDatasetFacetIdentifiers] = attr.ib(factory=dict)
258
258
 
259
259
  @staticmethod
260
260
  def _get_schema() -> str:
@@ -285,7 +285,7 @@ class OwnershipJobFacet(BaseFacet):
285
285
 
286
286
  """This facet represents ownership of a job."""
287
287
 
288
- owners: List[OwnershipJobFacetOwners] = attr.ib(factory=dict)
288
+ owners: list[OwnershipJobFacetOwners] = attr.ib(factory=dict)
289
289
 
290
290
  @staticmethod
291
291
  def _get_schema() -> str:
@@ -357,7 +357,7 @@ class OwnershipDatasetFacet(BaseFacet):
357
357
 
358
358
  """This facet represents ownership of a dataset."""
359
359
 
360
- owners: List[OwnershipDatasetFacetOwners] = attr.ib(factory=dict)
360
+ owners: list[OwnershipDatasetFacetOwners] = attr.ib(factory=dict)
361
361
 
362
362
  @staticmethod
363
363
  def _get_schema() -> str:
@@ -370,12 +370,12 @@ class ColumnLineageDatasetFacetFieldsAdditionalInputFields(RedactMixin):
370
370
  name: str = attr.ib()
371
371
  field: str = attr.ib()
372
372
 
373
- _skip_redact: ClassVar[List[str]] = ["namespace", "name", "field"]
373
+ _skip_redact: ClassVar[list[str]] = ["namespace", "name", "field"]
374
374
 
375
375
 
376
376
  @attr.s
377
377
  class ColumnLineageDatasetFacetFieldsAdditional:
378
- inputFields: ClassVar[List[ColumnLineageDatasetFacetFieldsAdditionalInputFields]] = attr.ib() # noqa: N815
378
+ inputFields: ClassVar[list[ColumnLineageDatasetFacetFieldsAdditionalInputFields]] = attr.ib() # noqa: N815
379
379
  transformationDescription: str = attr.ib() # noqa: N815
380
380
  transformationType: str = attr.ib() # noqa: N815
381
381
 
@@ -385,7 +385,7 @@ class ColumnLineageDatasetFacet(BaseFacet):
385
385
 
386
386
  """This facet contains column lineage of a dataset."""
387
387
 
388
- fields: Dict[str, ColumnLineageDatasetFacetFieldsAdditional] = attr.ib(factory=dict)
388
+ fields: dict[str, ColumnLineageDatasetFacetFieldsAdditional] = attr.ib(factory=dict)
389
389
 
390
390
  @staticmethod
391
391
  def _get_schema() -> str:
@@ -415,7 +415,7 @@ class ExtractionError(BaseFacet):
415
415
  class ExtractionErrorRunFacet(BaseFacet):
416
416
  totalTasks: int = attr.ib() # noqa: N815
417
417
  failedTasks: int = attr.ib() # noqa: N815
418
- errors: List[ExtractionError] = attr.ib()
418
+ errors: list[ExtractionError] = attr.ib()
419
419
 
420
420
  @staticmethod
421
421
  def _get_schema() -> str:
@@ -2,6 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
5
6
  import re
6
7
  import typing
7
8
 
@@ -9,6 +10,7 @@ import attr
9
10
  from openlineage.client.event_v2 import RunEvent as RunEvent_v2
10
11
  from openlineage.client.run import RunEvent
11
12
 
13
+ log = logging.getLogger(__name__)
12
14
  RunEventType = typing.Union[RunEvent, RunEvent_v2]
13
15
 
14
16
 
@@ -30,6 +32,7 @@ class ExactMatchFilter(Filter):
30
32
 
31
33
  def filter_event(self, event: RunEventType) -> RunEventType | None:
32
34
  if self.match == event.job.name:
35
+ log.debug("Job name `%s` matches exactly `%s`.", event.job.name, self.match)
33
36
  return None
34
37
  return event
35
38
 
@@ -40,16 +43,20 @@ class RegexFilter(Filter):
40
43
 
41
44
  def filter_event(self, event: RunEventType) -> RunEventType | None:
42
45
  if self.pattern.match(event.job.name):
46
+ log.debug("Job name `%s` matches regex `%s`.", event.job.name, self.pattern.pattern)
43
47
  return None
44
48
  return event
45
49
 
46
50
 
47
51
  def create_filter(conf: FilterConfig) -> Filter | None:
48
52
  if not conf.type:
53
+ log.warning("OpenLineage filter config must have a `type`.")
49
54
  return None
50
- # Switch in 3.10 🙂
51
55
  if conf.type == "exact" and conf.match:
56
+ log.debug("Creating ExactMatchFilter with match='%s'", conf.match)
52
57
  return ExactMatchFilter(match=conf.match)
53
58
  if conf.type == "regex" and conf.regex:
59
+ log.debug("Creating RegexFilter with regex='%s'", conf.regex)
54
60
  return RegexFilter(regex=conf.regex)
61
+ log.warning("Unsupported OpenLineage filter type: `%s`", conf.type)
55
62
  return None
@@ -3,7 +3,7 @@
3
3
  import uuid
4
4
  import warnings
5
5
  from enum import Enum
6
- from typing import Any, ClassVar, Dict, List, Optional
6
+ from typing import Any, ClassVar, Optional
7
7
 
8
8
  import attr
9
9
  from dateutil import parser
@@ -36,19 +36,19 @@ _RUN_FACETS = [
36
36
  class Dataset(RedactMixin):
37
37
  namespace: str = attr.ib()
38
38
  name: str = attr.ib()
39
- facets: Dict[Any, Any] = attr.ib(factory=dict)
39
+ facets: dict[Any, Any] = attr.ib(factory=dict)
40
40
 
41
- _skip_redact: ClassVar[List[str]] = ["namespace", "name"]
41
+ _skip_redact: ClassVar[list[str]] = ["namespace", "name"]
42
42
 
43
43
 
44
44
  @attr.s
45
45
  class InputDataset(Dataset):
46
- inputFacets: Dict[Any, Any] = attr.ib(factory=dict) # noqa: N815
46
+ inputFacets: dict[Any, Any] = attr.ib(factory=dict) # noqa: N815
47
47
 
48
48
 
49
49
  @attr.s
50
50
  class OutputDataset(Dataset):
51
- outputFacets: Dict[Any, Any] = attr.ib(factory=dict) # noqa: N815
51
+ outputFacets: dict[Any, Any] = attr.ib(factory=dict) # noqa: N815
52
52
 
53
53
 
54
54
  @attr.s
@@ -58,16 +58,16 @@ class DatasetEvent(RedactMixin):
58
58
  schemaURL: str = attr.ib() # noqa: N815
59
59
  dataset: Dataset = attr.ib()
60
60
 
61
- _skip_redact: ClassVar[List[str]] = ["producer"]
61
+ _skip_redact: ClassVar[list[str]] = ["producer"]
62
62
 
63
63
 
64
64
  @attr.s
65
65
  class Job(RedactMixin):
66
66
  namespace: str = attr.ib()
67
67
  name: str = attr.ib()
68
- facets: Dict[Any, Any] = attr.ib(factory=dict)
68
+ facets: dict[Any, Any] = attr.ib(factory=dict)
69
69
 
70
- _skip_redact: ClassVar[List[str]] = ["namespace", "name"]
70
+ _skip_redact: ClassVar[list[str]] = ["namespace", "name"]
71
71
 
72
72
 
73
73
  @attr.s
@@ -76,18 +76,18 @@ class JobEvent(RedactMixin):
76
76
  producer: str = attr.ib()
77
77
  schemaURL: str = attr.ib() # noqa: N815
78
78
  job: Job = attr.ib()
79
- inputs: Optional[List[Dataset]] = attr.ib(factory=list)
80
- outputs: Optional[List[Dataset]] = attr.ib(factory=list)
79
+ inputs: Optional[list[Dataset]] = attr.ib(factory=list)
80
+ outputs: Optional[list[Dataset]] = attr.ib(factory=list)
81
81
 
82
- _skip_redact: ClassVar[List[str]] = ["producer"]
82
+ _skip_redact: ClassVar[list[str]] = ["producer"]
83
83
 
84
84
 
85
85
  @attr.s
86
86
  class Run(RedactMixin):
87
87
  runId: str = attr.ib() # noqa: N815
88
- facets: Dict[Any, Any] = attr.ib(factory=dict)
88
+ facets: dict[Any, Any] = attr.ib(factory=dict)
89
89
 
90
- _skip_redact: ClassVar[List[str]] = ["runId"]
90
+ _skip_redact: ClassVar[list[str]] = ["runId"]
91
91
 
92
92
  @runId.validator
93
93
  def check(self, attribute: str, value: str) -> None: # noqa: ARG002
@@ -101,11 +101,11 @@ class RunEvent(RedactMixin):
101
101
  run: Run = attr.ib()
102
102
  job: Job = attr.ib()
103
103
  producer: str = attr.ib()
104
- inputs: Optional[List[Dataset]] = attr.ib(factory=list)
105
- outputs: Optional[List[Dataset]] = attr.ib(factory=list)
104
+ inputs: Optional[list[Dataset]] = attr.ib(factory=list)
105
+ outputs: Optional[list[Dataset]] = attr.ib(factory=list)
106
106
  schemaURL: str = attr.ib(default=SCHEMA_URL) # noqa: N815
107
107
 
108
- _skip_redact: ClassVar[List[str]] = ["eventType", "eventTime", "producer", "schemaURL"]
108
+ _skip_redact: ClassVar[list[str]] = ["eventType", "eventTime", "producer", "schemaURL"]
109
109
 
110
110
  @eventTime.validator
111
111
  def check(self, attribute: str, value: str) -> None: # noqa: ARG002
@@ -6,7 +6,7 @@ import json
6
6
  import logging
7
7
  import sys
8
8
  from enum import Enum
9
- from typing import Any, Dict, List, cast
9
+ from typing import Any, cast
10
10
 
11
11
  import attr
12
12
 
@@ -23,14 +23,14 @@ class Serde:
23
23
  def remove_nulls_and_enums(cls, obj: Any) -> Any:
24
24
  if isinstance(obj, Enum):
25
25
  return obj.value
26
- if isinstance(obj, Dict):
26
+ if isinstance(obj, dict):
27
27
  return dict(
28
28
  filter(
29
29
  lambda x: x[1] is not None,
30
30
  {k: cls.remove_nulls_and_enums(v) for k, v in obj.items()}.items(),
31
31
  ),
32
32
  )
33
- if isinstance(obj, List):
33
+ if isinstance(obj, list):
34
34
  return list(
35
35
  filter(
36
36
  lambda x: x is not None and (not isinstance(x, dict) or x != {}),
@@ -47,7 +47,7 @@ class Serde:
47
47
  def to_dict(cls, obj: Any) -> dict[Any, Any]:
48
48
  if not isinstance(obj, dict):
49
49
  obj = attr.asdict(obj)
50
- return cast(Dict[Any, Any], cls.remove_nulls_and_enums(obj))
50
+ return cast(dict[Any, Any], cls.remove_nulls_and_enums(obj))
51
51
 
52
52
  @classmethod
53
53
  def to_json(cls, obj: Any) -> str:
@@ -24,9 +24,10 @@ class CompositeConfig(Config):
24
24
 
25
25
  Attributes:
26
26
  transports:
27
- A list of dictionaries, where each dictionary represents the configuration
27
+ A list or dict of dictionaries, where each dictionary represents the configuration
28
28
  for a child transport. Each dictionary should contain the necessary parameters
29
- to initialize a specific transport instance.
29
+ to initialize a specific transport instance. If dict of dictionaries is passed,
30
+ keys of the dict will be treated as transport names.
30
31
 
31
32
  continue_on_failure:
32
33
  If set to True, the CompositeTransport will attempt to emit the event using
@@ -56,6 +57,10 @@ class CompositeTransport(Transport):
56
57
  def __init__(self, config: CompositeConfig) -> None:
57
58
  """Initialize a CompositeTransport object."""
58
59
  self.config = config
60
+ log.debug(
61
+ "Constructing OpenLineage composite transport with the following transports: %s",
62
+ [str(x) for x in self.transports],
63
+ )
59
64
 
60
65
  @cached_property
61
66
  def transports(self) -> list[Transport]:
@@ -76,11 +81,11 @@ class CompositeTransport(Transport):
76
81
  """Emit an event using all transports in the config."""
77
82
  for transport in self.transports:
78
83
  try:
79
- log.debug("Emitting event using %s", transport.kind)
84
+ log.debug("Emitting event using transport %s", transport)
80
85
  transport.emit(event)
81
86
  except Exception as e: # noqa: BLE001
82
87
  if self.config.continue_on_failure:
83
- log.warning("Transport %s failed to emit event with error: %s", transport.kind, e)
88
+ log.warning("Transport %s failed to emit event with error: %s", transport, e)
84
89
  else:
85
- msg = f"Transport {transport.kind} failed to emit event"
90
+ msg = f"Transport {transport} failed to emit event"
86
91
  raise RuntimeError(msg) from e
@@ -11,6 +11,8 @@ from openlineage.client.transport.transport import Config, Transport
11
11
  if TYPE_CHECKING:
12
12
  from openlineage.client.client import Event
13
13
 
14
+ log = logging.getLogger(__name__)
15
+
14
16
 
15
17
  class ConsoleConfig(Config):
16
18
  ...
@@ -21,10 +23,9 @@ class ConsoleTransport(Transport):
21
23
  config_class = ConsoleConfig
22
24
 
23
25
  def __init__(self, config: ConsoleConfig) -> None: # noqa: ARG002
24
- self.log = logging.getLogger(__name__)
25
- self.log.debug("Constructing OpenLineage transport that will send events to console or logs")
26
+ log.debug("Constructing OpenLineage transport that will send events to console or logs")
26
27
 
27
28
  def emit(self, event: Event) -> None:
28
29
  # Note: When the logging level is set to DEBUG, the content of events is logged twice:
29
30
  # here on the INFO level and in client.py on the DEBUG level for when different transport is used.
30
- self.log.info(Serde.to_json(event))
31
+ log.info(Serde.to_json(event))
@@ -11,10 +11,11 @@ from typing import TYPE_CHECKING, Any
11
11
  from urllib.parse import urljoin
12
12
 
13
13
  import attr
14
+ import urllib3.util
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from openlineage.client.client import Event, OpenLineageClientOptions
17
- from requests.adapters import HTTPAdapter, Response
18
+ from requests import Response
18
19
 
19
20
  import http.client as http_client
20
21
 
@@ -22,6 +23,7 @@ from openlineage.client.serde import Serde
22
23
  from openlineage.client.transport.transport import Config, Transport
23
24
  from openlineage.client.utils import get_only_specified_fields, try_import_from_string
24
25
  from requests import Session
26
+ from requests.adapters import HTTPAdapter
25
27
 
26
28
  log = logging.getLogger(__name__)
27
29
 
@@ -89,6 +91,17 @@ class HttpConfig(Config):
89
91
  adapter: HTTPAdapter | None = attr.ib(default=None)
90
92
  # custom headers support
91
93
  custom_headers: dict[str, str] = attr.ib(factory=dict)
94
+ # retry settings
95
+ retry: dict[str, Any] = attr.ib(
96
+ default={
97
+ "total": 5,
98
+ "read": 5,
99
+ "connect": 5,
100
+ "backoff_factor": 0.3,
101
+ "status_forcelist": [500, 502, 503, 504],
102
+ "allowed_methods": ["HEAD", "POST"],
103
+ }
104
+ )
92
105
 
93
106
  @classmethod
94
107
  def from_dict(cls, params: dict[str, Any]) -> HttpConfig:
@@ -149,21 +162,11 @@ class HttpTransport(Transport):
149
162
  self.session = None
150
163
  if config.session:
151
164
  self.session = config.session
152
- self.session.headers["Content-Type"] = "application/json"
153
- auth_headers = self._auth_headers(config.auth)
154
- self.session.headers.update(auth_headers)
155
- self.session.headers.update(config.custom_headers)
165
+ self._prepare_session(self.session)
156
166
  self.timeout = config.timeout
157
167
  self.verify = config.verify
158
168
  self.compression = config.compression
159
169
 
160
- if config.adapter:
161
- self.set_adapter(config.adapter)
162
-
163
- def set_adapter(self, adapter: HTTPAdapter) -> None:
164
- if self.session:
165
- self.session.mount(self.url, adapter)
166
-
167
170
  def emit(self, event: Event) -> Response:
168
171
  # If anyone overrides debuglevel manually, we can potentially leak secrets to logs.
169
172
  # Override this setting to make sure it does not happen.
@@ -171,9 +174,6 @@ class HttpTransport(Transport):
171
174
  http_client.HTTPConnection.debuglevel = 0
172
175
  body, headers = self._prepare_request(Serde.to_json(event))
173
176
 
174
- # Update headers with custom headers from the config
175
- headers.update(self.config.custom_headers)
176
-
177
177
  if self.session:
178
178
  resp = self.session.post(
179
179
  url=urljoin(self.url, self.endpoint),
@@ -183,10 +183,8 @@ class HttpTransport(Transport):
183
183
  verify=self.verify,
184
184
  )
185
185
  else:
186
- headers["Content-Type"] = "application/json"
187
- headers.update(self._auth_headers(self.config.auth))
188
- headers.update(self.config.custom_headers)
189
186
  with Session() as session:
187
+ self._prepare_session(session)
190
188
  resp = session.post(
191
189
  url=urljoin(self.url, self.endpoint),
192
190
  data=body,
@@ -205,8 +203,24 @@ class HttpTransport(Transport):
205
203
  return {"Authorization": bearer}
206
204
  return {}
207
205
 
206
+ def _prepare_session(self, session: Session) -> None:
207
+ if self.config.adapter:
208
+ session.mount(self.url, self.config.adapter)
209
+ else:
210
+ session.mount(self.url, self._prepare_adapter())
211
+
212
+ def _prepare_adapter(self) -> HTTPAdapter:
213
+ retry = urllib3.util.Retry(**self.config.retry)
214
+ return HTTPAdapter(max_retries=retry)
215
+
208
216
  def _prepare_request(self, event_str: str) -> tuple[bytes | str, dict[str, str]]:
217
+ headers = {
218
+ "Content-Type": "application/json",
219
+ **self._auth_headers(self.config.auth),
220
+ **self.config.custom_headers,
221
+ }
209
222
  if self.compression == HttpCompression.GZIP:
210
- return gzip.compress(event_str.encode("utf-8")), {"Content-Encoding": "gzip"}
223
+ headers["Content-Encoding"] = "gzip"
224
+ return gzip.compress(event_str.encode("utf-8")), headers
211
225
 
212
- return event_str, {}
226
+ return event_str, headers
@@ -74,12 +74,7 @@ class KafkaTransport(Transport):
74
74
  self.producer = None
75
75
  if not self._is_airflow_sqlalchemy:
76
76
  self._setup_producer(self.kafka_config.config)
77
- log.debug(
78
- "Constructing OpenLineage transport that will send events "
79
- "to kafka topic `%s` using the following config: %s",
80
- self.topic,
81
- self.kafka_config,
82
- )
77
+ log.debug("Constructing OpenLineage transport that will send events to kafka topic `%s`", self.topic)
83
78
 
84
79
  def _get_message_key(self, event: Event) -> str | None:
85
80
  if isinstance(event, (DatasetEvent, event_v2.DatasetEvent)):
@@ -44,6 +44,9 @@ class Transport:
44
44
  def emit(self, event: Event) -> Any:
45
45
  raise NotImplementedError
46
46
 
47
+ def __str__(self) -> str:
48
+ return f"<{self.__class__.__name__}(name={self.name}, kind={self.kind})>"
49
+
47
50
 
48
51
  class TransportFactory:
49
52
  def create(self, config: dict[str, str] | None = None) -> Transport:
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import importlib
6
6
  import logging
7
- from typing import Any, ClassVar, Type, cast
7
+ from typing import Any, ClassVar, cast
8
8
 
9
9
  import attr
10
10
 
@@ -15,7 +15,7 @@ def import_from_string(path: str) -> type[Any]:
15
15
  try:
16
16
  module_path, target = path.rsplit(".", 1)
17
17
  module = importlib.import_module(module_path)
18
- return cast(Type[Any], getattr(module, target))
18
+ return cast(type[Any], getattr(module, target))
19
19
  except Exception as e: # noqa: BLE001
20
20
  log.warning(e)
21
21
  msg = f"Failed to import {path}"