openlineage-python 1.31.0__tar.gz → 1.32.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/PKG-INFO +5 -5
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/client.py +5 -2
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/constants.py +1 -1
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/facet.py +26 -26
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/filter.py +8 -1
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/run.py +16 -16
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/serde.py +4 -4
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/composite.py +10 -5
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/console.py +4 -3
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/http.py +34 -20
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/kafka.py +1 -6
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/transport.py +3 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/utils.py +2 -2
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/pyproject.toml +5 -5
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_client.py +71 -5
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_file.py +3 -3
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_http.py +70 -3
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tox.ini +1 -1
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/.gitignore +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/README.md +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/docs/Makefile +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/docs/conf.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/docs/convert.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/docs/index.rst +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/docs/make.bat +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/docs/source/openlineage.client.rst +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/__init__.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/event_v2.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/facet_v2.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/facets.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/base.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/dataset_type_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/datasource_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/documentation_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/documentation_job.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/environment_variables_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/error_message_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/external_query_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/extraction_error_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/job_type_job.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/nominal_time_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/ownership_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/ownership_job.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/parent_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/processing_engine_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/schema_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/source_code_job.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/source_code_location_job.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/sql_job.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/storage_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/tags_dataset.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/tags_job.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generated/tags_run.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/base.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/cli.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/generate.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/header.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/templates/Enum.jinja2 +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/templates/dataclass.jinja2 +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/templates/facet_v2.jinja2 +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/templates/root.jinja2 +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/generator/templates/validators.jinja2 +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/py.typed +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/tags.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/__init__.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/factory.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/file.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/msk_iam.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/noop.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/uuid.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/redact_fields.yml +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/__init__.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/config/config.yml +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/config/exact_filter.yml +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/config/http.yml +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/config/openlineage.yml +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/config/regex_filter.yml +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/conftest.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/example_full_event.json +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/generator/__init__.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/generator/test_base.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/generator/test_cli.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/nominal_time_without_end.json +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/serde_example_dataset_event.json +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/serde_example_job_event.json +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/serde_example_run_event.json +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_composite.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_events.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_facet.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_facet_v2.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_factory.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_kafka.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_msk_iam.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_utils.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/test_uuid.py +0 -0
- {openlineage_python-1.31.0 → openlineage_python-1.32.0}/tests/transport.py +0 -0
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openlineage-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.32.0
|
|
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
|
-
|
|
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
|
+
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
|
|
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 (
|
|
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
|
import warnings
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from typing import Any, ClassVar,
|
|
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[
|
|
31
|
-
_additional_skip_redact: ClassVar[
|
|
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) ->
|
|
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[
|
|
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:
|
|
61
|
-
job:
|
|
60
|
+
run: dict[Any, Any] = attr.ib()
|
|
61
|
+
job: dict[Any, Any] = attr.ib()
|
|
62
62
|
|
|
63
|
-
_additional_skip_redact: ClassVar[
|
|
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[
|
|
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[
|
|
127
|
+
_do_not_redact: ClassVar[list[str]] = ["name", "type"]
|
|
128
128
|
|
|
129
129
|
|
|
130
130
|
@attr.s
|
|
131
131
|
class SchemaDatasetFacet(BaseFacet):
|
|
132
|
-
fields:
|
|
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[
|
|
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[
|
|
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[
|
|
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:
|
|
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[
|
|
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:
|
|
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[
|
|
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[
|
|
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:
|
|
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:
|
|
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:
|
|
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[
|
|
373
|
+
_skip_redact: ClassVar[list[str]] = ["namespace", "name", "field"]
|
|
374
374
|
|
|
375
375
|
|
|
376
376
|
@attr.s
|
|
377
377
|
class ColumnLineageDatasetFacetFieldsAdditional:
|
|
378
|
-
inputFields: ClassVar[
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
39
|
+
facets: dict[Any, Any] = attr.ib(factory=dict)
|
|
40
40
|
|
|
41
|
-
_skip_redact: ClassVar[
|
|
41
|
+
_skip_redact: ClassVar[list[str]] = ["namespace", "name"]
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
@attr.s
|
|
45
45
|
class InputDataset(Dataset):
|
|
46
|
-
inputFacets:
|
|
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:
|
|
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[
|
|
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:
|
|
68
|
+
facets: dict[Any, Any] = attr.ib(factory=dict)
|
|
69
69
|
|
|
70
|
-
_skip_redact: ClassVar[
|
|
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[
|
|
80
|
-
outputs: Optional[
|
|
79
|
+
inputs: Optional[list[Dataset]] = attr.ib(factory=list)
|
|
80
|
+
outputs: Optional[list[Dataset]] = attr.ib(factory=list)
|
|
81
81
|
|
|
82
|
-
_skip_redact: ClassVar[
|
|
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:
|
|
88
|
+
facets: dict[Any, Any] = attr.ib(factory=dict)
|
|
89
89
|
|
|
90
|
-
_skip_redact: ClassVar[
|
|
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[
|
|
105
|
-
outputs: Optional[
|
|
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[
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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:
|
{openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/composite.py
RENAMED
|
@@ -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
|
|
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
|
|
88
|
+
log.warning("Transport %s failed to emit event with error: %s", transport, e)
|
|
84
89
|
else:
|
|
85
|
-
msg = f"Transport {transport
|
|
90
|
+
msg = f"Transport {transport} failed to emit event"
|
|
86
91
|
raise RuntimeError(msg) from e
|
{openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/console.py
RENAMED
|
@@ -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
|
-
|
|
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
|
-
|
|
31
|
+
log.info(Serde.to_json(event))
|
{openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/http.py
RENAMED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
{openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/kafka.py
RENAMED
|
@@ -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)):
|
{openlineage_python-1.31.0 → openlineage_python-1.32.0}/openlineage/client/transport/transport.py
RENAMED
|
@@ -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,
|
|
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(
|
|
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}"
|