openlineage-python 1.23.0__tar.gz → 1.24.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.23.0 → openlineage_python-1.24.0}/PKG-INFO +1 -1
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/client.py +98 -25
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/constants.py +1 -1
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/facet_v2.py +2 -0
- openlineage_python-1.24.0/openlineage/client/facets.py +10 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/filter.py +14 -6
- openlineage_python-1.24.0/openlineage/client/generated/environment_variables_run.py +35 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/transport.py +3 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/utils.py +19 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/pyproject.toml +1 -1
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/redact_fields.yml +7 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_client.py +276 -7
- openlineage_python-1.24.0/tests/test_utils.py +65 -0
- openlineage_python-1.23.0/tests/test_utils.py +0 -30
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/.gitignore +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/README.md +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/docs/Makefile +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/docs/conf.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/docs/convert.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/docs/index.rst +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/docs/make.bat +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/docs/source/openlineage.client.rst +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/__init__.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/event_v2.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/facet.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/base.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/datasource_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/documentation_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/documentation_job.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/error_message_run.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/external_query_run.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/extraction_error_run.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/job_type_job.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/nominal_time_run.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/ownership_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/ownership_job.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/parent_run.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/processing_engine_run.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/schema_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/source_code_job.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/source_code_location_job.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/sql_job.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/storage_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/py.typed +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/run.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/serde.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/__init__.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/composite.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/console.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/factory.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/file.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/http.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/kafka.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/msk_iam.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/noop.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/uuid.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/__init__.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/config/config.yml +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/config/exact_filter.yml +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/config/http.yml +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/config/openlineage.yml +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/config/regex_filter.yml +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/conftest.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/example_full_event.json +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/nominal_time_without_end.json +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/serde_example_dataset_event.json +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/serde_example_job_event.json +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/serde_example_run_event.json +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_composite.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_events.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_facet.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_facet_v2.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_factory.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_file.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_http.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_kafka.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_msk_iam.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/test_uuid.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/transport.py +0 -0
- {openlineage_python-1.23.0 → openlineage_python-1.24.0}/tox.ini +0 -0
|
@@ -10,8 +10,9 @@ from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
|
|
10
10
|
|
|
11
11
|
import attr
|
|
12
12
|
import yaml
|
|
13
|
-
from openlineage.client.filter import Filter, create_filter
|
|
13
|
+
from openlineage.client.filter import Filter, FilterConfig, create_filter
|
|
14
14
|
from openlineage.client.serde import Serde
|
|
15
|
+
from openlineage.client.utils import deep_merge_dicts
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from requests import Session
|
|
@@ -20,8 +21,17 @@ if TYPE_CHECKING:
|
|
|
20
21
|
import contextlib
|
|
21
22
|
|
|
22
23
|
from openlineage.client import event_v2
|
|
24
|
+
from openlineage.client.facets import FacetsConfig
|
|
25
|
+
from openlineage.client.generated.environment_variables_run import (
|
|
26
|
+
EnvironmentVariable,
|
|
27
|
+
EnvironmentVariablesRunFacet,
|
|
28
|
+
)
|
|
23
29
|
from openlineage.client.run import DatasetEvent, JobEvent, RunEvent
|
|
24
|
-
from openlineage.client.transport import
|
|
30
|
+
from openlineage.client.transport import (
|
|
31
|
+
Transport,
|
|
32
|
+
TransportFactory,
|
|
33
|
+
get_default_factory,
|
|
34
|
+
)
|
|
25
35
|
from openlineage.client.transport.http import HttpConfig, HttpTransport, create_token_provider
|
|
26
36
|
from openlineage.client.transport.noop import NoopConfig, NoopTransport
|
|
27
37
|
|
|
@@ -38,6 +48,24 @@ class OpenLineageClientOptions:
|
|
|
38
48
|
adapter: HTTPAdapter = attr.ib(default=None)
|
|
39
49
|
|
|
40
50
|
|
|
51
|
+
@attr.s
|
|
52
|
+
class OpenLineageConfig:
|
|
53
|
+
transport: dict[str, Any] | None = attr.ib(factory=dict)
|
|
54
|
+
facets: FacetsConfig = attr.ib(factory=FacetsConfig)
|
|
55
|
+
filters: list[FilterConfig] = attr.ib(factory=list)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_dict(cls, params: dict[str, Any]) -> OpenLineageConfig:
|
|
59
|
+
config = cls()
|
|
60
|
+
if "transport" in params:
|
|
61
|
+
config.transport = params["transport"]
|
|
62
|
+
if "facets" in params:
|
|
63
|
+
config.facets = FacetsConfig(**params["facets"])
|
|
64
|
+
if "filters" in params:
|
|
65
|
+
config.filters = [FilterConfig(**filter_config) for filter_config in params["filters"]]
|
|
66
|
+
return config
|
|
67
|
+
|
|
68
|
+
|
|
41
69
|
log = logging.getLogger(__name__)
|
|
42
70
|
_T = TypeVar("_T", bound="OpenLineageClient")
|
|
43
71
|
|
|
@@ -53,6 +81,8 @@ class OpenLineageClient:
|
|
|
53
81
|
session: Session | None = None,
|
|
54
82
|
transport: Transport | None = None,
|
|
55
83
|
factory: TransportFactory | None = None,
|
|
84
|
+
*,
|
|
85
|
+
config: dict[str, str] | None = None,
|
|
56
86
|
) -> None:
|
|
57
87
|
# Set parent's logging level if environment variable is present
|
|
58
88
|
custom_logging_level = os.getenv("OPENLINEAGE_CLIENT_LOGGING", None)
|
|
@@ -68,7 +98,9 @@ class OpenLineageClient:
|
|
|
68
98
|
|
|
69
99
|
# Make config ellipsis - as a guard value to not try to
|
|
70
100
|
# reload yaml each time config is referred to.
|
|
71
|
-
self._config:
|
|
101
|
+
self._config: OpenLineageConfig | None = None
|
|
102
|
+
|
|
103
|
+
self.user_defined_config: dict[str, str] | None = config
|
|
72
104
|
|
|
73
105
|
self._alias_env_vars()
|
|
74
106
|
|
|
@@ -78,7 +110,7 @@ class OpenLineageClient:
|
|
|
78
110
|
log.info("OpenLineageClient will use `%s` transport", self.transport.kind)
|
|
79
111
|
|
|
80
112
|
self._filters: list[Filter] = []
|
|
81
|
-
for conf in self.config.
|
|
113
|
+
for conf in self.config.filters:
|
|
82
114
|
_filter = create_filter(conf)
|
|
83
115
|
if _filter:
|
|
84
116
|
self._filters.append(_filter)
|
|
@@ -94,7 +126,7 @@ class OpenLineageClient:
|
|
|
94
126
|
|
|
95
127
|
@classmethod
|
|
96
128
|
def from_dict(cls: type[_T], config: dict[str, str]) -> _T:
|
|
97
|
-
return cls(
|
|
129
|
+
return cls(config=config)
|
|
98
130
|
|
|
99
131
|
def filter_event(
|
|
100
132
|
self,
|
|
@@ -122,6 +154,8 @@ class OpenLineageClient:
|
|
|
122
154
|
log.debug("OpenLineage event has been filtered out and will not be emitted.")
|
|
123
155
|
return
|
|
124
156
|
|
|
157
|
+
event = self.add_environment_facets(event)
|
|
158
|
+
|
|
125
159
|
if log.isEnabledFor(logging.DEBUG):
|
|
126
160
|
val = Serde.to_json(event).encode("utf-8")
|
|
127
161
|
log.debug("OpenLineageClient will emit event %s", val)
|
|
@@ -129,17 +163,39 @@ class OpenLineageClient:
|
|
|
129
163
|
log.debug("OpenLineage event successfully emitted.")
|
|
130
164
|
|
|
131
165
|
@property
|
|
132
|
-
def config(self) ->
|
|
133
|
-
"""
|
|
166
|
+
def config(self) -> OpenLineageConfig:
|
|
167
|
+
"""
|
|
168
|
+
Retrieves the OpenLineage configuration.
|
|
169
|
+
|
|
170
|
+
This property method returns the content of the OpenLineage YAML config file.
|
|
171
|
+
The configuration is determined by merging sources in the following order of precedence:
|
|
172
|
+
1. User-defined configuration passed to the client constructor.
|
|
173
|
+
2. YAML config file located in one of the following paths:
|
|
174
|
+
- Path specified by the `OPENLINEAGE_CONFIG` environment variable.
|
|
175
|
+
- Current working directory.
|
|
176
|
+
- `$HOME/.openlineage`.
|
|
177
|
+
3. Environment variables with the `OPENLINEAGE__` prefix.
|
|
178
|
+
If the configuration is not already loaded, it will be constructed by merging the above sources.
|
|
179
|
+
In case of a TypeError during the parsing of the configuration, a ValueError will be raised indicating
|
|
180
|
+
that the structure of the config does not match the expected format.
|
|
181
|
+
"""
|
|
134
182
|
if self._config is None:
|
|
135
|
-
|
|
136
|
-
if
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
self.
|
|
183
|
+
config_dict: dict[str, Any] = {}
|
|
184
|
+
if self.user_defined_config:
|
|
185
|
+
config_dict = self.user_defined_config
|
|
186
|
+
if config_path := self._find_yaml_config_path():
|
|
187
|
+
config_dict = deep_merge_dicts(self._get_config_file_content(config_path), config_dict)
|
|
188
|
+
if config_from_env_vars := self._load_config_from_env_variables():
|
|
189
|
+
config_dict = deep_merge_dicts(config_from_env_vars, config_dict)
|
|
190
|
+
try:
|
|
191
|
+
self._config = OpenLineageConfig.from_dict(config_dict)
|
|
192
|
+
except TypeError as e:
|
|
193
|
+
# raise exception that structure of the config does not match
|
|
194
|
+
msg = "Failed to parse OpenLineage config."
|
|
195
|
+
raise ValueError(msg) from e
|
|
140
196
|
return self._config
|
|
141
197
|
|
|
142
|
-
def _resolve_transport(self, **kwargs: Any) -> Transport:
|
|
198
|
+
def _resolve_transport(self, **kwargs: Any) -> Transport:
|
|
143
199
|
"""
|
|
144
200
|
Resolves the transport mechanism based on the provided arguments or environment settings.
|
|
145
201
|
|
|
@@ -164,9 +220,9 @@ class OpenLineageClient:
|
|
|
164
220
|
return cast(Transport, kwargs["transport"])
|
|
165
221
|
|
|
166
222
|
# 3. Check if transport configuration is provided in YAML config file
|
|
167
|
-
if self.config.get("
|
|
223
|
+
if self.config.transport and self.config.transport.get("type"):
|
|
168
224
|
factory = kwargs.get("factory") or get_default_factory()
|
|
169
|
-
return factory.create(self.config
|
|
225
|
+
return factory.create(self.config.transport)
|
|
170
226
|
|
|
171
227
|
# 4. Check legacy HTTP transport initialization with url and options
|
|
172
228
|
if kwargs.get("url"):
|
|
@@ -174,16 +230,11 @@ class OpenLineageClient:
|
|
|
174
230
|
url=kwargs["url"], options=kwargs.get("options"), session=kwargs.get("session")
|
|
175
231
|
)
|
|
176
232
|
|
|
177
|
-
# 5. Check transport initialization with env variables
|
|
178
|
-
if config := self._load_config_from_env_variables():
|
|
179
|
-
factory = kwargs.get("factory") or get_default_factory()
|
|
180
|
-
return factory.create(config["transport"])
|
|
181
|
-
|
|
182
|
-
# 6. Check HTTP transport initialization with env variables
|
|
233
|
+
# 5. Check HTTP transport initialization with env variables
|
|
183
234
|
if os.environ.get("OPENLINEAGE_URL"):
|
|
184
235
|
return self._http_transport_from_env_variables()
|
|
185
236
|
|
|
186
|
-
#
|
|
237
|
+
# 6. If all else fails, print events to console
|
|
187
238
|
from openlineage.client.transport.console import ConsoleConfig, ConsoleTransport
|
|
188
239
|
|
|
189
240
|
log.warning("Couldn't find any OpenLineage transport configuration; will print events to console.")
|
|
@@ -306,9 +357,6 @@ class OpenLineageClient:
|
|
|
306
357
|
|
|
307
358
|
cls._insert_into_config(config, keys, env_value)
|
|
308
359
|
|
|
309
|
-
if (transport_config := config.get("transport")) is None or transport_config.get("type") is None:
|
|
310
|
-
return None
|
|
311
|
-
|
|
312
360
|
return config
|
|
313
361
|
|
|
314
362
|
@staticmethod
|
|
@@ -323,3 +371,28 @@ class OpenLineageClient:
|
|
|
323
371
|
|
|
324
372
|
# Overwrite if key already exists
|
|
325
373
|
current[keys[-1]] = value
|
|
374
|
+
|
|
375
|
+
def add_environment_facets(self, event: Event) -> Event:
|
|
376
|
+
"""
|
|
377
|
+
Adds environment variables as facets to the event object.
|
|
378
|
+
"""
|
|
379
|
+
if isinstance(event, RunEvent) and (env_vars := self._collect_environment_variables()):
|
|
380
|
+
event.run.facets["environmentVariables"] = EnvironmentVariablesRunFacet(
|
|
381
|
+
environmentVariables=[
|
|
382
|
+
EnvironmentVariable(name=name, value=value) for name, value in env_vars.items()
|
|
383
|
+
]
|
|
384
|
+
)
|
|
385
|
+
return event
|
|
386
|
+
|
|
387
|
+
def _collect_environment_variables(self) -> dict[str, str]:
|
|
388
|
+
"""
|
|
389
|
+
Collects and returns a dictionary of relevant environment variables.
|
|
390
|
+
"""
|
|
391
|
+
filtered_vars = {k: v for k, v in os.environ.items() if k in self.config.facets.environment_variables}
|
|
392
|
+
missing_vars = set(self.config.facets.environment_variables) - set(filtered_vars)
|
|
393
|
+
if missing_vars:
|
|
394
|
+
log.warning(
|
|
395
|
+
"The following environment variables are missing: %s when adding to OpenLineage event",
|
|
396
|
+
missing_vars,
|
|
397
|
+
)
|
|
398
|
+
return filtered_vars
|
|
@@ -9,6 +9,7 @@ from openlineage.client.generated import (
|
|
|
9
9
|
datasource_dataset,
|
|
10
10
|
documentation_dataset,
|
|
11
11
|
documentation_job,
|
|
12
|
+
environment_variables_run,
|
|
12
13
|
error_message_run,
|
|
13
14
|
external_query_run,
|
|
14
15
|
extraction_error_run,
|
|
@@ -54,6 +55,7 @@ __all__ = [
|
|
|
54
55
|
"datasource_dataset",
|
|
55
56
|
"documentation_dataset",
|
|
56
57
|
"documentation_job",
|
|
58
|
+
"environment_variables_run",
|
|
57
59
|
"error_message_run",
|
|
58
60
|
"external_query_run",
|
|
59
61
|
"extraction_error_run",
|
|
@@ -5,12 +5,20 @@ from __future__ import annotations
|
|
|
5
5
|
import re
|
|
6
6
|
import typing
|
|
7
7
|
|
|
8
|
+
import attr
|
|
8
9
|
from openlineage.client.event_v2 import RunEvent as RunEvent_v2
|
|
9
10
|
from openlineage.client.run import RunEvent
|
|
10
11
|
|
|
11
12
|
RunEventType = typing.Union[RunEvent, RunEvent_v2]
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
@attr.s
|
|
16
|
+
class FilterConfig:
|
|
17
|
+
type: str | None = attr.ib(default=None)
|
|
18
|
+
match: str | None = attr.ib(default=None)
|
|
19
|
+
regex: str | None = attr.ib(default=None)
|
|
20
|
+
|
|
21
|
+
|
|
14
22
|
class Filter:
|
|
15
23
|
def filter_event(self, event: RunEventType) -> RunEventType | None:
|
|
16
24
|
...
|
|
@@ -36,12 +44,12 @@ class RegexFilter(Filter):
|
|
|
36
44
|
return event
|
|
37
45
|
|
|
38
46
|
|
|
39
|
-
def create_filter(conf:
|
|
40
|
-
if
|
|
47
|
+
def create_filter(conf: FilterConfig) -> Filter | None:
|
|
48
|
+
if not conf.type:
|
|
41
49
|
return None
|
|
42
50
|
# Switch in 3.10 🙂
|
|
43
|
-
if conf
|
|
44
|
-
return ExactMatchFilter(match=conf
|
|
45
|
-
if conf
|
|
46
|
-
return RegexFilter(regex=conf
|
|
51
|
+
if conf.type == "exact" and conf.match:
|
|
52
|
+
return ExactMatchFilter(match=conf.match)
|
|
53
|
+
if conf.type == "regex" and conf.regex:
|
|
54
|
+
return RegexFilter(regex=conf.regex)
|
|
47
55
|
return None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright 2018-2024 contributors to the OpenLineage project
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import ClassVar
|
|
7
|
+
|
|
8
|
+
import attr
|
|
9
|
+
from openlineage.client.generated.base import RunFacet
|
|
10
|
+
from openlineage.client.utils import RedactMixin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@attr.define
|
|
14
|
+
class EnvironmentVariable(RedactMixin):
|
|
15
|
+
name: str
|
|
16
|
+
"""The name of the environment variable."""
|
|
17
|
+
|
|
18
|
+
value: str
|
|
19
|
+
"""The value of the environment variable."""
|
|
20
|
+
|
|
21
|
+
_skip_redact: ClassVar[list[str]] = ["name"]
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def _get_schema() -> str:
|
|
25
|
+
return "https://openlineage.io/spec/facets/1-0-0/EnvironmentVariablesRunFacet.json#/$defs/EnvironmentVariable"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@attr.define
|
|
29
|
+
class EnvironmentVariablesRunFacet(RunFacet):
|
|
30
|
+
environmentVariables: list[EnvironmentVariable] # noqa: N815
|
|
31
|
+
"""The environment variables for the run."""
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _get_schema() -> str:
|
|
35
|
+
return "https://openlineage.io/spec/facets/1-0-0/EnvironmentVariablesRunFacet.json#/$defs/EnvironmentVariablesRunFacet"
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/transport.py
RENAMED
|
@@ -20,6 +20,8 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
22
22
|
|
|
23
|
+
import attr
|
|
24
|
+
|
|
23
25
|
if TYPE_CHECKING:
|
|
24
26
|
from openlineage.client.client import Event
|
|
25
27
|
|
|
@@ -27,6 +29,7 @@ if TYPE_CHECKING:
|
|
|
27
29
|
_T = TypeVar("_T", bound="Config")
|
|
28
30
|
|
|
29
31
|
|
|
32
|
+
@attr.s
|
|
30
33
|
class Config:
|
|
31
34
|
@classmethod
|
|
32
35
|
def from_dict(cls: type[_T], params: dict[str, Any]) -> _T: # noqa: ARG003
|
|
@@ -36,6 +36,25 @@ def get_only_specified_fields(clazz: type[Any], params: dict[str, Any]) -> dict[
|
|
|
36
36
|
return {key: value for key, value in params.items() if key in field_keys}
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def deep_merge_dicts(dict1: dict[Any, Any], dict2: dict[Any, Any]) -> dict[Any, Any]:
|
|
40
|
+
"""Deep merges two dictionaries.
|
|
41
|
+
|
|
42
|
+
This function merges two dictionaries while handling nested dictionaries.
|
|
43
|
+
For keys that exist in both dictionaries, the values from dict2 take precedence.
|
|
44
|
+
If a key exists in both dictionaries and the values are dictionaries themselves,
|
|
45
|
+
they are merged recursively.
|
|
46
|
+
This function merges only dictionaries. If key is of different type, e.g. list
|
|
47
|
+
it does not work properly.
|
|
48
|
+
"""
|
|
49
|
+
merged = dict1.copy()
|
|
50
|
+
for k, v in dict2.items():
|
|
51
|
+
if k in merged and isinstance(v, dict):
|
|
52
|
+
merged[k] = deep_merge_dicts(merged.get(k, {}), v)
|
|
53
|
+
else:
|
|
54
|
+
merged[k] = v
|
|
55
|
+
return merged
|
|
56
|
+
|
|
57
|
+
|
|
39
58
|
class RedactMixin:
|
|
40
59
|
_skip_redact: ClassVar[list[str]] = []
|
|
41
60
|
|
|
@@ -92,6 +92,13 @@
|
|
|
92
92
|
classes:
|
|
93
93
|
- class_name: DocumentationJobFacet
|
|
94
94
|
redact_fields: []
|
|
95
|
+
- module: environment_variables_run
|
|
96
|
+
classes:
|
|
97
|
+
- class_name: EnvironmentVariable
|
|
98
|
+
redact_fields:
|
|
99
|
+
- name
|
|
100
|
+
- class_name: EnvironmentVariablesRunFacet
|
|
101
|
+
redact_fields: []
|
|
95
102
|
- module: error_message_run
|
|
96
103
|
classes:
|
|
97
104
|
- class_name: ErrorMessageRunFacet
|
|
@@ -9,7 +9,12 @@ from typing import TYPE_CHECKING
|
|
|
9
9
|
from unittest.mock import MagicMock, patch
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
|
-
from openlineage.client.client import OpenLineageClient, OpenLineageClientOptions
|
|
12
|
+
from openlineage.client.client import OpenLineageClient, OpenLineageClientOptions, OpenLineageConfig
|
|
13
|
+
from openlineage.client.facets import FacetsConfig
|
|
14
|
+
from openlineage.client.generated.environment_variables_run import (
|
|
15
|
+
EnvironmentVariable,
|
|
16
|
+
EnvironmentVariablesRunFacet,
|
|
17
|
+
)
|
|
13
18
|
from openlineage.client.run import (
|
|
14
19
|
SCHEMA_URL,
|
|
15
20
|
Dataset,
|
|
@@ -351,22 +356,137 @@ def test_find_yaml_config_path_checks_all_paths(mock_is_file, mocker: MockerFixt
|
|
|
351
356
|
assert result is None
|
|
352
357
|
|
|
353
358
|
|
|
359
|
+
def test_ol_config_from_dict():
|
|
360
|
+
# Test with complete config
|
|
361
|
+
config_dict = {
|
|
362
|
+
"transport": {"url": "http://localhost:5050"},
|
|
363
|
+
"facets": {"environment_variables": ["VAR1", "VAR2"]},
|
|
364
|
+
"filters": [{"type": "exact", "match": "job_name"}],
|
|
365
|
+
}
|
|
366
|
+
config = OpenLineageConfig.from_dict(config_dict)
|
|
367
|
+
assert config.transport["url"] == "http://localhost:5050"
|
|
368
|
+
assert config.facets.environment_variables == ["VAR1", "VAR2"]
|
|
369
|
+
assert config.filters[0].type == "exact"
|
|
370
|
+
assert config.filters[0].match == "job_name"
|
|
371
|
+
|
|
372
|
+
# Test with missing keys
|
|
373
|
+
config_dict = {}
|
|
374
|
+
config = OpenLineageConfig.from_dict(config_dict)
|
|
375
|
+
assert config.transport == {}
|
|
376
|
+
assert config.facets == FacetsConfig()
|
|
377
|
+
assert config.filters == []
|
|
378
|
+
|
|
379
|
+
# Test with invalid data type
|
|
380
|
+
with pytest.raises(TypeError):
|
|
381
|
+
OpenLineageConfig.from_dict({"facets": "invalid_data"})
|
|
382
|
+
|
|
383
|
+
|
|
354
384
|
@patch("yaml.safe_load", return_value=None)
|
|
355
385
|
def test_config_file_content_empty_file(mock_yaml) -> None: # noqa: ARG001
|
|
356
|
-
assert OpenLineageClient().config ==
|
|
386
|
+
assert OpenLineageClient().config == OpenLineageConfig()
|
|
357
387
|
|
|
358
388
|
|
|
359
389
|
def test_config(mocker: MockerFixture, root: Path) -> None:
|
|
360
390
|
mocker.patch.dict(os.environ, {"OPENLINEAGE_CONFIG": str(root / "config" / "http.yml")})
|
|
361
|
-
assert OpenLineageClient().config ==
|
|
391
|
+
assert OpenLineageClient().config == OpenLineageConfig.from_dict(
|
|
392
|
+
{
|
|
393
|
+
"transport": {
|
|
394
|
+
"auth": {"apiKey": "random_token", "type": "api_key"},
|
|
395
|
+
"compression": "gzip",
|
|
396
|
+
"endpoint": "api/v1/lineage",
|
|
397
|
+
"type": "http",
|
|
398
|
+
"url": "http://localhost:5050",
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def test_openlineage_client_from_dict() -> None:
|
|
405
|
+
config_dict = {"transport": {"type": "http", "url": "http://localhost:5050"}}
|
|
406
|
+
client = OpenLineageClient.from_dict(config_dict)
|
|
407
|
+
assert client.transport.url == "http://localhost:5050"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def test_openlineage_client_from_empty_dict() -> None:
|
|
411
|
+
client = OpenLineageClient.from_dict({})
|
|
412
|
+
assert isinstance(client.transport, ConsoleTransport)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def test_openlineage_config_from_dict() -> None:
|
|
416
|
+
config_dict = {
|
|
362
417
|
"transport": {
|
|
363
|
-
"auth": {"apiKey": "random_token", "type": "api_key"},
|
|
364
|
-
"compression": "gzip",
|
|
365
|
-
"endpoint": "api/v1/lineage",
|
|
366
418
|
"type": "http",
|
|
367
419
|
"url": "http://localhost:5050",
|
|
368
|
-
|
|
420
|
+
"auth": {"api_key": "random_token"},
|
|
421
|
+
},
|
|
422
|
+
"facets": {
|
|
423
|
+
"environment_variables": ["VAR1", "VAR2"],
|
|
424
|
+
},
|
|
425
|
+
"filters": [
|
|
426
|
+
{"type": "regex", "match": ".*"},
|
|
427
|
+
],
|
|
369
428
|
}
|
|
429
|
+
config = OpenLineageConfig.from_dict(config_dict)
|
|
430
|
+
|
|
431
|
+
assert config.transport == config_dict["transport"]
|
|
432
|
+
assert config.facets.environment_variables == config_dict["facets"]["environment_variables"]
|
|
433
|
+
assert len(config.filters) == 1
|
|
434
|
+
assert config.filters[0].type == "regex"
|
|
435
|
+
assert config.filters[0].match == ".*"
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def test_openlineage_config_default_values() -> None:
|
|
439
|
+
config = OpenLineageConfig()
|
|
440
|
+
|
|
441
|
+
assert config.transport == {}
|
|
442
|
+
assert isinstance(config.facets, FacetsConfig)
|
|
443
|
+
assert config.filters == []
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
@patch.dict(os.environ, {"ENV_VAR_1": "value1", "ENV_VAR_2": "value2"})
|
|
447
|
+
def test_collect_environment_variables():
|
|
448
|
+
client = OpenLineageClient()
|
|
449
|
+
client._config = OpenLineageConfig( # noqa: SLF001
|
|
450
|
+
facets=FacetsConfig(environment_variables=["ENV_VAR_1", "ENV_VAR_2", "MISSING_VAR"])
|
|
451
|
+
)
|
|
452
|
+
env_vars = client._collect_environment_variables() # noqa: SLF001
|
|
453
|
+
assert env_vars == {"ENV_VAR_1": "value1", "ENV_VAR_2": "value2"}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@patch.dict(os.environ, {"ENV_VAR_1": "value1", "SENSITIVE_VAR": "PII"})
|
|
457
|
+
def test_add_environment_facets():
|
|
458
|
+
client = OpenLineageClient()
|
|
459
|
+
client._config = OpenLineageConfig( # noqa: SLF001
|
|
460
|
+
facets=FacetsConfig(environment_variables=["ENV_VAR_1"])
|
|
461
|
+
)
|
|
462
|
+
run = Run(runId=str(generate_new_uuid()))
|
|
463
|
+
event = RunEvent(
|
|
464
|
+
eventType=RunState.START,
|
|
465
|
+
eventTime="2021-11-03T10:53:52.427343",
|
|
466
|
+
run=run,
|
|
467
|
+
job=Job(name="name", namespace=""),
|
|
468
|
+
producer="",
|
|
469
|
+
schemaURL="",
|
|
470
|
+
)
|
|
471
|
+
event.run.facets = {}
|
|
472
|
+
|
|
473
|
+
modified_event = client.add_environment_facets(event)
|
|
474
|
+
|
|
475
|
+
assert "environmentVariables" in modified_event.run.facets
|
|
476
|
+
assert modified_event.run.facets["environmentVariables"] == EnvironmentVariablesRunFacet(
|
|
477
|
+
[EnvironmentVariable(name="ENV_VAR_1", value="value1")]
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@patch("openlineage.client.client.OpenLineageClient._find_yaml_config_path")
|
|
482
|
+
@patch("openlineage.client.client.OpenLineageClient._get_config_file_content")
|
|
483
|
+
def test_config_property_loads_yaml(mock_get_config_content, mock_find_yaml):
|
|
484
|
+
mock_find_yaml.return_value = "config.yml"
|
|
485
|
+
mock_get_config_content.return_value = {"transport": {"type": "http", "url": "http://localhost:5050"}}
|
|
486
|
+
|
|
487
|
+
config = OpenLineageClient().config
|
|
488
|
+
assert config.transport["type"] == "http"
|
|
489
|
+
assert config.transport["url"] == "http://localhost:5050"
|
|
370
490
|
|
|
371
491
|
|
|
372
492
|
@patch.dict(
|
|
@@ -490,6 +610,155 @@ def test_kafka_transport_configured_with_aliased_message_key() -> None:
|
|
|
490
610
|
}
|
|
491
611
|
|
|
492
612
|
|
|
613
|
+
@patch.dict(
|
|
614
|
+
os.environ,
|
|
615
|
+
{
|
|
616
|
+
"CUSTOM_ENV_VAR": "custom_value",
|
|
617
|
+
"OPENLINEAGE__TRANSPORT__TYPE": "console",
|
|
618
|
+
"OPENLINEAGE__FACETS__ENVIRONMENT_VARIABLES": '["CUSTOM_ENV_VAR"]',
|
|
619
|
+
},
|
|
620
|
+
)
|
|
621
|
+
@patch("openlineage.client.client.OpenLineageClient._resolve_transport")
|
|
622
|
+
def test_add_environment_facets_with_custom_env_var(mock_resolve_transport) -> None:
|
|
623
|
+
mock_resolve_transport.return_value = mock_transport = MagicMock()
|
|
624
|
+
client = OpenLineageClient()
|
|
625
|
+
run = Run(runId=str(generate_new_uuid()))
|
|
626
|
+
event = RunEvent(
|
|
627
|
+
eventType=RunState.START,
|
|
628
|
+
eventTime="2021-11-03T10:53:52.427343",
|
|
629
|
+
run=run,
|
|
630
|
+
job=Job(name="name", namespace=""),
|
|
631
|
+
producer="",
|
|
632
|
+
schemaURL="",
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
client.emit(event)
|
|
636
|
+
assert mock_transport.emit.call_args[0][0].run.facets[
|
|
637
|
+
"environmentVariables"
|
|
638
|
+
] == EnvironmentVariablesRunFacet([EnvironmentVariable(name="CUSTOM_ENV_VAR", value="custom_value")])
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
@patch.dict(
|
|
642
|
+
os.environ,
|
|
643
|
+
{
|
|
644
|
+
"OPENLINEAGE__TRANSPORT__TYPE": "http",
|
|
645
|
+
"OPENLINEAGE__TRANSPORT__URL": "http://localhost:5050",
|
|
646
|
+
"OPENLINEAGE__TRANSPORT__AUTH__API_KEY": "random_token",
|
|
647
|
+
},
|
|
648
|
+
)
|
|
649
|
+
@patch("openlineage.client.client.OpenLineageClient._find_yaml_config_path")
|
|
650
|
+
def test_config_property_loads_env_vars(mock_find_yaml) -> None:
|
|
651
|
+
mock_find_yaml.return_value = None
|
|
652
|
+
client = OpenLineageClient()
|
|
653
|
+
config = client.config
|
|
654
|
+
assert config.transport["type"] == "http"
|
|
655
|
+
assert config.transport["url"] == "http://localhost:5050"
|
|
656
|
+
assert config.transport["auth"]["api_key"] == "random_token"
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def test_config_property_loads_user_defined_config() -> None:
|
|
660
|
+
user_defined_config = {
|
|
661
|
+
"transport": {
|
|
662
|
+
"type": "http",
|
|
663
|
+
"url": "http://localhost:5050",
|
|
664
|
+
"auth": {"api_key": "random_token"},
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
client = OpenLineageClient(config=user_defined_config)
|
|
668
|
+
config = client.config
|
|
669
|
+
assert config.transport["type"] == "http"
|
|
670
|
+
assert config.transport["url"] == "http://localhost:5050"
|
|
671
|
+
assert config.transport["auth"]["api_key"] == "random_token"
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@patch.dict(
|
|
675
|
+
os.environ,
|
|
676
|
+
{
|
|
677
|
+
"OPENLINEAGE__TRANSPORT__TYPE": "http",
|
|
678
|
+
"OPENLINEAGE__TRANSPORT__URL": "http://localhost:5050",
|
|
679
|
+
},
|
|
680
|
+
)
|
|
681
|
+
def test_client_from_empty_dict_with_dynamic_env_vars() -> None:
|
|
682
|
+
client = OpenLineageClient.from_dict({})
|
|
683
|
+
assert client.transport.url == "http://localhost:5050"
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
@patch.dict(
|
|
687
|
+
os.environ,
|
|
688
|
+
{
|
|
689
|
+
"OPENLINEAGE_URL": "http://example.com",
|
|
690
|
+
},
|
|
691
|
+
)
|
|
692
|
+
def test_client_from_empty_dict_with_url_env_var() -> None:
|
|
693
|
+
client = OpenLineageClient.from_dict({})
|
|
694
|
+
assert client.transport.url == "http://example.com"
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
@patch.dict(
|
|
698
|
+
os.environ,
|
|
699
|
+
{
|
|
700
|
+
"OPENLINEAGE__TRANSPORT__TYPE": "http",
|
|
701
|
+
"OPENLINEAGE__TRANSPORT__URL": "http://localhost:5050",
|
|
702
|
+
},
|
|
703
|
+
)
|
|
704
|
+
def test_client_from_facets_config_in_dict_and_env_vars() -> None:
|
|
705
|
+
user_defined_config = {
|
|
706
|
+
"facets": {
|
|
707
|
+
"environment_variables": ["VAR1", "VAR2"],
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
client = OpenLineageClient.from_dict(user_defined_config)
|
|
711
|
+
assert client.config.facets.environment_variables == ["VAR1", "VAR2"]
|
|
712
|
+
assert client.transport.url == "http://localhost:5050"
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
@patch.dict(
|
|
716
|
+
os.environ,
|
|
717
|
+
{
|
|
718
|
+
"OPENLINEAGE__FACETS__ENVIRONMENT_VARIABLES": '["VAR1", "VAR2"]',
|
|
719
|
+
},
|
|
720
|
+
)
|
|
721
|
+
def test_client_from_facets_config_in_env_vars_and_transport_in_config() -> None:
|
|
722
|
+
user_defined_config = {
|
|
723
|
+
"transport": {
|
|
724
|
+
"type": "http",
|
|
725
|
+
"url": "http://localhost:5050",
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
client = OpenLineageClient.from_dict(user_defined_config)
|
|
729
|
+
assert client.config.facets.environment_variables == ["VAR1", "VAR2"]
|
|
730
|
+
assert client.transport.url == "http://localhost:5050"
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
@patch.dict(
|
|
734
|
+
os.environ,
|
|
735
|
+
{
|
|
736
|
+
"OPENLINEAGE__TRANSPORT__AUTH__API_KEY": "random_token",
|
|
737
|
+
},
|
|
738
|
+
)
|
|
739
|
+
@patch("openlineage.client.client.OpenLineageClient._find_yaml_config_path")
|
|
740
|
+
@patch("openlineage.client.client.OpenLineageClient._get_config_file_content")
|
|
741
|
+
def test_config_merge_precedence(mock_get_config_content, mock_find_yaml) -> None:
|
|
742
|
+
user_defined_config = {
|
|
743
|
+
"transport": {
|
|
744
|
+
"type": "http",
|
|
745
|
+
"url": "http://localhost:5050",
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
mock_find_yaml.return_value = "config.yml"
|
|
749
|
+
mock_get_config_content.return_value = {
|
|
750
|
+
"transport": {
|
|
751
|
+
"url": "http://another.host:5050",
|
|
752
|
+
"auth": {"api_key": "another_token"},
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
client = OpenLineageClient.from_dict(user_defined_config)
|
|
756
|
+
config = client.config
|
|
757
|
+
assert config.transport["type"] == "http"
|
|
758
|
+
assert config.transport["url"] == "http://localhost:5050"
|
|
759
|
+
assert config.transport["auth"]["api_key"] == "another_token"
|
|
760
|
+
|
|
761
|
+
|
|
493
762
|
class TestOpenLineageConfigLoader:
|
|
494
763
|
@pytest.mark.parametrize(
|
|
495
764
|
("env_vars", "expected_config"),
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright 2018-2024 contributors to the OpenLineage project
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from openlineage.client.utils import deep_merge_dicts, import_from_string, try_import_from_string
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_import_from_string():
|
|
10
|
+
from openlineage.client.filter import ExactMatchFilter
|
|
11
|
+
|
|
12
|
+
result = import_from_string("openlineage.client.filter.ExactMatchFilter")
|
|
13
|
+
assert result == ExactMatchFilter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_import_from_string_unknown_path():
|
|
17
|
+
with pytest.raises(ImportError):
|
|
18
|
+
import_from_string("openlineage.client.non-existing-module")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_try_import_from_string():
|
|
22
|
+
from openlineage.client.filter import ExactMatchFilter
|
|
23
|
+
|
|
24
|
+
result = try_import_from_string("openlineage.client.filter.ExactMatchFilter")
|
|
25
|
+
assert result == ExactMatchFilter
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_try_import_from_string_unknown():
|
|
29
|
+
result = try_import_from_string("openlineage.client.non-existing-module")
|
|
30
|
+
assert result is None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_deep_merge_dicts_simple():
|
|
34
|
+
dict1 = {"a": 1, "b": 2}
|
|
35
|
+
dict2 = {"b": 3, "c": 4}
|
|
36
|
+
expected = {"a": 1, "b": 3, "c": 4}
|
|
37
|
+
assert deep_merge_dicts(dict1, dict2) == expected
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_deep_merge_dicts_nested():
|
|
41
|
+
dict1 = {"a": {"x": 1}, "b": 2}
|
|
42
|
+
dict2 = {"a": {"y": 2}, "b": 3, "c": 4}
|
|
43
|
+
expected = {"a": {"x": 1, "y": 2}, "b": 3, "c": 4}
|
|
44
|
+
assert deep_merge_dicts(dict1, dict2) == expected
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_deep_merge_dicts_overwrite():
|
|
48
|
+
dict1 = {"a": {"x": 1}}
|
|
49
|
+
dict2 = {"a": {"x": 2}}
|
|
50
|
+
expected = {"a": {"x": 2}}
|
|
51
|
+
assert deep_merge_dicts(dict1, dict2) == expected
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_deep_merge_dicts_non_dict_values():
|
|
55
|
+
dict1 = {"a": 1, "b": {"x": 1}}
|
|
56
|
+
dict2 = {"b": 2, "c": 3}
|
|
57
|
+
expected = {"a": 1, "b": 2, "c": 3}
|
|
58
|
+
assert deep_merge_dicts(dict1, dict2) == expected
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_deep_merge_dicts_empty_dicts():
|
|
62
|
+
dict1 = {}
|
|
63
|
+
dict2 = {"a": 1}
|
|
64
|
+
expected = {"a": 1}
|
|
65
|
+
assert deep_merge_dicts(dict1, dict2) == expected
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Copyright 2018-2024 contributors to the OpenLineage project
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from openlineage.client.utils import import_from_string, try_import_from_string
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def test_import_from_string():
|
|
10
|
-
from openlineage.client.filter import ExactMatchFilter
|
|
11
|
-
|
|
12
|
-
result = import_from_string("openlineage.client.filter.ExactMatchFilter")
|
|
13
|
-
assert result == ExactMatchFilter
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_import_from_string_unknown_path():
|
|
17
|
-
with pytest.raises(ImportError):
|
|
18
|
-
import_from_string("openlineage.client.non-existing-module")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def test_try_import_from_string():
|
|
22
|
-
from openlineage.client.filter import ExactMatchFilter
|
|
23
|
-
|
|
24
|
-
result = try_import_from_string("openlineage.client.filter.ExactMatchFilter")
|
|
25
|
-
assert result == ExactMatchFilter
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def test_try_import_from_string_unknown():
|
|
29
|
-
result = try_import_from_string("openlineage.client.non-existing-module")
|
|
30
|
-
assert result is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/job_type_job.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/parent_run.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/generated/sql_job.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/__init__.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/composite.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/console.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/factory.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/file.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/http.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/kafka.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/msk_iam.py
RENAMED
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/openlineage/client/transport/noop.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlineage_python-1.23.0 → openlineage_python-1.24.0}/tests/serde_example_dataset_event.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|