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,378 @@
|
|
|
1
|
+
"""Soubor dílčích a agregujících funkcí sloužících k vytváření nových pojmů v DataHubu skrze API post."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from unidecode import unidecode
|
|
5
|
+
import numpy as np
|
|
6
|
+
import datetime
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from tacrpy.datahub.openapi import post_data
|
|
9
|
+
from tacrpy.datahub.import_checks import (is_column_filled, has_specific_values, contains_boolean_values,
|
|
10
|
+
no_duplicates_in_column, is_column_list_of_dicts)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _preprocess_regs(df: pd.DataFrame) -> pd.DataFrame:
|
|
14
|
+
"""
|
|
15
|
+
Specifická pomocná dílčí funkce k preprocessingu tabulky se souhrnem vnitřních předpisů.
|
|
16
|
+
|
|
17
|
+
:param df: pd.DataFrame se souhrnem vnitřních předpisů
|
|
18
|
+
:return: pd.DataFrame k využití v dalších krocích modulu
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
df.columns = df.iloc[0]
|
|
22
|
+
df = df.iloc[1:]
|
|
23
|
+
df['Verze'] = df['Verze'].astype(str)
|
|
24
|
+
df1 = df.dropna(subset=['Odkaz'])
|
|
25
|
+
df1['verze_s'] = ('v' + df1['Verze'])
|
|
26
|
+
df1['předpis_full'] = df1['ID předpisu'] + ' ' + df1['Název vnitřního předpisu'] + ' ' + df1['verze_s']
|
|
27
|
+
links = df1[['ID předpisu', 'předpis_full', 'Odkaz']]
|
|
28
|
+
return links
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def records_into_rows(df: pd.DataFrame, key_column: object, parse_column: object, separator: any) -> pd.DataFrame:
|
|
32
|
+
"""
|
|
33
|
+
Dílčí funkce, která rozparsuje buňky v cílovém sloupci s více záznamy do jednotlivých řádků (na základě separátoru),
|
|
34
|
+
přičemž je zachována příslušnost k hlavnímu klíči.
|
|
35
|
+
|
|
36
|
+
:param df: název pd.DataFrame, v rámci kterého chci danou funkci použít
|
|
37
|
+
:param key_column: název sloupce s klíčem/ID, ke kterému chci rozparsované záznamy vztáhnout
|
|
38
|
+
:param parse_column: název sloupce, v rámci kterého chci záznamy rozparsovat do řádků
|
|
39
|
+
:param separator: volba separátoru, kterým jsou jednotlivé záznamy v buňce odděleny
|
|
40
|
+
:return: pd.DataFrame obsahující sloupec s rozparsovanými záznamy a klíč, ke kterému se dané záznamy vztahují
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
df[parse_column] = df[parse_column].astype(str)
|
|
44
|
+
new_rows = []
|
|
45
|
+
for index, row in df.iterrows():
|
|
46
|
+
records = row[parse_column].split(separator)
|
|
47
|
+
for record in records:
|
|
48
|
+
new_rows.append({key_column: row[key_column], parse_column: record})
|
|
49
|
+
|
|
50
|
+
split_df = pd.DataFrame(new_rows)
|
|
51
|
+
split_df[parse_column] = split_df[parse_column].str.lstrip()
|
|
52
|
+
split_df = split_df[split_df[parse_column] != '']
|
|
53
|
+
return split_df
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _add_owners_urn(df: pd.DataFrame, dh_users: pd. DataFrame, name_column: object,
|
|
57
|
+
separator: any, owner_type: str) -> pd.DataFrame:
|
|
58
|
+
"""
|
|
59
|
+
Pomocná dílčí funkce zpracující formát, ve kterém jsou vlastníci pojmů vedeni ve zdrojovém souboru,
|
|
60
|
+
a vytvoří klíč na základě kterého jsou vlastníkům následně přiřazeny jejich urns v souladu s účty v datahubu.
|
|
61
|
+
|
|
62
|
+
:param df: pd.DataFrame obsahující sloupec s rozparsovanými vlastníky pojmů
|
|
63
|
+
:param dh_users: pd.DataFrame obsahující seznam urn existujících uživatelů v datahubu
|
|
64
|
+
:param name_column: název sloupce obsahující jméno a příjmení vlastníka pojmu ve formátu "příjmení, jméno"
|
|
65
|
+
:param separator: nastavení separátoru, kterým jsou části jména oddělené (v případě zdroj. souboru se jedná o ","
|
|
66
|
+
:param owner_type: nastavení druhu vlastnictví (defaultně se pro pojmy používá "BUSINESS OWNER")
|
|
67
|
+
:return: pd.DataFrame, který primárně obsahuje sloupce z df doplněné o urn jednotlivých vlastníků
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
parsed_df = records_into_rows(df, 'Název pojmu', 'Garanti pojmů', ';')
|
|
71
|
+
owner_preprocess = parsed_df[name_column].str.split(separator, expand=True)
|
|
72
|
+
for col in owner_preprocess:
|
|
73
|
+
owner_preprocess[col + 10] = (owner_preprocess[col].apply(lambda text: unidecode(text).lower()
|
|
74
|
+
if text else '').str.lstrip())
|
|
75
|
+
|
|
76
|
+
owner_preprocess_v2 = pd.merge(parsed_df, owner_preprocess[[10, 11]],
|
|
77
|
+
left_index=True,
|
|
78
|
+
right_index=True)
|
|
79
|
+
owner_preprocess_v3 = owner_preprocess_v2[10].str.split(' ', expand=True)
|
|
80
|
+
owner_preprocess_v4 = pd.merge(owner_preprocess_v2, owner_preprocess_v3,
|
|
81
|
+
left_index=True,
|
|
82
|
+
right_index=True)
|
|
83
|
+
|
|
84
|
+
desired_column = 1
|
|
85
|
+
if desired_column in owner_preprocess_v4.columns:
|
|
86
|
+
owner_preprocess_v4['base_urn_long'] = (owner_preprocess_v4.apply
|
|
87
|
+
(lambda row: row[11] + '.' + row[0] + ('.' + row[1] if not pd.isna(row[1]) else ''), axis=1))
|
|
88
|
+
else:
|
|
89
|
+
owner_preprocess_v4['base_urn_long'] = (owner_preprocess_v4.apply(lambda row: row[11] + '.' + row[0], axis=1))
|
|
90
|
+
|
|
91
|
+
owner_preprocess_v4['urn_long'] = (owner_preprocess_v4.apply
|
|
92
|
+
(lambda row: 'urn:li:corpuser:' + row['base_urn_long'], axis=1))
|
|
93
|
+
owner_preprocess_v4['base_urn_short'] = owner_preprocess_v4.apply(lambda row: row[0], axis=1)
|
|
94
|
+
owner_preprocess_v4['urn_short'] = (owner_preprocess_v4.apply
|
|
95
|
+
(lambda row: 'urn:li:corpuser:' + row['base_urn_short'], axis=1))
|
|
96
|
+
|
|
97
|
+
users_datahub = dh_users[["urn"]]
|
|
98
|
+
users_datahub['dh'] = True
|
|
99
|
+
intersect = pd.merge(owner_preprocess_v4, users_datahub,
|
|
100
|
+
left_on='urn_long',
|
|
101
|
+
right_on='urn',
|
|
102
|
+
how='left')
|
|
103
|
+
intersect['urn'] = intersect['urn'].fillna(intersect['urn_short'])
|
|
104
|
+
final_owners = intersect[['Název pojmu', 'urn']].rename(columns={'urn': 'owner'})
|
|
105
|
+
final_owners['type'] = owner_type
|
|
106
|
+
return final_owners
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def row_transform(df: pd.DataFrame, new_column: object, key_column: object,
|
|
110
|
+
base_column_1: object, base_column_2: object) -> pd.DataFrame:
|
|
111
|
+
"""
|
|
112
|
+
Dílčí funkce agregující řádky vstupního df na základě klíče a ze záznámů z vybraných sloupců vytvoří list slovníků.
|
|
113
|
+
|
|
114
|
+
:param df: název pd.DataFrame, v rámci kterého chci danou funkci použít
|
|
115
|
+
:param new_column: název nového sloupce, jehož obsahem bude agregace záznamů do struktury listu slovníků
|
|
116
|
+
:param key_column: název sloupce, který představuje klíč pro agregaci řádků
|
|
117
|
+
:param base_column_1: název prvního sloupce, jehož hodnoty jsou agregovány do struktury listu slovníků
|
|
118
|
+
:param base_column_2: název druhého sloupce, jehož hodnoty jsou agregovány do struktury listu slovníků
|
|
119
|
+
:return: pd.DataFrame obsahující dva sloupce - klíč a hodnoty ve struktuře listu slovníků
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
df[new_column] = df.apply(lambda row: {base_column_1: row[base_column_1], base_column_2: row[base_column_2]}
|
|
123
|
+
if not (pd.isna(row[base_column_1]) or pd.isna(row[base_column_2])) else None, axis=1)
|
|
124
|
+
df = df[df[new_column].notna()]
|
|
125
|
+
dictionary = df.groupby(key_column)[new_column].apply(list).to_dict()
|
|
126
|
+
df_list_of_dicts = pd.DataFrame(dictionary.items(), columns=[key_column, new_column])
|
|
127
|
+
return df_list_of_dicts
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _create_lovs(row):
|
|
131
|
+
"""
|
|
132
|
+
Pomocná dílčí funkce odkazující se na hodnotu stejného řádku v jiném sloupci.
|
|
133
|
+
Slouží jako interní funkce pro vstup do funkce _expand_data.
|
|
134
|
+
|
|
135
|
+
:param row: vstupní hodnota, na základě které funkce vrací výsledek
|
|
136
|
+
:return: podle podnímky bool hodnota (False) nebo np.nan
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
if row:
|
|
140
|
+
return False
|
|
141
|
+
else:
|
|
142
|
+
return np.nan
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _expand_data(df: pd.DataFrame, base_column: object, new_column: object,
|
|
146
|
+
unidecode_column: object = None, parentnode: str = None) -> pd.DataFrame:
|
|
147
|
+
"""
|
|
148
|
+
Pomocná dílčí funkce připravující strukturu dat do podoby, která je nutná pro import dat skrze API do datahubu
|
|
149
|
+
(předtím než se dataframe překlopí do JSON struktury).
|
|
150
|
+
|
|
151
|
+
:param df: pd.DataFrame, který obsahuje již všechna klíčová data potřebná pro import skrze API do datahubu,
|
|
152
|
+
která jsou ovšem ještě potřeba doupravit a doplnit o určité položky
|
|
153
|
+
:param base_column: název sloupce s bool typem, na základě kterého bude vytvořen sloupec nový (kompletní)
|
|
154
|
+
:param new_column: název nového sloupce s bool hodnotami, na základě kterého budou skrze interní funkci
|
|
155
|
+
vytvořeny další nové sloupce dat nutné pro vstup do importu
|
|
156
|
+
:param unidecode_column (optional): název sloupce, který je potřeba upravti skrze unidecode script
|
|
157
|
+
:param parentnode (optional): název sloupce pro případné upřesnění parentnodu
|
|
158
|
+
:return: pd.DataFrame s kompletními daty pro import do datahubu skrze API
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
df[new_column] = df[base_column]
|
|
162
|
+
for i in range(1, 4): # tvorba tří nových lov sloupců
|
|
163
|
+
new_column_name_a = f'lov_column_{i}'
|
|
164
|
+
df[new_column_name_a] = df[new_column].apply(_create_lovs)
|
|
165
|
+
df.fillna('', inplace=True)
|
|
166
|
+
if unidecode_column:
|
|
167
|
+
df[unidecode_column] = df[unidecode_column].apply(lambda text: unidecode(text).upper() if text else '')
|
|
168
|
+
if parentnode:
|
|
169
|
+
df['parentNode'] = parentnode
|
|
170
|
+
return df
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _df_to_dict(df: pd.DataFrame, expanded_column: str, urn_actor: str) -> dict:
|
|
174
|
+
"""
|
|
175
|
+
Pomocná dílčí funkce konvertující vstupní data ve formátu pd.DataFrame do JSON struktury (dict)
|
|
176
|
+
a rozšiřuje položku u daného klíče o další vnořený dict.
|
|
177
|
+
|
|
178
|
+
:param df: pd.DataFrame, který obsahuje kompletní data potřebná pro import
|
|
179
|
+
:param expanded_column: název klíče, u kterého je nutné rozšířit strukturu o další vnořený dict
|
|
180
|
+
:param urn_actor: urn uživatele, který data nahrává (ve struktuře 'urn:li:corpuser:jmeno.prijmeni'
|
|
181
|
+
nebo 'urn:li:corpuser:prijmeni' - dle struktury gmailu)
|
|
182
|
+
:return: data v JSON struktuře připravená pro import skrze API post do datahubu
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
data_dict = df.to_dict('index')
|
|
186
|
+
new_keys = ['createStamp']
|
|
187
|
+
counter = 0
|
|
188
|
+
for key, inner_dict in data_dict.items():
|
|
189
|
+
list_of_dict = inner_dict[expanded_column]
|
|
190
|
+
for d in list_of_dict:
|
|
191
|
+
dt = datetime.datetime.now()
|
|
192
|
+
ts = int(datetime.datetime.timestamp(dt) * 1000) + counter
|
|
193
|
+
counter += 1
|
|
194
|
+
d.update({new_key: {"time": ts, "actor": urn_actor} for new_key in new_keys})
|
|
195
|
+
inner_dict[expanded_column] = list_of_dict
|
|
196
|
+
return data_dict
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def preprocess_data(df_entities: pd.DataFrame, df_regs: pd.DataFrame, df_owners: pd.DataFrame,
|
|
200
|
+
parentnode: str = None) -> pd.DataFrame:
|
|
201
|
+
"""
|
|
202
|
+
Finální agregující funkce (1/2), která vstupní data (z dataframů) transformuje
|
|
203
|
+
a doplní do potřebné struktury tak, aby mohla posloužit jako vstup do importovací funkce.
|
|
204
|
+
|
|
205
|
+
:param df_entities: pd.DataFrame obsahující vstupní data s informacemi o nahrávaných položkách
|
|
206
|
+
(musí obsahovat následující povinné sloupce:'Název pojmu', 'Definice pojmu', 'Kód výskyt pojmu', 'Garanti pojmů',
|
|
207
|
+
'Zkratka pojmu', 'Název pojmu (EN)', 'Zkratka (EN)', 'Kategorie', 'Datová entita')
|
|
208
|
+
:param df_regs: pd.DataFrame obsahující přehled přespisů (dostupný na intranetu)
|
|
209
|
+
:param df_owners: pd.DataFrame obsahující seznam existujících uživatelských účtú v datahubu
|
|
210
|
+
:param parentnode: v případě hromadného zařazení všech nahrávaných položek do jedné nadřazené složky
|
|
211
|
+
:return: pd.DataFrame obsahující všechny položky nutné k importu do datahubu
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
df_regs_ = _preprocess_regs(df_regs)
|
|
215
|
+
df_parse_regs = records_into_rows(df_entities, 'Název pojmu', 'Kód výskyt pojmu', ';')
|
|
216
|
+
df_parse_owners = _add_owners_urn(df_entities, df_owners, 'Garanti pojmů', ',', 'BUSINESS_OWNER')
|
|
217
|
+
df_parse_regs_url = (pd.merge(df_parse_regs, df_regs_,
|
|
218
|
+
left_on='Kód výskyt pojmu',
|
|
219
|
+
right_on='ID předpisu',
|
|
220
|
+
how='left').rename(columns={'předpis_full': 'description', 'Odkaz': 'url'}))
|
|
221
|
+
df_regs_url = row_transform(df_parse_regs_url, 'elements', 'Název pojmu', 'url', 'description')
|
|
222
|
+
df_ownership = row_transform(df_parse_owners, 'owners', 'Název pojmu', 'owner', 'type')
|
|
223
|
+
df_merged2 = pd.merge(df_entities, df_regs_url,
|
|
224
|
+
left_on='Název pojmu',
|
|
225
|
+
right_on='Název pojmu',
|
|
226
|
+
how='left')
|
|
227
|
+
df_merged = (pd.merge(df_merged2, df_ownership,
|
|
228
|
+
left_on='Název pojmu',
|
|
229
|
+
right_on='Název pojmu',
|
|
230
|
+
how='left').rename(columns={'Název pojmu': 'name', 'Definice pojmu': 'definition',
|
|
231
|
+
'Zkratka pojmu': 'abbrev', 'Název pojmu (EN)': 'nameEn',
|
|
232
|
+
'Zkratka (EN)': 'abbrevEn', 'Kategorie': 'termCategory'}))
|
|
233
|
+
df_prepared = _expand_data(df_merged, 'Datová entita', 'isEntity', 'termCategory', parentnode)
|
|
234
|
+
return df_prepared
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def import_data(df: pd.DataFrame, urn_actor: str, test: bool = True):
|
|
238
|
+
"""
|
|
239
|
+
Finální agregující funkce (2/2), která otestuje úplnost a správnost vstupních dat
|
|
240
|
+
a skrze kterou se přes API importují jednotlivé prvky glossary terms do datahubu.
|
|
241
|
+
|
|
242
|
+
:param df: předpřipravený a otestovaný df, v kterém jsou obsažena veškerá data
|
|
243
|
+
tvořící jednotlivé glossary terms potřebná pro import do datahubu
|
|
244
|
+
:param urn_actor: urn osoby, která glossary terms nahrává (urn:li:corpuser:jmeno.prijmeni
|
|
245
|
+
nebo urn:li:corpuser:prijmeni - dle struktury gmailu)
|
|
246
|
+
:param test: určení, jestli se jedná o test (True) nebo produkci (False)
|
|
247
|
+
:return: nahrání glossary terms na zvolený server datahubu (s informací o průběhu uploadu
|
|
248
|
+
na úrovni jednotlivých terms a nově vytvořeném urn pro daný term)
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
columns_for_is_column_filled = ['name', 'termCategory', 'isEntity']
|
|
252
|
+
for column_filled in columns_for_is_column_filled:
|
|
253
|
+
if not is_column_filled(df, column_filled):
|
|
254
|
+
raise ValueError(f"Not all values are filled in column '{column_filled}' .")
|
|
255
|
+
|
|
256
|
+
predefined_values = ['ROLE', 'ICT', 'DOKUMENT', 'OSTATNI', 'INSTITUCE']
|
|
257
|
+
if not has_specific_values(df, 'termCategory', predefined_values):
|
|
258
|
+
raise ValueError("The 'termCategory' column contains an invalid value.")
|
|
259
|
+
|
|
260
|
+
if not contains_boolean_values(df, 'isEntity'):
|
|
261
|
+
raise ValueError("The 'isEntity' column contains an invalid value (only bool accepted).")
|
|
262
|
+
|
|
263
|
+
if not no_duplicates_in_column(df, 'name'):
|
|
264
|
+
raise ValueError("The 'name' column contains duplicate values.")
|
|
265
|
+
|
|
266
|
+
columns_for_is_list_of_dicts = ['elements', 'owners']
|
|
267
|
+
for column_with_list in columns_for_is_list_of_dicts:
|
|
268
|
+
if not is_column_list_of_dicts(df, column_with_list):
|
|
269
|
+
raise ValueError(f"Not all values are proper list_of_dicts in column '{column_with_list}' .")
|
|
270
|
+
|
|
271
|
+
json_data = _df_to_dict(df, 'elements', urn_actor)
|
|
272
|
+
for key, value in json_data.items():
|
|
273
|
+
term_id = uuid.uuid1().hex
|
|
274
|
+
if value['parentNode'] == '':
|
|
275
|
+
glossary_term_info = [
|
|
276
|
+
{
|
|
277
|
+
'entityType': 'glossaryTerm',
|
|
278
|
+
'entityKeyAspect': {
|
|
279
|
+
'__type': 'GlossaryTermKey',
|
|
280
|
+
'name': term_id
|
|
281
|
+
},
|
|
282
|
+
'aspect': {
|
|
283
|
+
'__type': 'GlossaryTermInfo',
|
|
284
|
+
'name': value['name'],
|
|
285
|
+
'definition': value['definition'],
|
|
286
|
+
'termSource': 'INTERNAL'
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
else:
|
|
291
|
+
glossary_term_info = [
|
|
292
|
+
{
|
|
293
|
+
'entityType': 'glossaryTerm',
|
|
294
|
+
'entityKeyAspect': {
|
|
295
|
+
'__type': 'GlossaryTermKey',
|
|
296
|
+
'name': term_id
|
|
297
|
+
},
|
|
298
|
+
'aspect': {
|
|
299
|
+
'__type': 'GlossaryTermInfo',
|
|
300
|
+
'name': value['name'],
|
|
301
|
+
'definition': value['definition'],
|
|
302
|
+
'parentNode': value['parentNode'],
|
|
303
|
+
'termSource': 'INTERNAL'
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
]
|
|
307
|
+
r = post_data(glossary_term_info, test)
|
|
308
|
+
|
|
309
|
+
urn = r.json()[0]
|
|
310
|
+
|
|
311
|
+
glossary_term_tacr_gen = {
|
|
312
|
+
'entityType': 'glossaryTerm',
|
|
313
|
+
'entityUrn': urn,
|
|
314
|
+
'aspect': {
|
|
315
|
+
'__type': 'GlossaryTermTacrGen',
|
|
316
|
+
'abbrev': value['abbrev'],
|
|
317
|
+
'nameEn': value['nameEn'],
|
|
318
|
+
'abbrevEn': value['abbrevEn'],
|
|
319
|
+
'termCategory': value['termCategory']
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
glossary_term_tacr = {
|
|
324
|
+
'entityType': 'glossaryTerm',
|
|
325
|
+
'entityUrn': urn,
|
|
326
|
+
'aspect': {
|
|
327
|
+
'__type': 'GlossaryTermTacr',
|
|
328
|
+
'isEntity': value['isEntity']
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
glossary_term_tacr_entity = {
|
|
333
|
+
'entityType': 'glossaryTerm',
|
|
334
|
+
'entityUrn': urn,
|
|
335
|
+
'aspect': {
|
|
336
|
+
'__type': 'GlossaryTermTacrEntity',
|
|
337
|
+
'lov': value['lov_column_1'],
|
|
338
|
+
'externalLov': value['lov_column_2'],
|
|
339
|
+
'lovSource': value['lov_column_3']
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if value['elements'] != '':
|
|
344
|
+
institutional_memory = {
|
|
345
|
+
'entityType': 'glossaryTerm',
|
|
346
|
+
'entityUrn': urn,
|
|
347
|
+
'aspect': {
|
|
348
|
+
'__type': 'InstitutionalMemory',
|
|
349
|
+
'elements': value['elements']
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else:
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
if value['owners'] != '':
|
|
357
|
+
ownership = {
|
|
358
|
+
'entityType': 'glossaryTerm',
|
|
359
|
+
'entityUrn': urn,
|
|
360
|
+
'aspect': {
|
|
361
|
+
'__type': 'Ownership',
|
|
362
|
+
'owners': value['owners']
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else:
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
api_structure = [glossary_term_tacr_gen, glossary_term_tacr,
|
|
370
|
+
glossary_term_tacr_entity, institutional_memory, ownership]
|
|
371
|
+
|
|
372
|
+
r = post_data(api_structure, test)
|
|
373
|
+
|
|
374
|
+
if 200 <= r.status_code <= 250:
|
|
375
|
+
print("Post successful:", value['name'], urn)
|
|
376
|
+
else:
|
|
377
|
+
print(f"Post failed. Status code: {r.status_code}")
|
|
378
|
+
print(value['name'], urn)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
""" Modul obsahuje předdefinované funkce, které slouží jako kontrola
|
|
2
|
+
úplnosti dat importovaných skrze API do datahubu.
|
|
3
|
+
Primárním využitím je testování skrze tyto předdefinované funkce nad cílovým souborem.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import ast
|
|
8
|
+
|
|
9
|
+
# kontrola vyplnění všech hodnot ve sloupci
|
|
10
|
+
def is_column_filled(df: pd.DataFrame, column_name: object) -> bool:
|
|
11
|
+
""" Ověřovací funkce určená primárně jako vstup do testu, indikuje, zda je sloupec kompletně vyplněný,
|
|
12
|
+
tj. zda v daném sloupci nejsou obsaženy prázdné hodnoty.
|
|
13
|
+
|
|
14
|
+
:param df: dataframe, v rámci kterého chci danou podmínku ověřovat
|
|
15
|
+
:param column_name: sloupec v rámci dataframu, na který chci danou podmínku aplikovat
|
|
16
|
+
:return: bool hodnota podle toho, jestli je nebo není podmínka pro vybraný sloupec splněna
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
return df[column_name].notnull().all()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# kontrola, jestli jsou ve sloupci předepsané hodnoty
|
|
23
|
+
def has_specific_values(df: pd.DataFrame, column_name: object, predefined_values: list[any]) -> bool:
|
|
24
|
+
""" Ověřovací funkce určená primárně jako vstup do testu, indikuje, zda se ve sloupci vyskytují pouze předdefinované hodnoty.
|
|
25
|
+
|
|
26
|
+
:param df: dataframe, v rámci kterého chci danou podmínku ověřovat
|
|
27
|
+
:param column_name: sloupec v rámci dataframu, na který chci danou podmínku aplikovat
|
|
28
|
+
:param predefined_values: list předdefinovaných hodnot
|
|
29
|
+
:return: bool hodnota podle toho, jestli je nebo není podmínka pro vybraný sloupec splněna
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
return df[column_name].isin(predefined_values).all()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# kontrola, zda sloupec obsahuje pouze bool hodnoty (True nebo False)
|
|
36
|
+
def contains_boolean_values(df: pd.DataFrame, column_name: object) -> bool:
|
|
37
|
+
""" Ověřovací funkce určená primárně jako vstup do testu, indikuje, zda se ve sloupci vyskytují pouze bool hodnoty.
|
|
38
|
+
|
|
39
|
+
:param df: dataframe, v rámci kterého chci danou podmínku ověřovat
|
|
40
|
+
:param column_name: sloupec v rámci dataframu, na který chci danou podmínku aplikovat
|
|
41
|
+
:return: bool hodnota podle toho, jestli je nebo není podmínka pro vybraný sloupec splněna
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
return df[column_name].apply(lambda x: isinstance(x, bool)).all()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# kontrola, zda sloupec obsahuje pouze object values
|
|
48
|
+
def are_all_values_objects_in_column(df: pd.DataFrame, column_name: object) -> bool:
|
|
49
|
+
""" Ověřovací funkce určená primárně jako vstup do testu, indikuje, zda se ve sloupci vyskytují pouze object hodnoty.
|
|
50
|
+
|
|
51
|
+
:param df: dataframe, v rámci kterého chci danou podmínku ověřovat
|
|
52
|
+
:param column_name: sloupec v rámci dataframu, na který chci danou podmínku aplikovat
|
|
53
|
+
:return: bool hodnota podle toho, jestli je nebo není podmínka pro vybraný sloupec splněna
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
column = df[column_name]
|
|
57
|
+
return all(isinstance(value, object) for value in column)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# kontrola duplicit
|
|
61
|
+
def no_duplicates_in_column(df: pd.DataFrame, column_name: object) -> bool:
|
|
62
|
+
""" Ověřovací funkce určená primárně jako vstup do testu, indikuje, zda se ve sloupci vyskytují duplicitní hodnoty.
|
|
63
|
+
|
|
64
|
+
:param df: dataframe, v rámci kterého chci danou podmínku ověřovat
|
|
65
|
+
:param column_name: sloupec v rámci dataframu, na který chci danou podmínku aplikovat
|
|
66
|
+
:return: bool hodnota podle toho, jestli je nebo není podmínka pro vybraný sloupec splněna
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
return not df[column_name].duplicated().any()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# kontrola, zda se ve sloupci nachází hodnoty strukturované do list[dict].
|
|
73
|
+
def is_column_list_of_dicts(df: pd.DataFrame, column_name: object) -> bool:
|
|
74
|
+
""" Ověřovací funkce určená primárně jako vstup do testu, ověřuje, zda buňky ve sloupci obsahují pouze obsah strukturovaný do listu slovníků (v případě, že nejsou prázdné).
|
|
75
|
+
|
|
76
|
+
:param df: dataframe, v rámci kterého chci danou podmínku ověřovat
|
|
77
|
+
:param column_name: sloupec v rámci dataframu, na který chci danou podmínku aplikovat
|
|
78
|
+
:return: bool hodnota podle toho, jestli je nebo není podmínka pro vybraný sloupec splněna
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
filtered_df = df[df[column_name].notna() & (df[column_name] != '')]
|
|
82
|
+
column = filtered_df[column_name]
|
|
83
|
+
for i, cell_value in enumerate(column):
|
|
84
|
+
if isinstance(cell_value, list):
|
|
85
|
+
for item in cell_value:
|
|
86
|
+
if not isinstance(item, dict):
|
|
87
|
+
print(f"Row {i} contains a non-dict element: {item}")
|
|
88
|
+
return False
|
|
89
|
+
elif isinstance(cell_value, str):
|
|
90
|
+
try:
|
|
91
|
+
parsed_value = ast.literal_eval(cell_value)
|
|
92
|
+
if not isinstance(parsed_value, list):
|
|
93
|
+
print(f"Row {i} is not a list: {parsed_value}")
|
|
94
|
+
return False
|
|
95
|
+
for item in parsed_value:
|
|
96
|
+
if not isinstance(item, dict):
|
|
97
|
+
print(f"Row {i} contains a non-dict element: {item}")
|
|
98
|
+
return False
|
|
99
|
+
except (ValueError, SyntaxError):
|
|
100
|
+
print(f"Row {i} is not a valid list/dictionary: {cell_value}")
|
|
101
|
+
return False
|
|
102
|
+
else:
|
|
103
|
+
print(f"Row {i} is not a list or string: {cell_value}")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# kontrola zda se ve sloupci v rámci list[dict] nenachází nespárované komponenty (např. prázdné values)
|
|
110
|
+
def notnan_in_columns_with_list_of_dicts(df: pd.DataFrame, column_name: object) -> bool:
|
|
111
|
+
""" Ověřovací funkce určená primárně jako vstup do testu, ověřuje, zda buňky s obsahem strukturovaným do listu slovníků neobsahují Nan values uvnitř slovníků.
|
|
112
|
+
|
|
113
|
+
:param df: dataframe, v rámci kterého chci danou podmínku ověřovat
|
|
114
|
+
:param column_name: sloupec v rámci dataframu, na který chci danou podmínku aplikovat
|
|
115
|
+
:return: bool hodnota podle toho, jestli je nebo není podmínka pro vybraný sloupec splněna
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
notnan_values = [': nan',': NaN']
|
|
119
|
+
for value in notnan_values:
|
|
120
|
+
if df[column_name].str.contains(value).any():
|
|
121
|
+
return False
|
|
122
|
+
else:
|
|
123
|
+
return True
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Soubor funkcí k práci s OpenAPI DataHubu."""
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def post_data(json_data: list[dict], test: bool = True) -> requests.Response:
|
|
7
|
+
""" Nahraje přes OpenAPI data do DataHubu.
|
|
8
|
+
|
|
9
|
+
Než se data nahrají je vhodné provést kontroly správnosti dat.
|
|
10
|
+
|
|
11
|
+
:param json_data: data ve formátu JSON, které se mají nahrát do DataHubu
|
|
12
|
+
:param test: nahraje data buď na testovací nebo produkční prostředí
|
|
13
|
+
:return: objekt třídy Response, který reprezentuje odezvu serveru
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
if test:
|
|
17
|
+
server = 'http://datahub-test.tacr.cz:8080/'
|
|
18
|
+
else:
|
|
19
|
+
server = 'http://datahub.tacr.cz:8080/'
|
|
20
|
+
|
|
21
|
+
query = 'openapi/entities/v1/'
|
|
22
|
+
url = server + query
|
|
23
|
+
|
|
24
|
+
r = requests.post(url, json=json_data)
|
|
25
|
+
|
|
26
|
+
return r
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_data(urn: str, test: bool = True) -> dict:
|
|
30
|
+
""" Získá data k vybrané entitě přes OpenAPI.
|
|
31
|
+
|
|
32
|
+
:param urn: URN entity, pro kterou chceme získat data
|
|
33
|
+
:param test: získá data buď z testovacího nebo produkčního prostředí
|
|
34
|
+
:return: data ve formátu JSON
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if test:
|
|
38
|
+
server = 'http://datahub-test.tacr.cz:8080/'
|
|
39
|
+
else:
|
|
40
|
+
server = 'http://datahub.tacr.cz:8080/'
|
|
41
|
+
|
|
42
|
+
query = f'openapi/entities/v1/latest?urns={urn}'
|
|
43
|
+
|
|
44
|
+
url = server + query
|
|
45
|
+
|
|
46
|
+
r = requests.get(url)
|
|
47
|
+
|
|
48
|
+
json_data = r.json()['responses']
|
|
49
|
+
|
|
50
|
+
return json_data
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def delete_data(urn: str, test: bool = True, soft: bool = False) -> requests.Response:
|
|
54
|
+
""" Vymaže data vybrané entity přes OpenAPI
|
|
55
|
+
|
|
56
|
+
:param urn: URN entity, kterou chceme vymazat
|
|
57
|
+
:param test: vymaže data z testovacího nebo produkčního prostředí
|
|
58
|
+
:param soft: můžeme entitu vymazat pouze z vyhledávání (soft = True) nebo kompletně (soft = False)
|
|
59
|
+
:return: objekt třídy Response, který reprezentuje odezvu serveru
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
if test:
|
|
63
|
+
server = 'http://datahub-test.tacr.cz:8080/'
|
|
64
|
+
else:
|
|
65
|
+
server = 'http://datahub.tacr.cz:8080/'
|
|
66
|
+
|
|
67
|
+
if soft:
|
|
68
|
+
soft_delete = '&soft=true'
|
|
69
|
+
else:
|
|
70
|
+
soft_delete = '&soft=false'
|
|
71
|
+
|
|
72
|
+
query = f'openapi/entities/v1/?urns={urn}'
|
|
73
|
+
|
|
74
|
+
url = server + query + soft_delete
|
|
75
|
+
|
|
76
|
+
r = requests.delete(url)
|
|
77
|
+
|
|
78
|
+
return r
|