openlineage-python 1.18.0__tar.gz → 1.20.3__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 (85) hide show
  1. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/PKG-INFO +1 -1
  2. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/docs/source/openlineage.client.rst +8 -0
  3. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/constants.py +1 -1
  4. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/__init__.py +2 -0
  5. openlineage_python-1.20.3/openlineage/client/transport/composite.py +81 -0
  6. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/pyproject.toml +1 -1
  7. openlineage_python-1.20.3/tests/test_composite.py +130 -0
  8. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/.gitignore +0 -0
  9. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/README.md +0 -0
  10. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/docs/Makefile +0 -0
  11. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/docs/conf.py +0 -0
  12. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/docs/convert.py +0 -0
  13. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/docs/index.rst +0 -0
  14. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/docs/make.bat +0 -0
  15. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/docs/release-api-reference.sh +0 -0
  16. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/__init__.py +0 -0
  17. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/client.py +0 -0
  18. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/event_v2.py +0 -0
  19. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/facet.py +0 -0
  20. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/facet_v2.py +0 -0
  21. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/filter.py +0 -0
  22. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/base.py +0 -0
  23. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/column_lineage_dataset.py +0 -0
  24. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/data_quality_assertions_dataset.py +0 -0
  25. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/data_quality_metrics_input_dataset.py +0 -0
  26. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/dataset_version_dataset.py +0 -0
  27. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/datasource_dataset.py +0 -0
  28. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/documentation_dataset.py +0 -0
  29. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/documentation_job.py +0 -0
  30. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/error_message_run.py +0 -0
  31. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/external_query_run.py +0 -0
  32. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/extraction_error_run.py +0 -0
  33. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/job_type_job.py +0 -0
  34. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/lifecycle_state_change_dataset.py +0 -0
  35. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/nominal_time_run.py +0 -0
  36. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/output_statistics_output_dataset.py +0 -0
  37. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/ownership_dataset.py +0 -0
  38. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/ownership_job.py +0 -0
  39. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/parent_run.py +0 -0
  40. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/processing_engine_run.py +0 -0
  41. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/schema_dataset.py +0 -0
  42. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/source_code_job.py +0 -0
  43. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/source_code_location_job.py +0 -0
  44. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/sql_job.py +0 -0
  45. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/storage_dataset.py +0 -0
  46. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/generated/symlinks_dataset.py +0 -0
  47. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/py.typed +0 -0
  48. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/run.py +0 -0
  49. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/serde.py +0 -0
  50. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/console.py +0 -0
  51. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/factory.py +0 -0
  52. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/file.py +0 -0
  53. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/http.py +0 -0
  54. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/kafka.py +0 -0
  55. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/msk_iam.py +0 -0
  56. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/noop.py +0 -0
  57. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/transport/transport.py +0 -0
  58. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/utils.py +0 -0
  59. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/openlineage/client/uuid.py +0 -0
  60. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/redact_fields.yml +0 -0
  61. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/__init__.py +0 -0
  62. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/config/config.yml +0 -0
  63. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/config/exact_filter.yml +0 -0
  64. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/config/http.yml +0 -0
  65. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/config/openlineage.yml +0 -0
  66. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/config/regex_filter.yml +0 -0
  67. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/conftest.py +0 -0
  68. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/example_full_event.json +0 -0
  69. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/nominal_time_without_end.json +0 -0
  70. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/serde_example_dataset_event.json +0 -0
  71. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/serde_example_job_event.json +0 -0
  72. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/serde_example_run_event.json +0 -0
  73. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_client.py +0 -0
  74. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_events.py +0 -0
  75. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_facet.py +0 -0
  76. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_facet_v2.py +0 -0
  77. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_factory.py +0 -0
  78. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_file.py +0 -0
  79. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_http.py +0 -0
  80. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_kafka.py +0 -0
  81. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_msk_iam.py +0 -0
  82. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_utils.py +0 -0
  83. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/test_uuid.py +0 -0
  84. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tests/transport.py +0 -0
  85. {openlineage_python-1.18.0 → openlineage_python-1.20.3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openlineage-python
3
- Version: 1.18.0
3
+ Version: 1.20.3
4
4
  Summary: OpenLineage Python Client
5
5
  Author-email: OpenLineage <info@openlineage.io>
6
6
  Keywords: openlineage
@@ -273,6 +273,14 @@ openlineage.client.generated.symlinks\_dataset module
273
273
  :undoc-members:
274
274
  :show-inheritance:
275
275
 
276
+ openlineage.client.transport.composite module
277
+ ----------------------------------------
278
+
279
+ .. automodule:: openlineage.client.transport.composite
280
+ :members:
281
+ :undoc-members:
282
+ :show-inheritance:
283
+
276
284
  openlineage.client.transport.console module
277
285
  -------------------------------------------
278
286
 
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "1.18.0"
5
+ __version__ = "1.20.3"
6
6
 
7
7
  DEFAULT_TIMEOUT_MS = 5000
8
8
  DEFAULT_NAMESPACE_NAME = "default"
@@ -2,6 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
5
+ from openlineage.client.transport.composite import CompositeTransport
5
6
  from openlineage.client.transport.console import ConsoleTransport
6
7
  from openlineage.client.transport.factory import DefaultTransportFactory
7
8
  from openlineage.client.transport.file import FileTransport
@@ -12,6 +13,7 @@ from openlineage.client.transport.noop import NoopTransport
12
13
  from openlineage.client.transport.transport import Config, Transport, TransportFactory
13
14
 
14
15
  _factory = DefaultTransportFactory()
16
+ _factory.register_transport(CompositeTransport.kind, CompositeTransport)
15
17
  _factory.register_transport(HttpTransport.kind, HttpTransport)
16
18
  _factory.register_transport(KafkaTransport.kind, KafkaTransport)
17
19
  _factory.register_transport(MSKIAMTransport.kind, MSKIAMTransport)
@@ -0,0 +1,81 @@
1
+ # Copyright 2018-2024 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ from functools import cached_property
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ import attr
11
+ from openlineage.client.transport.transport import Config, Transport
12
+ from openlineage.client.utils import get_only_specified_fields
13
+
14
+ if TYPE_CHECKING:
15
+ from openlineage.client.client import Event
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ @attr.s
21
+ class CompositeConfig(Config):
22
+ """
23
+ CompositeConfig is a configuration class for CompositeTransport.
24
+
25
+ Attributes:
26
+ transports:
27
+ A list of dictionaries, where each dictionary represents the configuration
28
+ for a child transport. Each dictionary should contain the necessary parameters
29
+ to initialize a specific transport instance.
30
+
31
+ continue_on_failure:
32
+ If set to True, the CompositeTransport will attempt to emit the event using
33
+ all configured transports, regardless of whether any previous transport
34
+ in the list failed to emit the event. If set to False, an error in one
35
+ transport will halt the emission process for subsequent transports.
36
+ """
37
+
38
+ transports: list[dict[str, Any]] = attr.ib()
39
+ continue_on_failure: bool = attr.ib(default=True)
40
+
41
+ @classmethod
42
+ def from_dict(cls, params: dict[str, Any]) -> CompositeConfig:
43
+ """Create a CompositeConfig object from a dictionary."""
44
+ if "transports" not in params:
45
+ msg = "composite `transports` not passed to CompositeConfig"
46
+ raise RuntimeError(msg)
47
+ return cls(**get_only_specified_fields(cls, params))
48
+
49
+
50
+ class CompositeTransport(Transport):
51
+ """CompositeTransport is a transport class that emits events using multiple transports."""
52
+
53
+ kind = "composite"
54
+ config_class = CompositeConfig
55
+
56
+ def __init__(self, config: CompositeConfig) -> None:
57
+ """Initialize a CompositeTransport object."""
58
+ self.config = config
59
+
60
+ @cached_property
61
+ def transports(self) -> list[Transport]:
62
+ """Create and return a list of transports based on the config."""
63
+ from openlineage.client.transport import get_default_factory
64
+
65
+ transports = []
66
+ for transport_config in self.config.transports:
67
+ transports.append(get_default_factory().create(transport_config))
68
+ return transports
69
+
70
+ def emit(self, event: Event) -> None:
71
+ """Emit an event using all transports in the config."""
72
+ for transport in self.transports:
73
+ try:
74
+ log.debug("Emitting event using %s", transport.kind)
75
+ transport.emit(event)
76
+ except Exception as e: # noqa: BLE001
77
+ if self.config.continue_on_failure:
78
+ log.warning("Transport %s failed to emit event with error: %s", transport.kind, e)
79
+ else:
80
+ msg = f"Transport {transport.kind} failed to emit event"
81
+ raise RuntimeError(msg) from e
@@ -6,7 +6,7 @@ requires = [
6
6
 
7
7
  [project]
8
8
  name = "openlineage-python"
9
- version = "1.18.0"
9
+ version = "1.20.3"
10
10
  description = "OpenLineage Python Client"
11
11
  readme = "README.md"
12
12
  keywords = [
@@ -0,0 +1,130 @@
1
+ # Copyright 2018-2024 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from __future__ import annotations
4
+
5
+ from unittest import mock
6
+ from unittest.mock import MagicMock
7
+
8
+ import pytest
9
+ from openlineage.client.transport.composite import CompositeConfig, CompositeTransport
10
+ from openlineage.client.transport.transport import Transport
11
+
12
+
13
+ def test_composite_loads_full_config() -> None:
14
+ config = CompositeConfig.from_dict(
15
+ {
16
+ "type": "composite",
17
+ "transports": {
18
+ "kafka": {
19
+ "type": "kafka",
20
+ "config": {"bootstrap.servers": "localhost:9092"},
21
+ "topic": "random-topic",
22
+ "messageKey": "key",
23
+ "flush": False,
24
+ },
25
+ "console": {"type": "console"},
26
+ },
27
+ "continue_on_failure": False,
28
+ },
29
+ )
30
+
31
+ assert config.transports["kafka"]["config"]["bootstrap.servers"] == "localhost:9092"
32
+ assert config.transports["kafka"]["topic"] == "random-topic"
33
+ assert config.transports["kafka"]["messageKey"] == "key"
34
+ assert config.transports["kafka"]["flush"] is False
35
+ assert config.transports["console"] == {"type": "console"}
36
+ assert config.continue_on_failure is False
37
+
38
+
39
+ def test_composite_loads_partial_config_with_defaults() -> None:
40
+ config = CompositeConfig.from_dict(
41
+ {
42
+ "type": "composite",
43
+ "transports": [
44
+ {
45
+ "type": "kafka",
46
+ "config": {"bootstrap.servers": "localhost:9092"},
47
+ "topic": "random-topic",
48
+ "messageKey": "key",
49
+ "flush": False,
50
+ },
51
+ {"type": "console"},
52
+ ],
53
+ },
54
+ )
55
+ assert config.transports[0]["config"]["bootstrap.servers"] == "localhost:9092"
56
+ assert config.transports[0]["topic"] == "random-topic"
57
+ assert config.transports[0]["messageKey"] == "key"
58
+ assert config.transports[0]["flush"] is False
59
+ assert config.transports[1] == {"type": "console"}
60
+ assert config.continue_on_failure is True
61
+
62
+
63
+ def test_composite_transport_create_transports():
64
+ config = CompositeConfig(transports=[{"type": "tests.transport.FakeTransport"}], continue_on_failure=True)
65
+ transport = CompositeTransport(config)
66
+ assert len(transport.transports) == 1
67
+ assert transport.transports[0].kind == "fake"
68
+
69
+
70
+ @mock.patch("openlineage.client.transport.get_default_factory")
71
+ def test_transports(mock_factory):
72
+ mock_transport1 = MagicMock(spec=Transport)
73
+ mock_transport2 = MagicMock(spec=Transport)
74
+ mock_factory.return_value.create.side_effect = [mock_transport1, mock_transport2]
75
+
76
+ config = CompositeConfig(transports=[{}, {}])
77
+ transport = CompositeTransport(config)
78
+
79
+ assert transport.transports == [mock_transport1, mock_transport2]
80
+
81
+
82
+ @mock.patch("openlineage.client.transport.get_default_factory")
83
+ def test_emit_success(mock_factory):
84
+ mock_transport1 = MagicMock(spec=Transport)
85
+ mock_transport2 = MagicMock(spec=Transport)
86
+ mock_factory.return_value.create.side_effect = [mock_transport1, mock_transport2]
87
+
88
+ config = CompositeConfig(transports=[{}, {}])
89
+ transport = CompositeTransport(config)
90
+ event = MagicMock()
91
+
92
+ transport.emit(event)
93
+
94
+ mock_transport1.emit.assert_called_once_with(event)
95
+ mock_transport2.emit.assert_called_once_with(event)
96
+
97
+
98
+ @mock.patch("openlineage.client.transport.get_default_factory")
99
+ def test_emit_failure_no_continue_on_failure(mock_factory):
100
+ mock_transport1 = MagicMock(spec=Transport)
101
+ mock_transport2 = MagicMock(spec=Transport)
102
+ mock_transport2.emit.side_effect = Exception("Error")
103
+ mock_factory.return_value.create.side_effect = [mock_transport1, mock_transport2]
104
+
105
+ config = CompositeConfig(transports=[{}, {}], continue_on_failure=False)
106
+ transport = CompositeTransport(config)
107
+ event = MagicMock()
108
+
109
+ with pytest.raises(RuntimeError):
110
+ transport.emit(event)
111
+
112
+ mock_transport1.emit.assert_called_once_with(event)
113
+ mock_transport2.emit.assert_called_once_with(event)
114
+
115
+
116
+ @mock.patch("openlineage.client.transport.get_default_factory")
117
+ def test_emit_failure_continue_on_failure(mock_factory):
118
+ mock_transport1 = MagicMock(spec=Transport)
119
+ mock_transport2 = MagicMock(spec=Transport)
120
+ mock_factory.return_value.create.side_effect = [mock_transport1, mock_transport2]
121
+
122
+ config = CompositeConfig(transports=[{}, {}], continue_on_failure=False)
123
+ transport = CompositeTransport(config)
124
+ event = MagicMock()
125
+
126
+ # Emit should not raise an exception because continue_on_failure is True.
127
+ transport.emit(event)
128
+
129
+ mock_transport1.emit.assert_called_once_with(event)
130
+ mock_transport2.emit.assert_called_once_with(event)