sista-tacrpy 1.0.12__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.
- sista_tacrpy-1.0.12.dist-info/METADATA +18 -0
- sista_tacrpy-1.0.12.dist-info/RECORD +17 -0
- sista_tacrpy-1.0.12.dist-info/WHEEL +5 -0
- sista_tacrpy-1.0.12.dist-info/top_level.txt +1 -0
- tacrpy/__init__.py +3 -0
- tacrpy/analytics/__init__.py +0 -0
- tacrpy/data_fetcher/__init__.py +3 -0
- tacrpy/data_fetcher/googlesheets.py +237 -0
- tacrpy/data_fetcher/isvavai.py +162 -0
- tacrpy/data_fetcher/ssot.py +1006 -0
- tacrpy/data_operations.py +70 -0
- tacrpy/datahub/__init__.py +5 -0
- tacrpy/datahub/data_lineage.py +86 -0
- tacrpy/datahub/datasets.py +421 -0
- tacrpy/datahub/glossary.py +378 -0
- tacrpy/datahub/import_checks.py +123 -0
- tacrpy/datahub/openapi.py +78 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modul pro běžné transformace a zpracování dat.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_mapping_dict(df: pd.DataFrame) -> dict:
|
|
9
|
+
""" Z dataframe, kde se k jedné hodnotě váže více pozorování v samostatných řádcích (např. projekt má N uchazečů)
|
|
10
|
+
vytvoří mapovací dict
|
|
11
|
+
|
|
12
|
+
:param df: dataframe s hodnotami one-to-many
|
|
13
|
+
:return: mapovací dict, kde unikátní ID je klíč a hodnotou je seznam hodnot, které patří k danému unikátnímu ID
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
if df.shape[1] < 2:
|
|
17
|
+
raise ValueError("DataFrame mmusí mít aspoň da sloupce pro vytvoření mapovacího slovníku.")
|
|
18
|
+
|
|
19
|
+
grouped_data = df.groupby(df.columns[0])[df.columns[1]]
|
|
20
|
+
return grouped_data.apply(list).to_dict()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def list_intersection(list1: list, list2: list, percentages: bool = True) -> dict:
|
|
24
|
+
""" Získá průnik hodnot mezi dvěma seznamy (listy) a vypočítá metriky průniku.
|
|
25
|
+
|
|
26
|
+
Metriky průniku:
|
|
27
|
+
|
|
28
|
+
- *intersect (list)* - seznam stejných hodnot
|
|
29
|
+
- *intersect_count (int)* - počet stejných hodnot
|
|
30
|
+
- *intersect_ratio (float)* - podíl stejných hodnot vůči všem unikátním hodnotám z obou seznamů
|
|
31
|
+
- *intersect_l1_ratio (float)* - podíl stejných hodnot vůči všem hodnotám v prvnímu seznamu
|
|
32
|
+
- *intersect_l2_ratio (float)* - podíl stejných hodnot vůči všem hodnotám v druhému seznamu
|
|
33
|
+
|
|
34
|
+
:param list1: seznam hodnot
|
|
35
|
+
:param list2: seznam hodnot
|
|
36
|
+
:param percentages: poměrové metriky zobrazí vrátí v procentech (0-100) s přesností na dvě desetinná místa
|
|
37
|
+
:return: dict metrik průniků
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
if not list1:
|
|
41
|
+
raise ValueError("list1 je prázdný")
|
|
42
|
+
if not list2:
|
|
43
|
+
raise ValueError("list2 je prázdný")
|
|
44
|
+
|
|
45
|
+
set1, set2 = set(list1), set(list2)
|
|
46
|
+
intersect = set1.intersection(set2)
|
|
47
|
+
l1_count = len(set1)
|
|
48
|
+
l2_count = len(set2)
|
|
49
|
+
all_count = len(set1.union(set2))
|
|
50
|
+
|
|
51
|
+
intersect_count = len(intersect)
|
|
52
|
+
|
|
53
|
+
intersect_dict = dict()
|
|
54
|
+
intersect_dict['intersect'] = list(intersect)
|
|
55
|
+
intersect_dict['intersect_count'] = intersect_count
|
|
56
|
+
|
|
57
|
+
intersect_ratio = intersect_count / all_count
|
|
58
|
+
intersect_l1_ratio = intersect_count / l1_count
|
|
59
|
+
intersect_l2_ratio = intersect_count / l2_count
|
|
60
|
+
|
|
61
|
+
if percentages:
|
|
62
|
+
rounding = lambda x: round(x * 100, 2) # prevod na procenta
|
|
63
|
+
intersect_dict['intersect_ratio'] = rounding(intersect_ratio)
|
|
64
|
+
intersect_dict['intersect_ratio_l1'] = rounding(intersect_l1_ratio)
|
|
65
|
+
intersect_dict['intersect_ratio_l2'] = rounding(intersect_l2_ratio)
|
|
66
|
+
else:
|
|
67
|
+
intersect_dict['intersect_ratio'] = intersect_ratio
|
|
68
|
+
intersect_dict['intersect_ratio_l1'] = intersect_l1_ratio
|
|
69
|
+
intersect_dict['intersect_ratio_l2'] = intersect_l2_ratio
|
|
70
|
+
return intersect_dict
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Soubor funkcí, které slouží k hromadnému vytváření podkladů k tvorbě a úpravě data lineage \
|
|
2
|
+
v DataHubu přes API."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _create_field_lineage(entity_urn: str, upstream_urn: str, field_dict: dict[str, str]) -> dict[str, any]:
|
|
6
|
+
""" Vytvoří data lineage na úrovni dvou fieldů.
|
|
7
|
+
|
|
8
|
+
:param entity_urn: URN datasetu, ve kterém je field, pro které tvoříme data lineage
|
|
9
|
+
:param upstream_urn: URN datasetu, ve kterém je field, na který má vazbu (lineage)
|
|
10
|
+
:param field_dict: informace a filed level lineage
|
|
11
|
+
:return: dict s informacemi o vazbě mezi dvěma fieldy
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
field_upstream_name = field_dict['upstream_lineage_field']
|
|
15
|
+
field_upstream_urn = f'urn:li:schemaField:({upstream_urn},{field_upstream_name})'
|
|
16
|
+
field_upstream_type = field_dict['upstream_field_type']
|
|
17
|
+
|
|
18
|
+
field_downstream_name = field_dict['downstream_lineage_field']
|
|
19
|
+
field_downstream_urn = f'urn:li:schemaField:({entity_urn},{field_downstream_name})'
|
|
20
|
+
field_downstream_type = field_dict['downstream_field_type']
|
|
21
|
+
|
|
22
|
+
field_lineage = {
|
|
23
|
+
'upstreamType': field_upstream_type,
|
|
24
|
+
'upstreams': [
|
|
25
|
+
field_upstream_urn
|
|
26
|
+
],
|
|
27
|
+
'downstreamType': field_downstream_type,
|
|
28
|
+
'downstreams': [
|
|
29
|
+
field_downstream_urn
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return field_lineage
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_dataset_lineage(entity_name: str, platform: str, lineage_dict: dict[str, any]
|
|
37
|
+
, field_level: bool = False) -> dict[str, any]:
|
|
38
|
+
""" Vytvoří data lineage (vazby) pro daný dataset.
|
|
39
|
+
|
|
40
|
+
Umožňuje vytvářet data lineage na úrovni datasetů i na úrovni jednotlivých fieldů.
|
|
41
|
+
|
|
42
|
+
:param entity_name: název datasetu, pro který se data lineage vytváří
|
|
43
|
+
:param platform: název platformy, která je zdrojem datasetu
|
|
44
|
+
(např. etalon, googlesheets, ISTA, Postgres, OpenAPI...)
|
|
45
|
+
:param lineage_dict: dict datasetu, který obsahuje jednotlivé vazby (na úrovni datasetů nebo na úrovni fieldů)
|
|
46
|
+
:param field_level: informace, jestli obsahuje dataset lineage na úrovni fieldů
|
|
47
|
+
:return: dict vazeb (data lineage) pro daný dataset
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
entity_urn = f'urn:li:dataset:(urn:li:dataPlatform:{platform},{entity_name},PROD)'
|
|
51
|
+
|
|
52
|
+
upstreams = []
|
|
53
|
+
field_lineages = []
|
|
54
|
+
|
|
55
|
+
for key, value in lineage_dict.items():
|
|
56
|
+
upstream_name = value['upstream_lineage_dataset']
|
|
57
|
+
upstream_platform = value['upstream_platform']
|
|
58
|
+
upstream_urn = f'urn:li:dataset:(urn:li:dataPlatform:{upstream_platform},{upstream_name},PROD)'
|
|
59
|
+
upstream_type = value['upstream_dataset_type']
|
|
60
|
+
|
|
61
|
+
upstream_dict = {
|
|
62
|
+
'dataset': upstream_urn,
|
|
63
|
+
'type': upstream_type
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# pro field_lineage bude existovat více záznamů pro vazbu dvou datasetů, proto se "filtruje"
|
|
67
|
+
if upstream_dict not in upstreams:
|
|
68
|
+
upstreams.append(upstream_dict)
|
|
69
|
+
|
|
70
|
+
if field_level:
|
|
71
|
+
field_lineage = _create_field_lineage(entity_urn, upstream_urn, value)
|
|
72
|
+
field_lineages.append(field_lineage)
|
|
73
|
+
|
|
74
|
+
upstream_lineage = {
|
|
75
|
+
"entityType": "dataset",
|
|
76
|
+
"entityUrn": entity_urn,
|
|
77
|
+
"aspect": {
|
|
78
|
+
"__type": "UpstreamLineage",
|
|
79
|
+
"upstreams": upstreams,
|
|
80
|
+
"fineGrainedLineages": field_lineages
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return upstream_lineage
|
|
85
|
+
|
|
86
|
+
# TODO: rozdílné funkce na vytvoření nové data lineage a update stávající data lineage
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"""Soubor funkcí, které slouží k vytváření podkladů pro DataHub a k nahrávání dat přes API \
|
|
2
|
+
včetně checků vyplněných údajů.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import os
|
|
7
|
+
import copy
|
|
8
|
+
import datetime
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
import typing
|
|
12
|
+
|
|
13
|
+
# template pro přidání fieldů
|
|
14
|
+
FIELD_TEMPLATE = {
|
|
15
|
+
'fieldPath': str(), # název pole, odpovídá názvu sloupce v DB, pro etalon anglický název
|
|
16
|
+
'nullable': None, # hodnoty nemusí být ve slouci vyplněny - True/False
|
|
17
|
+
'description': None, # popis pole, co daný sloupec reprezentuje, pro etalon může být český ekvivalent názvu
|
|
18
|
+
'type': {
|
|
19
|
+
# specifický datový typ DataHub - za tečkou Nulltype, pokud chceme pouze klasické datové typy
|
|
20
|
+
# com.linkedin.pegasus2avro.schema.NullType
|
|
21
|
+
# BooleanType, FixedType, StringType, BytesType, NumberType, DateType, TimeType,
|
|
22
|
+
# EnumType, MapType, ArrayType, UnionType, RecordType
|
|
23
|
+
'type': {
|
|
24
|
+
"com.linkedin.pegasus2avro.schema.NullType": {}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
# klasický datový typ - string, date, integer atd.
|
|
28
|
+
'nativeDataType': None,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# template pro jeden dataset
|
|
32
|
+
DATASET_TEMPLATE = {
|
|
33
|
+
"auditHeader": None,
|
|
34
|
+
"proposedSnapshot": {
|
|
35
|
+
"com.linkedin.pegasus2avro.metadata.snapshot.DatasetSnapshot": {
|
|
36
|
+
# vloží (vytvoří) strukturu, kde se má dataset nacházet
|
|
37
|
+
# dataPlatform - PostgreSQL, MySQL, Hive ... i "vlastní" platformy - Etalon
|
|
38
|
+
# struktura v rámci platformy DB.schema.dataset
|
|
39
|
+
# např. promo--rejstrik.public.rejstrik_entity
|
|
40
|
+
# prostředí PROD, PROMO, TEST atd.
|
|
41
|
+
# pro etalony vkládat rovnou do platformy bez struktury
|
|
42
|
+
# urn:li:dataPlatform:etalon,odhad,PROD
|
|
43
|
+
"urn": str(),
|
|
44
|
+
# základní informace o data setu, struktura datasetu, vlastníci, dokumentace atd.
|
|
45
|
+
"aspects": [
|
|
46
|
+
{
|
|
47
|
+
"com.linkedin.pegasus2avro.dataset.DatasetProperties": {
|
|
48
|
+
"description": None,
|
|
49
|
+
"uri": None,
|
|
50
|
+
"tags": [],
|
|
51
|
+
"customProperties": {},
|
|
52
|
+
"name": str() # nastaví název, který se jinak generuje ze schemaName
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"com.linkedin.pegasus2avro.schema.SchemaMetadata": {
|
|
57
|
+
# v GUI se rálně nezobrazuje, v GUI název odpovídá struktuře v rámci platformy
|
|
58
|
+
"schemaName": str(),
|
|
59
|
+
"platform": "urn:li:dataPlatform:etalon",
|
|
60
|
+
"version": 0,
|
|
61
|
+
"hash": "", # nevím k čemu, ale bez toho to nefunguje
|
|
62
|
+
"platformSchema": { # nevím k čemu, ale bez toho to nefunguje
|
|
63
|
+
"com.linkedin.pegasus2avro.schema.OtherSchema": {
|
|
64
|
+
"rawSchema": ""
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"fields": None, # sloupce v tabulce (datasetu)
|
|
68
|
+
"foreignKeys": None # cizí klíče a odkaz na tabulky (datasety) cizích klíčů
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# template pro přidání cizích klíčů
|
|
77
|
+
FOREIGN_KEYS_TEMPLATE = {
|
|
78
|
+
"name": None,
|
|
79
|
+
"foreignFields": [], # list fieldů v cizím datasetu, na které se má vytvořit vazba
|
|
80
|
+
"sourceFields": [], # list fieldů ve zdrojovém datasetu, pro které se má vytvořit vazba
|
|
81
|
+
"foreignDataset": str() # URN cizího datasetu, na který se odkazujeme
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class OwnerType(Enum):
|
|
86
|
+
DATA_STEWARD = 'Data Steward'
|
|
87
|
+
TECHNICAL_OWNER = 'Technical Owner'
|
|
88
|
+
BUSINESS_OWNER = 'Business Owner'
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _create_fields(entity_dict: list[dict], nested_fields: bool) -> list[dict]:
|
|
92
|
+
"""Vytvoří záznamy pro jednotlivé fieldy v datasetu na základě šablony.
|
|
93
|
+
|
|
94
|
+
V případě, že dataset (datová entita) obsahuje nested fields, pak volá funkci (_create_nested_fields),
|
|
95
|
+
která vytvoří jejich technický název. (do formátu nested_field.field, nested_field.other_nested_field.field).
|
|
96
|
+
|
|
97
|
+
:param entity_dict: dict datové entity, který obsahuje jednotlivé fieldy a další informace fieldů
|
|
98
|
+
:param nested_fields: informace, jestli dataset obsahuje vnořené fieldy (nested fields)
|
|
99
|
+
:return: list dictů jednotlivých fieldů
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
all_fields = []
|
|
103
|
+
|
|
104
|
+
for field in entity_dict:
|
|
105
|
+
field_dict = FIELD_TEMPLATE.copy()
|
|
106
|
+
|
|
107
|
+
if nested_fields:
|
|
108
|
+
if pd.isnull(field['upstream_lineage']):
|
|
109
|
+
field_dict['fieldPath'] = field['field_name']
|
|
110
|
+
else:
|
|
111
|
+
field_dict['fieldPath'] = _create_nested_field(field['field_name']
|
|
112
|
+
, field['upstream_lineage'])
|
|
113
|
+
else:
|
|
114
|
+
field_dict['fieldPath'] = field['field_name']
|
|
115
|
+
|
|
116
|
+
field_dict['description'] = field['field_description']
|
|
117
|
+
field_dict['nativeDataType'] = field['field_data_type']
|
|
118
|
+
|
|
119
|
+
all_fields.append(field_dict)
|
|
120
|
+
|
|
121
|
+
return all_fields
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _create_nested_field(field_name: str, upstream_lineage: str) -> str:
|
|
125
|
+
"""Vytvoří složený název pro nested fields.
|
|
126
|
+
|
|
127
|
+
Obecně slouží pro funkcionalitu rozbalování a zobrazení vnořených polí (nested fields) v DataHub.
|
|
128
|
+
Nejvyšší nested field -> nižší nested field -> technický název fieldů.
|
|
129
|
+
Formát - nested_field.field, nested_field.other_nested_field.field
|
|
130
|
+
|
|
131
|
+
:param field_name: technický název fieldů
|
|
132
|
+
:param upstream_lineage: seznam nested fieldů, ve kterých se field zobrazí. Odělené středníkem
|
|
133
|
+
:return: složený název fieldů (Formát - nested_field.field, nested_field.other_nested_field.field)
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
upstream_lineage_list = upstream_lineage.split(';')
|
|
137
|
+
upstream_lineage = '.'.join(upstream_lineage_list)
|
|
138
|
+
if field_name in upstream_lineage_list:
|
|
139
|
+
field_name = upstream_lineage
|
|
140
|
+
|
|
141
|
+
else:
|
|
142
|
+
field_name = f'{upstream_lineage}.{field_name}'
|
|
143
|
+
return field_name
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _create_foreign_keys(entity_dict: list[dict], urn: str, platform: str) -> list[dict]:
|
|
147
|
+
"""Vytvoří odkaz na jiné datasety skrze cizí klíče (foreign keys) na základě šablony.
|
|
148
|
+
|
|
149
|
+
Funkce vytvoří potřebné URN datasetů a fieldů. V současné chvíli je možné přiřadit pouze
|
|
150
|
+
jeden foreign key k jednomu fieldu.
|
|
151
|
+
|
|
152
|
+
:param entity_dict: dict datové entity, který obsahuje jednotlivé fieldy a další informace fieldů
|
|
153
|
+
:param urn: URN zdrojového datasetu
|
|
154
|
+
:param platform: název platformy, která je zdrojem datasetu
|
|
155
|
+
(např. etalon, googlesheets, ISTA, Postgres, OpenAPI...)
|
|
156
|
+
:return: list dictů jednotlivých cizích klíčů (foreign keys)
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
foreign_keys = []
|
|
160
|
+
|
|
161
|
+
for field in entity_dict:
|
|
162
|
+
if pd.isnull(field['foreign_key']):
|
|
163
|
+
pass
|
|
164
|
+
else:
|
|
165
|
+
fk_dict = FOREIGN_KEYS_TEMPLATE.copy()
|
|
166
|
+
fk_dict['name'] = field['field_name']
|
|
167
|
+
|
|
168
|
+
if pd.isnull(field['upstream_lineage']):
|
|
169
|
+
fieldPath = field['field_name']
|
|
170
|
+
else:
|
|
171
|
+
fieldPath = _create_nested_field(field['field_name'], field['upstream_lineage'])
|
|
172
|
+
|
|
173
|
+
foreign_dataset_name = '_'.join(field['foreign_key'].lower().split(' '))
|
|
174
|
+
foreign_dataset_urn = f'urn:li:dataset:(urn:li:dataPlatform:{platform},{foreign_dataset_name},PROD)'
|
|
175
|
+
foreign_field_urn = f'urn:li:schemaField:({foreign_dataset_urn},Entity Id)'
|
|
176
|
+
source_urn = f'urn:li:schemaField:({urn},{fieldPath})'
|
|
177
|
+
|
|
178
|
+
fk_dict['foreignFields'] = [foreign_field_urn]
|
|
179
|
+
fk_dict['sourceFields'] = [source_urn]
|
|
180
|
+
fk_dict['foreignDataset'] = foreign_dataset_urn
|
|
181
|
+
|
|
182
|
+
foreign_keys.append(fk_dict)
|
|
183
|
+
|
|
184
|
+
return foreign_keys
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _create_dataset(entity: str, platform: str, entity_dict: list[dict]
|
|
188
|
+
, nested_fields: bool, foreign_keys: bool, subfolder: str = None) -> dict:
|
|
189
|
+
""" Doplní template dictu datasetu o potřebné atributy a objekty.
|
|
190
|
+
|
|
191
|
+
Vytvoří urn (ID) datasetu na základě jmenné konvence.
|
|
192
|
+
Volá interní funkce pro vytvoření záznamů jednoltivých fieldů (_create_fields) \
|
|
193
|
+
a přiřazení cizích klíčů (_create_foreign_keys).
|
|
194
|
+
|
|
195
|
+
:param entity: název datové entity (datasetu)
|
|
196
|
+
:param platform: název platformy, která je zdrojem datasetu
|
|
197
|
+
(např. etalon, googlesheets, ISTA, Postgres, OpenAPI...)
|
|
198
|
+
:param entity_dict: dict datové entity, který obsahuje jednotlivé fieldy a další informace fieldů
|
|
199
|
+
:param nested_fields: informace, jestli dataset obsahuje vnořené fieldy (nested fields)
|
|
200
|
+
:param foreign_keys: informace, jestli dataset obsahuje cizí klíče (foreign keys)
|
|
201
|
+
:param subfolder: podsložka, v rámci které má být dataset uložený
|
|
202
|
+
:return: dict reprezentace datové entity (datasetu), včetně fieldů a cizích klíčů
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
dataset = DATASET_TEMPLATE.copy()
|
|
206
|
+
|
|
207
|
+
if subfolder is None:
|
|
208
|
+
entity_name = entity
|
|
209
|
+
else:
|
|
210
|
+
entity_name = f'{subfolder}.{entity}'
|
|
211
|
+
|
|
212
|
+
urn = f"urn:li:dataset:(urn:li:dataPlatform:{platform},{entity_name},PROD)"
|
|
213
|
+
|
|
214
|
+
fields = _create_fields(entity_dict, nested_fields)
|
|
215
|
+
|
|
216
|
+
if foreign_keys:
|
|
217
|
+
foreign_keys_list = _create_foreign_keys(entity_dict, urn, platform)
|
|
218
|
+
else:
|
|
219
|
+
foreign_keys_list = None
|
|
220
|
+
|
|
221
|
+
level1 = 'proposedSnapshot'
|
|
222
|
+
level2 = 'com.linkedin.pegasus2avro.metadata.snapshot.DatasetSnapshot'
|
|
223
|
+
|
|
224
|
+
level4a = 'com.linkedin.pegasus2avro.dataset.DatasetProperties'
|
|
225
|
+
level4b = 'com.linkedin.pegasus2avro.schema.SchemaMetadata'
|
|
226
|
+
|
|
227
|
+
dataset[level1][level2]['aspects'][0][level4a]['name'] = entity
|
|
228
|
+
|
|
229
|
+
dataset[level1][level2]['urn'] = urn
|
|
230
|
+
dataset[level1][level2]['aspects'][1][level4b]['schemaName'] = entity_name
|
|
231
|
+
dataset[level1][level2]['aspects'][1][level4b]['platform'] = f'urn:li:dataPlatform:{platform}'
|
|
232
|
+
dataset[level1][level2]['aspects'][1][level4b]['fields'] = fields
|
|
233
|
+
dataset[level1][level2]['aspects'][1][level4b]['foreignKeys'] = foreign_keys_list
|
|
234
|
+
|
|
235
|
+
return dataset
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def create_etalon(etalon_df: pd.DataFrame, etalon_name: str, platform: str
|
|
239
|
+
, nested_fields: bool, foreign_keys: bool, subfolder: str = None) -> dict[str, any]:
|
|
240
|
+
"""
|
|
241
|
+
Vytvoří dict datasetu k importu do DataHub pro jednu datovou entitu.
|
|
242
|
+
|
|
243
|
+
Z načteného souboru získá, případně vytvoří potřebné informace k vytvoření informací o datasetu.
|
|
244
|
+
Název entity získává z názvu souboru => v názvu souboru musí být název entity.
|
|
245
|
+
|
|
246
|
+
Povinné sloupce v načteném souboru:
|
|
247
|
+
|
|
248
|
+
* field_name
|
|
249
|
+
* field_description
|
|
250
|
+
* field_data_type
|
|
251
|
+
|
|
252
|
+
Volitelné sloupce:
|
|
253
|
+
|
|
254
|
+
* upstream_lineage
|
|
255
|
+
* foreign_key
|
|
256
|
+
|
|
257
|
+
:param etalon_df: zdrojový dataframe s informacemi o etalonu
|
|
258
|
+
:param etalon_name: název etalonu
|
|
259
|
+
:param platform: název platformy, která je zdrojem datasetu
|
|
260
|
+
(např. etalon, googlesheets, ISTA, Postgres, OpenAPI...)
|
|
261
|
+
:param nested_fields: informace, jestli dataset obsahuje vnořené fieldy (nested fields)
|
|
262
|
+
:param foreign_keys: informace, jestli dataset obsahuje cizí klíče (foreign keys)
|
|
263
|
+
:param subfolder: podsložka, v rámci které má být dataset uložený
|
|
264
|
+
:return: etalon ve formě dict, které je možné převést do JSON a importovat do DataHub
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
entity_dict = etalon_df.to_dict('records')
|
|
268
|
+
|
|
269
|
+
etalon = _create_dataset(etalon_name, platform, entity_dict, nested_fields, foreign_keys, subfolder)
|
|
270
|
+
|
|
271
|
+
return etalon
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def create_etalon_bulk(etalon_df: pd.DataFrame, platform: str
|
|
275
|
+
, nested_fields: bool, foreign_keys: bool, subfolder: str = None) -> list[dict[str, any]]:
|
|
276
|
+
"""
|
|
277
|
+
Vytvoří dict datasetu k importu do DataHub pro více datových entit.
|
|
278
|
+
|
|
279
|
+
Z načteného souboru získá, případně vytvoří potřebné informace k vytvoření informací o datasetu.
|
|
280
|
+
Název entity získává ze sloupce entity_name, ten slouží i k filtrování jednotlivých entit
|
|
281
|
+
a jejich následné zpracování.
|
|
282
|
+
|
|
283
|
+
Povinné sloupce v načteném souboru:
|
|
284
|
+
|
|
285
|
+
* entity_name
|
|
286
|
+
* field_name
|
|
287
|
+
* field_description
|
|
288
|
+
* field_data_type
|
|
289
|
+
|
|
290
|
+
Volitelné sloupce:
|
|
291
|
+
|
|
292
|
+
* upstream_lineage
|
|
293
|
+
* foreign_key
|
|
294
|
+
|
|
295
|
+
:param etalon_df: zdrojový dataframe s informacemi o etalonu
|
|
296
|
+
:param platform: název platformy, která je zdrojem datasetu
|
|
297
|
+
(např. etalon, googlesheets, ISTA, Postgres, OpenAPI...)
|
|
298
|
+
:param nested_fields: informace, jestli dataset obsahuje vnořené fieldy (nested fields)
|
|
299
|
+
:param foreign_keys: informace, jestli dataset obsahuje cizí klíče (foreign keys)
|
|
300
|
+
:param subfolder: podsložka, v rámci které má být dataset uložený
|
|
301
|
+
:return: etalon ve formě dict, které je možné převést do JSON a importovat do DataHub
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
etalon_list = []
|
|
305
|
+
|
|
306
|
+
for entity in etalon_df['entity_name'].unique():
|
|
307
|
+
entity_df = etalon_df[etalon_df['entity_name'] == entity]
|
|
308
|
+
entity_df = entity_df.drop(columns=['entity_name'])
|
|
309
|
+
|
|
310
|
+
entity_dict = entity_df.to_dict('records')
|
|
311
|
+
|
|
312
|
+
entity_name = '_'.join(entity.lower().split(' '))
|
|
313
|
+
etalon = _create_dataset(entity_name, platform, entity_dict, nested_fields, foreign_keys, subfolder)
|
|
314
|
+
etalon_list.append(copy.deepcopy(etalon))
|
|
315
|
+
|
|
316
|
+
return etalon_list
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def dataset_description(urn: str, description: str) -> list[dict[str, any]]:
|
|
320
|
+
""" Vytvoří JSON pro přiřazení popisu datové sady k existující datové sadě.
|
|
321
|
+
|
|
322
|
+
:param urn: URN existujícího datasetu, ve kterém se mají vytvořit/upravit údaje
|
|
323
|
+
:param description: popis datasetu
|
|
324
|
+
:return: list dictů s potřebnými informace k nahrání do DataHub
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
json_data = [{
|
|
328
|
+
'entityType': 'dataset',
|
|
329
|
+
'entityUrn': urn,
|
|
330
|
+
'aspect': {
|
|
331
|
+
'__type': 'EditableDatasetProperties',
|
|
332
|
+
'description': description
|
|
333
|
+
}
|
|
334
|
+
}]
|
|
335
|
+
return json_data
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def dataset_link(urn: str, link_name: str, link_url: str, corpuser: str) -> list[dict[str, any]]:
|
|
339
|
+
""" Vytvoří JSON pro přiřazení odkazů k existujícímu datasetu.
|
|
340
|
+
|
|
341
|
+
V současné chvíli funguje pouze pro jeden odkaz.
|
|
342
|
+
|
|
343
|
+
:param urn: URN existujícího datasetu, ve kterém se mají vytvořit/upravit údaje
|
|
344
|
+
:param link_name: zobrazený název odkazu
|
|
345
|
+
:param link_url: odkaz ve formátu url
|
|
346
|
+
:param corpuser: uživatel, který odkaz vytvořil (ve formátu jmeno.prijmeni, bez diakritiky)
|
|
347
|
+
:return: list dictů s potřebnými informace k nahrání do DataHub
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
dt = datetime.datetime.now()
|
|
351
|
+
ts = int(datetime.datetime.timestamp(dt) * 1000)
|
|
352
|
+
|
|
353
|
+
corpuser_urn = f'urn:li:corpuser:{corpuser}'
|
|
354
|
+
json_data = [{
|
|
355
|
+
'entityType': 'dataset',
|
|
356
|
+
'entityUrn': urn,
|
|
357
|
+
'aspect': {
|
|
358
|
+
'__type': 'InstitutionalMemory',
|
|
359
|
+
'elements': [
|
|
360
|
+
{
|
|
361
|
+
'url': link_url,
|
|
362
|
+
'description': link_name,
|
|
363
|
+
'createStamp': {
|
|
364
|
+
'time': ts,
|
|
365
|
+
'actor': corpuser_urn
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
}
|
|
370
|
+
}]
|
|
371
|
+
return json_data
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def dataset_ownership(urn: str, corpuser: str, owner_type: OwnerType) -> list[dict[str, any]]:
|
|
375
|
+
""" Vytvoří JSON pro přiřazení vlastníka k existujícímu datasetu.
|
|
376
|
+
|
|
377
|
+
V současné chvíli funguje pouze pro jednoho vlastníka.
|
|
378
|
+
|
|
379
|
+
:param urn: URN existujícího datasetu, ve kterém se mají vytvořit/upravit údaje
|
|
380
|
+
:param corpuser: uživatel, který se má přiřadit jako vlastník datasetu (ve formátu jmeno.prijmeni, bez diakritiky)
|
|
381
|
+
:param owner_type: typ vlastníka (DATA_STEWARD, TECHNICAL_OWNER, BUSINESS_OWNER)
|
|
382
|
+
:return: list dictů s potřebnými informace k nahrání do DataHub
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
corpuser_urn = f'urn:li:corpuser:{corpuser}'
|
|
386
|
+
json_data = [{
|
|
387
|
+
'entityType': 'dataset',
|
|
388
|
+
'entityUrn': urn,
|
|
389
|
+
'aspect': {
|
|
390
|
+
'__type': 'Ownership',
|
|
391
|
+
'owners': [
|
|
392
|
+
{
|
|
393
|
+
'owner': corpuser_urn,
|
|
394
|
+
'type': owner_type,
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
}
|
|
398
|
+
}]
|
|
399
|
+
return json_data
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def dataset_tag(urn: str, tags: list[str]) -> list[dict[str, any]]:
|
|
403
|
+
""" Vytvoří JSON pro přiřazení tagu/tagů k existujícímu datasetu.
|
|
404
|
+
|
|
405
|
+
:param urn: URN existujícího datasetu, ve kterém se mají vytvořit/upravit údaje
|
|
406
|
+
:param tags: list tagů, které se mají přiřadit k datasetu
|
|
407
|
+
:return: list dictů s potřebnými informace k nahrání do DataHub
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
tag_urns = [f'urn:li:tag:{tag}' for tag in tags]
|
|
411
|
+
json_data = [{
|
|
412
|
+
'entityType': 'dataset',
|
|
413
|
+
'entityUrn': urn,
|
|
414
|
+
'aspect': {
|
|
415
|
+
'__type': 'GlobalTags',
|
|
416
|
+
'tags': [
|
|
417
|
+
{'tag': tag_urn} for tag_urn in tag_urns
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
}]
|
|
421
|
+
return json_data
|