pointblank 0.9.1__py3-none-any.whl → 0.9.2__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.
- pointblank/_constants.py +14 -0
- pointblank/_constants_translations.py +54 -0
- pointblank/_interrogation.py +101 -0
- pointblank/_utils.py +1 -0
- pointblank/actions.py +2 -2
- pointblank/data/api-docs.txt +305 -4
- pointblank/thresholds.py +3 -2
- pointblank/validate.py +393 -11
- {pointblank-0.9.1.dist-info → pointblank-0.9.2.dist-info}/METADATA +1 -1
- {pointblank-0.9.1.dist-info → pointblank-0.9.2.dist-info}/RECORD +13 -13
- {pointblank-0.9.1.dist-info → pointblank-0.9.2.dist-info}/WHEEL +1 -1
- {pointblank-0.9.1.dist-info → pointblank-0.9.2.dist-info}/licenses/LICENSE +0 -0
- {pointblank-0.9.1.dist-info → pointblank-0.9.2.dist-info}/top_level.txt +0 -0
pointblank/_constants.py
CHANGED
|
@@ -44,6 +44,7 @@ ASSERTION_TYPE_METHOD_MAP = {
|
|
|
44
44
|
"row_count_match": "row_count_match",
|
|
45
45
|
"col_count_match": "col_count_match",
|
|
46
46
|
"conjointly": "conjointly",
|
|
47
|
+
"specially": "specially",
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
METHOD_CATEGORY_MAP = {
|
|
@@ -69,6 +70,7 @@ METHOD_CATEGORY_MAP = {
|
|
|
69
70
|
"row_count_match": "ROW_COUNT_MATCH",
|
|
70
71
|
"col_count_match": "COL_COUNT_MATCH",
|
|
71
72
|
"conjointly": "CONJOINTLY",
|
|
73
|
+
"specially": "SPECIALLY",
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
COMPARISON_OPERATORS = {
|
|
@@ -455,6 +457,18 @@ SVG_ICONS_FOR_ASSERTION_TYPES = {
|
|
|
455
457
|
<path d="M51.8485976,12 L15.5758703,12 C13.9986329,12 12.712234,13.2863989 12.712234,14.8636364 L12.712234,51.1363636 C12.712234,52.7136011 13.9986329,54 15.5758703,54 L51.8485976,54 C53.4258351,54 54.712234,52.7136011 54.712234,51.1363636 L54.712234,14.8636364 C54.712234,13.2863989 53.4258351,12 51.8485976,12 Z M37.072234,44 L20.272234,44 L20.272234,42 L37.072234,42 L37.072234,44 Z M37.072234,34 L20.272234,34 L20.272234,32 L37.072234,32 L37.072234,34 Z M37.072234,24 L20.272234,24 L20.272234,22 L37.072234,22 L37.072234,24 Z M47.9233279,41.773438 L45.5706719,45.773438 C45.4427029,45.996094 45.239265,46.148438 45.0095779,46.1875 C44.9702029,46.195313 44.9275469,46.199219 44.88489,46.199219 C44.70114,46.199219 44.5206719,46.128906 44.373015,45.992188 L42.1877029,43.992188 C41.8202029,43.65625 41.7512969,43.027344 42.033484,42.589844 C42.3156719,42.152344 42.8439529,42.070313 43.2114529,42.40625 L44.697859,43.769531 L46.548484,40.625 C46.814265,40.171875 47.335984,40.0625 47.716609,40.378906 C48.097234,40.695313 48.189109,41.320313 47.9233279,41.773438 Z M47.9233279,31.773438 L45.5706719,35.773438 C45.4427029,35.996094 45.239265,36.148438 45.0095779,36.1875 C44.9702029,36.195313 44.9275469,36.199219 44.88489,36.199219 C44.70114,36.199219 44.5206719,36.128906 44.373015,35.992188 L42.1877029,33.992188 C41.8202029,33.65625 41.7512969,33.027344 42.033484,32.589844 C42.3156719,32.152344 42.8439529,32.070313 43.2114529,32.40625 L44.697859,33.769531 L46.548484,30.628906 C46.814265,30.175781 47.335984,30.0625 47.716609,30.382813 C48.097234,30.699219 48.189109,31.320313 47.9233279,31.773438 Z M47.9233279,21.773438 L45.5706719,25.773438 C45.4427029,25.996094 45.239265,26.148438 45.0095779,26.1875 C44.9702029,26.195313 44.9275469,26.199219 44.88489,26.199219 C44.70114,26.199219 44.5206719,26.128906 44.373015,25.992188 L42.1877029,23.992188 C41.8202029,23.65625 41.7512969,23.027344 42.033484,22.589844 C42.3156719,22.152344 42.8439529,22.070313 43.2114529,22.40625 L44.697859,23.769531 L46.548484,20.625 C46.814265,20.171875 47.335984,20.0625 47.716609,20.378906 C48.097234,20.699219 48.189109,21.320313 47.9233279,21.773438 Z" id="conjoint" fill="#000000" fill-rule="nonzero"></path>
|
|
456
458
|
</g>
|
|
457
459
|
</g>
|
|
460
|
+
</svg>""",
|
|
461
|
+
"specially": """<?xml version="1.0" encoding="UTF-8"?>
|
|
462
|
+
<svg width="67px" height="67px" viewBox="0 0 67 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
463
|
+
<title>specially</title>
|
|
464
|
+
<g id="All-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
465
|
+
<g id="specially" transform="translate(0.000000, 0.206897)">
|
|
466
|
+
<path d="M56.712234,1 C59.1975153,1 61.4475153,2.00735931 63.076195,3.63603897 C64.7048747,5.26471863 65.712234,7.51471863 65.712234,10 L65.712234,10 L65.712234,65 L10.712234,65 C8.22695259,65 5.97695259,63.9926407 4.34827294,62.363961 C2.71959328,60.7352814 1.71223397,58.4852814 1.71223397,56 L1.71223397,56 L1.71223397,10 C1.71223397,7.51471863 2.71959328,5.26471863 4.34827294,3.63603897 C5.97695259,2.00735931 8.22695259,1 10.712234,1 L10.712234,1 Z" id="rectangle" stroke="#000000" stroke-width="2" fill="#FFFFFF"></path>
|
|
467
|
+
<g id="star" transform="translate(8.500000, 8.500000)" fill="#000000" fill-rule="nonzero">
|
|
468
|
+
<path d="M25,0 C24.5874484,0 24.2174517,0.254002847 24.068359,0.6386719 L17.902344,16.535156 L0.94921875,17.400391 C0.536124409,17.4213013 0.17852458,17.6943523 0.0495446395,18.0873515 C-0.0794353012,18.4803507 0.046820452,18.9122002 0.3671875,19.173828 L13.568359,29.966797 L9.2324219,46.34375 C9.12646963,46.7428009 9.27663058,47.1659433 9.61042698,47.4089402 C9.94422338,47.651937 10.3930345,47.664834 10.740234,47.441406 L25,38.289062 L39.259766,47.441406 C39.6069655,47.6648339 40.0557766,47.6519369 40.3895729,47.4089401 C40.7233693,47.1659432 40.8735302,46.7428009 40.767578,46.34375 L36.431641,29.966797 L49.632812,19.173828 C49.953179,18.9122002 50.0794348,18.4803507 49.9504549,18.0873516 C49.821475,17.6943524 49.4638753,17.4213014 49.050781,17.400391 L32.097656,16.535156 L25.931641,0.6386719 C25.7825483,0.254002847 25.4125516,0 25,0 Z M25,3.7636719 L30.466797,17.861328 C30.609689,18.2291416 30.9554962,18.4785515 31.349609,18.498047 L46.359375,19.265625 L34.667969,28.826172 C34.3646054,29.0742114 34.2340493,29.4765679 34.333984,29.855469 L38.175781,44.369141 L25.541016,36.257812 C25.2114789,36.0458536 24.7885211,36.0458536 24.458984,36.257812 L11.824219,44.369141 L15.666016,29.855469 C15.7659507,29.4765679 15.6353946,29.0742114 15.332031,28.826172 L3.640625,19.265625 L18.650391,18.498047 C19.0445038,18.4785515 19.390311,18.2291416 19.533203,17.861328 L25,3.7636719 Z" id="Shape"></path>
|
|
469
|
+
</g>
|
|
470
|
+
</g>
|
|
471
|
+
</g>
|
|
458
472
|
</svg>""",
|
|
459
473
|
}
|
|
460
474
|
|
|
@@ -1160,6 +1160,60 @@ EXPECT_FAIL_TEXT = {
|
|
|
1160
1160
|
"hi": "असफल परीक्षण इकाइयों की अधिकता जहां संयुक्त 'पास' इकाइयां होनी चाहिए थीं।",
|
|
1161
1161
|
"el": "Υπέρβαση αποτυχημένων μονάδων δοκιμής όπου θα έπρεπε να υπάρχουν κοινές μονάδες 'επιτυχίας'.",
|
|
1162
1162
|
},
|
|
1163
|
+
"specially_expectation_text": {
|
|
1164
|
+
"en": "Expect that special testing with a given function yields agreement.",
|
|
1165
|
+
"fr": "On s'attend à ce que les tests spéciaux avec une fonction donnée produisent un accord.",
|
|
1166
|
+
"de": "Erwarten Sie, dass spezielle Tests mit einer bestimmten Funktion Übereinstimmung ergeben.",
|
|
1167
|
+
"it": "Aspettati che i test speciali con una funzione data producano accordo.",
|
|
1168
|
+
"es": "Se espera que las pruebas especiales con una función dada produzcan concordancia.",
|
|
1169
|
+
"pt": "Espera-se que testes especiais com uma função dada produzam concordância.",
|
|
1170
|
+
"ro": "Se așteaptă ca testarea specială cu o funcție dată să producă acord.",
|
|
1171
|
+
"tr": "Belirli bir fonksiyonla özel testlerin uyum sağlamasını bekleyin.",
|
|
1172
|
+
"zh-Hans": "预期使用给定函数的特殊测试会产生一致结果。",
|
|
1173
|
+
"zh-Hant": "預期使用給定函數的特殊測試會產生一致結果。",
|
|
1174
|
+
"ja": "指定された関数による特別なテストが一致することを期待します。",
|
|
1175
|
+
"ko": "주어진 함수로 특수 테스트를 수행하면 일치함을 기대합니다.",
|
|
1176
|
+
"vi": "Kỳ vọng rằng kiểm tra đặc biệt với một hàm đã cho sẽ cho kết quả phù hợp.",
|
|
1177
|
+
"ru": "Ожидайте, что специальное тестирование с заданной функцией дает согласие.",
|
|
1178
|
+
"cs": "Očekává se, že speciální testování s danou funkcí přinese shodu.",
|
|
1179
|
+
"pl": "Oczekuj, że specjalne testowanie z użyciem danej funkcji przyniesie zgodność.",
|
|
1180
|
+
"da": "Forvent at speciel test med en given funktion giver overensstemmelse.",
|
|
1181
|
+
"sv": "Förvänta dig att speciell testning med en given funktion ger överensstämmelse.",
|
|
1182
|
+
"nb": "Forvent at spesiell testing med en gitt funksjon gir samsvar.",
|
|
1183
|
+
"nl": "Verwacht dat speciale tests met een gegeven functie overeenstemming opleveren.",
|
|
1184
|
+
"fi": "Odota, että erityinen testaus annetulla funktiolla tuottaa yhdenmukaisuuden.",
|
|
1185
|
+
"is": "Væntir þess að sérstök prófun með gefnu falli leiði til samræmis.",
|
|
1186
|
+
"ar": "توقع أن الاختبار الخاص بدالة معينة يؤدي إلى التوافق.",
|
|
1187
|
+
"hi": "अपेक्षा है कि दिए गए फ़ंक्शन के साथ विशेष परीक्षण सहमति प्रदान करेगा।",
|
|
1188
|
+
"el": "Αναμένεται ότι ο ειδικός έλεγχος με μια δεδομένη συνάρτηση αποδίδει συμφωνία.",
|
|
1189
|
+
},
|
|
1190
|
+
"specially_failure_text": {
|
|
1191
|
+
"en": "Exceedance of failed test units when performing specialized testing with a given function.",
|
|
1192
|
+
"fr": "Dépassement des unités de test ayant échoué lors de l'exécution de tests spécialisés avec une fonction donnée.",
|
|
1193
|
+
"de": "Überschreitung fehlgeschlagener Testeinheiten bei der Durchführung spezialisierter Tests mit einer bestimmten Funktion.",
|
|
1194
|
+
"it": "Superamento delle unità di test fallite durante l'esecuzione di test specializzati con una funzione data.",
|
|
1195
|
+
"es": "Se superó el número de unidades de prueba fallidas al realizar pruebas especializadas con una función dada.",
|
|
1196
|
+
"pt": "Excedeu o número de unidades de teste com falha ao realizar testes especializados com uma função dada.",
|
|
1197
|
+
"ro": "Depășirea unităților de test eșuate la efectuarea testării specializate cu o funcție dată.",
|
|
1198
|
+
"tr": "Belirli bir fonksiyonla özel testler yapılırken başarısız test birimlerinin aşılması.",
|
|
1199
|
+
"zh-Hans": "使用给定函数进行专门测试时,失败的测试单元数量超标。",
|
|
1200
|
+
"zh-Hant": "使用給定函數進行專門測試時,失敗的測試單元數量超標。",
|
|
1201
|
+
"ja": "指定された関数を使用した特殊テスト実行時のテスト単位の失敗の超過。",
|
|
1202
|
+
"ko": "주어진 함수로 특수 테스트를 수행할 때 실패한 테스트 단위 초과.",
|
|
1203
|
+
"vi": "Vượt quá số đơn vị kiểm tra thất bại khi thực hiện kiểm tra chuyên biệt với một hàm đã cho.",
|
|
1204
|
+
"ru": "Превышение неудачных тестовых единиц при выполнении специализированного тестирования с заданной функцией.",
|
|
1205
|
+
"cs": "Překročení počtu neúspěšných testovacích jednotek při provádění specializovaného testování s danou funkcí.",
|
|
1206
|
+
"pl": "Przekroczenie nieudanych jednostek testowych podczas przeprowadzania specjalistycznych testów z daną funkcją.",
|
|
1207
|
+
"da": "Overskridelse af fejlslagne testenheder ved udførelse af specialiseret test med en given funktion.",
|
|
1208
|
+
"sv": "Överskrider antalet misslyckade testenheter vid utförande av specialiserad testning med en given funktion.",
|
|
1209
|
+
"nb": "Overskridelse av mislykkede testenheter ved utførelse av spesialisert testing med en gitt funksjon.",
|
|
1210
|
+
"nl": "Overschrijding van mislukte testeenheden bij het uitvoeren van gespecialiseerde tests met een gegeven functie.",
|
|
1211
|
+
"fi": "Epäonnistuneiden testiyksiköiden ylitys suoritettaessa erikoistestejä annetulla funktiolla.",
|
|
1212
|
+
"is": "Of mörg misheppnuð próf við framkvæmd sérhæfðra prófana með gefnu falli.",
|
|
1213
|
+
"ar": "تجاوز وحدات الاختبار الفاشلة عند إجراء اختبار متخصص بدالة معينة.",
|
|
1214
|
+
"hi": "दिए गए फ़ंक्शन के साथ विशेष परीक्षण करते समय असफल परीक्षण इकाइयों की अधिकता।",
|
|
1215
|
+
"el": "Υπέρβαση αποτυχημένων μονάδων δοκιμής κατά την εκτέλεση εξειδικευμένων ελέγχων με μια δεδομένη συνάρτηση.",
|
|
1216
|
+
},
|
|
1163
1217
|
}
|
|
1164
1218
|
|
|
1165
1219
|
|
pointblank/_interrogation.py
CHANGED
|
@@ -2248,6 +2248,107 @@ class ConjointlyValidation:
|
|
|
2248
2248
|
return results_tbl
|
|
2249
2249
|
|
|
2250
2250
|
|
|
2251
|
+
class SpeciallyValidation:
|
|
2252
|
+
def __init__(self, data_tbl, expression, threshold, tbl_type):
|
|
2253
|
+
self.data_tbl = data_tbl
|
|
2254
|
+
self.expression = expression
|
|
2255
|
+
self.threshold = threshold
|
|
2256
|
+
|
|
2257
|
+
# Detect the table type
|
|
2258
|
+
if tbl_type in (None, "local"):
|
|
2259
|
+
# Detect the table type using _get_tbl_type()
|
|
2260
|
+
self.tbl_type = _get_tbl_type(data=data_tbl)
|
|
2261
|
+
else:
|
|
2262
|
+
self.tbl_type = tbl_type
|
|
2263
|
+
|
|
2264
|
+
def get_test_results(self) -> any | list[bool]:
|
|
2265
|
+
"""Evaluate the expression get either a list of booleans or a results table."""
|
|
2266
|
+
|
|
2267
|
+
# Get the expression and inspect whether there is a `data` argument
|
|
2268
|
+
expression = self.expression
|
|
2269
|
+
|
|
2270
|
+
import inspect
|
|
2271
|
+
|
|
2272
|
+
# During execution of `specially` validation
|
|
2273
|
+
sig = inspect.signature(expression)
|
|
2274
|
+
params = list(sig.parameters.keys())
|
|
2275
|
+
|
|
2276
|
+
# Execute the function based on its signature
|
|
2277
|
+
if len(params) == 0:
|
|
2278
|
+
# No parameters: call without arguments
|
|
2279
|
+
result = expression()
|
|
2280
|
+
elif len(params) == 1:
|
|
2281
|
+
# One parameter: pass the data table
|
|
2282
|
+
data_tbl = self.data_tbl
|
|
2283
|
+
result = expression(data_tbl)
|
|
2284
|
+
else:
|
|
2285
|
+
# More than one parameter - this doesn't match either allowed signature
|
|
2286
|
+
raise ValueError(
|
|
2287
|
+
f"The function provided to 'specially()' should have either no parameters or a "
|
|
2288
|
+
f"single 'data' parameter, but it has {len(params)} parameters: {params}"
|
|
2289
|
+
)
|
|
2290
|
+
|
|
2291
|
+
# Determine if the object is a DataFrame by inspecting the string version of its type
|
|
2292
|
+
if (
|
|
2293
|
+
"pandas" in str(type(result))
|
|
2294
|
+
or "polars" in str(type(result))
|
|
2295
|
+
or "ibis" in str(type(result))
|
|
2296
|
+
):
|
|
2297
|
+
# Get the type of the table
|
|
2298
|
+
tbl_type = _get_tbl_type(data=result)
|
|
2299
|
+
|
|
2300
|
+
if "pandas" in tbl_type:
|
|
2301
|
+
# If it's a Pandas DataFrame, check if the last column is a boolean column
|
|
2302
|
+
last_col = result.iloc[:, -1]
|
|
2303
|
+
|
|
2304
|
+
import pandas as pd
|
|
2305
|
+
|
|
2306
|
+
if last_col.dtype == bool or pd.api.types.is_bool_dtype(last_col):
|
|
2307
|
+
# If the last column is a boolean column, rename it as `pb_is_good_`
|
|
2308
|
+
result.rename(columns={result.columns[-1]: "pb_is_good_"}, inplace=True)
|
|
2309
|
+
elif "polars" in tbl_type:
|
|
2310
|
+
# If it's a Polars DataFrame, check if the last column is a boolean column
|
|
2311
|
+
last_col_name = result.columns[-1]
|
|
2312
|
+
last_col_dtype = result.schema[last_col_name]
|
|
2313
|
+
|
|
2314
|
+
import polars as pl
|
|
2315
|
+
|
|
2316
|
+
if last_col_dtype == pl.Boolean:
|
|
2317
|
+
# If the last column is a boolean column, rename it as `pb_is_good_`
|
|
2318
|
+
result = result.rename({last_col_name: "pb_is_good_"})
|
|
2319
|
+
elif tbl_type in IBIS_BACKENDS:
|
|
2320
|
+
# If it's an Ibis table, check if the last column is a boolean column
|
|
2321
|
+
last_col_name = result.columns[-1]
|
|
2322
|
+
result_schema = result.schema()
|
|
2323
|
+
is_last_col_bool = str(result_schema[last_col_name]) == "boolean"
|
|
2324
|
+
|
|
2325
|
+
if is_last_col_bool:
|
|
2326
|
+
# If the last column is a boolean column, rename it as `pb_is_good_`
|
|
2327
|
+
result = result.rename(pb_is_good_=last_col_name)
|
|
2328
|
+
|
|
2329
|
+
else: # pragma: no cover
|
|
2330
|
+
raise NotImplementedError(f"Support for {tbl_type} is not yet implemented")
|
|
2331
|
+
|
|
2332
|
+
elif isinstance(result, bool):
|
|
2333
|
+
# If it's a single boolean, return that as a list
|
|
2334
|
+
return [result]
|
|
2335
|
+
|
|
2336
|
+
elif isinstance(result, list):
|
|
2337
|
+
# If it's a list, check that it is a boolean list
|
|
2338
|
+
if all(isinstance(x, bool) for x in result):
|
|
2339
|
+
# If it's a list of booleans, return it as is
|
|
2340
|
+
return result
|
|
2341
|
+
else:
|
|
2342
|
+
# If it's not a list of booleans, raise an error
|
|
2343
|
+
raise TypeError("The result is not a list of booleans.")
|
|
2344
|
+
else: # pragma: no cover
|
|
2345
|
+
# If it's not a DataFrame or a list, raise an error
|
|
2346
|
+
raise TypeError("The result is not a DataFrame or a list of booleans.")
|
|
2347
|
+
|
|
2348
|
+
# Return the results table or list of booleans
|
|
2349
|
+
return result
|
|
2350
|
+
|
|
2351
|
+
|
|
2251
2352
|
@dataclass
|
|
2252
2353
|
class NumberOfTestUnits:
|
|
2253
2354
|
"""
|
pointblank/_utils.py
CHANGED
pointblank/actions.py
CHANGED
|
@@ -225,7 +225,7 @@ def send_slack_notification(
|
|
|
225
225
|
validation
|
|
226
226
|
```
|
|
227
227
|
|
|
228
|
-
By placing the `notify_slack` function in the `Validate(actions=Actions(critical=))` argument,
|
|
228
|
+
By placing the `notify_slack()` function in the `Validate(actions=Actions(critical=))` argument,
|
|
229
229
|
you can ensure that the notification is sent whenever the 'critical' threshold is reached (as
|
|
230
230
|
set here, when 15% or more of the test units fail). The notification will include information
|
|
231
231
|
about the validation step that triggered the alert.
|
|
@@ -255,7 +255,7 @@ def send_slack_notification(
|
|
|
255
255
|
)
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
-
In this case, the same `notify_slack` function is used, but it is placed in
|
|
258
|
+
In this case, the same `notify_slack()` function is used, but it is placed in
|
|
259
259
|
`Validate(final_actions=FinalActions())`. This results in the summary notification being sent
|
|
260
260
|
after all validation steps are completed, regardless of whether any steps failed or not.
|
|
261
261
|
|
pointblank/data/api-docs.txt
CHANGED
|
@@ -708,8 +708,9 @@ FinalActions(*args)
|
|
|
708
708
|
In this example, the `send_alert()` function is defined to check the validation summary for
|
|
709
709
|
critical failures. If any are found, an alert message is printed to the console. The function is
|
|
710
710
|
passed to the `FinalActions` class, which ensures it will be executed after all validation steps
|
|
711
|
-
are complete. Note that we used the
|
|
712
|
-
|
|
711
|
+
are complete. Note that we used the
|
|
712
|
+
[`get_validation_summary()`](`pointblank.get_validation_summary`) function to retrieve the
|
|
713
|
+
summary of the validation results to help craft the alert message.
|
|
713
714
|
|
|
714
715
|
Multiple final actions can be provided in a sequence. They will be executed in the order they
|
|
715
716
|
are specified after all validation steps have completed:
|
|
@@ -5177,6 +5178,306 @@ conjointly(self, *exprs: 'Callable', pre: 'Callable | None' = None, thresholds:
|
|
|
5177
5178
|
information on how to use it with different table backends.
|
|
5178
5179
|
|
|
5179
5180
|
|
|
5181
|
+
specially(self, expr: 'Callable', pre: 'Callable | None' = None, thresholds: 'int | float | bool | tuple | dict | Thresholds' = None, actions: 'Actions | None' = None, brief: 'str | bool | None' = None, active: 'bool' = True) -> 'Validate'
|
|
5182
|
+
|
|
5183
|
+
Perform a specialized validation with customized logic.
|
|
5184
|
+
|
|
5185
|
+
The `specially()` validation method allows for the creation of specialized validation
|
|
5186
|
+
expressions that can be used to validate specific conditions or logic in the data. This
|
|
5187
|
+
method provides maximum flexibility by accepting a custom callable that encapsulates
|
|
5188
|
+
your validation logic.
|
|
5189
|
+
|
|
5190
|
+
The callable function can have one of two signatures:
|
|
5191
|
+
|
|
5192
|
+
- a function accepting a single parameter (the data table): `def validate(data): ...`
|
|
5193
|
+
- a function with no parameters: `def validate(): ...`
|
|
5194
|
+
|
|
5195
|
+
The second form is particularly useful for environment validations that don't need to
|
|
5196
|
+
inspect the data table.
|
|
5197
|
+
|
|
5198
|
+
The callable function must ultimately return one of:
|
|
5199
|
+
|
|
5200
|
+
1. a single boolean value or boolean list
|
|
5201
|
+
2. a table where the final column contains boolean values (column name is unimportant)
|
|
5202
|
+
|
|
5203
|
+
The validation will operate over the number of test units that is equal to the number of
|
|
5204
|
+
rows in the data table (if returning a table with boolean values). If returning a scalar
|
|
5205
|
+
boolean value, the validation will operate over a single test unit. For a return of a list
|
|
5206
|
+
of boolean values, the length of the list constitutes the number of test units.
|
|
5207
|
+
|
|
5208
|
+
Parameters
|
|
5209
|
+
----------
|
|
5210
|
+
expr
|
|
5211
|
+
A callable function that defines the specialized validation logic. This function should:
|
|
5212
|
+
(1) accept the target data table as its single argument (though it may ignore it), or
|
|
5213
|
+
(2) take no parameters at all (for environment validations). The function must
|
|
5214
|
+
ultimately return boolean values representing validation results. Design your function
|
|
5215
|
+
to incorporate any custom parameters directly within the function itself using closure
|
|
5216
|
+
variables or default parameters.
|
|
5217
|
+
pre
|
|
5218
|
+
An optional preprocessing function or lambda to apply to the data table during
|
|
5219
|
+
interrogation. This function should take a table as input and return a modified table.
|
|
5220
|
+
Have a look at the *Preprocessing* section for more information on how to use this
|
|
5221
|
+
argument.
|
|
5222
|
+
thresholds
|
|
5223
|
+
Set threshold failure levels for reporting and reacting to exceedences of the levels.
|
|
5224
|
+
The thresholds are set at the step level and will override any global thresholds set in
|
|
5225
|
+
`Validate(thresholds=...)`. The default is `None`, which means that no thresholds will
|
|
5226
|
+
be set locally and global thresholds (if any) will take effect. Look at the *Thresholds*
|
|
5227
|
+
section for information on how to set threshold levels.
|
|
5228
|
+
actions
|
|
5229
|
+
Optional actions to take when the validation step meets or exceeds any set threshold
|
|
5230
|
+
levels. If provided, the [`Actions`](`pointblank.Actions`) class should be used to
|
|
5231
|
+
define the actions.
|
|
5232
|
+
brief
|
|
5233
|
+
An optional brief description of the validation step that will be displayed in the
|
|
5234
|
+
reporting table. You can use the templating elements like `"{step}"` to insert
|
|
5235
|
+
the step number, or `"{auto}"` to include an automatically generated brief. If `True`
|
|
5236
|
+
the entire brief will be automatically generated. If `None` (the default) then there
|
|
5237
|
+
won't be a brief.
|
|
5238
|
+
active
|
|
5239
|
+
A boolean value indicating whether the validation step should be active. Using `False`
|
|
5240
|
+
will make the validation step inactive (still reporting its presence and keeping indexes
|
|
5241
|
+
for the steps unchanged).
|
|
5242
|
+
|
|
5243
|
+
Returns
|
|
5244
|
+
-------
|
|
5245
|
+
Validate
|
|
5246
|
+
The `Validate` object with the added validation step.
|
|
5247
|
+
|
|
5248
|
+
Preprocessing
|
|
5249
|
+
-------------
|
|
5250
|
+
The `pre=` argument allows for a preprocessing function or lambda to be applied to the data
|
|
5251
|
+
table during interrogation. This function should take a table as input and return a modified
|
|
5252
|
+
table. This is useful for performing any necessary transformations or filtering on the data
|
|
5253
|
+
before the validation step is applied.
|
|
5254
|
+
|
|
5255
|
+
The preprocessing function can be any callable that takes a table as input and returns a
|
|
5256
|
+
modified table. For example, you could use a lambda function to filter the table based on
|
|
5257
|
+
certain criteria or to apply a transformation to the data. Regarding the lifetime of the
|
|
5258
|
+
transformed table, it only exists during the validation step and is not stored in the
|
|
5259
|
+
`Validate` object or used in subsequent validation steps.
|
|
5260
|
+
|
|
5261
|
+
Thresholds
|
|
5262
|
+
----------
|
|
5263
|
+
The `thresholds=` parameter is used to set the failure-condition levels for the validation
|
|
5264
|
+
step. If they are set here at the step level, these thresholds will override any thresholds
|
|
5265
|
+
set at the global level in `Validate(thresholds=...)`.
|
|
5266
|
+
|
|
5267
|
+
There are three threshold levels: 'warning', 'error', and 'critical'. The threshold values
|
|
5268
|
+
can either be set as a proportion failing of all test units (a value between `0` to `1`),
|
|
5269
|
+
or, the absolute number of failing test units (as integer that's `1` or greater).
|
|
5270
|
+
|
|
5271
|
+
Thresholds can be defined using one of these input schemes:
|
|
5272
|
+
|
|
5273
|
+
1. use the [`Thresholds`](`pointblank.Thresholds`) class (the most direct way to create
|
|
5274
|
+
thresholds)
|
|
5275
|
+
2. provide a tuple of 1-3 values, where position `0` is the 'warning' level, position `1` is
|
|
5276
|
+
the 'error' level, and position `2` is the 'critical' level
|
|
5277
|
+
3. create a dictionary of 1-3 value entries; the valid keys: are 'warning', 'error', and
|
|
5278
|
+
'critical'
|
|
5279
|
+
4. a single integer/float value denoting absolute number or fraction of failing test units
|
|
5280
|
+
for the 'warning' level only
|
|
5281
|
+
|
|
5282
|
+
If the number of failing test units exceeds set thresholds, the validation step will be
|
|
5283
|
+
marked as 'warning', 'error', or 'critical'. All of the threshold levels don't need to be
|
|
5284
|
+
set, you're free to set any combination of them.
|
|
5285
|
+
|
|
5286
|
+
Aside from reporting failure conditions, thresholds can be used to determine the actions to
|
|
5287
|
+
take for each level of failure (using the `actions=` parameter).
|
|
5288
|
+
|
|
5289
|
+
Examples
|
|
5290
|
+
--------
|
|
5291
|
+
The `specially()` method offers maximum flexibility for validation, allowing you to create
|
|
5292
|
+
custom validation logic that fits your specific needs. The following examples demonstrate
|
|
5293
|
+
different patterns and use cases for this powerful validation approach.
|
|
5294
|
+
|
|
5295
|
+
### Simple validation with direct table access
|
|
5296
|
+
|
|
5297
|
+
This example shows the most straightforward use case where we create a function that
|
|
5298
|
+
directly checks if the sum of two columns is positive.
|
|
5299
|
+
|
|
5300
|
+
```python
|
|
5301
|
+
import pointblank as pb
|
|
5302
|
+
import polars as pl
|
|
5303
|
+
|
|
5304
|
+
simple_tbl = pl.DataFrame({
|
|
5305
|
+
"a": [5, 7, 1, 3, 9, 4],
|
|
5306
|
+
"b": [6, 3, 0, 5, 8, 2]
|
|
5307
|
+
})
|
|
5308
|
+
|
|
5309
|
+
# Simple function that validates directly on the table
|
|
5310
|
+
def validate_sum_positive(data):
|
|
5311
|
+
return data.select(pl.col("a") + pl.col("b") > 0)
|
|
5312
|
+
|
|
5313
|
+
(
|
|
5314
|
+
pb.Validate(data=simple_tbl)
|
|
5315
|
+
.specially(expr=validate_sum_positive)
|
|
5316
|
+
.interrogate()
|
|
5317
|
+
)
|
|
5318
|
+
```
|
|
5319
|
+
|
|
5320
|
+
The function returns a Polars DataFrame with a single boolean column indicating whether
|
|
5321
|
+
the sum of columns `a` and `b` is positive for each row. Each row in the resulting DataFrame
|
|
5322
|
+
is a distinct test unit. This pattern works well for simple validations where you don't need
|
|
5323
|
+
configurable parameters.
|
|
5324
|
+
|
|
5325
|
+
### Advanced validation with closure variables for parameters
|
|
5326
|
+
|
|
5327
|
+
When you need to make your validation configurable, you can use the function factory pattern
|
|
5328
|
+
(also known as closures) to create parameterized validations:
|
|
5329
|
+
|
|
5330
|
+
```python
|
|
5331
|
+
# Create a parameterized validation function using closures
|
|
5332
|
+
def make_column_ratio_validator(col1, col2, min_ratio):
|
|
5333
|
+
def validate_column_ratio(data):
|
|
5334
|
+
return data.select((pl.col(col1) / pl.col(col2)) > min_ratio)
|
|
5335
|
+
return validate_column_ratio
|
|
5336
|
+
|
|
5337
|
+
(
|
|
5338
|
+
pb.Validate(data=simple_tbl)
|
|
5339
|
+
.specially(
|
|
5340
|
+
expr=make_column_ratio_validator(col1="a", col2="b", min_ratio=0.5)
|
|
5341
|
+
)
|
|
5342
|
+
.interrogate()
|
|
5343
|
+
)
|
|
5344
|
+
```
|
|
5345
|
+
|
|
5346
|
+
This approach allows you to create reusable validation functions that can be configured with
|
|
5347
|
+
different parameters without modifying the function itself.
|
|
5348
|
+
|
|
5349
|
+
### Validation function returning a list of booleans
|
|
5350
|
+
|
|
5351
|
+
This example demonstrates how to create a validation function that returns a list of boolean
|
|
5352
|
+
values, where each element represents a separate test unit:
|
|
5353
|
+
|
|
5354
|
+
```python
|
|
5355
|
+
import pointblank as pb
|
|
5356
|
+
import polars as pl
|
|
5357
|
+
import random
|
|
5358
|
+
|
|
5359
|
+
# Create sample data
|
|
5360
|
+
transaction_tbl = pl.DataFrame({
|
|
5361
|
+
"transaction_id": [f"TX{i:04d}" for i in range(1, 11)],
|
|
5362
|
+
"amount": [120.50, 85.25, 50.00, 240.75, 35.20, 150.00, 85.25, 65.00, 210.75, 90.50],
|
|
5363
|
+
"category": ["food", "shopping", "entertainment", "travel", "utilities",
|
|
5364
|
+
"food", "shopping", "entertainment", "travel", "utilities"]
|
|
5365
|
+
})
|
|
5366
|
+
|
|
5367
|
+
# Define a validation function that returns a list of booleans
|
|
5368
|
+
def validate_transaction_rules(data):
|
|
5369
|
+
# Create a list to store individual test results
|
|
5370
|
+
test_results = []
|
|
5371
|
+
|
|
5372
|
+
# Check each row individually against multiple business rules
|
|
5373
|
+
for row in data.iter_rows(named=True):
|
|
5374
|
+
# Rule: transaction IDs must start with "TX" and be 6 chars long
|
|
5375
|
+
valid_id = row["transaction_id"].startswith("TX") and len(row["transaction_id"]) == 6
|
|
5376
|
+
|
|
5377
|
+
# Rule: Amounts must be appropriate for their category
|
|
5378
|
+
valid_amount = True
|
|
5379
|
+
if row["category"] == "food" and (row["amount"] < 10 or row["amount"] > 200):
|
|
5380
|
+
valid_amount = False
|
|
5381
|
+
elif row["category"] == "utilities" and (row["amount"] < 20 or row["amount"] > 300):
|
|
5382
|
+
valid_amount = False
|
|
5383
|
+
elif row["category"] == "entertainment" and row["amount"] > 100:
|
|
5384
|
+
valid_amount = False
|
|
5385
|
+
|
|
5386
|
+
# A transaction passes if it satisfies both rules
|
|
5387
|
+
test_results.append(valid_id and valid_amount)
|
|
5388
|
+
|
|
5389
|
+
return test_results
|
|
5390
|
+
|
|
5391
|
+
(
|
|
5392
|
+
pb.Validate(data=transaction_tbl)
|
|
5393
|
+
.specially(
|
|
5394
|
+
expr=validate_transaction_rules,
|
|
5395
|
+
brief="Validate transaction IDs and amounts by category."
|
|
5396
|
+
)
|
|
5397
|
+
.interrogate()
|
|
5398
|
+
)
|
|
5399
|
+
```
|
|
5400
|
+
|
|
5401
|
+
This example shows how to create a validation function that applies multiple business rules
|
|
5402
|
+
to each row and returns a list of boolean results. Each boolean in the list represents a
|
|
5403
|
+
separate test unit, and a test unit passes only if all rules are satisfied for a given row.
|
|
5404
|
+
|
|
5405
|
+
The function iterates through each row in the data table, checking:
|
|
5406
|
+
|
|
5407
|
+
1. if transaction IDs follow the required format
|
|
5408
|
+
2. if transaction amounts are appropriate for their respective categories
|
|
5409
|
+
|
|
5410
|
+
This approach is powerful when you need to apply complex, conditional logic that can't be
|
|
5411
|
+
easily expressed using the built-in validation functions.
|
|
5412
|
+
|
|
5413
|
+
### Table-level validation returning a single boolean
|
|
5414
|
+
|
|
5415
|
+
Sometimes you need to validate properties of the entire table rather than row-by-row. In
|
|
5416
|
+
these cases, your function can return a single boolean value:
|
|
5417
|
+
|
|
5418
|
+
```python
|
|
5419
|
+
def validate_table_properties(data):
|
|
5420
|
+
# Check if table has at least one row with column 'a' > 10
|
|
5421
|
+
has_large_values = data.filter(pl.col("a") > 10).height > 0
|
|
5422
|
+
|
|
5423
|
+
# Check if mean of column 'b' is positive
|
|
5424
|
+
has_positive_mean = data.select(pl.mean("b")).item() > 0
|
|
5425
|
+
|
|
5426
|
+
# Return a single boolean for the entire table
|
|
5427
|
+
return has_large_values and has_positive_mean
|
|
5428
|
+
|
|
5429
|
+
(
|
|
5430
|
+
pb.Validate(data=simple_tbl)
|
|
5431
|
+
.specially(expr=validate_table_properties)
|
|
5432
|
+
.interrogate()
|
|
5433
|
+
)
|
|
5434
|
+
```
|
|
5435
|
+
|
|
5436
|
+
This example demonstrates how to perform multiple checks on the table as a whole and combine
|
|
5437
|
+
them into a single validation result.
|
|
5438
|
+
|
|
5439
|
+
### Environment validation that doesn't use the data table
|
|
5440
|
+
|
|
5441
|
+
The `specially()` validation method can even be used to validate aspects of your environment
|
|
5442
|
+
that are completely independent of the data:
|
|
5443
|
+
|
|
5444
|
+
```python
|
|
5445
|
+
def validate_pointblank_version():
|
|
5446
|
+
try:
|
|
5447
|
+
import importlib.metadata
|
|
5448
|
+
version = importlib.metadata.version("pointblank")
|
|
5449
|
+
version_parts = version.split(".")
|
|
5450
|
+
|
|
5451
|
+
# Get major and minor components regardless of how many parts there are
|
|
5452
|
+
major = int(version_parts[0])
|
|
5453
|
+
minor = int(version_parts[1])
|
|
5454
|
+
|
|
5455
|
+
# Check both major and minor components for version `0.9+`
|
|
5456
|
+
return (major > 0) or (major == 0 and minor >= 9)
|
|
5457
|
+
|
|
5458
|
+
except Exception as e:
|
|
5459
|
+
# More specific error handling could be added here
|
|
5460
|
+
print(f"Version check failed: {e}")
|
|
5461
|
+
return False
|
|
5462
|
+
|
|
5463
|
+
(
|
|
5464
|
+
pb.Validate(data=simple_tbl)
|
|
5465
|
+
.specially(
|
|
5466
|
+
expr=validate_pointblank_version,
|
|
5467
|
+
brief="Check Pointblank version `>=0.9.0`."
|
|
5468
|
+
)
|
|
5469
|
+
.interrogate()
|
|
5470
|
+
)
|
|
5471
|
+
```
|
|
5472
|
+
|
|
5473
|
+
This pattern shows how to validate external dependencies or environment conditions as part
|
|
5474
|
+
of your validation workflow. Notice that the function doesn't take any parameters at all,
|
|
5475
|
+
which makes it cleaner when the validation doesn't need to access the data table.
|
|
5476
|
+
|
|
5477
|
+
By combining these patterns, you can create sophisticated validation workflows that address
|
|
5478
|
+
virtually any data quality requirement in your organization.
|
|
5479
|
+
|
|
5480
|
+
|
|
5180
5481
|
|
|
5181
5482
|
## The Column Selection family
|
|
5182
5483
|
|
|
@@ -9160,7 +9461,7 @@ send_slack_notification(webhook_url: 'str | None' = None, step_msg: 'str | None'
|
|
|
9160
9461
|
validation
|
|
9161
9462
|
```
|
|
9162
9463
|
|
|
9163
|
-
By placing the `notify_slack` function in the `Validate(actions=Actions(critical=))` argument,
|
|
9464
|
+
By placing the `notify_slack()` function in the `Validate(actions=Actions(critical=))` argument,
|
|
9164
9465
|
you can ensure that the notification is sent whenever the 'critical' threshold is reached (as
|
|
9165
9466
|
set here, when 15% or more of the test units fail). The notification will include information
|
|
9166
9467
|
about the validation step that triggered the alert.
|
|
@@ -9190,7 +9491,7 @@ send_slack_notification(webhook_url: 'str | None' = None, step_msg: 'str | None'
|
|
|
9190
9491
|
)
|
|
9191
9492
|
```
|
|
9192
9493
|
|
|
9193
|
-
In this case, the same `notify_slack` function is used, but it is placed in
|
|
9494
|
+
In this case, the same `notify_slack()` function is used, but it is placed in
|
|
9194
9495
|
`Validate(final_actions=FinalActions())`. This results in the summary notification being sent
|
|
9195
9496
|
after all validation steps are completed, regardless of whether any steps failed or not.
|
|
9196
9497
|
|
pointblank/thresholds.py
CHANGED
|
@@ -574,8 +574,9 @@ class FinalActions:
|
|
|
574
574
|
In this example, the `send_alert()` function is defined to check the validation summary for
|
|
575
575
|
critical failures. If any are found, an alert message is printed to the console. The function is
|
|
576
576
|
passed to the `FinalActions` class, which ensures it will be executed after all validation steps
|
|
577
|
-
are complete. Note that we used the
|
|
578
|
-
|
|
577
|
+
are complete. Note that we used the
|
|
578
|
+
[`get_validation_summary()`](`pointblank.get_validation_summary`) function to retrieve the
|
|
579
|
+
summary of the validation results to help craft the alert message.
|
|
579
580
|
|
|
580
581
|
Multiple final actions can be provided in a sequence. They will be executed in the order they
|
|
581
582
|
are specified after all validation steps have completed:
|
pointblank/validate.py
CHANGED
|
@@ -58,6 +58,7 @@ from pointblank._interrogation import (
|
|
|
58
58
|
RowCountMatch,
|
|
59
59
|
RowsComplete,
|
|
60
60
|
RowsDistinct,
|
|
61
|
+
SpeciallyValidation,
|
|
61
62
|
)
|
|
62
63
|
from pointblank._typing import SegmentSpec
|
|
63
64
|
from pointblank._utils import (
|
|
@@ -7633,7 +7634,7 @@ class Validate:
|
|
|
7633
7634
|
|
|
7634
7635
|
val_info = _ValidationInfo(
|
|
7635
7636
|
assertion_type=assertion_type,
|
|
7636
|
-
column=None, # This is
|
|
7637
|
+
column=None, # This validation is not specific to any column(s)
|
|
7637
7638
|
values=values,
|
|
7638
7639
|
pre=pre,
|
|
7639
7640
|
thresholds=thresholds,
|
|
@@ -7646,6 +7647,351 @@ class Validate:
|
|
|
7646
7647
|
|
|
7647
7648
|
return self
|
|
7648
7649
|
|
|
7650
|
+
def specially(
|
|
7651
|
+
self,
|
|
7652
|
+
expr: Callable,
|
|
7653
|
+
pre: Callable | None = None,
|
|
7654
|
+
thresholds: int | float | bool | tuple | dict | Thresholds = None,
|
|
7655
|
+
actions: Actions | None = None,
|
|
7656
|
+
brief: str | bool | None = None,
|
|
7657
|
+
active: bool = True,
|
|
7658
|
+
) -> Validate:
|
|
7659
|
+
"""
|
|
7660
|
+
Perform a specialized validation with customized logic.
|
|
7661
|
+
|
|
7662
|
+
The `specially()` validation method allows for the creation of specialized validation
|
|
7663
|
+
expressions that can be used to validate specific conditions or logic in the data. This
|
|
7664
|
+
method provides maximum flexibility by accepting a custom callable that encapsulates
|
|
7665
|
+
your validation logic.
|
|
7666
|
+
|
|
7667
|
+
The callable function can have one of two signatures:
|
|
7668
|
+
|
|
7669
|
+
- a function accepting a single parameter (the data table): `def validate(data): ...`
|
|
7670
|
+
- a function with no parameters: `def validate(): ...`
|
|
7671
|
+
|
|
7672
|
+
The second form is particularly useful for environment validations that don't need to
|
|
7673
|
+
inspect the data table.
|
|
7674
|
+
|
|
7675
|
+
The callable function must ultimately return one of:
|
|
7676
|
+
|
|
7677
|
+
1. a single boolean value or boolean list
|
|
7678
|
+
2. a table where the final column contains boolean values (column name is unimportant)
|
|
7679
|
+
|
|
7680
|
+
The validation will operate over the number of test units that is equal to the number of
|
|
7681
|
+
rows in the data table (if returning a table with boolean values). If returning a scalar
|
|
7682
|
+
boolean value, the validation will operate over a single test unit. For a return of a list
|
|
7683
|
+
of boolean values, the length of the list constitutes the number of test units.
|
|
7684
|
+
|
|
7685
|
+
Parameters
|
|
7686
|
+
----------
|
|
7687
|
+
expr
|
|
7688
|
+
A callable function that defines the specialized validation logic. This function should:
|
|
7689
|
+
(1) accept the target data table as its single argument (though it may ignore it), or
|
|
7690
|
+
(2) take no parameters at all (for environment validations). The function must
|
|
7691
|
+
ultimately return boolean values representing validation results. Design your function
|
|
7692
|
+
to incorporate any custom parameters directly within the function itself using closure
|
|
7693
|
+
variables or default parameters.
|
|
7694
|
+
pre
|
|
7695
|
+
An optional preprocessing function or lambda to apply to the data table during
|
|
7696
|
+
interrogation. This function should take a table as input and return a modified table.
|
|
7697
|
+
Have a look at the *Preprocessing* section for more information on how to use this
|
|
7698
|
+
argument.
|
|
7699
|
+
thresholds
|
|
7700
|
+
Set threshold failure levels for reporting and reacting to exceedences of the levels.
|
|
7701
|
+
The thresholds are set at the step level and will override any global thresholds set in
|
|
7702
|
+
`Validate(thresholds=...)`. The default is `None`, which means that no thresholds will
|
|
7703
|
+
be set locally and global thresholds (if any) will take effect. Look at the *Thresholds*
|
|
7704
|
+
section for information on how to set threshold levels.
|
|
7705
|
+
actions
|
|
7706
|
+
Optional actions to take when the validation step meets or exceeds any set threshold
|
|
7707
|
+
levels. If provided, the [`Actions`](`pointblank.Actions`) class should be used to
|
|
7708
|
+
define the actions.
|
|
7709
|
+
brief
|
|
7710
|
+
An optional brief description of the validation step that will be displayed in the
|
|
7711
|
+
reporting table. You can use the templating elements like `"{step}"` to insert
|
|
7712
|
+
the step number, or `"{auto}"` to include an automatically generated brief. If `True`
|
|
7713
|
+
the entire brief will be automatically generated. If `None` (the default) then there
|
|
7714
|
+
won't be a brief.
|
|
7715
|
+
active
|
|
7716
|
+
A boolean value indicating whether the validation step should be active. Using `False`
|
|
7717
|
+
will make the validation step inactive (still reporting its presence and keeping indexes
|
|
7718
|
+
for the steps unchanged).
|
|
7719
|
+
|
|
7720
|
+
Returns
|
|
7721
|
+
-------
|
|
7722
|
+
Validate
|
|
7723
|
+
The `Validate` object with the added validation step.
|
|
7724
|
+
|
|
7725
|
+
Preprocessing
|
|
7726
|
+
-------------
|
|
7727
|
+
The `pre=` argument allows for a preprocessing function or lambda to be applied to the data
|
|
7728
|
+
table during interrogation. This function should take a table as input and return a modified
|
|
7729
|
+
table. This is useful for performing any necessary transformations or filtering on the data
|
|
7730
|
+
before the validation step is applied.
|
|
7731
|
+
|
|
7732
|
+
The preprocessing function can be any callable that takes a table as input and returns a
|
|
7733
|
+
modified table. For example, you could use a lambda function to filter the table based on
|
|
7734
|
+
certain criteria or to apply a transformation to the data. Regarding the lifetime of the
|
|
7735
|
+
transformed table, it only exists during the validation step and is not stored in the
|
|
7736
|
+
`Validate` object or used in subsequent validation steps.
|
|
7737
|
+
|
|
7738
|
+
Thresholds
|
|
7739
|
+
----------
|
|
7740
|
+
The `thresholds=` parameter is used to set the failure-condition levels for the validation
|
|
7741
|
+
step. If they are set here at the step level, these thresholds will override any thresholds
|
|
7742
|
+
set at the global level in `Validate(thresholds=...)`.
|
|
7743
|
+
|
|
7744
|
+
There are three threshold levels: 'warning', 'error', and 'critical'. The threshold values
|
|
7745
|
+
can either be set as a proportion failing of all test units (a value between `0` to `1`),
|
|
7746
|
+
or, the absolute number of failing test units (as integer that's `1` or greater).
|
|
7747
|
+
|
|
7748
|
+
Thresholds can be defined using one of these input schemes:
|
|
7749
|
+
|
|
7750
|
+
1. use the [`Thresholds`](`pointblank.Thresholds`) class (the most direct way to create
|
|
7751
|
+
thresholds)
|
|
7752
|
+
2. provide a tuple of 1-3 values, where position `0` is the 'warning' level, position `1` is
|
|
7753
|
+
the 'error' level, and position `2` is the 'critical' level
|
|
7754
|
+
3. create a dictionary of 1-3 value entries; the valid keys: are 'warning', 'error', and
|
|
7755
|
+
'critical'
|
|
7756
|
+
4. a single integer/float value denoting absolute number or fraction of failing test units
|
|
7757
|
+
for the 'warning' level only
|
|
7758
|
+
|
|
7759
|
+
If the number of failing test units exceeds set thresholds, the validation step will be
|
|
7760
|
+
marked as 'warning', 'error', or 'critical'. All of the threshold levels don't need to be
|
|
7761
|
+
set, you're free to set any combination of them.
|
|
7762
|
+
|
|
7763
|
+
Aside from reporting failure conditions, thresholds can be used to determine the actions to
|
|
7764
|
+
take for each level of failure (using the `actions=` parameter).
|
|
7765
|
+
|
|
7766
|
+
Examples
|
|
7767
|
+
--------
|
|
7768
|
+
```{python}
|
|
7769
|
+
#| echo: false
|
|
7770
|
+
#| output: false
|
|
7771
|
+
import pointblank as pb
|
|
7772
|
+
pb.config(report_incl_header=False, report_incl_footer=False, preview_incl_header=False)
|
|
7773
|
+
```
|
|
7774
|
+
The `specially()` method offers maximum flexibility for validation, allowing you to create
|
|
7775
|
+
custom validation logic that fits your specific needs. The following examples demonstrate
|
|
7776
|
+
different patterns and use cases for this powerful validation approach.
|
|
7777
|
+
|
|
7778
|
+
### Simple validation with direct table access
|
|
7779
|
+
|
|
7780
|
+
This example shows the most straightforward use case where we create a function that
|
|
7781
|
+
directly checks if the sum of two columns is positive.
|
|
7782
|
+
|
|
7783
|
+
```{python}
|
|
7784
|
+
import pointblank as pb
|
|
7785
|
+
import polars as pl
|
|
7786
|
+
|
|
7787
|
+
simple_tbl = pl.DataFrame({
|
|
7788
|
+
"a": [5, 7, 1, 3, 9, 4],
|
|
7789
|
+
"b": [6, 3, 0, 5, 8, 2]
|
|
7790
|
+
})
|
|
7791
|
+
|
|
7792
|
+
# Simple function that validates directly on the table
|
|
7793
|
+
def validate_sum_positive(data):
|
|
7794
|
+
return data.select(pl.col("a") + pl.col("b") > 0)
|
|
7795
|
+
|
|
7796
|
+
(
|
|
7797
|
+
pb.Validate(data=simple_tbl)
|
|
7798
|
+
.specially(expr=validate_sum_positive)
|
|
7799
|
+
.interrogate()
|
|
7800
|
+
)
|
|
7801
|
+
```
|
|
7802
|
+
|
|
7803
|
+
The function returns a Polars DataFrame with a single boolean column indicating whether
|
|
7804
|
+
the sum of columns `a` and `b` is positive for each row. Each row in the resulting DataFrame
|
|
7805
|
+
is a distinct test unit. This pattern works well for simple validations where you don't need
|
|
7806
|
+
configurable parameters.
|
|
7807
|
+
|
|
7808
|
+
### Advanced validation with closure variables for parameters
|
|
7809
|
+
|
|
7810
|
+
When you need to make your validation configurable, you can use the function factory pattern
|
|
7811
|
+
(also known as closures) to create parameterized validations:
|
|
7812
|
+
|
|
7813
|
+
```{python}
|
|
7814
|
+
# Create a parameterized validation function using closures
|
|
7815
|
+
def make_column_ratio_validator(col1, col2, min_ratio):
|
|
7816
|
+
def validate_column_ratio(data):
|
|
7817
|
+
return data.select((pl.col(col1) / pl.col(col2)) > min_ratio)
|
|
7818
|
+
return validate_column_ratio
|
|
7819
|
+
|
|
7820
|
+
(
|
|
7821
|
+
pb.Validate(data=simple_tbl)
|
|
7822
|
+
.specially(
|
|
7823
|
+
expr=make_column_ratio_validator(col1="a", col2="b", min_ratio=0.5)
|
|
7824
|
+
)
|
|
7825
|
+
.interrogate()
|
|
7826
|
+
)
|
|
7827
|
+
```
|
|
7828
|
+
|
|
7829
|
+
This approach allows you to create reusable validation functions that can be configured with
|
|
7830
|
+
different parameters without modifying the function itself.
|
|
7831
|
+
|
|
7832
|
+
### Validation function returning a list of booleans
|
|
7833
|
+
|
|
7834
|
+
This example demonstrates how to create a validation function that returns a list of boolean
|
|
7835
|
+
values, where each element represents a separate test unit:
|
|
7836
|
+
|
|
7837
|
+
```{python}
|
|
7838
|
+
import pointblank as pb
|
|
7839
|
+
import polars as pl
|
|
7840
|
+
import random
|
|
7841
|
+
|
|
7842
|
+
# Create sample data
|
|
7843
|
+
transaction_tbl = pl.DataFrame({
|
|
7844
|
+
"transaction_id": [f"TX{i:04d}" for i in range(1, 11)],
|
|
7845
|
+
"amount": [120.50, 85.25, 50.00, 240.75, 35.20, 150.00, 85.25, 65.00, 210.75, 90.50],
|
|
7846
|
+
"category": ["food", "shopping", "entertainment", "travel", "utilities",
|
|
7847
|
+
"food", "shopping", "entertainment", "travel", "utilities"]
|
|
7848
|
+
})
|
|
7849
|
+
|
|
7850
|
+
# Define a validation function that returns a list of booleans
|
|
7851
|
+
def validate_transaction_rules(data):
|
|
7852
|
+
# Create a list to store individual test results
|
|
7853
|
+
test_results = []
|
|
7854
|
+
|
|
7855
|
+
# Check each row individually against multiple business rules
|
|
7856
|
+
for row in data.iter_rows(named=True):
|
|
7857
|
+
# Rule: transaction IDs must start with "TX" and be 6 chars long
|
|
7858
|
+
valid_id = row["transaction_id"].startswith("TX") and len(row["transaction_id"]) == 6
|
|
7859
|
+
|
|
7860
|
+
# Rule: Amounts must be appropriate for their category
|
|
7861
|
+
valid_amount = True
|
|
7862
|
+
if row["category"] == "food" and (row["amount"] < 10 or row["amount"] > 200):
|
|
7863
|
+
valid_amount = False
|
|
7864
|
+
elif row["category"] == "utilities" and (row["amount"] < 20 or row["amount"] > 300):
|
|
7865
|
+
valid_amount = False
|
|
7866
|
+
elif row["category"] == "entertainment" and row["amount"] > 100:
|
|
7867
|
+
valid_amount = False
|
|
7868
|
+
|
|
7869
|
+
# A transaction passes if it satisfies both rules
|
|
7870
|
+
test_results.append(valid_id and valid_amount)
|
|
7871
|
+
|
|
7872
|
+
return test_results
|
|
7873
|
+
|
|
7874
|
+
(
|
|
7875
|
+
pb.Validate(data=transaction_tbl)
|
|
7876
|
+
.specially(
|
|
7877
|
+
expr=validate_transaction_rules,
|
|
7878
|
+
brief="Validate transaction IDs and amounts by category."
|
|
7879
|
+
)
|
|
7880
|
+
.interrogate()
|
|
7881
|
+
)
|
|
7882
|
+
```
|
|
7883
|
+
|
|
7884
|
+
This example shows how to create a validation function that applies multiple business rules
|
|
7885
|
+
to each row and returns a list of boolean results. Each boolean in the list represents a
|
|
7886
|
+
separate test unit, and a test unit passes only if all rules are satisfied for a given row.
|
|
7887
|
+
|
|
7888
|
+
The function iterates through each row in the data table, checking:
|
|
7889
|
+
|
|
7890
|
+
1. if transaction IDs follow the required format
|
|
7891
|
+
2. if transaction amounts are appropriate for their respective categories
|
|
7892
|
+
|
|
7893
|
+
This approach is powerful when you need to apply complex, conditional logic that can't be
|
|
7894
|
+
easily expressed using the built-in validation functions.
|
|
7895
|
+
|
|
7896
|
+
### Table-level validation returning a single boolean
|
|
7897
|
+
|
|
7898
|
+
Sometimes you need to validate properties of the entire table rather than row-by-row. In
|
|
7899
|
+
these cases, your function can return a single boolean value:
|
|
7900
|
+
|
|
7901
|
+
```{python}
|
|
7902
|
+
def validate_table_properties(data):
|
|
7903
|
+
# Check if table has at least one row with column 'a' > 10
|
|
7904
|
+
has_large_values = data.filter(pl.col("a") > 10).height > 0
|
|
7905
|
+
|
|
7906
|
+
# Check if mean of column 'b' is positive
|
|
7907
|
+
has_positive_mean = data.select(pl.mean("b")).item() > 0
|
|
7908
|
+
|
|
7909
|
+
# Return a single boolean for the entire table
|
|
7910
|
+
return has_large_values and has_positive_mean
|
|
7911
|
+
|
|
7912
|
+
(
|
|
7913
|
+
pb.Validate(data=simple_tbl)
|
|
7914
|
+
.specially(expr=validate_table_properties)
|
|
7915
|
+
.interrogate()
|
|
7916
|
+
)
|
|
7917
|
+
```
|
|
7918
|
+
|
|
7919
|
+
This example demonstrates how to perform multiple checks on the table as a whole and combine
|
|
7920
|
+
them into a single validation result.
|
|
7921
|
+
|
|
7922
|
+
### Environment validation that doesn't use the data table
|
|
7923
|
+
|
|
7924
|
+
The `specially()` validation method can even be used to validate aspects of your environment
|
|
7925
|
+
that are completely independent of the data:
|
|
7926
|
+
|
|
7927
|
+
```{python}
|
|
7928
|
+
def validate_pointblank_version():
|
|
7929
|
+
try:
|
|
7930
|
+
import importlib.metadata
|
|
7931
|
+
version = importlib.metadata.version("pointblank")
|
|
7932
|
+
version_parts = version.split(".")
|
|
7933
|
+
|
|
7934
|
+
# Get major and minor components regardless of how many parts there are
|
|
7935
|
+
major = int(version_parts[0])
|
|
7936
|
+
minor = int(version_parts[1])
|
|
7937
|
+
|
|
7938
|
+
# Check both major and minor components for version `0.9+`
|
|
7939
|
+
return (major > 0) or (major == 0 and minor >= 9)
|
|
7940
|
+
|
|
7941
|
+
except Exception as e:
|
|
7942
|
+
# More specific error handling could be added here
|
|
7943
|
+
print(f"Version check failed: {e}")
|
|
7944
|
+
return False
|
|
7945
|
+
|
|
7946
|
+
(
|
|
7947
|
+
pb.Validate(data=simple_tbl)
|
|
7948
|
+
.specially(
|
|
7949
|
+
expr=validate_pointblank_version,
|
|
7950
|
+
brief="Check Pointblank version `>=0.9.0`."
|
|
7951
|
+
)
|
|
7952
|
+
.interrogate()
|
|
7953
|
+
)
|
|
7954
|
+
```
|
|
7955
|
+
|
|
7956
|
+
This pattern shows how to validate external dependencies or environment conditions as part
|
|
7957
|
+
of your validation workflow. Notice that the function doesn't take any parameters at all,
|
|
7958
|
+
which makes it cleaner when the validation doesn't need to access the data table.
|
|
7959
|
+
|
|
7960
|
+
By combining these patterns, you can create sophisticated validation workflows that address
|
|
7961
|
+
virtually any data quality requirement in your organization.
|
|
7962
|
+
"""
|
|
7963
|
+
|
|
7964
|
+
assertion_type = _get_fn_name()
|
|
7965
|
+
|
|
7966
|
+
# TODO: add a check for the expression to be a callable
|
|
7967
|
+
# _check_expr_specially(expr=expr)
|
|
7968
|
+
_check_pre(pre=pre)
|
|
7969
|
+
_check_thresholds(thresholds=thresholds)
|
|
7970
|
+
_check_boolean_input(param=active, param_name="active")
|
|
7971
|
+
|
|
7972
|
+
# Determine threshold to use (global or local) and normalize a local `thresholds=` value
|
|
7973
|
+
thresholds = (
|
|
7974
|
+
self.thresholds if thresholds is None else _normalize_thresholds_creation(thresholds)
|
|
7975
|
+
)
|
|
7976
|
+
|
|
7977
|
+
# Determine brief to use (global or local) and transform any shorthands of `brief=`
|
|
7978
|
+
brief = self.brief if brief is None else _transform_auto_brief(brief=brief)
|
|
7979
|
+
|
|
7980
|
+
val_info = _ValidationInfo(
|
|
7981
|
+
assertion_type=assertion_type,
|
|
7982
|
+
column=None, # This validation is not specific to any column(s)
|
|
7983
|
+
values=expr,
|
|
7984
|
+
pre=pre,
|
|
7985
|
+
thresholds=thresholds,
|
|
7986
|
+
actions=actions,
|
|
7987
|
+
brief=brief,
|
|
7988
|
+
active=active,
|
|
7989
|
+
)
|
|
7990
|
+
|
|
7991
|
+
self._add_validation(validation_info=val_info)
|
|
7992
|
+
|
|
7993
|
+
return self
|
|
7994
|
+
|
|
7649
7995
|
def interrogate(
|
|
7650
7996
|
self,
|
|
7651
7997
|
collect_extracts: bool = True,
|
|
@@ -8060,12 +8406,39 @@ class Validate:
|
|
|
8060
8406
|
tbl_type=tbl_type,
|
|
8061
8407
|
).get_test_results()
|
|
8062
8408
|
|
|
8063
|
-
if assertion_category
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8409
|
+
if assertion_category == "SPECIALLY":
|
|
8410
|
+
results_tbl_list = SpeciallyValidation(
|
|
8411
|
+
data_tbl=data_tbl_step,
|
|
8412
|
+
expression=value,
|
|
8413
|
+
threshold=threshold,
|
|
8414
|
+
tbl_type=tbl_type,
|
|
8415
|
+
).get_test_results()
|
|
8416
|
+
|
|
8417
|
+
#
|
|
8418
|
+
# The result from this could either be a table in the conventional form, or,
|
|
8419
|
+
# a list of boolean values; handle both cases
|
|
8420
|
+
#
|
|
8421
|
+
|
|
8422
|
+
if isinstance(results_tbl_list, list):
|
|
8423
|
+
# If the result is a list of boolean values, then we need to convert it to a
|
|
8424
|
+
# set the validation results from the list
|
|
8425
|
+
validation.all_passed = all(results_tbl_list)
|
|
8426
|
+
validation.n = len(results_tbl_list)
|
|
8427
|
+
validation.n_passed = results_tbl_list.count(True)
|
|
8428
|
+
validation.n_failed = results_tbl_list.count(False)
|
|
8429
|
+
|
|
8430
|
+
results_tbl = None
|
|
8431
|
+
|
|
8432
|
+
else:
|
|
8433
|
+
# If the result is not a list, then we assume it's a table in the conventional
|
|
8434
|
+
# form (where the column is `pb_is_good_` exists, with boolean values)
|
|
8435
|
+
|
|
8436
|
+
results_tbl = results_tbl_list
|
|
8437
|
+
|
|
8438
|
+
# If the results table is not `None`, then we assume there is a table with a column
|
|
8439
|
+
# called `pb_is_good_` that contains boolean values; we can then use this table to
|
|
8440
|
+
# determine the number of test units that passed and failed
|
|
8441
|
+
if results_tbl is not None:
|
|
8069
8442
|
# Extract the `pb_is_good_` column from the table as a results list
|
|
8070
8443
|
if tbl_type in IBIS_BACKENDS:
|
|
8071
8444
|
# Select the DataFrame library to use for getting the results list
|
|
@@ -9967,7 +10340,7 @@ class Validate:
|
|
|
9967
10340
|
# With a column subset list, format with commas between the column names
|
|
9968
10341
|
columns_upd.append(", ".join(column))
|
|
9969
10342
|
|
|
9970
|
-
elif assertion_type[i] in ["conjointly"]:
|
|
10343
|
+
elif assertion_type[i] in ["conjointly", "specially"]:
|
|
9971
10344
|
columns_upd.append("")
|
|
9972
10345
|
else:
|
|
9973
10346
|
columns_upd.append(str(column))
|
|
@@ -10029,7 +10402,7 @@ class Validate:
|
|
|
10029
10402
|
elif assertion_type[i] in ["col_schema_match"]:
|
|
10030
10403
|
values_upd.append("SCHEMA")
|
|
10031
10404
|
|
|
10032
|
-
elif assertion_type[i] in ["col_vals_expr"]:
|
|
10405
|
+
elif assertion_type[i] in ["col_vals_expr", "conjointly"]:
|
|
10033
10406
|
values_upd.append("COLUMN EXPR")
|
|
10034
10407
|
|
|
10035
10408
|
elif assertion_type[i] in ["row_count_match", "col_count_match"]:
|
|
@@ -10041,8 +10414,8 @@ class Validate:
|
|
|
10041
10414
|
|
|
10042
10415
|
values_upd.append(str(count))
|
|
10043
10416
|
|
|
10044
|
-
elif assertion_type[i] in ["
|
|
10045
|
-
values_upd.append("
|
|
10417
|
+
elif assertion_type[i] in ["specially"]:
|
|
10418
|
+
values_upd.append("EXPR")
|
|
10046
10419
|
|
|
10047
10420
|
# If the assertion type is not recognized, add the value as a string
|
|
10048
10421
|
else:
|
|
@@ -11470,6 +11843,9 @@ def _create_autobrief_or_failure_text(
|
|
|
11470
11843
|
if assertion_type == "conjointly":
|
|
11471
11844
|
return _create_text_conjointly(lang=lang, for_failure=for_failure)
|
|
11472
11845
|
|
|
11846
|
+
if assertion_type == "specially":
|
|
11847
|
+
return _create_text_specially(lang=lang, for_failure=for_failure)
|
|
11848
|
+
|
|
11473
11849
|
return None # pragma: no cover
|
|
11474
11850
|
|
|
11475
11851
|
|
|
@@ -11668,6 +12044,12 @@ def _create_text_conjointly(lang: str, for_failure: bool = False) -> str:
|
|
|
11668
12044
|
return EXPECT_FAIL_TEXT[f"conjointly_{type_}_text"][lang]
|
|
11669
12045
|
|
|
11670
12046
|
|
|
12047
|
+
def _create_text_specially(lang: str, for_failure: bool = False) -> str:
|
|
12048
|
+
type_ = _expect_failure_type(for_failure=for_failure)
|
|
12049
|
+
|
|
12050
|
+
return EXPECT_FAIL_TEXT[f"specially_{type_}_text"][lang]
|
|
12051
|
+
|
|
12052
|
+
|
|
11671
12053
|
def _prep_column_text(column: str | list[str]) -> str:
|
|
11672
12054
|
if isinstance(column, list):
|
|
11673
12055
|
return "`" + str(column[0]) + "`"
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
pointblank/__init__.py,sha256=uHrX-ARZOhvWogXXqKV65RO2DXdYLZNCD1oNcm8hE6o,1585
|
|
2
|
-
pointblank/_constants.py,sha256=
|
|
2
|
+
pointblank/_constants.py,sha256=D4HF0NrNAd-mdb88gZ6VatkRYfVX-9gC6C7TOQjjAw4,81128
|
|
3
3
|
pointblank/_constants_docs.py,sha256=JBmtt16zTYQ-zaM4ElLExtKs-dKlnN553Ys2ML1Y1C8,2099
|
|
4
|
-
pointblank/_constants_translations.py,sha256=
|
|
5
|
-
pointblank/_interrogation.py,sha256=
|
|
4
|
+
pointblank/_constants_translations.py,sha256=HXcCYmKoMjoaFv-Ym4UWv3AsIVXik2zDyAy7xvTvv0Y,186710
|
|
5
|
+
pointblank/_interrogation.py,sha256=U4GQ8Ik5rP75BYBkmunBvHKwf3XvLPHcUx18JwiBQZI,89422
|
|
6
6
|
pointblank/_typing.py,sha256=ConITAbsFxU8CkNXY7l0Lua9hGofeDDJAWw-lGAIVgI,764
|
|
7
|
-
pointblank/_utils.py,sha256=
|
|
7
|
+
pointblank/_utils.py,sha256=CsuUYXNzox-Nc5CjQNhyy2XnmnvYJVJrS5cZxklzIFo,24745
|
|
8
8
|
pointblank/_utils_check_args.py,sha256=rFEc1nbCN8ftsQQWVjCNWmQ2QmUDxkfgmoJclrZeTLs,5489
|
|
9
9
|
pointblank/_utils_html.py,sha256=sTcmnBljkPjRZF1hbpoHl4HmnXOazsA91gC9iWVIrRk,2848
|
|
10
|
-
pointblank/actions.py,sha256=
|
|
10
|
+
pointblank/actions.py,sha256=ilk__kbQiS4ieJp-4dM7SDGuobQihUxLyS5ahgiP7qE,18272
|
|
11
11
|
pointblank/assistant.py,sha256=ZIQJKTy9rDwq_Wmr1FMp0J7Q3ekxSgF3_tK0p4PTEUM,14850
|
|
12
12
|
pointblank/column.py,sha256=LumGbnterw5VM7-2-7Za3jdlug1VVS9a3TOH0Y1E5eg,76548
|
|
13
13
|
pointblank/datascan.py,sha256=rRz0hR81uTgd1e9OfLdfsNYXRk8vcpE8PW8exu-GJoE,47697
|
|
14
14
|
pointblank/draft.py,sha256=cusr4fBiNncCKIOU8UwvJcvkBeBuUnqH_UfYp9dtNss,15777
|
|
15
15
|
pointblank/schema.py,sha256=gzUCmtccO2v15MH2bo9uHUYjkKEEne1okQucxcH39pc,44291
|
|
16
16
|
pointblank/tf.py,sha256=8o_8m4i01teulEe3-YYMotSNf3tImjBMInsvdjSAO5Q,8844
|
|
17
|
-
pointblank/thresholds.py,sha256=
|
|
18
|
-
pointblank/validate.py,sha256=
|
|
19
|
-
pointblank/data/api-docs.txt,sha256=
|
|
17
|
+
pointblank/thresholds.py,sha256=cweex25DwBPrsvPW12pRoaTQnwFpUUwqTdHyFJXTnN0,25760
|
|
18
|
+
pointblank/validate.py,sha256=0LWCuex5DeNcoRoq0BppcKn1J-WaqCc3TYyQGWB-a2E,606287
|
|
19
|
+
pointblank/data/api-docs.txt,sha256=jKjPSq6X_vU_RRSJAydnVc3C35WvTqNvu-lLKroVO4I,482044
|
|
20
20
|
pointblank/data/game_revenue-duckdb.zip,sha256=tKIVx48OGLYGsQPS3h5AjA2Nyq_rfEpLCjBiFUWhagU,35880
|
|
21
21
|
pointblank/data/game_revenue.zip,sha256=7c9EvHLyi93CHUd4p3dM4CZ-GucFCtXKSPxgLojL32U,33749
|
|
22
22
|
pointblank/data/nycflights-duckdb.zip,sha256=GQrHO9tp7d9cNGFNSbA9EKF19MLf6t2wZE0U9-hIKow,5293077
|
|
@@ -24,8 +24,8 @@ pointblank/data/nycflights.zip,sha256=yVjbUaKUz2LydSdF9cABuir0VReHBBgV7shiNWSd0m
|
|
|
24
24
|
pointblank/data/polars-api-docs.txt,sha256=KGcS-BOtUs9zgpkWfXD-GFdFh4O_zjdkpX7msHjztLg,198045
|
|
25
25
|
pointblank/data/small_table-duckdb.zip,sha256=BhTaZ2CRS4-9Z1uVhOU6HggvW3XCar7etMznfENIcOc,2028
|
|
26
26
|
pointblank/data/small_table.zip,sha256=lmFb90Nb-v5X559Ikjg31YLAXuRyMkD9yLRElkXPMzQ,472
|
|
27
|
-
pointblank-0.9.
|
|
28
|
-
pointblank-0.9.
|
|
29
|
-
pointblank-0.9.
|
|
30
|
-
pointblank-0.9.
|
|
31
|
-
pointblank-0.9.
|
|
27
|
+
pointblank-0.9.2.dist-info/licenses/LICENSE,sha256=apLF-HWPNU7pT5bmf5KmZpD5Cklpy2u-BN_0xBoRMLY,1081
|
|
28
|
+
pointblank-0.9.2.dist-info/METADATA,sha256=iUvV_QGj9ekzd3ddoPvT-HubBptqM7EIClXJ7HBs8-M,14732
|
|
29
|
+
pointblank-0.9.2.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
|
30
|
+
pointblank-0.9.2.dist-info/top_level.txt,sha256=-wHrS1SvV8-nhvc3w-PPYs1C1WtEc1pK-eGjubbCCKc,11
|
|
31
|
+
pointblank-0.9.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|