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.
- openlineage_python-0.26.0/.gitignore +3 -0
- openlineage_python-0.26.0/PKG-INFO +101 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/README.md +9 -9
- openlineage_python-0.26.0/openlineage/__init__.py +2 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/__init__.py +5 -1
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/client.py +1 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/constants.py +1 -1
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/facet.py +1 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/run.py +1 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/__init__.py +9 -8
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/factory.py +1 -1
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/http.py +1 -1
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/kafka.py +1 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/transport.py +3 -2
- openlineage_python-0.26.0/pyproject.toml +72 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/setup.cfg +5 -13
- openlineage_python-0.26.0/tests/__init__.py +2 -0
- openlineage_python-0.26.0/tests/config/config.yml +3 -0
- openlineage_python-0.26.0/tests/config/http.yml +7 -0
- openlineage_python-0.26.0/tests/nominal_time_without_end.json +5 -0
- openlineage_python-0.26.0/tests/openlineage.yml +3 -0
- openlineage_python-0.26.0/tests/serde_example.json +23 -0
- openlineage_python-0.26.0/tests/test_client.py +89 -0
- openlineage_python-0.26.0/tests/test_events.py +132 -0
- openlineage_python-0.26.0/tests/test_facet.py +420 -0
- openlineage_python-0.26.0/tests/test_factory.py +149 -0
- openlineage_python-0.26.0/tests/test_http.py +99 -0
- openlineage_python-0.26.0/tests/test_kafka.py +70 -0
- openlineage_python-0.26.0/tests/test_utils.py +2 -0
- openlineage_python-0.26.0/tests/transport.py +28 -0
- openlineage_python-0.26.0/tox.ini +63 -0
- openlineage-python-0.25.0/PKG-INFO +0 -86
- openlineage-python-0.25.0/openlineage_python.egg-info/PKG-INFO +0 -86
- openlineage-python-0.25.0/openlineage_python.egg-info/SOURCES.txt +0 -24
- openlineage-python-0.25.0/openlineage_python.egg-info/dependency_links.txt +0 -1
- openlineage-python-0.25.0/openlineage_python.egg-info/not-zip-safe +0 -1
- openlineage-python-0.25.0/openlineage_python.egg-info/requires.txt +0 -30
- openlineage-python-0.25.0/openlineage_python.egg-info/top_level.txt +0 -1
- openlineage-python-0.25.0/pyproject.toml +0 -12
- openlineage-python-0.25.0/setup.py +0 -50
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/serde.py +0 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/console.py +0 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/noop.py +0 -0
- {openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/utils.py +0 -0
|
@@ -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
|
|
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.
|
|
@@ -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__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"OpenLineageClient",
|
|
9
|
+
"OpenLineageClientOptions",
|
|
10
|
+
"set_producer",
|
|
11
|
+
]
|
{openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/__init__.py
RENAMED
|
@@ -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
|
]
|
{openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/factory.py
RENAMED
|
@@ -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()
|
{openlineage-python-0.25.0 → openlineage_python-0.26.0}/openlineage/client/transport/transport.py
RENAMED
|
@@ -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.
|
|
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:
|
|
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,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": []}'
|