openlineage-python 0.25.0__tar.gz → 0.26.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 (44) hide show
  1. openlineage_python-0.26.0/.gitignore +3 -0
  2. openlineage_python-0.26.0/PKG-INFO +101 -0
  3. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/README.md +9 -9
  4. openlineage_python-0.26.0/openlineage/__init__.py +2 -0
  5. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/__init__.py +5 -1
  6. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/client.py +1 -0
  7. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/constants.py +1 -1
  8. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/facet.py +1 -0
  9. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/run.py +1 -0
  10. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/__init__.py +9 -8
  11. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/factory.py +1 -1
  12. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/http.py +1 -1
  13. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/kafka.py +1 -0
  14. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/transport.py +3 -2
  15. openlineage_python-0.26.0/pyproject.toml +72 -0
  16. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/setup.cfg +5 -13
  17. openlineage_python-0.26.0/tests/__init__.py +2 -0
  18. openlineage_python-0.26.0/tests/config/config.yml +3 -0
  19. openlineage_python-0.26.0/tests/config/http.yml +7 -0
  20. openlineage_python-0.26.0/tests/nominal_time_without_end.json +5 -0
  21. openlineage_python-0.26.0/tests/openlineage.yml +3 -0
  22. openlineage_python-0.26.0/tests/serde_example.json +23 -0
  23. openlineage_python-0.26.0/tests/test_client.py +89 -0
  24. openlineage_python-0.26.0/tests/test_events.py +132 -0
  25. openlineage_python-0.26.0/tests/test_facet.py +420 -0
  26. openlineage_python-0.26.0/tests/test_factory.py +149 -0
  27. openlineage_python-0.26.0/tests/test_http.py +99 -0
  28. openlineage_python-0.26.0/tests/test_kafka.py +70 -0
  29. openlineage_python-0.26.0/tests/test_utils.py +2 -0
  30. openlineage_python-0.26.0/tests/transport.py +28 -0
  31. openlineage_python-0.26.0/tox.ini +63 -0
  32. openlineage-python-0.25.0/PKG-INFO +0 -86
  33. openlineage-python-0.25.0/openlineage_python.egg-info/PKG-INFO +0 -86
  34. openlineage-python-0.25.0/openlineage_python.egg-info/SOURCES.txt +0 -24
  35. openlineage-python-0.25.0/openlineage_python.egg-info/dependency_links.txt +0 -1
  36. openlineage-python-0.25.0/openlineage_python.egg-info/not-zip-safe +0 -1
  37. openlineage-python-0.25.0/openlineage_python.egg-info/requires.txt +0 -30
  38. openlineage-python-0.25.0/openlineage_python.egg-info/top_level.txt +0 -1
  39. openlineage-python-0.25.0/pyproject.toml +0 -12
  40. openlineage-python-0.25.0/setup.py +0 -50
  41. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/serde.py +0 -0
  42. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/console.py +0 -0
  43. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/noop.py +0 -0
  44. {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ __pycache__
2
+ /.tox
3
+ /dist
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.1
2
+ Name: openlineage-python
3
+ Version: 0.26.0
4
+ Summary: OpenLineage Python Client
5
+ Author-email: OpenLineage <info@openlineage.io>
6
+ Keywords: openlineage
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Python :: 3.7
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.7
17
+ Requires-Dist: attrs>=23.1
18
+ Requires-Dist: python-dateutil>=2.8.2
19
+ Requires-Dist: pyyaml>=6
20
+ Requires-Dist: requests>=2.30
21
+ Provides-Extra: kafka
22
+ Requires-Dist: confluent-kafka>=2.1.1; extra == 'kafka'
23
+ Provides-Extra: test
24
+ Requires-Dist: covdefaults>=2.3; extra == 'test'
25
+ Requires-Dist: pytest-cov>=4; extra == 'test'
26
+ Requires-Dist: pytest>=7.3.1; extra == 'test'
27
+ Requires-Dist: pyyaml>=6; extra == 'test'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # OpenLineage-python
31
+
32
+ To install from source, run:
33
+
34
+ ```bash
35
+ $ python -m pip install .
36
+ ```
37
+
38
+ ## Configuration
39
+ ### Config file
40
+
41
+ The most common way to configure the OpenLineage Client is by `.yaml` file, which contains all the
42
+ details of how to connect to your OpenLineage backend.
43
+
44
+ The config file is located by:
45
+ 1) looking at the `OPENLINEAGE_CONFIG` environment variable
46
+ 2) looking for the `openlineage.yml` file in the current working directory
47
+ 3) looking for the `openlineage.yml` file in the `$HOME/.openlineage` directory.
48
+
49
+ Different ways of connecting to OpenLineage backend are supported
50
+ by the standarized `Transport` interface.
51
+ This is an example config for specifying `http` transport:
52
+
53
+ ```yaml
54
+ transport:
55
+ type: "http"
56
+ url: "https://backend:5000"
57
+ auth:
58
+ type: "api_key"
59
+ api_key: "f048521b-dfe8-47cd-9c65-0cb07d57591e"
60
+ ```
61
+
62
+ The `type` property is required. It can be one of the built-in transports or a custom one.
63
+ There are three built-in transports, `http`, `kafka` and `console`.
64
+ Custom transports `type` is a fully qualified class name that can be imported.
65
+
66
+ The rest of the properties are defined by the particular transport.
67
+ Specifications for the built-in options are below.
68
+
69
+ #### HTTP
70
+
71
+ * `url` - required string parameter.
72
+ * `endpoint` - optional string parameter specifying the endpoint to which events are sent. By default `api/v1/lineage`.
73
+ * `timeout` - optional float parameter specifying the timeout when sending events. By default 5 seconds.
74
+ * `verify` optional boolean attribute specifying if the client should verify TLS certificates of the backend. By default true.
75
+ * `auth` - optional dictionary specifying authentication options. The type property is required.
76
+ * `type`: required property if an auth dictionary is set. Set to `api_key` or to the fully qualified class name of your TokenProvider.
77
+ * `api_key`: if `api_key` type is set, it sets value at `Authentication` HTTP header as `Bearer`.
78
+
79
+ #### Kafka
80
+
81
+ For KafkaTransport, `confluent-kafka` needs to be installed.
82
+ You can also install `pip install openlineage-python[kafka]`
83
+
84
+ * `config` - required string parameter. A dictionary that contains a Kafka producer config as in [Kafka producer config](https://docs.confluent.io/platform/current/clients/confluent-kafka-python/html/index.html#kafka-client-configuration).
85
+ * `topic` - required string parameter. The topic on what events will be sent.
86
+ * `flush` - optional boolean parameter. If set to True, Kafka will flush after each event. By default true.
87
+
88
+
89
+ ### Custom transport
90
+
91
+ To implement a custom transport, follow the instructions in the `openlineage/client/transport/transport.py` file.
92
+
93
+ ### Config as env vars
94
+
95
+ If there is no config file found, the OpenLineage client looks at environment variables.
96
+ This way of configuring the client supports only `http` transport, and only a subset of its config.
97
+
98
+ * `OPENLINEAGE_URL` - point to the service that will consume OpenLineage events.
99
+ * `OPENLINEAGE_API_KEY` - set if the consumer of OpenLineage events requires a `Bearer` authentication key.
100
+
101
+ `OPENLINEAGE_URL` and `OPENLINEAGE_API_KEY` can also be set up manually when creating a client instance.
@@ -3,22 +3,22 @@
3
3
  To install from source, run:
4
4
 
5
5
  ```bash
6
- $ python setup.py install
6
+ $ python -m pip install .
7
7
  ```
8
8
 
9
9
  ## Configuration
10
10
  ### Config file
11
11
 
12
12
  The most common way to configure the OpenLineage Client is by `.yaml` file, which contains all the
13
- details of how to connect to your OpenLineage backend.
13
+ details of how to connect to your OpenLineage backend.
14
14
 
15
15
  The config file is located by:
16
16
  1) looking at the `OPENLINEAGE_CONFIG` environment variable
17
17
  2) looking for the `openlineage.yml` file in the current working directory
18
18
  3) looking for the `openlineage.yml` file in the `$HOME/.openlineage` directory.
19
19
 
20
- Different ways of connecting to OpenLineage backend are supported
21
- by the standarized `Transport` interface.
20
+ Different ways of connecting to OpenLineage backend are supported
21
+ by the standarized `Transport` interface.
22
22
  This is an example config for specifying `http` transport:
23
23
 
24
24
  ```yaml
@@ -31,10 +31,10 @@ transport:
31
31
  ```
32
32
 
33
33
  The `type` property is required. It can be one of the built-in transports or a custom one.
34
- There are three built-in transports, `http`, `kafka` and `console`.
34
+ There are three built-in transports, `http`, `kafka` and `console`.
35
35
  Custom transports `type` is a fully qualified class name that can be imported.
36
36
 
37
- The rest of the properties are defined by the particular transport.
37
+ The rest of the properties are defined by the particular transport.
38
38
  Specifications for the built-in options are below.
39
39
 
40
40
  #### HTTP
@@ -45,7 +45,7 @@ Specifications for the built-in options are below.
45
45
  * `verify` optional boolean attribute specifying if the client should verify TLS certificates of the backend. By default true.
46
46
  * `auth` - optional dictionary specifying authentication options. The type property is required.
47
47
  * `type`: required property if an auth dictionary is set. Set to `api_key` or to the fully qualified class name of your TokenProvider.
48
- * `api_key`: if `api_key` type is set, it sets value at `Authentication` HTTP header as `Bearer`.
48
+ * `api_key`: if `api_key` type is set, it sets value at `Authentication` HTTP header as `Bearer`.
49
49
 
50
50
  #### Kafka
51
51
 
@@ -63,10 +63,10 @@ To implement a custom transport, follow the instructions in the `openlineage/cli
63
63
 
64
64
  ### Config as env vars
65
65
 
66
- If there is no config file found, the OpenLineage client looks at environment variables.
66
+ If there is no config file found, the OpenLineage client looks at environment variables.
67
67
  This way of configuring the client supports only `http` transport, and only a subset of its config.
68
68
 
69
69
  * `OPENLINEAGE_URL` - point to the service that will consume OpenLineage events.
70
70
  * `OPENLINEAGE_API_KEY` - set if the consumer of OpenLineage events requires a `Bearer` authentication key.
71
71
 
72
- `OPENLINEAGE_URL` and `OPENLINEAGE_API_KEY` can also be set up manually when creating a client instance.
72
+ `OPENLINEAGE_URL` and `OPENLINEAGE_API_KEY` can also be set up manually when creating a client instance.
@@ -0,0 +1,2 @@
1
+ # Copyright 2018-2023 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -4,4 +4,8 @@
4
4
  from openlineage.client.client import OpenLineageClient, OpenLineageClientOptions
5
5
  from openlineage.client.facet import set_producer
6
6
 
7
- __all__ = [OpenLineageClient, OpenLineageClientOptions, set_producer]
7
+ __all__ = [
8
+ "OpenLineageClient",
9
+ "OpenLineageClientOptions",
10
+ "set_producer",
11
+ ]
@@ -6,6 +6,7 @@ import typing
6
6
  from typing import Optional
7
7
 
8
8
  import attr
9
+
9
10
  from openlineage.client.serde import Serde
10
11
 
11
12
  if typing.TYPE_CHECKING:
@@ -1,7 +1,7 @@
1
1
  # Copyright 2018-2023 contributors to the OpenLineage project
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
- __version__ = "0.25.0"
4
+ __version__ = "0.26.0"
5
5
 
6
6
  DEFAULT_TIMEOUT_MS = 5000
7
7
  DEFAULT_NAMESPACE_NAME = 'default'
@@ -5,6 +5,7 @@ from enum import Enum
5
5
  from typing import Dict, List, Optional
6
6
 
7
7
  import attr
8
+
8
9
  from openlineage.client.constants import DEFAULT_PRODUCER
9
10
  from openlineage.client.utils import RedactMixin
10
11
 
@@ -7,6 +7,7 @@ from typing import Dict, List, Optional
7
7
 
8
8
  import attr
9
9
  from dateutil import parser
10
+
10
11
  from openlineage.client.facet import NominalTimeRunFacet, ParentRunFacet
11
12
  from openlineage.client.utils import RedactMixin
12
13
 
@@ -23,17 +23,18 @@ def get_default_factory():
23
23
 
24
24
  # decorator to wrap transports with
25
25
  def register_transport(clazz: Type[Transport]):
26
+ assert clazz.kind is not None
26
27
  _factory.register_transport(clazz.kind, clazz)
27
28
  return clazz
28
29
 
29
30
 
30
31
  __all__ = [
31
- Config,
32
- TransportFactory,
33
- HttpConfig,
34
- HttpTransport,
35
- KafkaConfig,
36
- KafkaTransport,
37
- ConsoleTransport,
38
- NoopTransport
32
+ "Config",
33
+ "TransportFactory",
34
+ "HttpConfig",
35
+ "HttpTransport",
36
+ "KafkaConfig",
37
+ "KafkaTransport",
38
+ "ConsoleTransport",
39
+ "NoopTransport"
39
40
  ]
@@ -72,7 +72,7 @@ class DefaultTransportFactory(TransportFactory):
72
72
  if not inspect.isclass(config_class) or not issubclass(config_class, Config):
73
73
  raise TypeError(f"Config {config_class} has to be class, and subclass of Config")
74
74
 
75
- return transport_class(config_class.from_dict(config))
75
+ return transport_class(config_class.from_dict(config)) # type: ignore[call-arg]
76
76
 
77
77
  def _try_config_from_yaml(self) -> Optional[dict]:
78
78
  file = self._find_yaml()
@@ -127,7 +127,7 @@ class HttpTransport(Transport):
127
127
  event = Serde.to_json(event)
128
128
  resp = self.session.post(
129
129
  urljoin(self.url, self.endpoint),
130
- event,
130
+ event, # type: ignore[arg-type]
131
131
  timeout=self.timeout,
132
132
  verify=self.verify
133
133
  )
@@ -5,6 +5,7 @@ import logging
5
5
  from typing import Dict
6
6
 
7
7
  import attr
8
+
8
9
  from openlineage.client.run import RunEvent
9
10
  from openlineage.client.serde import Serde
10
11
  from openlineage.client.transport.transport import Config, Transport
@@ -1,5 +1,6 @@
1
1
  # Copyright 2018-2023 contributors to the OpenLineage project
2
2
  # SPDX-License-Identifier: Apache-2.0
3
+ from typing import Optional, Type
3
4
 
4
5
  from openlineage.client.run import RunEvent
5
6
 
@@ -27,8 +28,8 @@ class Config:
27
28
 
28
29
 
29
30
  class Transport:
30
- kind = None
31
- config = Config
31
+ kind: Optional[str] = None
32
+ config: Type[Config] = Config
32
33
 
33
34
  def emit(self, event: RunEvent):
34
35
  raise NotImplementedError()
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ build-backend = "hatchling.build"
3
+ requires = [
4
+ "hatchling>=1.15",
5
+ ]
6
+
7
+ [project]
8
+ name = "openlineage-python"
9
+ version = "0.26.0"
10
+ description = "OpenLineage Python Client"
11
+ readme = "README.md"
12
+ keywords = [
13
+ "openlineage",
14
+ ]
15
+ authors = [{ name = "OpenLineage", email = "info@openlineage.io" }]
16
+ requires-python = ">=3.7"
17
+ classifiers = [
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.7",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ ]
28
+ dependencies = [
29
+ "attrs>=23.1",
30
+ "python-dateutil>=2.8.2",
31
+ "pyyaml>=6",
32
+ "requests>=2.30",
33
+ ]
34
+ optional-dependencies.kafka = [
35
+ "confluent-kafka>=2.1.1",
36
+ ]
37
+ optional-dependencies.test = [
38
+ "covdefaults>=2.3",
39
+ "pytest>=7.3.1",
40
+ "pytest-cov>=4",
41
+ "pyyaml>=6",
42
+ ]
43
+
44
+ [tool.hatch.build]
45
+ build.targets.wheel = ["openlineage"]
46
+
47
+ [tool.coverage]
48
+ html.show_contexts = true
49
+ html.skip_covered = false
50
+ report.fail_under = 92
51
+ run.parallel = true
52
+ run.plugins = ["covdefaults"]
53
+
54
+ [tool.mypy]
55
+ show_error_codes = true
56
+ overrides = [
57
+ { ignore_missing_imports = true, module = [
58
+ "confluent_kafka.*",
59
+ ] } ]
60
+
61
+ [tool.ruff]
62
+ line-length = 100
63
+ select = [
64
+ "F",
65
+ "E",
66
+ "W",
67
+ "I001"
68
+ ]
69
+ target-version = "py37"
70
+ ignore-init-module-imports = true
71
+ src = ["openlineage", "tests"]
72
+ namespace-packages = ["openlineage/client"]
@@ -1,24 +1,16 @@
1
1
  [bumpversion]
2
- current_version = 0.25.0
2
+ current_version = 0.26.0
3
3
  commit = False
4
4
  tag = False
5
5
  parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<rc>.*)
6
- serialize =
6
+ serialize =
7
7
  {major}.{minor}.{patch}{rc}
8
8
  {major}.{minor}.{patch}
9
9
 
10
- [bumpversion:file:setup.py]
11
- search = version="{current_version}"
12
- replace = version="{new_version}"
10
+ [bumpversion:file:pyproject.toml]
11
+ search = version = "{current_version}"
12
+ replace = version = "{new_version}"
13
13
 
14
14
  [bumpversion:file:openlineage/client/constants.py]
15
15
  search = __version__ = "{current_version}"
16
16
  replace = __version__ = "{new_version}"
17
-
18
- [mypy]
19
- ignore_missing_imports = True
20
-
21
- [egg_info]
22
- tag_build =
23
- tag_date = 0
24
-
@@ -0,0 +1,2 @@
1
+ # Copyright 2018-2023 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,3 @@
1
+ transport:
2
+ type: "tests.transport.FakeTransport"
3
+ something: "true"
@@ -0,0 +1,7 @@
1
+ transport:
2
+ type: http
3
+ url: http://localhost:5050
4
+ endpoint: api/v1/lineage
5
+ auth:
6
+ type: api_key
7
+ apiKey: random_token
@@ -0,0 +1,5 @@
1
+ {
2
+ "_producer": "https://github.com/OpenLineage/OpenLineage/tree/0.0.1/client/python",
3
+ "_schemaURL": "https://raw.githubusercontent.com/OpenLineage/OpenLineage/main/spec/OpenLineage.json#/definitions/NominalTimeRunFacet",
4
+ "nominalStartTime": "2020-01-01"
5
+ }
@@ -0,0 +1,3 @@
1
+ transport:
2
+ type: "tests.transport.AccumulatingTransport"
3
+ something: "false"
@@ -0,0 +1,23 @@
1
+ {
2
+ "eventTime": "2021-11-03T10:53:52.427343",
3
+ "eventType": "START",
4
+ "run": {
5
+ "facets": {
6
+ "nominalTime": {
7
+ "_producer": "https://github.com/OpenLineage/OpenLineage/tree/0.0.1/client/python",
8
+ "_schemaURL": "https://raw.githubusercontent.com/OpenLineage/OpenLineage/main/spec/OpenLineage.json#/definitions/NominalTimeRunFacet",
9
+ "nominalEndTime": "2020-01-02",
10
+ "nominalStartTime": "2020-01-01"
11
+ }
12
+ },
13
+ "runId": "69f4acab-b87d-4fc0-b27b-8ea950370ff3"
14
+ },
15
+ "job": {
16
+ "namespace": "openlineage",
17
+ "name": "name",
18
+ "facets": {}
19
+ },
20
+ "inputs": [],
21
+ "outputs": [],
22
+ "producer": "https://github.com/OpenLineage/OpenLineage/tree/0.0.1/client/python"
23
+ }
@@ -0,0 +1,89 @@
1
+ # Copyright 2018-2023 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from unittest.mock import MagicMock
5
+
6
+ import pytest
7
+ from openlineage.client.client import OpenLineageClient
8
+ from openlineage.client.run import Job, Run, RunEvent, RunState
9
+
10
+
11
+ def test_client_fails_with_wrong_event_type():
12
+ session = MagicMock()
13
+ client = OpenLineageClient(url="http://example.com", session=session)
14
+
15
+ with pytest.raises(ValueError):
16
+ client.emit("event")
17
+
18
+
19
+ def test_client_fails_to_create_with_wrong_url():
20
+ session = MagicMock()
21
+ with pytest.raises(ValueError):
22
+ OpenLineageClient(url="notanurl", session=session)
23
+ with pytest.raises(ValueError):
24
+ OpenLineageClient(url="http://", session=session)
25
+ with pytest.raises(ValueError):
26
+ OpenLineageClient(url="example.com", session=session)
27
+ with pytest.raises(ValueError):
28
+ OpenLineageClient(url="http:example.com", session=session)
29
+ with pytest.raises(ValueError):
30
+ OpenLineageClient(url="http:/example.com", session=session)
31
+ with pytest.raises(ValueError):
32
+ OpenLineageClient(url="196.168.0.1", session=session)
33
+
34
+
35
+ def test_client_passes_to_create_with_valid_url():
36
+ session = MagicMock()
37
+ assert OpenLineageClient(url="http://196.168.0.1", session=session).transport.url == \
38
+ "http://196.168.0.1"
39
+ assert OpenLineageClient(url="http://196.168.0.1", session=session).transport.url == \
40
+ "http://196.168.0.1"
41
+ assert OpenLineageClient(url="http://example.com ", session=session).transport.url == \
42
+ "http://example.com"
43
+ assert OpenLineageClient(url=" http://example.com", session=session).transport.url == \
44
+ "http://example.com"
45
+ assert OpenLineageClient(url=" http://marquez:5000 ", session=session).transport.url == \
46
+ "http://marquez:5000"
47
+ assert OpenLineageClient(url=" https://marquez ", session=session).transport.url == \
48
+ "https://marquez"
49
+
50
+
51
+ def test_client_sends_proper_json_with_minimal_event():
52
+ session = MagicMock()
53
+ client = OpenLineageClient(url="http://example.com", session=session)
54
+
55
+ client.emit(
56
+ RunEvent(
57
+ RunState.START,
58
+ "2021-11-03T10:53:52.427343",
59
+ Run("69f4acab-b87d-4fc0-b27b-8ea950370ff3"),
60
+ Job("openlineage", "job"),
61
+ "producer"
62
+ )
63
+ )
64
+
65
+ session.post.assert_called_with(
66
+ "http://example.com/api/v1/lineage",
67
+ '{"eventTime": "2021-11-03T10:53:52.427343", "eventType": "START", "inputs": [], "job": '
68
+ '{"facets": {}, "name": "job", "namespace": "openlineage"}, "outputs": [], '
69
+ '"producer": "producer", "run": {"facets": {}, "runId": '
70
+ '"69f4acab-b87d-4fc0-b27b-8ea950370ff3"}}',
71
+ timeout=5.0,
72
+ verify=True
73
+ )
74
+
75
+
76
+ def test_client_uses_passed_transport():
77
+ transport = MagicMock()
78
+ client = OpenLineageClient(transport=transport)
79
+ assert client.transport == transport
80
+
81
+ client.emit(event=RunEvent(
82
+ RunState.START,
83
+ "2021-11-03T10:53:52.427343",
84
+ Run("69f4acab-b87d-4fc0-b27b-8ea950370ff3"),
85
+ Job("openlineage", "job"),
86
+ "producer"
87
+ )
88
+ )
89
+ client.transport.emit.assert_called_once()
@@ -0,0 +1,132 @@
1
+ # Copyright 2018-2023 contributors to the OpenLineage project
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import json
5
+ import os
6
+ from typing import List, Optional
7
+
8
+ import attr
9
+ import pytest
10
+ from openlineage.client import facet, run, set_producer
11
+ from openlineage.client.run import RunState
12
+ from openlineage.client.serde import Serde
13
+
14
+
15
+ @pytest.fixture(scope='session', autouse=True)
16
+ def setup_producer():
17
+ set_producer('https://github.com/OpenLineage/OpenLineage/tree/0.0.1/client/python')
18
+
19
+
20
+ def get_sorted_json(file_name: str) -> str:
21
+ dirpath = os.path.dirname(os.path.realpath(__file__))
22
+ with open(os.path.join(dirpath, file_name), 'r') as f:
23
+ loaded = json.load(f)
24
+ return json.dumps(loaded, sort_keys=True)
25
+
26
+
27
+ def test_full_core_event_serializes_properly():
28
+ runEvent = run.RunEvent(
29
+ eventType=run.RunState.START,
30
+ eventTime='2021-11-03T10:53:52.427343',
31
+ run=run.Run(
32
+ runId='69f4acab-b87d-4fc0-b27b-8ea950370ff3',
33
+ facets={
34
+ "nominalTime": facet.NominalTimeRunFacet(
35
+ nominalStartTime='2020-01-01',
36
+ nominalEndTime='2020-01-02'
37
+ )
38
+ }
39
+ ),
40
+ job=run.Job(
41
+ namespace="openlineage",
42
+ name="name",
43
+ facets={}
44
+ ),
45
+ inputs=[],
46
+ outputs=[],
47
+ producer="https://github.com/OpenLineage/OpenLineage/tree/0.0.1/client/python"
48
+ )
49
+
50
+ assert Serde.to_json(runEvent) == get_sorted_json('serde_example.json')
51
+
52
+
53
+ def test_run_id_uuid_check():
54
+ # does not throw when passed uuid
55
+ run.Run(runId='69f4acab-b87d-4fc0-b27b-8ea950370ff3')
56
+
57
+ with pytest.raises(ValueError):
58
+ run.Run(
59
+ runId='1500100900',
60
+ facets={}
61
+ )
62
+
63
+
64
+ def test_run_event_type_validated():
65
+ valid_event = run.RunEvent(
66
+ RunState.START,
67
+ "2021-11-03T10:53:52.427343",
68
+ run.Run("69f4acab-b87d-4fc0-b27b-8ea950370ff3", {}),
69
+ run.Job("default", "name"),
70
+ "producer"
71
+ )
72
+ with pytest.raises(ValueError):
73
+ run.RunEvent(
74
+ "asdf",
75
+ valid_event.eventTime,
76
+ valid_event.run,
77
+ valid_event.job,
78
+ valid_event.producer
79
+ )
80
+
81
+ with pytest.raises(ValueError):
82
+ run.RunEvent(
83
+ valid_event.eventType,
84
+ "2021-11-03",
85
+ valid_event.run,
86
+ valid_event.job,
87
+ valid_event.producer
88
+ )
89
+
90
+
91
+ def test_nominal_time_facet_does_not_require_end_time():
92
+ assert Serde.to_json(facet.NominalTimeRunFacet(
93
+ nominalStartTime='2020-01-01',
94
+ )) == get_sorted_json("nominal_time_without_end.json")
95
+
96
+
97
+ def test_schema_field_default():
98
+ assert Serde.to_json(facet.SchemaField(name='asdf', type='int4')) == \
99
+ '{"name": "asdf", "type": "int4"}'
100
+
101
+ assert Serde.to_json(facet.SchemaField(
102
+ name='asdf',
103
+ type='int4',
104
+ description='primary key')
105
+ ) == '{"description": "primary key", "name": "asdf", "type": "int4"}'
106
+
107
+
108
+ @attr.s
109
+ class NestedObject:
110
+ value: Optional[int] = attr.ib(default=None)
111
+
112
+
113
+ @attr.s
114
+ class NestingObject:
115
+ nested: List[NestedObject] = attr.ib()
116
+ optional: Optional[int] = attr.ib(default=None)
117
+
118
+
119
+ def test_serde_nested_nulls():
120
+ assert Serde.to_json(NestingObject(
121
+ nested=[
122
+ NestedObject(),
123
+ NestedObject(41)
124
+ ],
125
+ optional=3
126
+ )) == '{"nested": [{"value": 41}], "optional": 3}'
127
+
128
+ assert Serde.to_json(NestingObject(
129
+ nested=[
130
+ NestedObject()
131
+ ]
132
+ )) == '{"nested": []}'