apache-airflow-providers-openlineage 1.3.1rc1__py3-none-any.whl
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.
Potentially problematic release.
This version of apache-airflow-providers-openlineage might be problematic. Click here for more details.
- airflow/providers/openlineage/LICENSE +253 -0
- airflow/providers/openlineage/__init__.py +42 -0
- airflow/providers/openlineage/extractors/__init__.py +27 -0
- airflow/providers/openlineage/extractors/base.py +153 -0
- airflow/providers/openlineage/extractors/bash.py +81 -0
- airflow/providers/openlineage/extractors/manager.py +199 -0
- airflow/providers/openlineage/extractors/python.py +97 -0
- airflow/providers/openlineage/get_provider_info.py +110 -0
- airflow/providers/openlineage/plugins/__init__.py +16 -0
- airflow/providers/openlineage/plugins/adapter.py +375 -0
- airflow/providers/openlineage/plugins/facets.py +73 -0
- airflow/providers/openlineage/plugins/listener.py +226 -0
- airflow/providers/openlineage/plugins/macros.py +60 -0
- airflow/providers/openlineage/plugins/openlineage.py +51 -0
- airflow/providers/openlineage/sqlparser.py +347 -0
- airflow/providers/openlineage/utils/__init__.py +16 -0
- airflow/providers/openlineage/utils/sql.py +199 -0
- airflow/providers/openlineage/utils/utils.py +432 -0
- apache_airflow_providers_openlineage-1.3.1rc1.dist-info/METADATA +139 -0
- apache_airflow_providers_openlineage-1.3.1rc1.dist-info/RECORD +22 -0
- apache_airflow_providers_openlineage-1.3.1rc1.dist-info/WHEEL +4 -0
- apache_airflow_providers_openlineage-1.3.1rc1.dist-info/entry_points.txt +6 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
|
3
|
+
# distributed with this work for additional information
|
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
|
6
|
+
# "License"); you may not use this file except in compliance
|
|
7
|
+
# with the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import datetime
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
from contextlib import suppress
|
|
25
|
+
from functools import wraps
|
|
26
|
+
from typing import TYPE_CHECKING, Any, Iterable
|
|
27
|
+
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
|
|
28
|
+
|
|
29
|
+
import attrs
|
|
30
|
+
from attrs import asdict
|
|
31
|
+
|
|
32
|
+
# TODO: move this maybe to Airflow's logic?
|
|
33
|
+
from openlineage.client.utils import RedactMixin
|
|
34
|
+
|
|
35
|
+
from airflow.compat.functools import cache
|
|
36
|
+
from airflow.configuration import conf
|
|
37
|
+
from airflow.providers.openlineage.plugins.facets import (
|
|
38
|
+
AirflowMappedTaskRunFacet,
|
|
39
|
+
AirflowRunFacet,
|
|
40
|
+
)
|
|
41
|
+
from airflow.utils.context import AirflowContextDeprecationWarning
|
|
42
|
+
from airflow.utils.log.secrets_masker import Redactable, Redacted, SecretsMasker, should_hide_value_for_key
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from airflow.models import DAG, BaseOperator, Connection, DagRun, TaskInstance
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
log = logging.getLogger(__name__)
|
|
49
|
+
_NOMINAL_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def openlineage_job_name(dag_id: str, task_id: str) -> str:
|
|
53
|
+
return f"{dag_id}.{task_id}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_operator_class(task: BaseOperator) -> type:
|
|
57
|
+
if task.__class__.__name__ in ("DecoratedMappedOperator", "MappedOperator"):
|
|
58
|
+
return task.operator_class
|
|
59
|
+
return task.__class__
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def to_json_encodable(task: BaseOperator) -> dict[str, object]:
|
|
63
|
+
def _task_encoder(obj):
|
|
64
|
+
from airflow.models import DAG
|
|
65
|
+
|
|
66
|
+
if isinstance(obj, datetime.datetime):
|
|
67
|
+
return obj.isoformat()
|
|
68
|
+
elif isinstance(obj, DAG):
|
|
69
|
+
return {
|
|
70
|
+
"dag_id": obj.dag_id,
|
|
71
|
+
"tags": obj.tags,
|
|
72
|
+
"schedule_interval": obj.schedule_interval,
|
|
73
|
+
"timetable": obj.timetable.serialize(),
|
|
74
|
+
}
|
|
75
|
+
else:
|
|
76
|
+
return str(obj)
|
|
77
|
+
|
|
78
|
+
return json.loads(json.dumps(task.__dict__, default=_task_encoder))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def url_to_https(url) -> str | None:
|
|
82
|
+
# Ensure URL exists
|
|
83
|
+
if not url:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
base_url = None
|
|
87
|
+
if url.startswith("git@"):
|
|
88
|
+
part = url.split("git@")[1:2]
|
|
89
|
+
if part:
|
|
90
|
+
base_url = f'https://{part[0].replace(":", "/", 1)}'
|
|
91
|
+
elif url.startswith("https://"):
|
|
92
|
+
base_url = url
|
|
93
|
+
|
|
94
|
+
if not base_url:
|
|
95
|
+
raise ValueError(f"Unable to extract location from: {url}")
|
|
96
|
+
|
|
97
|
+
if base_url.endswith(".git"):
|
|
98
|
+
base_url = base_url[:-4]
|
|
99
|
+
return base_url
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def redacted_connection_uri(conn: Connection, filtered_params=None, filtered_prefixes=None):
|
|
103
|
+
"""
|
|
104
|
+
Return the connection URI for the given Connection.
|
|
105
|
+
|
|
106
|
+
This method additionally filters URI by removing query parameters that are known to carry sensitive data
|
|
107
|
+
like username, password, access key.
|
|
108
|
+
"""
|
|
109
|
+
if filtered_prefixes is None:
|
|
110
|
+
filtered_prefixes = []
|
|
111
|
+
if filtered_params is None:
|
|
112
|
+
filtered_params = []
|
|
113
|
+
|
|
114
|
+
def filter_key_params(k: str):
|
|
115
|
+
return k not in filtered_params and any(substr in k for substr in filtered_prefixes)
|
|
116
|
+
|
|
117
|
+
conn_uri = conn.get_uri()
|
|
118
|
+
parsed = urlparse(conn_uri)
|
|
119
|
+
|
|
120
|
+
# Remove username and password
|
|
121
|
+
netloc = f"{parsed.hostname}" + (f":{parsed.port}" if parsed.port else "")
|
|
122
|
+
parsed = parsed._replace(netloc=netloc)
|
|
123
|
+
if parsed.query:
|
|
124
|
+
query_dict = dict(parse_qsl(parsed.query))
|
|
125
|
+
if conn.EXTRA_KEY in query_dict:
|
|
126
|
+
query_dict = json.loads(query_dict[conn.EXTRA_KEY])
|
|
127
|
+
filtered_qs = {k: v for k, v in query_dict.items() if not filter_key_params(k)}
|
|
128
|
+
parsed = parsed._replace(query=urlencode(filtered_qs))
|
|
129
|
+
return urlunparse(parsed)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_connection(conn_id) -> Connection | None:
|
|
133
|
+
from airflow.hooks.base import BaseHook
|
|
134
|
+
|
|
135
|
+
with suppress(Exception):
|
|
136
|
+
return BaseHook.get_connection(conn_id=conn_id)
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_job_name(task):
|
|
141
|
+
return f"{task.dag_id}.{task.task_id}"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def get_custom_facets(task_instance: TaskInstance | None = None) -> dict[str, Any]:
|
|
145
|
+
custom_facets = {}
|
|
146
|
+
# check for -1 comes from SmartSensor compatibility with dynamic task mapping
|
|
147
|
+
# this comes from Airflow code
|
|
148
|
+
if hasattr(task_instance, "map_index") and getattr(task_instance, "map_index") != -1:
|
|
149
|
+
custom_facets["airflow_mappedTask"] = AirflowMappedTaskRunFacet.from_task_instance(task_instance)
|
|
150
|
+
return custom_facets
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class InfoJsonEncodable(dict):
|
|
154
|
+
"""
|
|
155
|
+
Airflow objects might not be json-encodable overall.
|
|
156
|
+
|
|
157
|
+
The class provides additional attributes to control
|
|
158
|
+
what and how is encoded:
|
|
159
|
+
|
|
160
|
+
* renames: a dictionary of attribute name changes
|
|
161
|
+
* | casts: a dictionary consisting of attribute names
|
|
162
|
+
| and corresponding methods that should change
|
|
163
|
+
| object value
|
|
164
|
+
* includes: list of attributes to be included in encoding
|
|
165
|
+
* excludes: list of attributes to be excluded from encoding
|
|
166
|
+
|
|
167
|
+
Don't use both includes and excludes.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
renames: dict[str, str] = {}
|
|
171
|
+
casts: dict[str, Any] = {}
|
|
172
|
+
includes: list[str] = []
|
|
173
|
+
excludes: list[str] = []
|
|
174
|
+
|
|
175
|
+
def __init__(self, obj):
|
|
176
|
+
self.obj = obj
|
|
177
|
+
self._fields = []
|
|
178
|
+
|
|
179
|
+
self._cast_fields()
|
|
180
|
+
self._rename_fields()
|
|
181
|
+
self._include_fields()
|
|
182
|
+
dict.__init__(
|
|
183
|
+
self,
|
|
184
|
+
**{field: InfoJsonEncodable._cast_basic_types(getattr(self, field)) for field in self._fields},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def _cast_basic_types(value):
|
|
189
|
+
if isinstance(value, datetime.datetime):
|
|
190
|
+
return value.isoformat()
|
|
191
|
+
if isinstance(value, (set, list, tuple)):
|
|
192
|
+
return str(list(value))
|
|
193
|
+
return value
|
|
194
|
+
|
|
195
|
+
def _rename_fields(self):
|
|
196
|
+
for field, renamed in self.renames.items():
|
|
197
|
+
if hasattr(self.obj, field):
|
|
198
|
+
setattr(self, renamed, getattr(self.obj, field))
|
|
199
|
+
self._fields.append(renamed)
|
|
200
|
+
|
|
201
|
+
def _cast_fields(self):
|
|
202
|
+
for field, func in self.casts.items():
|
|
203
|
+
setattr(self, field, func(self.obj))
|
|
204
|
+
self._fields.append(field)
|
|
205
|
+
|
|
206
|
+
def _include_fields(self):
|
|
207
|
+
if self.includes and self.excludes:
|
|
208
|
+
raise Exception("Don't use both includes and excludes.")
|
|
209
|
+
if self.includes:
|
|
210
|
+
for field in self.includes:
|
|
211
|
+
if field not in self._fields and hasattr(self.obj, field):
|
|
212
|
+
setattr(self, field, getattr(self.obj, field))
|
|
213
|
+
self._fields.append(field)
|
|
214
|
+
else:
|
|
215
|
+
for field, val in self.obj.__dict__.items():
|
|
216
|
+
if field not in self._fields and field not in self.excludes and field not in self.renames:
|
|
217
|
+
setattr(self, field, val)
|
|
218
|
+
self._fields.append(field)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class DagInfo(InfoJsonEncodable):
|
|
222
|
+
"""Defines encoding DAG object to JSON."""
|
|
223
|
+
|
|
224
|
+
includes = ["dag_id", "schedule_interval", "tags", "start_date"]
|
|
225
|
+
casts = {"timetable": lambda dag: dag.timetable.serialize() if getattr(dag, "timetable", None) else None}
|
|
226
|
+
renames = {"_dag_id": "dag_id"}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class DagRunInfo(InfoJsonEncodable):
|
|
230
|
+
"""Defines encoding DagRun object to JSON."""
|
|
231
|
+
|
|
232
|
+
includes = [
|
|
233
|
+
"conf",
|
|
234
|
+
"dag_id",
|
|
235
|
+
"data_interval_start",
|
|
236
|
+
"data_interval_end",
|
|
237
|
+
"external_trigger",
|
|
238
|
+
"run_id",
|
|
239
|
+
"run_type",
|
|
240
|
+
"start_date",
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class TaskInstanceInfo(InfoJsonEncodable):
|
|
245
|
+
"""Defines encoding TaskInstance object to JSON."""
|
|
246
|
+
|
|
247
|
+
includes = ["duration", "try_number", "pool"]
|
|
248
|
+
casts = {
|
|
249
|
+
"map_index": lambda ti: ti.map_index
|
|
250
|
+
if hasattr(ti, "map_index") and getattr(ti, "map_index") != -1
|
|
251
|
+
else None
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class TaskInfo(InfoJsonEncodable):
|
|
256
|
+
"""Defines encoding BaseOperator/AbstractOperator object to JSON."""
|
|
257
|
+
|
|
258
|
+
renames = {
|
|
259
|
+
"_BaseOperator__init_kwargs": "args",
|
|
260
|
+
"_BaseOperator__from_mapped": "mapped",
|
|
261
|
+
"_downstream_task_ids": "downstream_task_ids",
|
|
262
|
+
"_upstream_task_ids": "upstream_task_ids",
|
|
263
|
+
}
|
|
264
|
+
excludes = [
|
|
265
|
+
"_BaseOperator__instantiated",
|
|
266
|
+
"_dag",
|
|
267
|
+
"_hook",
|
|
268
|
+
"_log",
|
|
269
|
+
"_outlets",
|
|
270
|
+
"_inlets",
|
|
271
|
+
"_lock_for_execution",
|
|
272
|
+
"handler",
|
|
273
|
+
"params",
|
|
274
|
+
"python_callable",
|
|
275
|
+
"retry_delay",
|
|
276
|
+
]
|
|
277
|
+
casts = {
|
|
278
|
+
"operator_class": lambda task: task.task_type,
|
|
279
|
+
"task_group": lambda task: TaskGroupInfo(task.task_group)
|
|
280
|
+
if hasattr(task, "task_group") and getattr(task.task_group, "_group_id", None)
|
|
281
|
+
else None,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class TaskGroupInfo(InfoJsonEncodable):
|
|
286
|
+
"""Defines encoding TaskGroup object to JSON."""
|
|
287
|
+
|
|
288
|
+
renames = {
|
|
289
|
+
"_group_id": "group_id",
|
|
290
|
+
}
|
|
291
|
+
includes = [
|
|
292
|
+
"downstream_group_ids",
|
|
293
|
+
"downstream_task_ids",
|
|
294
|
+
"prefix_group_id",
|
|
295
|
+
"tooltip",
|
|
296
|
+
"upstream_group_ids",
|
|
297
|
+
"upstream_task_ids",
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_airflow_run_facet(
|
|
302
|
+
dag_run: DagRun,
|
|
303
|
+
dag: DAG,
|
|
304
|
+
task_instance: TaskInstance,
|
|
305
|
+
task: BaseOperator,
|
|
306
|
+
task_uuid: str,
|
|
307
|
+
):
|
|
308
|
+
return {
|
|
309
|
+
"airflow": json.loads(
|
|
310
|
+
json.dumps(
|
|
311
|
+
asdict(
|
|
312
|
+
AirflowRunFacet(
|
|
313
|
+
dag=DagInfo(dag),
|
|
314
|
+
dagRun=DagRunInfo(dag_run),
|
|
315
|
+
taskInstance=TaskInstanceInfo(task_instance),
|
|
316
|
+
task=TaskInfo(task),
|
|
317
|
+
taskUuid=task_uuid,
|
|
318
|
+
)
|
|
319
|
+
),
|
|
320
|
+
default=str,
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class OpenLineageRedactor(SecretsMasker):
|
|
327
|
+
"""
|
|
328
|
+
This class redacts sensitive data similar to SecretsMasker in Airflow logs.
|
|
329
|
+
|
|
330
|
+
The difference is that our default max recursion depth is way higher - due to
|
|
331
|
+
the structure of OL events we need more depth.
|
|
332
|
+
Additionally, we allow data structures to specify data that needs not to be
|
|
333
|
+
redacted by specifying _skip_redact list by deriving RedactMixin.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
@classmethod
|
|
337
|
+
def from_masker(cls, other: SecretsMasker) -> OpenLineageRedactor:
|
|
338
|
+
instance = cls()
|
|
339
|
+
instance.patterns = other.patterns
|
|
340
|
+
instance.replacer = other.replacer
|
|
341
|
+
return instance
|
|
342
|
+
|
|
343
|
+
def _redact(self, item: Redactable, name: str | None, depth: int, max_depth: int) -> Redacted:
|
|
344
|
+
if depth > max_depth:
|
|
345
|
+
return item
|
|
346
|
+
try:
|
|
347
|
+
# It's impossible to check the type of variable in a dict without accessing it, and
|
|
348
|
+
# this already causes warning - so suppress it
|
|
349
|
+
with suppress(AirflowContextDeprecationWarning):
|
|
350
|
+
if type(item).__name__ == "Proxy":
|
|
351
|
+
# Those are deprecated values in _DEPRECATION_REPLACEMENTS
|
|
352
|
+
# in airflow.utils.context.Context
|
|
353
|
+
return "<<non-redactable: Proxy>>"
|
|
354
|
+
if name and should_hide_value_for_key(name):
|
|
355
|
+
return self._redact_all(item, depth, max_depth)
|
|
356
|
+
if attrs.has(type(item)):
|
|
357
|
+
# TODO: FIXME when mypy gets compatible with new attrs
|
|
358
|
+
for dict_key, subval in attrs.asdict(
|
|
359
|
+
item, # type: ignore[arg-type]
|
|
360
|
+
recurse=False,
|
|
361
|
+
).items():
|
|
362
|
+
if _is_name_redactable(dict_key, item):
|
|
363
|
+
setattr(
|
|
364
|
+
item,
|
|
365
|
+
dict_key,
|
|
366
|
+
self._redact(subval, name=dict_key, depth=(depth + 1), max_depth=max_depth),
|
|
367
|
+
)
|
|
368
|
+
return item
|
|
369
|
+
elif is_json_serializable(item) and hasattr(item, "__dict__"):
|
|
370
|
+
for dict_key, subval in item.__dict__.items():
|
|
371
|
+
if type(subval).__name__ == "Proxy":
|
|
372
|
+
return "<<non-redactable: Proxy>>"
|
|
373
|
+
if _is_name_redactable(dict_key, item):
|
|
374
|
+
setattr(
|
|
375
|
+
item,
|
|
376
|
+
dict_key,
|
|
377
|
+
self._redact(subval, name=dict_key, depth=(depth + 1), max_depth=max_depth),
|
|
378
|
+
)
|
|
379
|
+
return item
|
|
380
|
+
else:
|
|
381
|
+
return super()._redact(item, name, depth, max_depth)
|
|
382
|
+
except Exception as exc:
|
|
383
|
+
log.warning("Unable to redact %r. Error was: %s: %s", item, type(exc).__name__, exc)
|
|
384
|
+
return item
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def is_json_serializable(item):
|
|
388
|
+
try:
|
|
389
|
+
json.dumps(item)
|
|
390
|
+
return True
|
|
391
|
+
except (TypeError, ValueError):
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _is_name_redactable(name, redacted):
|
|
396
|
+
if not issubclass(redacted.__class__, RedactMixin):
|
|
397
|
+
return not name.startswith("_")
|
|
398
|
+
return name not in redacted.skip_redact
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def print_warning(log):
|
|
402
|
+
def decorator(f):
|
|
403
|
+
@wraps(f)
|
|
404
|
+
def wrapper(*args, **kwargs):
|
|
405
|
+
try:
|
|
406
|
+
return f(*args, **kwargs)
|
|
407
|
+
except Exception as e:
|
|
408
|
+
log.warning(e)
|
|
409
|
+
|
|
410
|
+
return wrapper
|
|
411
|
+
|
|
412
|
+
return decorator
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@cache
|
|
416
|
+
def is_source_enabled() -> bool:
|
|
417
|
+
source_var = conf.get(
|
|
418
|
+
"openlineage", "disable_source_code", fallback=os.getenv("OPENLINEAGE_AIRFLOW_DISABLE_SOURCE_CODE")
|
|
419
|
+
)
|
|
420
|
+
return isinstance(source_var, str) and source_var.lower() not in ("true", "1", "t")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def get_filtered_unknown_operator_keys(operator: BaseOperator) -> dict:
|
|
424
|
+
not_required_keys = {"dag", "task_group"}
|
|
425
|
+
return {attr: value for attr, value in operator.__dict__.items() if attr not in not_required_keys}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def normalize_sql(sql: str | Iterable[str]):
|
|
429
|
+
if isinstance(sql, str):
|
|
430
|
+
sql = [stmt for stmt in sql.split(";") if stmt != ""]
|
|
431
|
+
sql = [obj for stmt in sql for obj in stmt.split(";") if obj != ""]
|
|
432
|
+
return ";\n".join(sql)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: apache-airflow-providers-openlineage
|
|
3
|
+
Version: 1.3.1rc1
|
|
4
|
+
Summary: Provider package apache-airflow-providers-openlineage for Apache Airflow
|
|
5
|
+
Keywords: airflow-provider,openlineage,airflow,integration
|
|
6
|
+
Author-email: Apache Software Foundation <dev@airflow.apache.org>
|
|
7
|
+
Maintainer-email: Apache Software Foundation <dev@airflow.apache.org>
|
|
8
|
+
Requires-Python: ~=3.8
|
|
9
|
+
Description-Content-Type: text/x-rst
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Framework :: Apache Airflow
|
|
16
|
+
Classifier: Framework :: Apache Airflow :: Provider
|
|
17
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Requires-Dist: apache-airflow-providers-common-sql>=1.6.0.dev0
|
|
24
|
+
Requires-Dist: apache-airflow>=2.7.0.dev0
|
|
25
|
+
Requires-Dist: attrs>=22.2
|
|
26
|
+
Requires-Dist: openlineage-integration-common>=0.28.0
|
|
27
|
+
Requires-Dist: openlineage-python>=0.28.0
|
|
28
|
+
Requires-Dist: apache-airflow-providers-common-sql ; extra == "common.sql"
|
|
29
|
+
Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
|
|
30
|
+
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-openlineage/1.3.1/changelog.html
|
|
31
|
+
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-openlineage/1.3.1
|
|
32
|
+
Project-URL: Slack Chat, https://s.apache.org/airflow-slack
|
|
33
|
+
Project-URL: Source Code, https://github.com/apache/airflow
|
|
34
|
+
Project-URL: Twitter, https://twitter.com/ApacheAirflow
|
|
35
|
+
Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
|
|
36
|
+
Provides-Extra: common.sql
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
.. Licensed to the Apache Software Foundation (ASF) under one
|
|
40
|
+
or more contributor license agreements. See the NOTICE file
|
|
41
|
+
distributed with this work for additional information
|
|
42
|
+
regarding copyright ownership. The ASF licenses this file
|
|
43
|
+
to you under the Apache License, Version 2.0 (the
|
|
44
|
+
"License"); you may not use this file except in compliance
|
|
45
|
+
with the License. You may obtain a copy of the License at
|
|
46
|
+
|
|
47
|
+
.. http://www.apache.org/licenses/LICENSE-2.0
|
|
48
|
+
|
|
49
|
+
.. Unless required by applicable law or agreed to in writing,
|
|
50
|
+
software distributed under the License is distributed on an
|
|
51
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
52
|
+
KIND, either express or implied. See the License for the
|
|
53
|
+
specific language governing permissions and limitations
|
|
54
|
+
under the License.
|
|
55
|
+
|
|
56
|
+
.. Licensed to the Apache Software Foundation (ASF) under one
|
|
57
|
+
or more contributor license agreements. See the NOTICE file
|
|
58
|
+
distributed with this work for additional information
|
|
59
|
+
regarding copyright ownership. The ASF licenses this file
|
|
60
|
+
to you under the Apache License, Version 2.0 (the
|
|
61
|
+
"License"); you may not use this file except in compliance
|
|
62
|
+
with the License. You may obtain a copy of the License at
|
|
63
|
+
|
|
64
|
+
.. http://www.apache.org/licenses/LICENSE-2.0
|
|
65
|
+
|
|
66
|
+
.. Unless required by applicable law or agreed to in writing,
|
|
67
|
+
software distributed under the License is distributed on an
|
|
68
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
69
|
+
KIND, either express or implied. See the License for the
|
|
70
|
+
specific language governing permissions and limitations
|
|
71
|
+
under the License.
|
|
72
|
+
|
|
73
|
+
.. NOTE! THIS FILE IS AUTOMATICALLY GENERATED AND WILL BE
|
|
74
|
+
OVERWRITTEN WHEN PREPARING PACKAGES.
|
|
75
|
+
|
|
76
|
+
.. IF YOU WANT TO MODIFY TEMPLATE FOR THIS FILE, YOU SHOULD MODIFY THE TEMPLATE
|
|
77
|
+
`PROVIDER_README_TEMPLATE.rst.jinja2` IN the `dev/breeze/src/airflow_breeze/templates` DIRECTORY
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Package ``apache-airflow-providers-openlineage``
|
|
81
|
+
|
|
82
|
+
Release: ``1.3.1.rc1``
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
`OpenLineage <https://openlineage.io/>`__
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
Provider package
|
|
89
|
+
----------------
|
|
90
|
+
|
|
91
|
+
This is a provider package for ``openlineage`` provider. All classes for this provider package
|
|
92
|
+
are in ``airflow.providers.openlineage`` python package.
|
|
93
|
+
|
|
94
|
+
You can find package information and changelog for the provider
|
|
95
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-openlineage/1.3.1/>`_.
|
|
96
|
+
|
|
97
|
+
Installation
|
|
98
|
+
------------
|
|
99
|
+
|
|
100
|
+
You can install this package on top of an existing Airflow 2 installation (see ``Requirements`` below
|
|
101
|
+
for the minimum Airflow version supported) via
|
|
102
|
+
``pip install apache-airflow-providers-openlineage``
|
|
103
|
+
|
|
104
|
+
The package supports the following python versions: 3.8,3.9,3.10,3.11
|
|
105
|
+
|
|
106
|
+
Requirements
|
|
107
|
+
------------
|
|
108
|
+
|
|
109
|
+
======================================= ==================
|
|
110
|
+
PIP package Version required
|
|
111
|
+
======================================= ==================
|
|
112
|
+
``apache-airflow`` ``>=2.7.0``
|
|
113
|
+
``apache-airflow-providers-common-sql`` ``>=1.6.0``
|
|
114
|
+
``attrs`` ``>=22.2``
|
|
115
|
+
``openlineage-integration-common`` ``>=0.28.0``
|
|
116
|
+
``openlineage-python`` ``>=0.28.0``
|
|
117
|
+
======================================= ==================
|
|
118
|
+
|
|
119
|
+
Cross provider package dependencies
|
|
120
|
+
-----------------------------------
|
|
121
|
+
|
|
122
|
+
Those are dependencies that might be needed in order to use all the features of the package.
|
|
123
|
+
You need to install the specified provider packages in order to use them.
|
|
124
|
+
|
|
125
|
+
You can install such cross-provider dependencies when installing from PyPI. For example:
|
|
126
|
+
|
|
127
|
+
.. code-block:: bash
|
|
128
|
+
|
|
129
|
+
pip install apache-airflow-providers-openlineage[common.sql]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
============================================================================================================ ==============
|
|
133
|
+
Dependent package Extra
|
|
134
|
+
============================================================================================================ ==============
|
|
135
|
+
`apache-airflow-providers-common-sql <https://airflow.apache.org/docs/apache-airflow-providers-common-sql>`_ ``common.sql``
|
|
136
|
+
============================================================================================================ ==============
|
|
137
|
+
|
|
138
|
+
The changelog for the provider package can be found in the
|
|
139
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-openlineage/1.3.1/changelog.html>`_.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
airflow/providers/openlineage/LICENSE,sha256=ywUBpKZc7Jb96rVt5I3IDbg7dIJAbUSHkuoDcF3jbH4,13569
|
|
2
|
+
airflow/providers/openlineage/__init__.py,sha256=gie-OVx8Ck47h2pFcd-7v_fe2F9fNjheEYbAuF5DfzU,1586
|
|
3
|
+
airflow/providers/openlineage/get_provider_info.py,sha256=SXz-PbczkwBJmSNi9LyD2_F90EDlto9_U5amXWRzCeE,5413
|
|
4
|
+
airflow/providers/openlineage/sqlparser.py,sha256=cB2NFH9rPUnkHqZ4NGh7AsAnoR7Y0YeEtQN9kgMTtRg,13384
|
|
5
|
+
airflow/providers/openlineage/extractors/__init__.py,sha256=I0X4f6zUniclyD9zT0DFHRImpCpJVP4MkPJT3cd7X5I,1081
|
|
6
|
+
airflow/providers/openlineage/extractors/base.py,sha256=KUYdZa8B238BISeaKLPNghaAt4AGGGkC-ufzsI4FB5w,5897
|
|
7
|
+
airflow/providers/openlineage/extractors/bash.py,sha256=fz1nVywzk1kUsZWeEbQ8zV6osTGhmd_pLgAKoJla54g,2843
|
|
8
|
+
airflow/providers/openlineage/extractors/manager.py,sha256=K1hbmxmer7KtO_0730GLGTdBOle6MoArw7qcfyzy9m8,7917
|
|
9
|
+
airflow/providers/openlineage/extractors/python.py,sha256=HdSJi6r6EWNinLUroUdcVi3b_4vmuoc_-E51Xc8ocmo,3423
|
|
10
|
+
airflow/providers/openlineage/plugins/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
|
|
11
|
+
airflow/providers/openlineage/plugins/adapter.py,sha256=Pq_h4fGZ9m7kWVTfvDFakIkmg3wseZvBscsimIGx-hQ,13787
|
|
12
|
+
airflow/providers/openlineage/plugins/facets.py,sha256=pt8UvUHk-rXJdJreNq5B2NSWe-CtlfnmImdY5vesgjY,2202
|
|
13
|
+
airflow/providers/openlineage/plugins/listener.py,sha256=t0UuV0R__lBGSCT4W9RdegSE6aHlvKQGGdRJK0PR6u4,8511
|
|
14
|
+
airflow/providers/openlineage/plugins/macros.py,sha256=AtBwQZPqTOWO38OucjgYS2ooiKkMTuRKLnBXdQHnAuw,2356
|
|
15
|
+
airflow/providers/openlineage/plugins/openlineage.py,sha256=XiEznOts-q9Uq08rkorclK49FAmtIIsnkW5hsdoxeB0,1987
|
|
16
|
+
airflow/providers/openlineage/utils/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
|
|
17
|
+
airflow/providers/openlineage/utils/sql.py,sha256=9Hvzs_aKBRAmuxAO22Myaz-PwwY1XvcLEwRq0sAD33Q,7634
|
|
18
|
+
airflow/providers/openlineage/utils/utils.py,sha256=-i8W7LtBMlRZdIqlXuqEr8isDIUl6REyB0wOLdyv_KI,14281
|
|
19
|
+
apache_airflow_providers_openlineage-1.3.1rc1.dist-info/entry_points.txt,sha256=GAx0_i2OeZzqaiiiYuA-xchICDXiCT5kVqpKSxsOjt4,214
|
|
20
|
+
apache_airflow_providers_openlineage-1.3.1rc1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
21
|
+
apache_airflow_providers_openlineage-1.3.1rc1.dist-info/METADATA,sha256=FvvTSzEur02Lm53ZSU6zhGmAmKc2AZrbREdF1aAF5Mc,6329
|
|
22
|
+
apache_airflow_providers_openlineage-1.3.1rc1.dist-info/RECORD,,
|