data-sitter 0.1.3__tar.gz → 0.1.4__tar.gz

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.
Files changed (35) hide show
  1. {data_sitter-0.1.3 → data_sitter-0.1.4}/PKG-INFO +2 -1
  2. {data_sitter-0.1.3 → data_sitter-0.1.4}/README.md +26 -21
  3. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/Contract.py +32 -1
  4. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/FieldResolver.py +1 -3
  5. data_sitter-0.1.4/data_sitter/Validation.py +39 -0
  6. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/field_types/BaseField.py +5 -4
  7. data_sitter-0.1.4/data_sitter/field_types/FloatField.py +17 -0
  8. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/field_types/NumericField.py +22 -18
  9. data_sitter-0.1.4/data_sitter/field_types/StringField.py +122 -0
  10. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/Rule.py +19 -2
  11. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/RuleRegistry.py +4 -4
  12. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter.egg-info/PKG-INFO +2 -1
  13. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter.egg-info/requires.txt +1 -0
  14. {data_sitter-0.1.3 → data_sitter-0.1.4}/pyproject.toml +2 -1
  15. {data_sitter-0.1.3 → data_sitter-0.1.4}/setup.py +2 -1
  16. data_sitter-0.1.3/data_sitter/Validation.py +0 -30
  17. data_sitter-0.1.3/data_sitter/field_types/FloatField.py +0 -7
  18. data_sitter-0.1.3/data_sitter/field_types/StringField.py +0 -89
  19. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/__init__.py +0 -0
  20. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/cli.py +0 -0
  21. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/field_types/IntegerField.py +0 -0
  22. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/field_types/__init__.py +0 -0
  23. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/MatchedRule.py +0 -0
  24. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/Parser/RuleParser.py +0 -0
  25. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/Parser/__init__.py +0 -0
  26. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/Parser/alias_parameters_parser.py +0 -0
  27. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/Parser/parser_utils.py +0 -0
  28. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/rules/__init__.py +0 -0
  29. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/utils/__init__.py +0 -0
  30. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter/utils/logger_config.py +0 -0
  31. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter.egg-info/SOURCES.txt +0 -0
  32. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter.egg-info/dependency_links.txt +0 -0
  33. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter.egg-info/entry_points.txt +0 -0
  34. {data_sitter-0.1.3 → data_sitter-0.1.4}/data_sitter.egg-info/top_level.txt +0 -0
  35. {data_sitter-0.1.3 → data_sitter-0.1.4}/setup.cfg +0 -0
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: data-sitter
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A Python library that reads data contracts and generates Pydantic models for seamless data validation.
5
5
  Author-email: Lázaro Pereira Candea <lazaro@candea.es>
6
6
  Requires-Dist: python-dotenv==1.0.1
7
+ Requires-Dist: PyYAML==6.0.2
7
8
  Requires-Dist: parse_type==0.6.4
8
9
  Requires-Dist: pydantic==2.10.5
@@ -13,10 +13,8 @@ Data-Sitter is a Python library designed to simplify data validation by converti
13
13
 
14
14
  ## Installation
15
15
 
16
- You can install Data-Sitter directly from GitHub:
17
-
18
16
  ```sh
19
- pip install git+https://github.com/Kenr0t/data-sitter.git@main
17
+ pip install data-sitter
20
18
  ```
21
19
 
22
20
  ## Usage
@@ -94,39 +92,46 @@ Below are the available rules grouped by field type:
94
92
 
95
93
  #### BaseField
96
94
 
97
- - Validate Not Null
95
+ - Is not null
98
96
 
99
97
  #### StringField - (Inherits from `BaseField`)
100
98
 
101
99
  - Is not empty
102
- - Starts with `{prefix:String}`
103
- - Ends with `{sufix:String}`
104
- - Value in `{possible_values:Strings}`
105
- - Length between `{min_val:Integer}` and `{max_val:Integer}`
106
- - Maximum length of `{max_len:Integer}`
107
- - Length shorter than `{max_len:Integer}`
108
- - Minimum length of `{min_len:Integer}`
109
- - Length longer than `{min_len:Integer}`
100
+ - Starts with {prefix:String}
101
+ - Ends with {suffix:String}
102
+ - Is not one of {possible_values:Strings}
103
+ - Is one of {possible_values:Strings}
104
+ - Has length between {min_val:Integer} and {max_val:Integer}
105
+ - Has maximum length {max_len:Integer}
106
+ - Has minimum length {min_len:Integer}
110
107
  - Is uppercase
108
+ - Is lowercase
109
+ - Matches regex {pattern:String}
110
+ - Is valid email
111
+ - Is valid URL
112
+ - Has no digits
111
113
 
112
114
  #### NumericField - (Inherits from `BaseField`)
113
115
 
114
- - Not Zero
115
- - Positive
116
- - Negative
117
- - Minimum `{min_val:Number}`
118
- - Maximum `{max_val:Number}`
119
- - Greater than `{threshold:Number}`
120
- - Less than `{threshold:Number}`
121
- - Between `{min_val:Number}` and `{max_val:Number}`
116
+ - Is not zero
117
+ - Is positive
118
+ - Is negative
119
+ - Is at least {min_val:Number}
120
+ - Is at most {max_val:Number}
121
+ - Is greater than {threshold:Number}
122
+ - Is less than {threshold:Number}
123
+ - Is not between {min_val:Number} and {max_val:Number}
124
+ - Is between {min_val:Number} and {max_val:Number}
122
125
 
123
126
  #### IntegerField - (Inherits from `NumericField`)
124
127
 
125
128
  #### FloatField - (Inherits from `NumericField`)
126
129
 
130
+ - Has at most {decimal_places:Integer} decimal places
131
+
127
132
  ## Contributing
128
133
 
129
- Contributions are welcome! Feel free to submit issues or pull requests in the [GitHub repository](https://github.com/Kenr0t/data-sitter).
134
+ Contributions are welcome! Feel free to submit issues or pull requests in the [GitHub repository](https://github.com/lcandea/data-sitter).
130
135
 
131
136
  ## License
132
137
 
@@ -1,3 +1,5 @@
1
+ import json
2
+ import yaml
1
3
  from typing import Any, Dict, List, NamedTuple
2
4
  from functools import cached_property
3
5
 
@@ -52,6 +54,14 @@ class Contract:
52
54
  values=contract_dict.get("values", {}),
53
55
  )
54
56
 
57
+ @classmethod
58
+ def from_json(cls, contract_json: str):
59
+ return cls.from_dict(json.loads(contract_json))
60
+
61
+ @classmethod
62
+ def from_yaml(cls, contract_yaml: str):
63
+ return cls.from_dict(yaml.load(contract_yaml, yaml.Loader))
64
+
55
65
  @cached_property
56
66
  def field_validators(self) -> Dict[str, BaseField]:
57
67
  field_validators = {}
@@ -83,7 +93,28 @@ class Contract:
83
93
  }
84
94
  })
85
95
 
86
- def get_front_end_contract(self):
96
+ @cached_property
97
+ def contract(self) -> dict:
98
+ return {
99
+ "name": self.name,
100
+ "fields": [
101
+ {
102
+ "field_name": field_name,
103
+ "field_type": field_validator.__class__.__name__,
104
+ "field_rules": [rule.parsed_rule for rule in self.rules.get(field_name, [])]
105
+ }
106
+ for field_name, field_validator in self.field_validators.items()
107
+ ],
108
+ "values": self.rule_parser.values
109
+ }
110
+
111
+ def get_json_contract(self, indent: int=2) -> str:
112
+ return json.dumps(self.contract, indent=indent)
113
+
114
+ def get_yaml_contract(self, indent: int=2) -> str:
115
+ return yaml.dump(self.contract, Dumper=yaml.Dumper, indent=indent, sort_keys=False)
116
+
117
+ def get_front_end_contract(self) -> dict:
87
118
  return {
88
119
  "name": self.name,
89
120
  "fields": [
@@ -1,4 +1,3 @@
1
-
2
1
  from typing import Dict, List, Type
3
2
 
4
3
  from .field_types import BaseField
@@ -32,8 +31,7 @@ class FieldResolver:
32
31
  return matched_rules
33
32
 
34
33
  def get_field_validator(self, field_name: str, parsed_rules: List[str]) -> BaseField:
35
- is_optional = "Validate Not Null" not in parsed_rules
36
- validator = self.field_class(field_name, is_optional)
34
+ validator = self.field_class(field_name)
37
35
  matched_rules = self.get_matched_rules(parsed_rules)
38
36
  for matched_rule in matched_rules:
39
37
  matched_rule.add_to_instance(validator)
@@ -0,0 +1,39 @@
1
+ from collections import defaultdict
2
+ from typing import Any, Dict, List, Type
3
+
4
+ from pydantic import BaseModel, ValidationError
5
+
6
+
7
+ class Validation():
8
+ item: Dict[str, Any]
9
+ errors: Dict[str, List[str]]
10
+ unknowns: Dict[str, Any]
11
+
12
+ def __init__(self, item: dict, errors: dict = None, unknowns: dict = None):
13
+ self.item = item
14
+ self.errors = errors if errors else None
15
+ self.unknowns = unknowns if unknowns else None
16
+
17
+ def to_dict(self) -> dict:
18
+ return {key: value for key in ["item", "errors", "unknowns"] if (value := getattr(self, key))}
19
+
20
+ @classmethod
21
+ def validate(cls, PydanticModel: Type[BaseModel], input_item: dict) -> "Validation":
22
+ model_keys = PydanticModel.model_json_schema()['properties'].keys()
23
+ item = {key: None for key in model_keys} # Filling not present values with Nones
24
+ errors = defaultdict(list)
25
+ unknowns = {}
26
+ for key, value in input_item.items():
27
+ if key in item:
28
+ item[key] = value
29
+ else:
30
+ unknowns[key] = value
31
+ try:
32
+ validated = PydanticModel(**item).model_dump()
33
+ except ValidationError as e:
34
+ validated = item
35
+ for error in e.errors():
36
+ field = error['loc'][0] # Extract the field name
37
+ msg = error['msg']
38
+ errors[field].append(msg)
39
+ return Validation(item=validated, errors=dict(errors), unknowns=unknowns)
@@ -21,20 +21,21 @@ class BaseField(ABC):
21
21
  validators = None
22
22
  field_type = None
23
23
 
24
- def __init__(self, name: str, is_optional: bool) -> None:
24
+ def __init__(self, name: str) -> None:
25
25
  self.name = name
26
- self.is_optional = is_optional
26
+ self.is_optional = True
27
27
  self.validators = []
28
28
 
29
- @register_rule("Validate Not Null")
29
+ @register_rule("Is not null")
30
30
  def validator_not_null(self):
31
31
  def _validator(value):
32
32
  if self.is_optional:
33
33
  return value
34
34
  if value is None:
35
- raise ValueError("Value cannot be null")
35
+ raise ValueError("Value cannot be null.")
36
36
  return value
37
37
 
38
+ self.is_optional = False
38
39
  self.validators.append(_validator)
39
40
 
40
41
  def validate(self, value):
@@ -0,0 +1,17 @@
1
+ from .NumericField import NumericField
2
+ from ..rules import register_field, register_rule
3
+
4
+
5
+ @register_field
6
+ class FloatField(NumericField):
7
+ field_type = float
8
+
9
+ @register_rule("Has at most {decimal_places:Integer} decimal places")
10
+ def validate_max_decimal_places(self, decimal_places: int):
11
+ def validator(value):
12
+ if not isinstance(value, float):
13
+ raise ValueError("Value must be a floating-point number.")
14
+ if len(str(value).split(".")[1]) > decimal_places:
15
+ raise ValueError(f"Value must have at most {decimal_places} decimal places.")
16
+ return value
17
+ self.validators.append(validator)
@@ -10,66 +10,70 @@ Numeric = Union[int, float]
10
10
  class NumericField(BaseField):
11
11
  field_type = Numeric
12
12
 
13
- @register_rule("Not Zero")
13
+ @register_rule("Is not zero")
14
14
  def validate_non_zero(self):
15
15
  def validator(value: Numeric):
16
16
  if value == 0:
17
- raise ValueError("Value must not be zero")
17
+ raise ValueError("Value cannot be zero.")
18
18
  return value
19
19
  self.validators.append(validator)
20
20
 
21
- @register_rule("Positive")
21
+ @register_rule("Is positive")
22
22
  def validate_positive(self):
23
23
  def validator(value: Numeric):
24
24
  if value < 0:
25
- raise ValueError(f"Value {value} is not positive")
25
+ raise ValueError("Value must be positive.")
26
26
  return value
27
27
  self.validators.append(validator)
28
28
 
29
- @register_rule("Negative")
29
+ @register_rule("Is negative")
30
30
  def validate_negative(self):
31
31
  def validator(value: Numeric):
32
32
  if value >= 0:
33
- raise ValueError(f"Value {value} is not negative")
33
+ raise ValueError("Value must be less than zero.")
34
34
  return value
35
35
  self.validators.append(validator)
36
36
 
37
- @register_rule("Minimum {min_val:Number}")
37
+ @register_rule("Is at least {min_val:Number}")
38
38
  def validate_min(self, min_val: Numeric):
39
39
  def validator(value: Numeric):
40
40
  if value < min_val:
41
- raise ValueError(f"Value {value} is less than minimum {min_val}")
41
+ raise ValueError(f"Value must be at least {min_val}.")
42
42
  return value
43
43
  self.validators.append(validator)
44
44
 
45
- @register_rule("Maximum {max_val:Number}")
45
+ @register_rule("Is at most {max_val:Number}")
46
46
  def validate_max(self, max_val: Numeric):
47
47
  def validator(value: Numeric):
48
48
  if value > max_val:
49
- raise ValueError(f"Value {value} exceeds maximum {max_val}")
49
+ raise ValueError(f"Value must not exceed {max_val}.")
50
50
  return value
51
51
  self.validators.append(validator)
52
52
 
53
- @register_rule("Greate than {threshold:Number}")
53
+ @register_rule("Is greater than {threshold:Number}")
54
54
  def validate_greater_than(self, threshold: Numeric):
55
55
  def validator(value: Numeric):
56
56
  if value <= threshold:
57
- raise ValueError(f"Value {value} is not greater than {threshold}")
57
+ raise ValueError(f"Value must be greater than {threshold}.")
58
58
  return value
59
59
  self.validators.append(validator)
60
60
 
61
- @register_rule("Less than {threshold:Number}")
61
+ @register_rule("Is less than {threshold:Number}")
62
62
  def validate_less_than(self, threshold: Numeric):
63
63
  def validator(value: Numeric):
64
64
  if value >= threshold:
65
- raise ValueError(f"Value {value} is not less than {threshold}")
65
+ raise ValueError(f"Value must be less than {threshold}.")
66
66
  return value
67
67
  self.validators.append(validator)
68
68
 
69
- @register_rule("Between {min_val:Number} and {max_val:Number}")
70
- def validate_between(self, min_val: Numeric, max_val: Numeric):
69
+ @register_rule("Is between {min_val:Number} and {max_val:Number}", fixed_params={"negative": False})
70
+ @register_rule("Is not between {min_val:Number} and {max_val:Number}", fixed_params={"negative": True})
71
+ def validate_between(self, min_val: Numeric, max_val: Numeric, negative: bool):
71
72
  def validator(value: Numeric):
72
- if not (min_val < value < max_val):
73
- raise ValueError(f"Value {value} not in Between {min_val} and {max_val}.")
73
+ condition = (min_val < value < max_val)
74
+ if condition and negative:
75
+ raise ValueError(f"Value must not be between {min_val} and {max_val}.")
76
+ if not condition and not negative:
77
+ raise ValueError(f"Value must be between {min_val} and {max_val}.")
74
78
  return value
75
79
  self.validators.append(validator)
@@ -0,0 +1,122 @@
1
+ import re
2
+ from typing import List
3
+
4
+ from .BaseField import BaseField
5
+ from ..rules import register_rule, register_field
6
+
7
+
8
+ @register_field
9
+ class StringField(BaseField):
10
+ field_type = str
11
+
12
+ @register_rule("Is not empty")
13
+ def validate_not_empty(self):
14
+ def validator(value: str):
15
+ if value == "":
16
+ raise ValueError("String cannot be empty.")
17
+ return value
18
+ self.validators.append(validator)
19
+
20
+ @register_rule("Starts with {prefix:String}")
21
+ def validate_starts_with(self, prefix: List[str]):
22
+ def validator(value: str):
23
+ if not value.startswith(prefix):
24
+ raise ValueError(f"Value must start with '{prefix}'.")
25
+ return value
26
+ self.validators.append(validator)
27
+
28
+ @register_rule("Ends with {suffix:String}")
29
+ def validate_ends_with(self, suffix: List[str]):
30
+ def validator(value: str):
31
+ if not value.endswith(suffix):
32
+ raise ValueError(f"Value must end with '{suffix}'.")
33
+ return value
34
+ self.validators.append(validator)
35
+
36
+ @register_rule("Is one of {possible_values:Strings}", fixed_params={"negative": False})
37
+ @register_rule("Is not one of {possible_values:Strings}", fixed_params={"negative": True})
38
+ def validate_in(self, possible_values: List[str], negative: bool):
39
+ def validator(value: str):
40
+ condition = value in possible_values
41
+ if condition and negative:
42
+ raise ValueError(f"Value '{value}' is not allowed.")
43
+ if not condition and not negative:
44
+ raise ValueError(f"Value '{value}' must be one of the possible values.")
45
+ return value
46
+ self.validators.append(validator)
47
+
48
+ @register_rule("Has length between {min_val:Integer} and {max_val:Integer}")
49
+ def validate_length_between(self, min_val: int, max_val: int):
50
+ def validator(value: str):
51
+ if not (min_val < len(value) < max_val):
52
+ raise ValueError(f"Length must be between {min_val} and {max_val} characters.")
53
+ return value
54
+ self.validators.append(validator)
55
+
56
+ @register_rule("Has maximum length {max_len:Integer}")
57
+ def validate_max_length(self, max_len: int):
58
+ def validator(value: str):
59
+ if len(value) > max_len:
60
+ raise ValueError(f"Length must not exceed {max_len} characters.")
61
+ return value
62
+ self.validators.append(validator)
63
+
64
+ @register_rule("Has minimum length {min_len:Integer}")
65
+ def validate_min_length(self, min_len: int):
66
+ def validator(value: str):
67
+ if len(value) < min_len:
68
+ raise ValueError(f"Length must be at least {min_len} characters.")
69
+ return value
70
+ self.validators.append(validator)
71
+
72
+ @register_rule("Is uppercase")
73
+ def validate_uppercase(self):
74
+ def validator(value: str):
75
+ if not value.isupper():
76
+ raise ValueError("Value must be in uppercase.")
77
+ return value
78
+ self.validators.append(validator)
79
+
80
+ @register_rule("Is lowercase")
81
+ def validate_lowercase(self):
82
+ def validator(value: str):
83
+ if not value.islower():
84
+ raise ValueError("Value must be in lowercase.")
85
+ return value
86
+ self.validators.append(validator)
87
+
88
+ @register_rule("Matches regex {pattern:String}")
89
+ def validate_matches_regex(self, pattern: str):
90
+ def validator(value: str):
91
+ if not re.match(pattern, value):
92
+ raise ValueError(f"Value does not match the required pattern {pattern}.")
93
+ return value
94
+ self.validators.append(validator)
95
+
96
+ @register_rule("Is valid email")
97
+ def validate_email(self):
98
+ EMAIL_REGEX = r"^[\w\.-]+@[\w\.-]+\.\w+$"
99
+
100
+ def validator(value: str):
101
+ if not re.match(EMAIL_REGEX, value):
102
+ raise ValueError("Invalid email format.")
103
+ return value
104
+ self.validators.append(validator)
105
+
106
+ @register_rule("Is valid URL")
107
+ def validate_url(self):
108
+ URL_REGEX = r"^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$"
109
+
110
+ def validator(value: str):
111
+ if not re.match(URL_REGEX, value):
112
+ raise ValueError("Invalid URL format.")
113
+ return value
114
+ self.validators.append(validator)
115
+
116
+ @register_rule("Has no digits")
117
+ def validate_no_digits(self):
118
+ def validator(value: str):
119
+ if any(char.isdigit() for char in value):
120
+ raise ValueError("Value must not contain any digits.")
121
+ return value
122
+ self.validators.append(validator)
@@ -1,3 +1,4 @@
1
+ import functools
1
2
  import string
2
3
  from inspect import signature
3
4
  from typing import Callable
@@ -15,12 +16,15 @@ class Rule:
15
16
  field_type: str
16
17
  field_rule: str
17
18
  rule_setter: Callable
19
+ fixed_params: dict
18
20
 
19
- def __init__(self, field_type: str, field_rule: str, rule_setter: Callable) -> None:
21
+ def __init__(self, field_type: str, field_rule: str, rule_setter: Callable, fixed_params: dict = None) -> None:
20
22
  self.field_type = field_type
21
23
  self.field_rule = field_rule
22
24
  self.rule_setter = rule_setter
25
+ self.fixed_params = fixed_params or {}
23
26
  self.__validate_rule_function_params()
27
+ self.__apply_fixed_params()
24
28
 
25
29
  def __repr__(self):
26
30
  return self.field_rule
@@ -39,6 +43,19 @@ class Rule:
39
43
  if "self" not in rule_setter_params:
40
44
  raise NotAClassMethod()
41
45
 
46
+ rule_setter_params = rule_setter_params - set(self.fixed_params)
42
47
  rule_setter_params.remove("self")
43
48
  if set(self.rule_params) != rule_setter_params:
44
- raise RuleFunctionParamsMismatch(f"Rule Params: {self.rule_params}, Setter Params: {rule_setter_params}")
49
+ rule_total_params = set(self.rule_params).union(set(self.fixed_params))
50
+ raise RuleFunctionParamsMismatch(f"Rule Params: {rule_total_params}, Setter Params: {rule_setter_params}")
51
+
52
+ def __apply_fixed_params(self):
53
+ if not self.fixed_params:
54
+ return
55
+ rule_setter_sign = signature(self.rule_setter)
56
+ all_params = rule_setter_sign.parameters
57
+
58
+ for param in self.fixed_params:
59
+ if param not in all_params:
60
+ raise ValueError(f"The fixed parameter '{param}' is not in the function '{self.rule_setter.__name__}'.")
61
+ self.rule_setter = functools.partial(self.rule_setter, **self.fixed_params)
@@ -17,12 +17,12 @@ class RuleRegistry:
17
17
  type_map: Dict[str, Type["BaseField"]] = {}
18
18
 
19
19
  @classmethod
20
- def register_rule(cls, field_rule: str):
20
+ def register_rule(cls, field_rule: str, fixed_params: dict = None):
21
21
  def _register(func: callable):
22
22
  field_type, func_name = func.__qualname__.split(".")
23
23
  logger.debug("Registering function '%s' for %s. Rule: %s", func_name, field_type, field_rule)
24
24
 
25
- rule = Rule(field_type, field_rule, func)
25
+ rule = Rule(field_type, field_rule, func, fixed_params)
26
26
  cls.rules[field_type].append(rule)
27
27
  logger.debug("Function '%s' Registered", func_name)
28
28
  return func
@@ -57,8 +57,8 @@ class RuleRegistry:
57
57
  ]
58
58
 
59
59
 
60
- def register_rule(rule: str):
61
- return RuleRegistry.register_rule(rule)
60
+ def register_rule(rule: str, fixed_params: dict = None):
61
+ return RuleRegistry.register_rule(rule, fixed_params)
62
62
 
63
63
 
64
64
  def register_field(field_class: type):
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: data-sitter
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A Python library that reads data contracts and generates Pydantic models for seamless data validation.
5
5
  Author-email: Lázaro Pereira Candea <lazaro@candea.es>
6
6
  Requires-Dist: python-dotenv==1.0.1
7
+ Requires-Dist: PyYAML==6.0.2
7
8
  Requires-Dist: parse_type==0.6.4
8
9
  Requires-Dist: pydantic==2.10.5
@@ -1,3 +1,4 @@
1
1
  python-dotenv==1.0.1
2
+ PyYAML==6.0.2
2
3
  parse_type==0.6.4
3
4
  pydantic==2.10.5
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = 'data-sitter'
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "A Python library that reads data contracts and generates Pydantic models for seamless data validation."
9
9
  authors = [
10
10
  {name = 'Lázaro Pereira Candea', email = 'lazaro@candea.es'},
@@ -12,6 +12,7 @@ authors = [
12
12
  dependencies = [
13
13
  # Keep this in sync with setup.py
14
14
  "python-dotenv==1.0.1",
15
+ "PyYAML==6.0.2",
15
16
  "parse_type==0.6.4",
16
17
  "pydantic==2.10.5",
17
18
  ]
@@ -3,11 +3,12 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name='data-sitter',
6
- version='0.1.3',
6
+ version='0.1.4',
7
7
  packages=find_packages(),
8
8
  install_requires=[
9
9
  # Keep this in sync with pyproject.toml
10
10
  "python-dotenv==1.0.1",
11
+ "PyYAML==6.0.2",
11
12
  "parse_type==0.6.4",
12
13
  "pydantic==2.10.5",
13
14
  ],
@@ -1,30 +0,0 @@
1
-
2
- from collections import defaultdict
3
- from typing import Any, Dict, List, Type
4
-
5
- from pydantic import BaseModel, ValidationError
6
-
7
-
8
- class Validation():
9
- row: Dict[str, Any]
10
- errors: Dict[str, List[str]]
11
-
12
- def __init__(self, row: dict, errors: dict = None):
13
- self.row = row
14
- self.errors = errors or {}
15
-
16
- def to_dict(self) -> dict:
17
- return {"row": self.row, "errors": self.errors}
18
-
19
- @classmethod
20
- def validate(cls, model: Type[BaseModel], item: dict) -> "Validation":
21
- try:
22
- row = model(**item) # Validate the row
23
- return Validation(row=row.model_dump())
24
- except ValidationError as e:
25
- errors = defaultdict(list)
26
- for error in e.errors():
27
- field = error['loc'][0] # Extract the field name
28
- msg = error['msg']
29
- errors[field].append(msg)
30
- return Validation(row=item, errors=dict(errors))
@@ -1,7 +0,0 @@
1
- from .NumericField import NumericField
2
- from ..rules import register_field
3
-
4
-
5
- @register_field
6
- class FloatField(NumericField):
7
- field_type = float
@@ -1,89 +0,0 @@
1
- from typing import List
2
-
3
- from .BaseField import BaseField
4
- from ..rules import register_rule, register_field
5
-
6
-
7
- @register_field
8
- class StringField(BaseField):
9
- field_type = str
10
-
11
- @register_rule("Is not empty")
12
- def validate_not_empty(self):
13
- def validator(value: str):
14
- if value == "":
15
- raise ValueError("The value is empty")
16
- return value
17
- self.validators.append(validator)
18
-
19
- @register_rule("Starts with {prefix:String}")
20
- def validate_starts_with(self, prefix: List[str]):
21
- def validator(value: str):
22
- if not value.startswith(prefix):
23
- raise ValueError(f"The value '{value}' does not start with '{prefix}'.")
24
- return value
25
- self.validators.append(validator)
26
-
27
- @register_rule("Ends with {sufix:String}")
28
- def validate_ends_with(self, sufix: List[str]):
29
- def validator(value: str):
30
- if not value.endswith(sufix):
31
- raise ValueError(f"The value '{value}' does not ends with '{sufix}'.")
32
- return value
33
- self.validators.append(validator)
34
-
35
- @register_rule("Value in {possible_values:Strings}")
36
- def validate_in(self, possible_values: List[str]):
37
- def validator(value: str):
38
- if value not in possible_values:
39
- raise ValueError(f"The value '{value}' is not in the list.")
40
- return value
41
- self.validators.append(validator)
42
-
43
- @register_rule("Length between {min_val:Integer} and {max_val:Integer}")
44
- def validate_length_between(self, min_val: int, max_val: int):
45
- def validator(value: str):
46
- if not (min_val < len(value) < max_val):
47
- raise ValueError(f"Length {len(value)} is not in between {min_val} and {max_val}.")
48
- return value
49
- self.validators.append(validator)
50
-
51
- @register_rule("Maximum length of {max_len:Integer}")
52
- def validate_max_length(self, max_len: int):
53
- def validator(value: str):
54
- if len(value) > max_len:
55
- raise ValueError(f"Length {len(value)} is longer than {max_len}.")
56
- return value
57
- self.validators.append(validator)
58
-
59
- @register_rule("Length shorter than {max_len:Integer}")
60
- def validate_shorter_than(self, max_len: int):
61
- def validator(value: str):
62
- if len(value) >= max_len:
63
- raise ValueError(f"Length {len(value)} is not in shorter than {max_len}.")
64
- return value
65
- self.validators.append(validator)
66
-
67
- @register_rule("Minimum length of {min_len:Integer}")
68
- def validate_min_length(self, min_len: int):
69
- def validator(value: str):
70
- if len(value) < min_len:
71
- raise ValueError(f"Length {len(value)} is shorter than {min_len}.")
72
- return value
73
- self.validators.append(validator)
74
-
75
- @register_rule("Length longer than {min_len:Integer}")
76
- def validate_longer_than(self, min_len: int):
77
- def validator(value: str):
78
- if len(value) <= min_len:
79
- raise ValueError(f"Length {len(value)} is not in longer than {min_len}.")
80
- return value
81
- self.validators.append(validator)
82
-
83
- @register_rule("Is uppercase")
84
- def validate_uppercase(self):
85
- def validator(value: str):
86
- if not value.isupper():
87
- raise ValueError("Not Uppercase")
88
- return value
89
- self.validators.append(validator)
File without changes