openlineage-python 1.28.0__tar.gz → 1.30.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/PKG-INFO +1 -1
  2. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/client.py +59 -1
  3. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/constants.py +1 -1
  4. openlineage_python-1.30.0/openlineage/client/tags.py +17 -0
  5. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/pyproject.toml +1 -1
  6. openlineage_python-1.30.0/tests/conftest.py +54 -0
  7. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_client.py +202 -1
  8. openlineage_python-1.28.0/tests/conftest.py +0 -18
  9. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/.gitignore +0 -0
  10. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/README.md +0 -0
  11. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/docs/Makefile +0 -0
  12. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/docs/conf.py +0 -0
  13. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/docs/convert.py +0 -0
  14. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/docs/index.rst +0 -0
  15. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/docs/make.bat +0 -0
  16. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/docs/source/openlineage.client.rst +0 -0
  17. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/__init__.py +0 -0
  18. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/event_v2.py +0 -0
  19. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/facet.py +0 -0
  20. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/facet_v2.py +0 -0
  21. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/facets.py +0 -0
  22. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/filter.py +0 -0
  23. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/base.py +0 -0
  24. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/column_lineage_dataset.py +0 -0
  25. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
  26. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
  27. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/dataset_type_dataset.py +0 -0
  28. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/dataset_version_dataset.py +0 -0
  29. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/datasource_dataset.py +0 -0
  30. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/documentation_dataset.py +0 -0
  31. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/documentation_job.py +0 -0
  32. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/environment_variables_run.py +0 -0
  33. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/error_message_run.py +0 -0
  34. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/external_query_run.py +0 -0
  35. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/extraction_error_run.py +0 -0
  36. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/input_statistics_input_dataset.py +0 -0
  37. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/job_type_job.py +0 -0
  38. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
  39. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/nominal_time_run.py +0 -0
  40. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
  41. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/ownership_dataset.py +0 -0
  42. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/ownership_job.py +0 -0
  43. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/parent_run.py +0 -0
  44. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/processing_engine_run.py +0 -0
  45. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/schema_dataset.py +0 -0
  46. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/source_code_job.py +0 -0
  47. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/source_code_location_job.py +0 -0
  48. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/sql_job.py +0 -0
  49. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/storage_dataset.py +0 -0
  50. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/symlinks_dataset.py +0 -0
  51. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/tags_dataset.py +0 -0
  52. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/tags_job.py +0 -0
  53. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/generated/tags_run.py +0 -0
  54. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/py.typed +0 -0
  55. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/run.py +0 -0
  56. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/serde.py +0 -0
  57. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/__init__.py +0 -0
  58. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/composite.py +0 -0
  59. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/console.py +0 -0
  60. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/factory.py +0 -0
  61. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/file.py +0 -0
  62. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/http.py +0 -0
  63. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/kafka.py +0 -0
  64. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/msk_iam.py +0 -0
  65. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/noop.py +0 -0
  66. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/transport/transport.py +0 -0
  67. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/utils.py +0 -0
  68. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/openlineage/client/uuid.py +0 -0
  69. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/redact_fields.yml +0 -0
  70. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/__init__.py +0 -0
  71. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/config/config.yml +0 -0
  72. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/config/exact_filter.yml +0 -0
  73. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/config/http.yml +0 -0
  74. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/config/openlineage.yml +0 -0
  75. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/config/regex_filter.yml +0 -0
  76. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/example_full_event.json +0 -0
  77. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/nominal_time_without_end.json +0 -0
  78. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/serde_example_dataset_event.json +0 -0
  79. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/serde_example_job_event.json +0 -0
  80. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/serde_example_run_event.json +0 -0
  81. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_composite.py +0 -0
  82. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_events.py +0 -0
  83. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_facet.py +0 -0
  84. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_facet_v2.py +0 -0
  85. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_factory.py +0 -0
  86. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_file.py +0 -0
  87. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_http.py +0 -0
  88. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_kafka.py +0 -0
  89. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_msk_iam.py +0 -0
  90. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_utils.py +0 -0
  91. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/test_uuid.py +0 -0
  92. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tests/transport.py +0 -0
  93. {openlineage_python-1.28.0 → openlineage_python-1.30.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openlineage-python
3
- Version: 1.28.0
3
+ Version: 1.30.0
4
4
  Summary: OpenLineage Python Client
5
5
  Author-email: OpenLineage <info@openlineage.io>
6
6
  Keywords: openlineage
@@ -12,6 +12,7 @@ import attr
12
12
  import yaml
13
13
  from openlineage.client.filter import Filter, FilterConfig, create_filter
14
14
  from openlineage.client.serde import Serde
15
+ from openlineage.client.tags import TagsConfig
15
16
  from openlineage.client.utils import deep_merge_dicts
16
17
 
17
18
  if TYPE_CHECKING:
@@ -26,6 +27,8 @@ from openlineage.client.generated.environment_variables_run import (
26
27
  EnvironmentVariable,
27
28
  EnvironmentVariablesRunFacet,
28
29
  )
30
+ from openlineage.client.generated.tags_job import TagsJobFacet, TagsJobFacetFields
31
+ from openlineage.client.generated.tags_run import TagsRunFacet, TagsRunFacetFields
29
32
  from openlineage.client.run import DatasetEvent, JobEvent, RunEvent
30
33
  from openlineage.client.transport import (
31
34
  Transport,
@@ -53,6 +56,7 @@ class OpenLineageConfig:
53
56
  transport: dict[str, Any] | None = attr.ib(factory=dict)
54
57
  facets: FacetsConfig = attr.ib(factory=FacetsConfig)
55
58
  filters: list[FilterConfig] = attr.ib(factory=list)
59
+ tags: TagsConfig = attr.ib(factory=TagsConfig)
56
60
 
57
61
  @classmethod
58
62
  def from_dict(cls, params: dict[str, Any]) -> OpenLineageConfig:
@@ -63,6 +67,13 @@ class OpenLineageConfig:
63
67
  config.facets = FacetsConfig(**params["facets"])
64
68
  if "filters" in params:
65
69
  config.filters = [FilterConfig(**filter_config) for filter_config in params["filters"]]
70
+ if "tags" in params:
71
+ job_tags = params["tags"].get("job", {})
72
+ run_tags = params["tags"].get("run", {})
73
+ config.tags = TagsConfig(
74
+ job=[TagsJobFacetFields(key, value, "USER") for (key, value) in job_tags.items()],
75
+ run=[TagsRunFacetFields(key, value, "USER") for (key, value) in run_tags.items()],
76
+ )
66
77
  return config
67
78
 
68
79
 
@@ -163,6 +174,7 @@ class OpenLineageClient:
163
174
  return
164
175
 
165
176
  event = self.add_environment_facets(event)
177
+ event = self.update_event_tags_facets(event)
166
178
 
167
179
  if log.isEnabledFor(logging.DEBUG):
168
180
  val = Serde.to_json(event).encode("utf-8")
@@ -362,7 +374,6 @@ class OpenLineageClient:
362
374
  # Parse value (try to parse as JSON, otherwise lowercase the value)
363
375
  with contextlib.suppress(json.JSONDecodeError):
364
376
  env_value = json.loads(env_value) # noqa: PLW2901
365
-
366
377
  cls._insert_into_config(config, keys, env_value)
367
378
 
368
379
  return config
@@ -404,3 +415,50 @@ class OpenLineageClient:
404
415
  missing_vars,
405
416
  )
406
417
  return filtered_vars
418
+
419
+ def update_event_tags_facets(self, event: Event) -> Event:
420
+ """
421
+ Creates or updates job and run tag facets based on user-supplied environment variables
422
+ """
423
+ tags_job = self.config.tags.job
424
+ if (isinstance(event, (JobEvent, RunEvent))) and tags_job:
425
+ tags_facet = event.job.facets.get("tags", TagsJobFacet())
426
+ event.job.facets["tags"] = self._update_tag_facet(tags_facet, tags_job) # type: ignore [arg-type]
427
+
428
+ tags_run = self.config.tags.run
429
+ if (isinstance(event, RunEvent)) and tags_run:
430
+ tags_facet = event.run.facets.get("tags", TagsRunFacet())
431
+ event.run.facets["tags"] = self._update_tag_facet(tags_facet, tags_run) # type: ignore [arg-type]
432
+
433
+ return event
434
+
435
+ def _update_tag_facet(
436
+ self,
437
+ tags_facet: TagsJobFacet | TagsRunFacet,
438
+ user_tags: list[TagsJobFacetFields | TagsRunFacetFields],
439
+ ) -> TagsJobFacet | TagsRunFacet:
440
+ """
441
+ Handles updating tags in an existing tag facet
442
+ """
443
+
444
+ # Get tags from the facet that will not be updated (Do not have the same key as a user tag)
445
+ user_tag_keys = [tag.key for tag in user_tags]
446
+ keep_tags = []
447
+ if tags_facet.tags is not None:
448
+ keep_tags = [tag for tag in tags_facet.tags if tag.key.lower() not in user_tag_keys]
449
+
450
+ # Update tag key for any user tags that should override a facet tag.
451
+ # This preserves case of the facet tag key while updating value and source.
452
+ facet_tag_keys = {}
453
+ if tags_facet.tags is not None:
454
+ facet_tag_keys = {tag.key.lower(): tag.key for tag in tags_facet.tags}
455
+
456
+ for user_tag in user_tags:
457
+ if user_tag.key in facet_tag_keys:
458
+ facet_tag_key = facet_tag_keys[user_tag.key]
459
+ log.info("Overriding integration-supplied tag `%s` with user-supplied tag", facet_tag_key)
460
+ user_tag.key = facet_tag_key
461
+
462
+ all_tags = keep_tags + user_tags
463
+ tags_facet.tags = all_tags # type: ignore [assignment]
464
+ return tags_facet
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "1.28.0"
5
+ __version__ = "1.30.0"
6
6
 
7
7
  DEFAULT_TIMEOUT_MS = 5000
8
8
  DEFAULT_NAMESPACE_NAME = "default"
@@ -0,0 +1,17 @@
1
+ # Copyright 2018-2025 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import attr
8
+
9
+ if TYPE_CHECKING:
10
+ from openlineage.client.generated.tags_job import TagsJobFacetFields
11
+ from openlineage.client.generated.tags_run import TagsRunFacetFields
12
+
13
+
14
+ @attr.s
15
+ class TagsConfig:
16
+ job: list[TagsJobFacetFields] = attr.ib(factory=list)
17
+ run: list[TagsRunFacetFields] = attr.ib(factory=list)
@@ -6,7 +6,7 @@ requires = [
6
6
 
7
7
  [project]
8
8
  name = "openlineage-python"
9
- version = "1.28.0"
9
+ version = "1.30.0"
10
10
  description = "OpenLineage Python Client"
11
11
  readme = "README.md"
12
12
  keywords = [
@@ -0,0 +1,54 @@
1
+ # Copyright 2018-2025 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Union
7
+
8
+ import pytest
9
+ from openlineage.client import event_v2, set_producer
10
+ from openlineage.client.run import DatasetEvent, JobEvent, RunEvent
11
+ from openlineage.client.transport.transport import Config, Transport
12
+
13
+
14
+ @pytest.fixture(scope="session")
15
+ def root() -> Path:
16
+ return Path(__file__).parent
17
+
18
+
19
+ @pytest.fixture(scope="session", autouse=True)
20
+ def _setup_producer() -> None:
21
+ set_producer("https://github.com/OpenLineage/OpenLineage/tree/0.0.1/client/python")
22
+
23
+
24
+ # For testing events emitted by the client
25
+
26
+ Event_v1 = Union[RunEvent, DatasetEvent, JobEvent]
27
+ Event_v2 = Union[event_v2.RunEvent, event_v2.DatasetEvent, event_v2.JobEvent]
28
+ Event = Union[Event_v1, Event_v2]
29
+
30
+
31
+ class NoOutputConfig(Config):
32
+ ...
33
+
34
+
35
+ class NoOutputTransport(Transport):
36
+ """Minimal transport that stores the event for assertions"""
37
+
38
+ kind = "no_output"
39
+ config_class = NoOutputConfig
40
+
41
+ def __init__(self, config: NoOutputConfig) -> None: # noqa: ARG002
42
+ self._event = None
43
+
44
+ def emit(self, event: Event) -> None:
45
+ self._event = event
46
+
47
+ @property
48
+ def event(self):
49
+ return self._event
50
+
51
+
52
+ @pytest.fixture(scope="session")
53
+ def transport():
54
+ return NoOutputTransport(config=NoOutputConfig())
@@ -15,6 +15,8 @@ from openlineage.client.generated.environment_variables_run import (
15
15
  EnvironmentVariable,
16
16
  EnvironmentVariablesRunFacet,
17
17
  )
18
+ from openlineage.client.generated.tags_job import TagsJobFacet, TagsJobFacetFields
19
+ from openlineage.client.generated.tags_run import TagsRunFacet, TagsRunFacetFields
18
20
  from openlineage.client.run import (
19
21
  SCHEMA_URL,
20
22
  Dataset,
@@ -82,7 +84,6 @@ def test_client_passes_to_create_with_valid_url(url: str, res: str) -> None:
82
84
  def test_client_sends_proper_json_with_minimal_run_event() -> None:
83
85
  session = MagicMock()
84
86
  client = OpenLineageClient(url="http://example.com", session=session)
85
-
86
87
  client.emit(
87
88
  RunEvent(
88
89
  RunState.START,
@@ -839,6 +840,31 @@ class TestOpenLineageConfigLoader:
839
840
  },
840
841
  {"transport": {"type": "console"}},
841
842
  ),
843
+ (
844
+ {
845
+ "OPENLINEAGE__TAGS__JOB__ENVIRONMENT": "PRODUCTION",
846
+ "OPENLINEAGE__TAGS__JOB__pipeline": "finance",
847
+ "OPENLINEAGE__TAGS__RUN__environment": "PRODUCTION",
848
+ "OPENLINEAGE__TAGS__RUN__pipeline": "finance",
849
+ },
850
+ {
851
+ "tags": {
852
+ "job": {"environment": "PRODUCTION", "pipeline": "finance"},
853
+ "run": {"environment": "PRODUCTION", "pipeline": "finance"},
854
+ }
855
+ },
856
+ ),
857
+ (
858
+ {
859
+ "OPENLINEAGE__TAGS": '{"job": {"a": "b", "c": "d"}, "run": {"a": "b", "c": "d"}}',
860
+ },
861
+ {
862
+ "tags": {
863
+ "job": {"a": "b", "c": "d"},
864
+ "run": {"a": "b", "c": "d"},
865
+ }
866
+ },
867
+ ),
842
868
  ],
843
869
  )
844
870
  @patch.dict(os.environ, {})
@@ -846,3 +872,178 @@ class TestOpenLineageConfigLoader:
846
872
  with patch.dict(os.environ, env_vars):
847
873
  config = OpenLineageClient._load_config_from_env_variables() # noqa: SLF001
848
874
  assert config == expected_config
875
+
876
+
877
+ def test_update_job_tag_facet_creates_new_facet(transport):
878
+ tag_environment_variables = {
879
+ "OPENLINEAGE__TAGS__JOB__ENVIRONMENT": "PRODUCTION",
880
+ "OPENLINEAGE__TAGS__JOB__pipeline": "SALES",
881
+ }
882
+
883
+ run_event = RunEvent(
884
+ RunState.START,
885
+ "2021-11-03T10:53:52.427343",
886
+ Run("69f4acab-b87d-4fc0-b27b-8ea950370ff3"),
887
+ Job("openlineage", "job"),
888
+ "producer",
889
+ )
890
+
891
+ tags = [
892
+ TagsJobFacetFields("environment", "PRODUCTION", "USER"),
893
+ TagsJobFacetFields("pipeline", "SALES", "USER"),
894
+ ]
895
+
896
+ with patch.dict(os.environ, tag_environment_variables):
897
+ client = OpenLineageClient(transport=transport)
898
+ client.emit(run_event)
899
+ assert transport.event.job.facets.get("tags")
900
+ event_tags = sorted(transport.event.job.facets["tags"].tags, key=lambda x: x.key)
901
+ expected_tags = sorted(tags, key=lambda x: x.key)
902
+ assert event_tags == expected_tags
903
+
904
+
905
+ def test_update_job_tag_facet_updates_existing_facet(transport):
906
+ tag_environment_variables = {
907
+ "OPENLINEAGE__TAGS__JOB__ENVIRONMENT": "PRODUCTION",
908
+ "OPENLINEAGE__TAGS__JOB__pipeline": "SALES",
909
+ }
910
+
911
+ run_event = RunEvent(
912
+ RunState.START,
913
+ "2021-11-03T10:53:52.427343",
914
+ Run("69f4acab-b87d-4fc0-b27b-8ea950370ff3"),
915
+ Job(
916
+ namespace="openlineage",
917
+ name="job",
918
+ facets={
919
+ "tags": TagsRunFacet(
920
+ tags=[
921
+ TagsJobFacetFields("environment", "STAGING", "USER"),
922
+ TagsJobFacetFields("foo", "bar", "USER"),
923
+ ]
924
+ )
925
+ },
926
+ ),
927
+ "producer",
928
+ )
929
+
930
+ # One existing tag (not updated), one existing tag (updated), one new tag from the user
931
+ tags = [
932
+ TagsJobFacetFields("foo", "bar", "USER"),
933
+ TagsJobFacetFields("environment", "PRODUCTION", "USER"),
934
+ TagsJobFacetFields("pipeline", "SALES", "USER"),
935
+ ]
936
+
937
+ with patch.dict(os.environ, tag_environment_variables):
938
+ client = OpenLineageClient(transport=transport)
939
+ client.emit(run_event)
940
+ assert transport.event.job.facets.get("tags")
941
+ event_tags = sorted(transport.event.job.facets["tags"].tags, key=lambda x: x.key)
942
+ expected_tags = sorted(tags, key=lambda x: x.key)
943
+ assert event_tags == expected_tags
944
+
945
+
946
+ def test_update_run_tag_facet_creates_new_facet(transport):
947
+ tag_environment_variables = {
948
+ "OPENLINEAGE__TAGS__RUN__ENVIRONMENT": "PRODUCTION",
949
+ "OPENLINEAGE__TAGS__RUN__pipeline": "SALES",
950
+ }
951
+
952
+ run_event = RunEvent(
953
+ RunState.START,
954
+ "2021-11-03T10:53:52.427343",
955
+ Run("69f4acab-b87d-4fc0-b27b-8ea950370ff3"),
956
+ Job("openlineage", "job"),
957
+ "producer",
958
+ )
959
+
960
+ tags = [
961
+ TagsRunFacetFields("environment", "PRODUCTION", "USER"),
962
+ TagsRunFacetFields("pipeline", "SALES", "USER"),
963
+ ]
964
+
965
+ with patch.dict(os.environ, tag_environment_variables):
966
+ client = OpenLineageClient(transport=transport)
967
+ client.emit(run_event)
968
+ assert transport.event.run.facets.get("tags")
969
+ event_tags = sorted(transport.event.run.facets["tags"].tags, key=lambda x: x.key)
970
+ expected_tags = sorted(tags, key=lambda x: x.key)
971
+ assert event_tags == expected_tags
972
+
973
+
974
+ def test_update_run_tag_facet_updates_existing_facet(transport):
975
+ tag_environment_variables = {
976
+ "OPENLINEAGE__TAGS__RUN__ENVIRONMENT": "PRODUCTION",
977
+ "OPENLINEAGE__TAGS__RUN__pipeline": "SALES",
978
+ }
979
+
980
+ run_event = RunEvent(
981
+ RunState.START,
982
+ "2021-11-03T10:53:52.427343",
983
+ Run(
984
+ runId="69f4acab-b87d-4fc0-b27b-8ea950370ff3",
985
+ facets={
986
+ "tags": TagsJobFacet(
987
+ tags=[
988
+ TagsRunFacetFields("ENVIRONMENT", "STAGING", "USER"),
989
+ TagsRunFacetFields("foo", "bar", "USER"),
990
+ ]
991
+ )
992
+ },
993
+ ),
994
+ Job("openlineage", "job"),
995
+ "producer",
996
+ )
997
+
998
+ # One existing tag (not updated), one existing tag (updated), one new tag from the user
999
+ tags = [
1000
+ TagsRunFacetFields("foo", "bar", "USER"),
1001
+ TagsRunFacetFields("ENVIRONMENT", "PRODUCTION", "USER"),
1002
+ TagsRunFacetFields("pipeline", "SALES", "USER"),
1003
+ ]
1004
+
1005
+ with patch.dict(os.environ, tag_environment_variables):
1006
+ client = OpenLineageClient(transport=transport)
1007
+ client.emit(run_event)
1008
+ assert transport.event.run.facets.get("tags")
1009
+ event_tags = sorted(transport.event.run.facets["tags"].tags, key=lambda x: x.key)
1010
+ expected_tags = sorted(tags, key=lambda x: x.key)
1011
+ assert event_tags == expected_tags
1012
+
1013
+
1014
+ def test_update_tag_facet_keeps_key_case(transport):
1015
+ tag_environment_variables = {
1016
+ "OPENLINEAGE__TAGS__RUN__ENVIRONMENT": "PRODUCTION",
1017
+ "OPENLINEAGE__TAGS__RUN__pipeline": "SALES",
1018
+ }
1019
+
1020
+ run_event = RunEvent(
1021
+ RunState.START,
1022
+ "2021-11-03T10:53:52.427343",
1023
+ Run(
1024
+ runId="69f4acab-b87d-4fc0-b27b-8ea950370ff3",
1025
+ facets={
1026
+ "tags": TagsJobFacet(
1027
+ tags=[
1028
+ TagsRunFacetFields("environment", "STAGING", "USER"),
1029
+ TagsRunFacetFields("PIPELINE", "FINANCE", "USER"),
1030
+ ]
1031
+ )
1032
+ },
1033
+ ),
1034
+ Job("openlineage", "job"),
1035
+ "producer",
1036
+ )
1037
+
1038
+ tags = [
1039
+ TagsRunFacetFields("environment", "PRODUCTION", "USER"),
1040
+ TagsRunFacetFields("PIPELINE", "SALES", "USER"),
1041
+ ]
1042
+
1043
+ with patch.dict(os.environ, tag_environment_variables):
1044
+ client = OpenLineageClient(transport=transport)
1045
+ client.emit(run_event)
1046
+ assert transport.event.run.facets.get("tags")
1047
+ event_tags = sorted(transport.event.run.facets["tags"].tags, key=lambda x: x.key)
1048
+ expected_tags = sorted(tags, key=lambda x: x.key)
1049
+ assert event_tags == expected_tags
@@ -1,18 +0,0 @@
1
- # Copyright 2018-2025 contributors to the OpenLineage project
2
- # SPDX-License-Identifier: Apache-2.0
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
-
7
- import pytest
8
- from openlineage.client import set_producer
9
-
10
-
11
- @pytest.fixture(scope="session")
12
- def root() -> Path:
13
- return Path(__file__).parent
14
-
15
-
16
- @pytest.fixture(scope="session", autouse=True)
17
- def _setup_producer() -> None:
18
- set_producer("https://github.com/OpenLineage/OpenLineage/tree/0.0.1/client/python")