openlineage-python 1.30.1__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.30.1 → openlineage_python-1.32.0}/PKG-INFO +8 -4
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/client.py +10 -5
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/constants.py +1 -1
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/facet.py +26 -26
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/filter.py +8 -1
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/parent_run.py +42 -1
- openlineage_python-1.32.0/openlineage/client/generator/base.py +257 -0
- openlineage_python-1.32.0/openlineage/client/generator/cli.py +53 -0
- openlineage_python-1.32.0/openlineage/client/generator/generate.py +100 -0
- openlineage_python-1.32.0/openlineage/client/generator/templates/Enum.jinja2 +22 -0
- openlineage_python-1.32.0/openlineage/client/generator/templates/dataclass.jinja2 +196 -0
- openlineage_python-1.32.0/openlineage/client/generator/templates/facet_v2.jinja2 +25 -0
- openlineage_python-1.32.0/openlineage/client/generator/templates/root.jinja2 +1 -0
- openlineage_python-1.32.0/openlineage/client/generator/templates/validators.jinja2 +43 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/run.py +16 -16
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/serde.py +4 -4
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/composite.py +10 -5
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/console.py +4 -3
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/http.py +34 -20
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/kafka.py +41 -19
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/transport.py +3 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/utils.py +2 -2
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/pyproject.toml +13 -4
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/redact_fields.yml +11 -0
- openlineage_python-1.32.0/tests/__init__.py +2 -0
- openlineage_python-1.32.0/tests/generator/__init__.py +2 -0
- openlineage_python-1.32.0/tests/generator/test_base.py +148 -0
- openlineage_python-1.32.0/tests/generator/test_cli.py +53 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_client.py +71 -5
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_file.py +3 -3
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_http.py +70 -3
- openlineage_python-1.32.0/tests/test_kafka.py +477 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tox.ini +2 -1
- openlineage_python-1.30.1/tests/test_kafka.py +0 -338
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/.gitignore +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/README.md +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/docs/Makefile +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/docs/conf.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/docs/convert.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/docs/index.rst +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/docs/make.bat +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/docs/source/openlineage.client.rst +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/__init__.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/event_v2.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/facet_v2.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/facets.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/base.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/dataset_type_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/datasource_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/documentation_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/documentation_job.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/environment_variables_run.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/error_message_run.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/external_query_run.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/extraction_error_run.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/job_type_job.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/nominal_time_run.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/ownership_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/ownership_job.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/processing_engine_run.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/schema_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/source_code_job.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/source_code_location_job.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/sql_job.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/storage_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/tags_dataset.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/tags_job.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/tags_run.py +0 -0
- openlineage_python-1.30.1/tests/__init__.py → openlineage_python-1.32.0/openlineage/client/generator/header.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/py.typed +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/tags.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/__init__.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/factory.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/file.py +1 -1
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/msk_iam.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/transport/noop.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/uuid.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/config/config.yml +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/config/exact_filter.yml +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/config/http.yml +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/config/openlineage.yml +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/config/regex_filter.yml +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/conftest.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/example_full_event.json +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/nominal_time_without_end.json +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/serde_example_dataset_event.json +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/serde_example_job_event.json +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/serde_example_run_event.json +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_composite.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_events.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_facet.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_facet_v2.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_factory.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_msk_iam.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_utils.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/test_uuid.py +0 -0
- {openlineage_python-1.30.1 → openlineage_python-1.32.0}/tests/transport.py +0 -0
|
@@ -1,28 +1,32 @@
|
|
|
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'
|
|
24
24
|
Requires-Dist: sphinx-autodoc-typehints>=2.0.0; extra == 'docs'
|
|
25
25
|
Requires-Dist: sphinx>=7.0; extra == 'docs'
|
|
26
|
+
Provides-Extra: generator
|
|
27
|
+
Requires-Dist: click; extra == 'generator'
|
|
28
|
+
Requires-Dist: datamodel-code-generator==0.25.4; extra == 'generator'
|
|
29
|
+
Requires-Dist: ruff; extra == 'generator'
|
|
26
30
|
Provides-Extra: kafka
|
|
27
31
|
Requires-Dist: confluent-kafka>=2.1.1; extra == 'kafka'
|
|
28
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
|
|
|
@@ -163,6 +163,11 @@ class OpenLineageClient:
|
|
|
163
163
|
if type(event) not in get_args(Event):
|
|
164
164
|
msg = "`emit` only accepts RunEvent, DatasetEvent, JobEvent classes"
|
|
165
165
|
raise ValueError(msg)
|
|
166
|
+
|
|
167
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
168
|
+
val = Serde.to_json(event).encode("utf-8")
|
|
169
|
+
log.debug("OpenLineageClient will *try* to emit event %s", val)
|
|
170
|
+
|
|
166
171
|
if not self.transport:
|
|
167
172
|
log.error("Tried to emit OpenLineage event, but transport is not configured.")
|
|
168
173
|
return
|
|
@@ -176,9 +181,6 @@ class OpenLineageClient:
|
|
|
176
181
|
event = self.add_environment_facets(event)
|
|
177
182
|
event = self.update_event_tags_facets(event)
|
|
178
183
|
|
|
179
|
-
if log.isEnabledFor(logging.DEBUG):
|
|
180
|
-
val = Serde.to_json(event).encode("utf-8")
|
|
181
|
-
log.debug("OpenLineageClient will emit event %s", val)
|
|
182
184
|
self.transport.emit(event)
|
|
183
185
|
log.debug("OpenLineage event successfully emitted.")
|
|
184
186
|
|
|
@@ -395,7 +397,10 @@ class OpenLineageClient:
|
|
|
395
397
|
"""
|
|
396
398
|
Adds environment variables as facets to the event object.
|
|
397
399
|
"""
|
|
398
|
-
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 {}
|
|
399
404
|
event.run.facets["environmentVariables"] = EnvironmentVariablesRunFacet(
|
|
400
405
|
environmentVariables=[
|
|
401
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
|
{openlineage_python-1.30.1 → openlineage_python-1.32.0}/openlineage/client/generated/parent_run.py
RENAMED
|
@@ -28,11 +28,12 @@ class ParentRunFacet(RunFacet):
|
|
|
28
28
|
|
|
29
29
|
run: Run
|
|
30
30
|
job: Job
|
|
31
|
+
root: Root | None = attr.field(default=None)
|
|
31
32
|
_additional_skip_redact: ClassVar[list[str]] = ["job", "run"]
|
|
32
33
|
|
|
33
34
|
@staticmethod
|
|
34
35
|
def _get_schema() -> str:
|
|
35
|
-
return "https://openlineage.io/spec/facets/1-0
|
|
36
|
+
return "https://openlineage.io/spec/facets/1-1-0/ParentRunFacet.json#/$defs/ParentRunFacet"
|
|
36
37
|
|
|
37
38
|
@classmethod
|
|
38
39
|
def create(cls, runId: str, namespace: str, name: str) -> ParentRunFacet: # noqa: N803
|
|
@@ -46,6 +47,46 @@ class ParentRunFacet(RunFacet):
|
|
|
46
47
|
return cls(run=Run(runId=runId), job=Job(namespace=namespace, name=name))
|
|
47
48
|
|
|
48
49
|
|
|
50
|
+
@attr.define
|
|
51
|
+
class Root(RedactMixin):
|
|
52
|
+
run: RootRun
|
|
53
|
+
job: RootJob
|
|
54
|
+
_skip_redact: ClassVar[list[str]] = ["run", "job"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@attr.define
|
|
58
|
+
class RootJob(RedactMixin):
|
|
59
|
+
namespace: str
|
|
60
|
+
"""The namespace containing root job"""
|
|
61
|
+
|
|
62
|
+
name: str
|
|
63
|
+
"""The unique name containing root job within that namespace"""
|
|
64
|
+
|
|
65
|
+
_skip_redact: ClassVar[list[str]] = ["namespace", "name"]
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _get_schema() -> str:
|
|
69
|
+
return "https://openlineage.io/spec/facets/1-1-0/ParentRunFacet.json#/$defs/RootJob"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@attr.define
|
|
73
|
+
class RootRun(RedactMixin):
|
|
74
|
+
runId: str = attr.field() # noqa: N815
|
|
75
|
+
"""The globally unique ID of the root run associated with the root job."""
|
|
76
|
+
|
|
77
|
+
_skip_redact: ClassVar[list[str]] = ["runId"]
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _get_schema() -> str:
|
|
81
|
+
return "https://openlineage.io/spec/facets/1-1-0/ParentRunFacet.json#/$defs/RootRun"
|
|
82
|
+
|
|
83
|
+
@runId.validator
|
|
84
|
+
def runid_check(self, attribute: str, value: str) -> None: # noqa: ARG002
|
|
85
|
+
from uuid import UUID
|
|
86
|
+
|
|
87
|
+
UUID(value)
|
|
88
|
+
|
|
89
|
+
|
|
49
90
|
@attr.define
|
|
50
91
|
class Run(RedactMixin):
|
|
51
92
|
runId: str = attr.field() # noqa: N815
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Copyright 2018-2025 contributors to the OpenLineage project
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import pathlib
|
|
10
|
+
import re
|
|
11
|
+
import subprocess
|
|
12
|
+
import tempfile
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from datamodel_code_generator import DataModelType, PythonVersion
|
|
16
|
+
from datamodel_code_generator.imports import Import
|
|
17
|
+
from datamodel_code_generator.model import get_data_model_types
|
|
18
|
+
from datamodel_code_generator.model import pydantic as pydantic_model
|
|
19
|
+
from datamodel_code_generator.parser.jsonschema import JsonSchemaParser
|
|
20
|
+
from datamodel_code_generator.types import Types
|
|
21
|
+
|
|
22
|
+
log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def camel_to_snake(string: str) -> str:
|
|
26
|
+
"""Convert camel_case string to SnakeCase"""
|
|
27
|
+
_under_score_1 = re.compile(r"([^_])([A-Z][a-z]+)")
|
|
28
|
+
_under_score_2 = re.compile("([a-z0-9])([A-Z])")
|
|
29
|
+
subbed = _under_score_1.sub(r"\1_\2", string)
|
|
30
|
+
return _under_score_2.sub(r"\1_\2", subbed).lower()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# import attrs instead of dataclass
|
|
34
|
+
DATA_MODELS_TYPES = get_data_model_types(
|
|
35
|
+
DataModelType.DataclassesDataclass, target_python_version=PythonVersion.PY_38
|
|
36
|
+
)
|
|
37
|
+
NEW_MODEL = DATA_MODELS_TYPES.data_model
|
|
38
|
+
NEW_MODEL.DEFAULT_IMPORTS = (Import.from_full_path("attr"),)
|
|
39
|
+
|
|
40
|
+
# custom code
|
|
41
|
+
SET_PRODUCER_CODE = """
|
|
42
|
+
PRODUCER = DEFAULT_PRODUCER
|
|
43
|
+
|
|
44
|
+
def set_producer(producer: str) -> None:
|
|
45
|
+
global PRODUCER # noqa: PLW0603
|
|
46
|
+
PRODUCER = producer
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# locations definitions
|
|
50
|
+
FILE_LOCATION = pathlib.Path(__file__).resolve().parent
|
|
51
|
+
TEMPLATES_LOCATION = FILE_LOCATION / "templates"
|
|
52
|
+
|
|
53
|
+
# Global dictionaries intended to be imported by other modules
|
|
54
|
+
# Contains schema URLs and base IDs parsed from specification files
|
|
55
|
+
SCHEMA_URLS: dict[str, str] = {}
|
|
56
|
+
BASE_IDS: dict[str, str] = {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def deep_merge_dicts(dict1: dict[str, Any], dict2: dict[str, Any]) -> dict[str, Any]:
|
|
60
|
+
"""Deep merges two dictionaries.
|
|
61
|
+
|
|
62
|
+
This function merges two dictionaries while handling nested dictionaries.
|
|
63
|
+
For keys that exist in both dictionaries, the values from dict2 take precedence.
|
|
64
|
+
If a key exists in both dictionaries and the values are dictionaries themselves,
|
|
65
|
+
they are merged recursively.
|
|
66
|
+
This function merges only dictionaries. If key is of different type, e.g. list
|
|
67
|
+
it does not work properly.
|
|
68
|
+
"""
|
|
69
|
+
merged = dict1.copy()
|
|
70
|
+
for k, v in dict2.items():
|
|
71
|
+
if k in merged and isinstance(v, dict):
|
|
72
|
+
merged[k] = deep_merge_dicts(merged.get(k, {}), v)
|
|
73
|
+
else:
|
|
74
|
+
merged[k] = v
|
|
75
|
+
return merged
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_additional_data(spec: dict[str, Any], file_name: str) -> None:
|
|
79
|
+
"""Parse additional data from spec files.
|
|
80
|
+
|
|
81
|
+
Parses:
|
|
82
|
+
* schema URLs
|
|
83
|
+
* base IDs
|
|
84
|
+
|
|
85
|
+
Updates the module-level SCHEMA_URLS and BASE_IDS dictionaries
|
|
86
|
+
that can be imported by other modules.
|
|
87
|
+
"""
|
|
88
|
+
base_id = spec["$id"]
|
|
89
|
+
for name, _ in spec["$defs"].items():
|
|
90
|
+
SCHEMA_URLS[name] = f"{base_id}#/$defs/{name}"
|
|
91
|
+
BASE_IDS[file_name] = spec["$id"]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def load_specs(base_spec_location: pathlib.Path, facets_spec_location: pathlib.Path) -> list[pathlib.Path]:
|
|
95
|
+
"""Load base `OpenLineage.json` and other facets' spec files"""
|
|
96
|
+
locations = []
|
|
97
|
+
if facets_spec_location.is_dir():
|
|
98
|
+
facets_spec_files = sorted(pathlib.Path(os.path.abspath(facets_spec_location)).glob("*.json"))
|
|
99
|
+
else:
|
|
100
|
+
facets_spec_files = [pathlib.Path(os.path.abspath(facets_spec_location))]
|
|
101
|
+
|
|
102
|
+
file_specs = [
|
|
103
|
+
base_spec_location.resolve(), # Base spec must be the first element
|
|
104
|
+
*facets_spec_files,
|
|
105
|
+
]
|
|
106
|
+
for file_spec in file_specs:
|
|
107
|
+
spec = json.loads(file_spec.read_text())
|
|
108
|
+
parse_additional_data(spec, file_spec.name)
|
|
109
|
+
locations.append(file_spec)
|
|
110
|
+
|
|
111
|
+
return locations
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def parse_and_generate(
|
|
115
|
+
locations: list[pathlib.Path], extra_template_data: dict[str, Any] | None = None
|
|
116
|
+
) -> dict[str, Any]:
|
|
117
|
+
"""Parse and generate data models from a given specification."""
|
|
118
|
+
temporary_locations = []
|
|
119
|
+
|
|
120
|
+
current_dir = pathlib.Path.cwd()
|
|
121
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
122
|
+
tmp_directory = pathlib.Path(tmp).resolve()
|
|
123
|
+
for location in locations:
|
|
124
|
+
tmp_location = (tmp_directory / location.name).resolve()
|
|
125
|
+
tmp_location.write_text(location.read_text())
|
|
126
|
+
temporary_locations.append(tmp_location)
|
|
127
|
+
|
|
128
|
+
os.chdir(tmp_directory)
|
|
129
|
+
# first parse OpenLineage.json
|
|
130
|
+
parser = JsonSchemaParser(
|
|
131
|
+
source=temporary_locations[:1],
|
|
132
|
+
data_model_type=NEW_MODEL,
|
|
133
|
+
data_model_root_type=DATA_MODELS_TYPES.root_model,
|
|
134
|
+
data_model_field_type=DATA_MODELS_TYPES.field_model,
|
|
135
|
+
data_type_manager_type=DATA_MODELS_TYPES.data_type_manager,
|
|
136
|
+
dump_resolve_reference_action=DATA_MODELS_TYPES.dump_resolve_reference_action,
|
|
137
|
+
special_field_name_prefix="",
|
|
138
|
+
use_schema_description=True,
|
|
139
|
+
field_constraints=True,
|
|
140
|
+
use_union_operator=True,
|
|
141
|
+
use_standard_collections=True,
|
|
142
|
+
base_class="openlineage.client.utils.RedactMixin",
|
|
143
|
+
class_name="ClassToBeSkipped",
|
|
144
|
+
use_field_description=True,
|
|
145
|
+
use_double_quotes=True,
|
|
146
|
+
keep_model_order=True,
|
|
147
|
+
custom_template_dir=TEMPLATES_LOCATION,
|
|
148
|
+
extra_template_data=extra_template_data, # type: ignore[arg-type]
|
|
149
|
+
additional_imports=[
|
|
150
|
+
"typing.ClassVar",
|
|
151
|
+
"typing.Any",
|
|
152
|
+
"typing.cast",
|
|
153
|
+
"openlineage.client.constants.DEFAULT_PRODUCER",
|
|
154
|
+
],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# keep information about uuid and date-time formats
|
|
158
|
+
# this is going to be changed back to str type hint in jinja template
|
|
159
|
+
data_type_manager: pydantic_model.DataTypeManager = parser.data_type_manager # type: ignore[assignment]
|
|
160
|
+
uuid_type = copy.deepcopy(data_type_manager.type_map[Types.uuid])
|
|
161
|
+
uuid_type.type = "uuid"
|
|
162
|
+
data_type_manager.type_map[Types.uuid] = uuid_type
|
|
163
|
+
|
|
164
|
+
date_time_type = copy.deepcopy(data_type_manager.type_map[Types.date_time])
|
|
165
|
+
date_time_type.type = "date-time"
|
|
166
|
+
data_type_manager.type_map[Types.date_time] = date_time_type
|
|
167
|
+
|
|
168
|
+
uri_type = copy.deepcopy(data_type_manager.type_map[Types.date_time])
|
|
169
|
+
uri_type.type = "uri"
|
|
170
|
+
data_type_manager.type_map[Types.uri] = uri_type
|
|
171
|
+
|
|
172
|
+
parser.parse(format_=False)
|
|
173
|
+
|
|
174
|
+
# parse rest of spec
|
|
175
|
+
parser.source = temporary_locations[1:]
|
|
176
|
+
|
|
177
|
+
# change paths so that parser sees base objects as local
|
|
178
|
+
parser.model_resolver.references = {
|
|
179
|
+
k.replace("OpenLineage.json", BASE_IDS["OpenLineage.json"]): v
|
|
180
|
+
for k, v in parser.model_resolver.references.items()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
output = parser.parse(format_=False)
|
|
184
|
+
|
|
185
|
+
# go back to original directory
|
|
186
|
+
os.chdir(current_dir)
|
|
187
|
+
|
|
188
|
+
return output # type: ignore[return-value]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def separate_imports(code: str) -> tuple[str, str]:
|
|
192
|
+
"""Separates a Python script code (as string) into imports and the rest."""
|
|
193
|
+
imports_section = []
|
|
194
|
+
rest_of_code = []
|
|
195
|
+
in_import = False
|
|
196
|
+
for line in code.splitlines():
|
|
197
|
+
if line.startswith(("import ", "from ")):
|
|
198
|
+
in_import = True
|
|
199
|
+
elif in_import and line.strip() == "":
|
|
200
|
+
in_import = False
|
|
201
|
+
if in_import:
|
|
202
|
+
imports_section.append(line)
|
|
203
|
+
else:
|
|
204
|
+
rest_of_code.append(line)
|
|
205
|
+
return ("\n".join(imports_section), "\n".join(rest_of_code))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def format_and_save_output(
|
|
209
|
+
output: str,
|
|
210
|
+
location: pathlib.Path,
|
|
211
|
+
add_set_producer_code: bool | None = False,
|
|
212
|
+
header: str | None = None,
|
|
213
|
+
) -> None:
|
|
214
|
+
"""Adjust and format generated file."""
|
|
215
|
+
|
|
216
|
+
# save temporary file to the same directory that output
|
|
217
|
+
# so that ruff receives same rules
|
|
218
|
+
with tempfile.NamedTemporaryFile(
|
|
219
|
+
"w", prefix=location.stem.lower(), suffix=".py", dir=location.parent, delete=False
|
|
220
|
+
) as tmpfile:
|
|
221
|
+
if header:
|
|
222
|
+
tmpfile.write(header + "\n")
|
|
223
|
+
output = output.replace("from .OpenLineage", "from openlineage.client.generated.base")
|
|
224
|
+
imports_section, rest_of_code = separate_imports(output)
|
|
225
|
+
tmpfile.write(imports_section)
|
|
226
|
+
if add_set_producer_code:
|
|
227
|
+
tmpfile.write(SET_PRODUCER_CODE)
|
|
228
|
+
tmpfile.write(rest_of_code)
|
|
229
|
+
tmpfile.flush()
|
|
230
|
+
|
|
231
|
+
# run ruff lint
|
|
232
|
+
with subprocess.Popen(
|
|
233
|
+
args=["ruff", "check", tmpfile.name, "--fix"],
|
|
234
|
+
stdout=subprocess.DEVNULL,
|
|
235
|
+
stderr=subprocess.DEVNULL,
|
|
236
|
+
close_fds=True,
|
|
237
|
+
) as lint_process:
|
|
238
|
+
if lint_process.returncode:
|
|
239
|
+
log.warning("Ruff lint failed: %s", lint_process.returncode)
|
|
240
|
+
|
|
241
|
+
# run ruff format
|
|
242
|
+
with subprocess.Popen(
|
|
243
|
+
args=["ruff", "format", tmpfile.name],
|
|
244
|
+
stdout=subprocess.DEVNULL,
|
|
245
|
+
stderr=subprocess.DEVNULL,
|
|
246
|
+
close_fds=True,
|
|
247
|
+
) as format_process:
|
|
248
|
+
if format_process.returncode:
|
|
249
|
+
log.warning("Ruff lint failed: %s", format_process.returncode)
|
|
250
|
+
|
|
251
|
+
# move file to output location
|
|
252
|
+
if location.name == "open_lineage.py":
|
|
253
|
+
location = location.with_name("base.py")
|
|
254
|
+
os.rename(tmpfile.name, location)
|
|
255
|
+
|
|
256
|
+
if lint_process.returncode or format_process.returncode:
|
|
257
|
+
log.warning("%s failed on ruff.", location)
|