pointblank 0.13.1__py3-none-any.whl → 0.13.3__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/__init__.py +0 -2
- pointblank/_constants.py +2 -28
- pointblank/_constants_translations.py +54 -0
- pointblank/_interrogation.py +1483 -1735
- pointblank/column.py +6 -2
- pointblank/datascan.py +3 -2
- pointblank/schema.py +155 -1
- pointblank/validate.py +626 -334
- pointblank/yaml.py +154 -44
- {pointblank-0.13.1.dist-info → pointblank-0.13.3.dist-info}/METADATA +3 -2
- {pointblank-0.13.1.dist-info → pointblank-0.13.3.dist-info}/RECORD +15 -16
- pointblank/tf.py +0 -287
- {pointblank-0.13.1.dist-info → pointblank-0.13.3.dist-info}/WHEEL +0 -0
- {pointblank-0.13.1.dist-info → pointblank-0.13.3.dist-info}/entry_points.txt +0 -0
- {pointblank-0.13.1.dist-info → pointblank-0.13.3.dist-info}/licenses/LICENSE +0 -0
- {pointblank-0.13.1.dist-info → pointblank-0.13.3.dist-info}/top_level.txt +0 -0
pointblank/yaml.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from importlib import import_module
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from typing import Any, Union
|
|
5
|
+
from typing import Any, Iterable, Mapping, Optional, Union
|
|
5
6
|
|
|
6
7
|
import yaml
|
|
7
8
|
from narwhals.typing import FrameT
|
|
@@ -17,7 +18,9 @@ class YAMLValidationError(Exception):
|
|
|
17
18
|
pass
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def _safe_eval_python_code(
|
|
21
|
+
def _safe_eval_python_code(
|
|
22
|
+
code: str, namespaces: Optional[Union[Iterable[str], Mapping[str, str]]] = None
|
|
23
|
+
) -> Any:
|
|
21
24
|
"""Safely evaluate Python code with restricted namespace.
|
|
22
25
|
|
|
23
26
|
This function provides a controlled environment for executing Python code embedded in YAML
|
|
@@ -68,6 +71,7 @@ def _safe_eval_python_code(code: str) -> Any:
|
|
|
68
71
|
"abs": abs,
|
|
69
72
|
"round": round,
|
|
70
73
|
"print": print,
|
|
74
|
+
"__import__": __import__,
|
|
71
75
|
},
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -88,12 +92,25 @@ def _safe_eval_python_code(code: str) -> Any:
|
|
|
88
92
|
|
|
89
93
|
safe_namespace["pd"] = pd
|
|
90
94
|
|
|
91
|
-
|
|
95
|
+
if namespaces:
|
|
96
|
+
for alias, module_name in (
|
|
97
|
+
namespaces.items() if isinstance(namespaces, dict) else ((m, m) for m in namespaces)
|
|
98
|
+
):
|
|
99
|
+
try:
|
|
100
|
+
safe_namespace[alias] = import_module(module_name)
|
|
101
|
+
except ImportError as e:
|
|
102
|
+
raise ImportError(
|
|
103
|
+
f"Could not import requested namespace '{module_name}': {e}"
|
|
104
|
+
) from e
|
|
105
|
+
|
|
106
|
+
# Check for dangerous patterns and be more specific about __import__ to allow legitimate use
|
|
92
107
|
dangerous_patterns = [
|
|
93
|
-
r"import\s+os",
|
|
94
|
-
r"import\s+sys",
|
|
95
|
-
r"import\s+subprocess",
|
|
96
|
-
r"__import__",
|
|
108
|
+
r"import\s+os\b",
|
|
109
|
+
r"import\s+sys\b",
|
|
110
|
+
r"import\s+subprocess\b",
|
|
111
|
+
r"__import__\s*\(\s*['\"]os['\"]",
|
|
112
|
+
r"__import__\s*\(\s*['\"]sys['\"]",
|
|
113
|
+
r"__import__\s*\(\s*['\"]subprocess['\"]",
|
|
97
114
|
r"exec\s*\(",
|
|
98
115
|
r"eval\s*\(",
|
|
99
116
|
r"open\s*\(",
|
|
@@ -142,7 +159,9 @@ def _safe_eval_python_code(code: str) -> Any:
|
|
|
142
159
|
raise YAMLValidationError(f"Error executing Python code '{code}': {e}")
|
|
143
160
|
|
|
144
161
|
|
|
145
|
-
def _process_python_expressions(
|
|
162
|
+
def _process_python_expressions(
|
|
163
|
+
value: Any, namespaces: Optional[Union[Iterable[str], Mapping[str, str]]] = None
|
|
164
|
+
) -> Any:
|
|
146
165
|
"""Process Python code snippets embedded in YAML values.
|
|
147
166
|
|
|
148
167
|
This function supports the python: block syntax for embedding Python code:
|
|
@@ -152,7 +171,7 @@ def _process_python_expressions(value: Any) -> Any:
|
|
|
152
171
|
pl.scan_csv("data.csv").head(10)
|
|
153
172
|
|
|
154
173
|
Note: col_vals_expr() also supports a shortcut syntax where the expr parameter
|
|
155
|
-
can be written directly without the python: wrapper:
|
|
174
|
+
can be written directly without the python: wrapper: +
|
|
156
175
|
|
|
157
176
|
col_vals_expr:
|
|
158
177
|
expr: |
|
|
@@ -180,14 +199,14 @@ def _process_python_expressions(value: Any) -> Any:
|
|
|
180
199
|
# Handle python: block syntax
|
|
181
200
|
if "python" in value and len(value) == 1:
|
|
182
201
|
code = value["python"]
|
|
183
|
-
return _safe_eval_python_code(code)
|
|
202
|
+
return _safe_eval_python_code(code, namespaces=namespaces)
|
|
184
203
|
|
|
185
204
|
# Recursively process dictionary values
|
|
186
|
-
return {k: _process_python_expressions(v) for k, v in value.items()}
|
|
205
|
+
return {k: _process_python_expressions(v, namespaces=namespaces) for k, v in value.items()}
|
|
187
206
|
|
|
188
207
|
elif isinstance(value, list):
|
|
189
208
|
# Recursively process list items
|
|
190
|
-
return [_process_python_expressions(item) for item in value]
|
|
209
|
+
return [_process_python_expressions(item, namespaces=namespaces) for item in value]
|
|
191
210
|
|
|
192
211
|
else:
|
|
193
212
|
# Return primitive types unchanged
|
|
@@ -302,7 +321,7 @@ class YAMLValidator:
|
|
|
302
321
|
raise YAMLValidationError("YAML must contain 'steps' field")
|
|
303
322
|
|
|
304
323
|
if not isinstance(config["steps"], list):
|
|
305
|
-
raise YAMLValidationError("'steps' must be a list")
|
|
324
|
+
raise YAMLValidationError("'steps' must be a list") # pragma: no cover
|
|
306
325
|
|
|
307
326
|
if len(config["steps"]) == 0:
|
|
308
327
|
raise YAMLValidationError("'steps' cannot be empty")
|
|
@@ -393,9 +412,9 @@ class YAMLValidator:
|
|
|
393
412
|
if processed_data is processed_tbl_spec and isinstance(processed_tbl_spec, str):
|
|
394
413
|
return load_dataset(processed_tbl_spec, tbl_type=df_library)
|
|
395
414
|
else:
|
|
396
|
-
return processed_data
|
|
415
|
+
return processed_data # pragma: no cover
|
|
397
416
|
|
|
398
|
-
except Exception as e:
|
|
417
|
+
except Exception as e: # pragma: no cover
|
|
399
418
|
raise YAMLValidationError(f"Failed to load data source '{tbl_spec}': {e}")
|
|
400
419
|
|
|
401
420
|
def _load_csv_file(self, file_path: str, df_library: str) -> Any:
|
|
@@ -439,16 +458,16 @@ class YAMLValidator:
|
|
|
439
458
|
|
|
440
459
|
elif df_library == "duckdb":
|
|
441
460
|
# For DuckDB, we'll use the existing _process_data since it handles DuckDB
|
|
442
|
-
from pointblank.validate import _process_data
|
|
461
|
+
from pointblank.validate import _process_data # pragma: no cover
|
|
443
462
|
|
|
444
|
-
return _process_data(file_path)
|
|
463
|
+
return _process_data(file_path) # pragma: no cover
|
|
445
464
|
|
|
446
465
|
else:
|
|
447
466
|
raise YAMLValidationError(
|
|
448
467
|
f"Unsupported df_library: {df_library}. Use 'polars', 'pandas', or 'duckdb'"
|
|
449
468
|
)
|
|
450
469
|
|
|
451
|
-
except Exception as e:
|
|
470
|
+
except Exception as e: # pragma: no cover
|
|
452
471
|
raise YAMLValidationError(
|
|
453
472
|
f"Failed to load CSV file '{file_path}' with {df_library}: {e}"
|
|
454
473
|
)
|
|
@@ -547,7 +566,11 @@ class YAMLValidator:
|
|
|
547
566
|
f"Schema specification must be a dictionary, got: {type(schema_spec)}"
|
|
548
567
|
)
|
|
549
568
|
|
|
550
|
-
def _parse_validation_step(
|
|
569
|
+
def _parse_validation_step(
|
|
570
|
+
self,
|
|
571
|
+
step_config: Union[str, dict],
|
|
572
|
+
namespaces: Optional[Union[Iterable[str], Mapping[str, str]]] = None,
|
|
573
|
+
) -> tuple[str, dict]:
|
|
551
574
|
"""Parse a single validation step from YAML configuration.
|
|
552
575
|
|
|
553
576
|
Parameters
|
|
@@ -598,14 +621,16 @@ class YAMLValidator:
|
|
|
598
621
|
# Special case: `col_vals_expr()`'s `expr=` parameter can use shortcut syntax
|
|
599
622
|
if method_name == "col_vals_expr" and key == "expr" and isinstance(value, str):
|
|
600
623
|
# Treat string directly as Python code (shortcut syntax)
|
|
601
|
-
processed_parameters[key] = _safe_eval_python_code(value)
|
|
624
|
+
processed_parameters[key] = _safe_eval_python_code(value, namespaces=namespaces)
|
|
602
625
|
# Special case: `pre=` parameter can use shortcut syntax (like `expr=`)
|
|
603
626
|
elif key == "pre" and isinstance(value, str):
|
|
604
627
|
# Treat string directly as Python code (shortcut syntax)
|
|
605
|
-
processed_parameters[key] = _safe_eval_python_code(value)
|
|
628
|
+
processed_parameters[key] = _safe_eval_python_code(value, namespaces=namespaces)
|
|
606
629
|
else:
|
|
607
630
|
# Normal processing (requires python: block syntax)
|
|
608
|
-
processed_parameters[key] = _process_python_expressions(
|
|
631
|
+
processed_parameters[key] = _process_python_expressions(
|
|
632
|
+
value, namespaces=namespaces
|
|
633
|
+
)
|
|
609
634
|
parameters = processed_parameters
|
|
610
635
|
|
|
611
636
|
# Convert `columns=` specification
|
|
@@ -634,7 +659,7 @@ class YAMLValidator:
|
|
|
634
659
|
if isinstance(expr, str):
|
|
635
660
|
lambda_expressions.append(_safe_eval_python_code(expr))
|
|
636
661
|
else:
|
|
637
|
-
lambda_expressions.append(expr)
|
|
662
|
+
lambda_expressions.append(expr) # pragma: no cover
|
|
638
663
|
# Pass expressions as positional arguments (stored as special key)
|
|
639
664
|
parameters["_conjointly_expressions"] = lambda_expressions
|
|
640
665
|
else:
|
|
@@ -658,7 +683,9 @@ class YAMLValidator:
|
|
|
658
683
|
|
|
659
684
|
return self.validation_method_map[method_name], parameters
|
|
660
685
|
|
|
661
|
-
def build_validation(
|
|
686
|
+
def build_validation(
|
|
687
|
+
self, config: dict, namespaces: Optional[Union[Iterable[str], Mapping[str, str]]] = None
|
|
688
|
+
) -> Validate:
|
|
662
689
|
"""Convert YAML config to Validate object.
|
|
663
690
|
|
|
664
691
|
Parameters
|
|
@@ -693,7 +720,9 @@ class YAMLValidator:
|
|
|
693
720
|
# Set actions if provided
|
|
694
721
|
if "actions" in config:
|
|
695
722
|
# Process actions: handle `python:` block syntax for callables
|
|
696
|
-
processed_actions = _process_python_expressions(
|
|
723
|
+
processed_actions = _process_python_expressions(
|
|
724
|
+
config["actions"], namespaces=namespaces
|
|
725
|
+
)
|
|
697
726
|
# Convert to Actions object
|
|
698
727
|
validate_kwargs["actions"] = Actions(**processed_actions)
|
|
699
728
|
|
|
@@ -713,7 +742,9 @@ class YAMLValidator:
|
|
|
713
742
|
|
|
714
743
|
# Add validation steps
|
|
715
744
|
for step_config in config["steps"]:
|
|
716
|
-
method_name, parameters = self._parse_validation_step(
|
|
745
|
+
method_name, parameters = self._parse_validation_step(
|
|
746
|
+
step_config, namespaces=namespaces
|
|
747
|
+
)
|
|
717
748
|
|
|
718
749
|
# Get the method from the validation object
|
|
719
750
|
method = getattr(validation, method_name)
|
|
@@ -728,7 +759,9 @@ class YAMLValidator:
|
|
|
728
759
|
|
|
729
760
|
return validation
|
|
730
761
|
|
|
731
|
-
def execute_workflow(
|
|
762
|
+
def execute_workflow(
|
|
763
|
+
self, config: dict, namespaces: Optional[Union[Iterable[str], Mapping[str, str]]] = None
|
|
764
|
+
) -> Validate:
|
|
732
765
|
"""Execute a complete YAML validation workflow.
|
|
733
766
|
|
|
734
767
|
Parameters
|
|
@@ -742,7 +775,7 @@ class YAMLValidator:
|
|
|
742
775
|
Interrogated Validate object with results.
|
|
743
776
|
"""
|
|
744
777
|
# Build the validation plan
|
|
745
|
-
validation = self.build_validation(config)
|
|
778
|
+
validation = self.build_validation(config, namespaces=namespaces)
|
|
746
779
|
|
|
747
780
|
# Execute interrogation to get results
|
|
748
781
|
validation = validation.interrogate()
|
|
@@ -750,7 +783,11 @@ class YAMLValidator:
|
|
|
750
783
|
return validation
|
|
751
784
|
|
|
752
785
|
|
|
753
|
-
def yaml_interrogate(
|
|
786
|
+
def yaml_interrogate(
|
|
787
|
+
yaml: Union[str, Path],
|
|
788
|
+
set_tbl: Union[FrameT, Any, None] = None,
|
|
789
|
+
namespaces: Optional[Union[Iterable[str], Mapping[str, str]]] = None,
|
|
790
|
+
) -> Validate:
|
|
754
791
|
"""Execute a YAML-based validation workflow.
|
|
755
792
|
|
|
756
793
|
This is the main entry point for YAML-based validation workflows. It takes YAML configuration
|
|
@@ -772,6 +809,10 @@ def yaml_interrogate(yaml: Union[str, Path], set_tbl: Union[FrameT, Any, None] =
|
|
|
772
809
|
`tbl` field before executing the validation workflow. This can be any supported table type
|
|
773
810
|
including DataFrame objects, Ibis table objects, CSV file paths, Parquet file paths, GitHub
|
|
774
811
|
URLs, or database connection strings.
|
|
812
|
+
namespaces
|
|
813
|
+
Optional module namespaces to make available for Python code execution in YAML
|
|
814
|
+
configurations. Can be a dictionary mapping aliases to module names or a list of module
|
|
815
|
+
names. See the "Using Namespaces" section below for detailed examples.
|
|
775
816
|
|
|
776
817
|
Returns
|
|
777
818
|
-------
|
|
@@ -786,6 +827,71 @@ def yaml_interrogate(yaml: Union[str, Path], set_tbl: Union[FrameT, Any, None] =
|
|
|
786
827
|
If the YAML is invalid, malformed, or execution fails. This includes syntax errors, missing
|
|
787
828
|
required fields, unknown validation methods, or data loading failures.
|
|
788
829
|
|
|
830
|
+
Using Namespaces
|
|
831
|
+
----------------
|
|
832
|
+
The `namespaces=` parameter enables custom Python modules and functions in YAML configurations.
|
|
833
|
+
This is particularly useful for custom action functions and advanced Python expressions.
|
|
834
|
+
|
|
835
|
+
**Namespace formats:**
|
|
836
|
+
|
|
837
|
+
- Dictionary format: `{"alias": "module.name"}` maps aliases to module names
|
|
838
|
+
- List format: `["module.name", "another.module"]` imports modules directly
|
|
839
|
+
|
|
840
|
+
**Option 1: Inline expressions (no namespaces needed)**
|
|
841
|
+
|
|
842
|
+
```{python}
|
|
843
|
+
import pointblank as pb
|
|
844
|
+
|
|
845
|
+
# Simple inline custom action
|
|
846
|
+
yaml_config = '''
|
|
847
|
+
tbl: small_table
|
|
848
|
+
thresholds:
|
|
849
|
+
warning: 0.01
|
|
850
|
+
actions:
|
|
851
|
+
warning:
|
|
852
|
+
python: "lambda: print('Custom warning triggered')"
|
|
853
|
+
steps:
|
|
854
|
+
- col_vals_gt:
|
|
855
|
+
columns: [a]
|
|
856
|
+
value: 1000
|
|
857
|
+
'''
|
|
858
|
+
|
|
859
|
+
result = pb.yaml_interrogate(yaml_config)
|
|
860
|
+
result
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
**Option 2: External functions with namespaces**
|
|
864
|
+
|
|
865
|
+
```{python}
|
|
866
|
+
# Define a custom action function
|
|
867
|
+
def my_custom_action():
|
|
868
|
+
print("Data validation failed: please check your data.")
|
|
869
|
+
|
|
870
|
+
# Add to current module for demo
|
|
871
|
+
import sys
|
|
872
|
+
sys.modules[__name__].my_custom_action = my_custom_action
|
|
873
|
+
|
|
874
|
+
# YAML that references the external function
|
|
875
|
+
yaml_config = '''
|
|
876
|
+
tbl: small_table
|
|
877
|
+
thresholds:
|
|
878
|
+
warning: 0.01
|
|
879
|
+
actions:
|
|
880
|
+
warning:
|
|
881
|
+
python: actions.my_custom_action
|
|
882
|
+
steps:
|
|
883
|
+
- col_vals_gt:
|
|
884
|
+
columns: [a]
|
|
885
|
+
value: 1000 # This will fail
|
|
886
|
+
'''
|
|
887
|
+
|
|
888
|
+
# Use namespaces to make the function available
|
|
889
|
+
result = pb.yaml_interrogate(yaml_config, namespaces={'actions': '__main__'})
|
|
890
|
+
result
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
This approach enables modular, reusable validation workflows with custom business logic.
|
|
894
|
+
|
|
789
895
|
Examples
|
|
790
896
|
--------
|
|
791
897
|
```{python}
|
|
@@ -928,14 +1034,14 @@ def yaml_interrogate(yaml: Union[str, Path], set_tbl: Union[FrameT, Any, None] =
|
|
|
928
1034
|
# If `set_tbl=` is provided, we need to build the validation workflow and then use `set_tbl()`
|
|
929
1035
|
if set_tbl is not None:
|
|
930
1036
|
# First build the validation object without interrogation
|
|
931
|
-
validation = validator.build_validation(config)
|
|
1037
|
+
validation = validator.build_validation(config, namespaces=namespaces)
|
|
932
1038
|
# Then replace the table using set_tbl method
|
|
933
1039
|
validation = validation.set_tbl(tbl=set_tbl)
|
|
934
1040
|
# Finally interrogate with the new table
|
|
935
1041
|
return validation.interrogate()
|
|
936
1042
|
else:
|
|
937
1043
|
# Standard execution without table override (includes interrogation)
|
|
938
|
-
return validator.execute_workflow(config)
|
|
1044
|
+
return validator.execute_workflow(config, namespaces=namespaces)
|
|
939
1045
|
|
|
940
1046
|
|
|
941
1047
|
def load_yaml_config(file_path: Union[str, Path]) -> dict:
|
|
@@ -1223,7 +1329,7 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1223
1329
|
"""
|
|
1224
1330
|
# First, parse the raw YAML to detect Polars/Pandas expressions in the source code
|
|
1225
1331
|
if isinstance(yaml, Path):
|
|
1226
|
-
yaml_content = yaml.read_text()
|
|
1332
|
+
yaml_content = yaml.read_text() # pragma: no cover
|
|
1227
1333
|
elif isinstance(yaml, str):
|
|
1228
1334
|
# Check if it's a file path (single line, reasonable length, no newlines)
|
|
1229
1335
|
if len(yaml) < 260 and "\n" not in yaml and Path(yaml).exists():
|
|
@@ -1231,7 +1337,7 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1231
1337
|
else:
|
|
1232
1338
|
yaml_content = yaml
|
|
1233
1339
|
else:
|
|
1234
|
-
yaml_content = str(yaml)
|
|
1340
|
+
yaml_content = str(yaml) # pragma: no cover
|
|
1235
1341
|
|
|
1236
1342
|
# Track whether we need to import Polars and Pandas by analyzing the raw YAML content
|
|
1237
1343
|
needs_polars_import = False
|
|
@@ -1326,7 +1432,7 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1326
1432
|
validate_args.append(f'data=pb.load_dataset("{tbl_spec}", tbl_type="{df_library}")')
|
|
1327
1433
|
else:
|
|
1328
1434
|
# Fallback to placeholder if we couldn't extract the original expression
|
|
1329
|
-
validate_args.append("data=<python_expression_result>")
|
|
1435
|
+
validate_args.append("data=<python_expression_result>") # pragma: no cover
|
|
1330
1436
|
|
|
1331
1437
|
# Add table name if present
|
|
1332
1438
|
if "tbl_name" in config:
|
|
@@ -1359,7 +1465,7 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1359
1465
|
action_params.append(f'{key}="{value}"')
|
|
1360
1466
|
else:
|
|
1361
1467
|
# For callables or complex expressions, use placeholder
|
|
1362
|
-
action_params.append(f"{key}={value}")
|
|
1468
|
+
action_params.append(f"{key}={value}") # pragma: no cover
|
|
1363
1469
|
actions_str = "pb.Actions(" + ", ".join(action_params) + ")"
|
|
1364
1470
|
validate_args.append(f"actions={actions_str}")
|
|
1365
1471
|
|
|
@@ -1414,7 +1520,7 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1414
1520
|
elif isinstance(step_params["expr"], str):
|
|
1415
1521
|
original_expressions["expr"] = step_params["expr"]
|
|
1416
1522
|
|
|
1417
|
-
method_name, parameters = validator._parse_validation_step(step_config)
|
|
1523
|
+
method_name, parameters = validator._parse_validation_step(step_config, namespaces=None)
|
|
1418
1524
|
|
|
1419
1525
|
# Apply the original expressions to override the converted lambda functions
|
|
1420
1526
|
if method_name == "conjointly" and "expressions" in original_expressions:
|
|
@@ -1446,13 +1552,13 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1446
1552
|
expressions_str = "[" + ", ".join([f'"{expr}"' for expr in value]) + "]"
|
|
1447
1553
|
param_parts.append(f"expressions={expressions_str}")
|
|
1448
1554
|
else:
|
|
1449
|
-
param_parts.append(f"expressions={value}")
|
|
1555
|
+
param_parts.append(f"expressions={value}") # pragma: no cover
|
|
1450
1556
|
elif key == "expr" and method_name == "specially":
|
|
1451
1557
|
# Handle specially expr parameter: should be unquoted lambda expression
|
|
1452
1558
|
if isinstance(value, str):
|
|
1453
1559
|
param_parts.append(f"expr={value}")
|
|
1454
1560
|
else:
|
|
1455
|
-
param_parts.append(f"expr={value}")
|
|
1561
|
+
param_parts.append(f"expr={value}") # pragma: no cover
|
|
1456
1562
|
elif key in ["columns", "columns_subset"]:
|
|
1457
1563
|
if isinstance(value, list):
|
|
1458
1564
|
if len(value) == 1:
|
|
@@ -1463,7 +1569,7 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1463
1569
|
columns_str = "[" + ", ".join([f'"{col}"' for col in value]) + "]"
|
|
1464
1570
|
param_parts.append(f"{key}={columns_str}")
|
|
1465
1571
|
else:
|
|
1466
|
-
param_parts.append(f'{key}="{value}"')
|
|
1572
|
+
param_parts.append(f'{key}="{value}"') # pragma: no cover
|
|
1467
1573
|
elif key == "brief":
|
|
1468
1574
|
# Handle `brief=` parameter: can be a boolean or a string
|
|
1469
1575
|
if isinstance(value, bool):
|
|
@@ -1486,25 +1592,29 @@ def yaml_to_python(yaml: Union[str, Path]) -> str:
|
|
|
1486
1592
|
elif isinstance(value.warning, list) and len(value.warning) == 1:
|
|
1487
1593
|
action_params.append(f'warning="{value.warning[0]}"')
|
|
1488
1594
|
else:
|
|
1489
|
-
action_params.append(f"warning={value.warning}")
|
|
1595
|
+
action_params.append(f"warning={value.warning}") # pragma: no cover
|
|
1490
1596
|
|
|
1491
1597
|
if value.error is not None:
|
|
1492
1598
|
error_expr_path = f"{step_action_base}.error"
|
|
1493
1599
|
if error_expr_path in step_expressions:
|
|
1494
|
-
action_params.append(
|
|
1600
|
+
action_params.append(
|
|
1601
|
+
f"error={step_expressions[error_expr_path]}"
|
|
1602
|
+
) # pragma: no cover
|
|
1495
1603
|
elif isinstance(value.error, list) and len(value.error) == 1:
|
|
1496
1604
|
action_params.append(f'error="{value.error[0]}"')
|
|
1497
1605
|
else:
|
|
1498
|
-
action_params.append(f"error={value.error}")
|
|
1606
|
+
action_params.append(f"error={value.error}") # pragma: no cover
|
|
1499
1607
|
|
|
1500
1608
|
if value.critical is not None:
|
|
1501
1609
|
critical_expr_path = f"{step_action_base}.critical"
|
|
1502
1610
|
if critical_expr_path in step_expressions:
|
|
1503
|
-
action_params.append(
|
|
1611
|
+
action_params.append(
|
|
1612
|
+
f"critical={step_expressions[critical_expr_path]}"
|
|
1613
|
+
) # pragma: no cover
|
|
1504
1614
|
elif isinstance(value.critical, list) and len(value.critical) == 1:
|
|
1505
1615
|
action_params.append(f'critical="{value.critical[0]}"')
|
|
1506
1616
|
else:
|
|
1507
|
-
action_params.append(f"critical={value.critical}")
|
|
1617
|
+
action_params.append(f"critical={value.critical}") # pragma: no cover
|
|
1508
1618
|
|
|
1509
1619
|
if hasattr(value, "highest_only") and value.highest_only is not True:
|
|
1510
1620
|
action_params.append(f"highest_only={value.highest_only}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pointblank
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.3
|
|
4
4
|
Summary: Find out if your data is what you think it is.
|
|
5
5
|
Author-email: Richard Iannone <riannone@me.com>
|
|
6
6
|
License: MIT License
|
|
@@ -52,7 +52,7 @@ Requires-Dist: pyyaml>=6.0.0
|
|
|
52
52
|
Provides-Extra: pd
|
|
53
53
|
Requires-Dist: pandas>=2.2.3; extra == "pd"
|
|
54
54
|
Provides-Extra: pl
|
|
55
|
-
Requires-Dist: polars>=1.
|
|
55
|
+
Requires-Dist: polars>=1.33.0; extra == "pl"
|
|
56
56
|
Provides-Extra: pyspark
|
|
57
57
|
Requires-Dist: pyspark==3.5.6; extra == "pyspark"
|
|
58
58
|
Provides-Extra: generate
|
|
@@ -91,6 +91,7 @@ Requires-Dist: pandas>=2.2.3; extra == "docs"
|
|
|
91
91
|
Requires-Dist: polars>=1.17.1; extra == "docs"
|
|
92
92
|
Requires-Dist: pyspark==3.5.6; extra == "docs"
|
|
93
93
|
Requires-Dist: openpyxl>=3.0.0; extra == "docs"
|
|
94
|
+
Requires-Dist: duckdb<1.3.3,>=1.2.0; extra == "docs"
|
|
94
95
|
Dynamic: license-file
|
|
95
96
|
|
|
96
97
|
<div align="center">
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
pointblank/__init__.py,sha256=
|
|
2
|
-
pointblank/_constants.py,sha256=
|
|
1
|
+
pointblank/__init__.py,sha256=IZ-JwGWr2DOFBfq4XvE0SzceWMkFewtBN10V4F9pIGg,1876
|
|
2
|
+
pointblank/_constants.py,sha256=HHiGuso_To-PsKQLxVDYa_Iczy92iPoIYurTy6BAtL4,80778
|
|
3
3
|
pointblank/_constants_docs.py,sha256=JBmtt16zTYQ-zaM4ElLExtKs-dKlnN553Ys2ML1Y1C8,2099
|
|
4
|
-
pointblank/_constants_translations.py,sha256=
|
|
4
|
+
pointblank/_constants_translations.py,sha256=NBVGX9veULXMwEHwSrlzpCpM9hcFeyS1xBmyBt_8_mU,190053
|
|
5
5
|
pointblank/_datascan_utils.py,sha256=EMfeabXm_ZsCUKPROB7rFhyOpjtRs8jcnZ_9nBtMyws,1750
|
|
6
|
-
pointblank/_interrogation.py,sha256=
|
|
6
|
+
pointblank/_interrogation.py,sha256=FbzFa_QJIYQjH0l4D6XaMW73r71gwMrfA8lXiF4r2a4,76540
|
|
7
7
|
pointblank/_typing.py,sha256=aItbCbzhbzqjK3lCbL27ltRyXoAH1c3-U6xQdRzg-lU,1594
|
|
8
8
|
pointblank/_utils.py,sha256=ikgkFomoAEOxaiItHZUo3NTHu0MJHWfKAF_fnX9rRnA,30685
|
|
9
9
|
pointblank/_utils_check_args.py,sha256=rFEc1nbCN8ftsQQWVjCNWmQ2QmUDxkfgmoJclrZeTLs,5489
|
|
@@ -11,18 +11,17 @@ pointblank/_utils_html.py,sha256=uJWvS9JwQVEZgwsGmScA_u_EBRND75rzUvnJPalbRVs,373
|
|
|
11
11
|
pointblank/actions.py,sha256=D6o9B2_ES9PNQg9HZwREacrrt-3A5bhdrBkL1UXz__s,18281
|
|
12
12
|
pointblank/assistant.py,sha256=vcdFgXmdVSiylQgTW3e62c13X3vAicBPjyG8OTZXmGM,15902
|
|
13
13
|
pointblank/cli.py,sha256=p2cO-NKInQ41hqRMy6TNmxOj48pR5vq8s7JTLXfcP4M,228188
|
|
14
|
-
pointblank/column.py,sha256=
|
|
14
|
+
pointblank/column.py,sha256=w41CBg3lt3ZzKS_j-SYdkRmnOWcP5xXa0rRjsPVRE_M,76781
|
|
15
15
|
pointblank/compare.py,sha256=kFd18CehHz7g-2MF1kSmJSdOoAP80q_9PaF6QzHC1ds,866
|
|
16
|
-
pointblank/datascan.py,sha256=
|
|
16
|
+
pointblank/datascan.py,sha256=tQiwe9x0jAbK9OJLQe53R_qRl2uBKireaWsMlM8K1sw,24630
|
|
17
17
|
pointblank/draft.py,sha256=cusr4fBiNncCKIOU8UwvJcvkBeBuUnqH_UfYp9dtNss,15777
|
|
18
18
|
pointblank/scan_profile.py,sha256=lZU5hlnzznDATNn9W3gNdyuFm05WDP8y1RjDJEcE5zg,10426
|
|
19
19
|
pointblank/scan_profile_stats.py,sha256=qdzoGXB-zi2hmpA4mTz6LLTqMnb-NRG9ndxU9cxS72w,4461
|
|
20
|
-
pointblank/schema.py,sha256=
|
|
20
|
+
pointblank/schema.py,sha256=hjALMuYppNfELC_nAqfM9fLjPdN1w2M3rDMusrPqFYA,50757
|
|
21
21
|
pointblank/segments.py,sha256=RXp3lPr3FboVseadNqLgIeoMBh_mykrQSFp1WtV41Yg,5570
|
|
22
|
-
pointblank/tf.py,sha256=8o_8m4i01teulEe3-YYMotSNf3tImjBMInsvdjSAO5Q,8844
|
|
23
22
|
pointblank/thresholds.py,sha256=mybeLzTVdmN04NLKoV-jiSBXsWknwHO0Gox0ttVN_MU,25766
|
|
24
|
-
pointblank/validate.py,sha256=
|
|
25
|
-
pointblank/yaml.py,sha256=
|
|
23
|
+
pointblank/validate.py,sha256=v4jzFOYufrck_3CPIz4Jo53Y_5VYYTTFcqMq6B4LttY,713196
|
|
24
|
+
pointblank/yaml.py,sha256=cHwDvybhp_oLOGR1rA83trEDQWYuRGhT4iEa6FMXi6w,63074
|
|
26
25
|
pointblank/data/api-docs.txt,sha256=w2nIkIL_fJpXlPR9clogqcgdiv-uHvdSDI8gjkP_mCQ,531711
|
|
27
26
|
pointblank/data/game_revenue-duckdb.zip,sha256=tKIVx48OGLYGsQPS3h5AjA2Nyq_rfEpLCjBiFUWhagU,35880
|
|
28
27
|
pointblank/data/game_revenue.zip,sha256=7c9EvHLyi93CHUd4p3dM4CZ-GucFCtXKSPxgLojL32U,33749
|
|
@@ -33,9 +32,9 @@ pointblank/data/nycflights.zip,sha256=yVjbUaKUz2LydSdF9cABuir0VReHBBgV7shiNWSd0m
|
|
|
33
32
|
pointblank/data/polars-api-docs.txt,sha256=KGcS-BOtUs9zgpkWfXD-GFdFh4O_zjdkpX7msHjztLg,198045
|
|
34
33
|
pointblank/data/small_table-duckdb.zip,sha256=BhTaZ2CRS4-9Z1uVhOU6HggvW3XCar7etMznfENIcOc,2028
|
|
35
34
|
pointblank/data/small_table.zip,sha256=lmFb90Nb-v5X559Ikjg31YLAXuRyMkD9yLRElkXPMzQ,472
|
|
36
|
-
pointblank-0.13.
|
|
37
|
-
pointblank-0.13.
|
|
38
|
-
pointblank-0.13.
|
|
39
|
-
pointblank-0.13.
|
|
40
|
-
pointblank-0.13.
|
|
41
|
-
pointblank-0.13.
|
|
35
|
+
pointblank-0.13.3.dist-info/licenses/LICENSE,sha256=apLF-HWPNU7pT5bmf5KmZpD5Cklpy2u-BN_0xBoRMLY,1081
|
|
36
|
+
pointblank-0.13.3.dist-info/METADATA,sha256=jXGDWi-DW5kAdRyUTjgVfRTB-6tMgyYd-uqeeyCvvKk,19582
|
|
37
|
+
pointblank-0.13.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
pointblank-0.13.3.dist-info/entry_points.txt,sha256=GqqqOTOH8uZe22wLcvYjzpizqk_j4MNcUo2YM14ryCw,42
|
|
39
|
+
pointblank-0.13.3.dist-info/top_level.txt,sha256=-wHrS1SvV8-nhvc3w-PPYs1C1WtEc1pK-eGjubbCCKc,11
|
|
40
|
+
pointblank-0.13.3.dist-info/RECORD,,
|