data-sitter 0.1.4__py3-none-any.whl → 0.1.6__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.
data_sitter/Contract.py CHANGED
@@ -8,7 +8,7 @@ from pydantic import BaseModel
8
8
  from .Validation import Validation
9
9
  from .field_types import BaseField
10
10
  from .FieldResolver import FieldResolver
11
- from .rules import MatchedRule, RuleRegistry, RuleParser
11
+ from .rules import ProcessedRule, RuleRegistry, RuleParser
12
12
 
13
13
 
14
14
  class ContractWithoutFields(Exception):
@@ -20,9 +20,9 @@ class ContractWithoutName(Exception):
20
20
 
21
21
 
22
22
  class Field(NamedTuple):
23
- field_name: str
24
- field_type: str
25
- field_rules: List[str]
23
+ name: str
24
+ type: str
25
+ rules: List[str]
26
26
 
27
27
 
28
28
  class Contract:
@@ -37,8 +37,8 @@ class Contract:
37
37
  self.fields = fields
38
38
  self.rule_parser = RuleParser(values)
39
39
  self.field_resolvers = {
40
- field_type: FieldResolver(RuleRegistry.get_type(field_type), self.rule_parser)
41
- for field_type in list({field.field_type for field in self.fields}) # Unique types
40
+ _type: FieldResolver(RuleRegistry.get_type(_type), self.rule_parser)
41
+ for _type in list({field.type for field in self.fields}) # Unique types
42
42
  }
43
43
 
44
44
  @classmethod
@@ -66,21 +66,18 @@ class Contract:
66
66
  def field_validators(self) -> Dict[str, BaseField]:
67
67
  field_validators = {}
68
68
  for field in self.fields:
69
- field_resolver = self.field_resolvers[field.field_type]
70
- field_validators[field.field_name] = field_resolver.get_field_validator(field.field_name, field.field_rules)
69
+ field_resolver = self.field_resolvers[field.type]
70
+ field_validators[field.name] = field_resolver.get_field_validator(field.name, field.rules)
71
71
  return field_validators
72
72
 
73
73
  @cached_property
74
- def rules(self) -> Dict[str, List[MatchedRule]]:
74
+ def rules(self) -> Dict[str, List[ProcessedRule]]:
75
75
  rules = {}
76
76
  for field in self.fields:
77
- field_resolver = self.field_resolvers[field.field_type]
78
- rules[field.field_name] = field_resolver.get_matched_rules(field.field_rules)
77
+ field_resolver = self.field_resolvers[field.type]
78
+ rules[field.name] = field_resolver.get_processed_rules(field.rules)
79
79
  return rules
80
80
 
81
- def model_validate(self, item: dict):
82
- return self.pydantic_model.model_validate(item).model_dump()
83
-
84
81
  def validate(self, item: dict) -> Validation:
85
82
  return Validation.validate(self.pydantic_model, item)
86
83
 
@@ -88,8 +85,8 @@ class Contract:
88
85
  def pydantic_model(self) -> BaseModel:
89
86
  return type(self.name, (BaseModel,), {
90
87
  "__annotations__": {
91
- field_name: field_validator.get_annotation()
92
- for field_name, field_validator in self.field_validators.items()
88
+ name: field_validator.get_annotation()
89
+ for name, field_validator in self.field_validators.items()
93
90
  }
94
91
  })
95
92
 
@@ -99,11 +96,11 @@ class Contract:
99
96
  "name": self.name,
100
97
  "fields": [
101
98
  {
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, [])]
99
+ "name": name,
100
+ "type": field_validator.type_name.value,
101
+ "rules": [rule.parsed_rule for rule in self.rules.get(name, [])]
105
102
  }
106
- for field_name, field_validator in self.field_validators.items()
103
+ for name, field_validator in self.field_validators.items()
107
104
  ],
108
105
  "values": self.rule_parser.values
109
106
  }
@@ -119,19 +116,14 @@ class Contract:
119
116
  "name": self.name,
120
117
  "fields": [
121
118
  {
122
- "field_name": field_name,
123
- "field_type": field_validator.__class__.__name__,
124
- "field_rules": [
125
- {
126
- "rule": rule.field_rule,
127
- "parsed_rule": rule.parsed_rule,
128
- "rule_params": rule.rule_params,
129
- "parsed_values": rule.parsed_values,
130
- }
131
- for rule in self.rules.get(field_name, [])
119
+ "name": name,
120
+ "type": field_validator.type_name.value,
121
+ "rules": [
122
+ rule.get_front_end_repr()
123
+ for rule in self.rules.get(name, [])
132
124
  ]
133
125
  }
134
- for field_name, field_validator in self.field_validators.items()
126
+ for name, field_validator in self.field_validators.items()
135
127
  ],
136
128
  "values": self.rule_parser.values
137
129
  }
@@ -1,7 +1,7 @@
1
- from typing import Dict, List, Type
1
+ from typing import Dict, List, Type, Union
2
2
 
3
3
  from .field_types import BaseField
4
- from .rules import MatchedRule, Rule, RuleRegistry
4
+ from .rules import Rule, ProcessedRule, LogicalRule, MatchedRule, RuleRegistry, LogicalOperator
5
5
  from .rules.Parser import RuleParser
6
6
 
7
7
 
@@ -9,6 +9,10 @@ class RuleNotFoundError(Exception):
9
9
  """No matching rule found for the given parsed rule."""
10
10
 
11
11
 
12
+ class MalformedLogicalRuleError(Exception):
13
+ """Logical rule structure not recognised."""
14
+
15
+
12
16
  class FieldResolver:
13
17
  field_class: Type[BaseField]
14
18
  rule_parser: RuleParser
@@ -21,23 +25,32 @@ class FieldResolver:
21
25
  self.rules = RuleRegistry.get_rules_for(field_class)
22
26
  self._match_rule_cache = {}
23
27
 
24
- def get_matched_rules(self, parsed_rules: List[str]) -> List[MatchedRule]:
25
- matched_rules = []
28
+ def get_field_validator(self, name: str, parsed_rules: List[Union[str, dict]]) -> BaseField:
29
+ field_validator = self.field_class(name)
30
+ processed_rules = self.get_processed_rules(parsed_rules)
31
+ validators = [pr.get_validator(field_validator) for pr in processed_rules]
32
+ field_validator.validators = validators
33
+ return field_validator
34
+
35
+ def get_processed_rules(self, parsed_rules: List[Union[str, dict]]) -> List[ProcessedRule]:
36
+ processed_rules = []
26
37
  for parsed_rule in parsed_rules:
27
- matched_rule = self.match_rule(parsed_rule)
28
- if not matched_rule:
29
- raise RuleNotFoundError(f"Rule not found for parsed rule: '{parsed_rule}'")
30
- matched_rules.append(matched_rule)
31
- return matched_rules
32
-
33
- def get_field_validator(self, field_name: str, parsed_rules: List[str]) -> BaseField:
34
- validator = self.field_class(field_name)
35
- matched_rules = self.get_matched_rules(parsed_rules)
36
- for matched_rule in matched_rules:
37
- matched_rule.add_to_instance(validator)
38
- return validator
39
-
40
- def match_rule(self, parsed_rule: str) -> MatchedRule:
38
+ if isinstance(parsed_rule, dict):
39
+ if len(keys := tuple(parsed_rule)) != 1 or (operator := keys[0]) not in LogicalOperator:
40
+ raise MalformedLogicalRuleError()
41
+ if operator == LogicalOperator.NOT and not isinstance(parsed_rule[operator], list):
42
+ parsed_rule = {operator: [parsed_rule[operator]]} # NOT operator can be a single rule
43
+ processed_rule = LogicalRule(operator, self.get_processed_rules(parsed_rule[operator]))
44
+ elif isinstance(parsed_rule, str):
45
+ processed_rule = self._match_rule(parsed_rule)
46
+ if not processed_rule:
47
+ raise RuleNotFoundError(f"Rule not found for parsed rule: '{parsed_rule}'")
48
+ else:
49
+ raise TypeError(f'Parsed Rule type not recognised: {type(parsed_rule)}')
50
+ processed_rules.append(processed_rule)
51
+ return processed_rules
52
+
53
+ def _match_rule(self, parsed_rule: str) -> MatchedRule:
41
54
  if parsed_rule in self._match_rule_cache:
42
55
  return self._match_rule_cache[parsed_rule]
43
56
 
data_sitter/cli.py CHANGED
@@ -44,5 +44,5 @@ def main():
44
44
  print(f"The file {args.file} pass the contract {args.contract}")
45
45
 
46
46
 
47
- if __name__ == '__main__':
47
+ if __name__ == '__main__': # pragma: no cover
48
48
  main()
@@ -1,18 +1,24 @@
1
1
  from abc import ABC
2
- from typing import Annotated, List, Optional, Type
2
+ from typing import Annotated, Callable, List, Optional, Type
3
3
 
4
4
  from pydantic import AfterValidator
5
+
6
+ from .FieldTypes import FieldTypes
5
7
  from ..rules import register_rule, register_field
6
8
 
7
9
 
8
- def aggregated_validator(validators: List[callable], is_optional: bool):
9
- def _validator(value):
10
+ class NotInitialisedError(Exception):
11
+ """The field instance is initialised without validators"""
12
+
13
+
14
+ def aggregated_validator(validators: List[Callable], is_optional: bool):
15
+ def validator(value):
10
16
  if is_optional and value is None:
11
17
  return value
12
18
  for validator_func in validators:
13
19
  validator_func(value)
14
20
  return value
15
- return _validator
21
+ return validator
16
22
 
17
23
  @register_field
18
24
  class BaseField(ABC):
@@ -20,39 +26,42 @@ class BaseField(ABC):
20
26
  is_optional: bool
21
27
  validators = None
22
28
  field_type = None
29
+ type_name = FieldTypes.BASE
23
30
 
24
31
  def __init__(self, name: str) -> None:
25
32
  self.name = name
26
33
  self.is_optional = True
27
- self.validators = []
34
+ self.validators = None
28
35
 
29
36
  @register_rule("Is not null")
30
37
  def validator_not_null(self):
31
- def _validator(value):
32
- if self.is_optional:
33
- return value
38
+ def validator(value):
34
39
  if value is None:
35
40
  raise ValueError("Value cannot be null.")
36
41
  return value
37
42
 
38
43
  self.is_optional = False
39
- self.validators.append(_validator)
44
+ return validator
40
45
 
41
46
  def validate(self, value):
47
+ if self.validators is None:
48
+ raise NotInitialisedError()
42
49
  for validator in self.validators:
43
50
  validator(value)
44
51
 
45
52
  def get_annotation(self):
53
+ if self.validators is None:
54
+ raise NotInitialisedError()
46
55
  field_type = Optional[self.field_type] if self.is_optional else self.field_type
47
56
  return Annotated[field_type, AfterValidator(aggregated_validator(self.validators, self.is_optional))]
48
57
 
49
58
  @classmethod
50
59
  def get_parents(cls: Type["BaseField"]) -> List[Type["BaseField"]]:
51
- if cls.__name__ == "BaseField":
60
+ if cls == BaseField:
52
61
  return []
53
- ancestors = []
62
+ ancestors = set()
54
63
  for base in cls.__bases__:
55
- if base.__name__.endswith("Field"):
56
- ancestors.append(base)
57
- ancestors.extend(base.get_parents()) # It wont break because we have a base case
58
- return ancestors
64
+ if issubclass(base, BaseField):
65
+ ancestors.add(base)
66
+ ancestors.update(base.get_parents())
67
+ return list(ancestors)
@@ -0,0 +1,9 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class FieldTypes(StrEnum):
5
+ BASE = "Base"
6
+ INT = "Integer"
7
+ FLOAT = "Float"
8
+ STRING = "String"
9
+ NUMERIC = "Numeric"
@@ -1,17 +1,26 @@
1
+ from .FieldTypes import FieldTypes
1
2
  from .NumericField import NumericField
2
3
  from ..rules import register_field, register_rule
4
+ from decimal import Decimal
3
5
 
4
6
 
5
7
  @register_field
6
8
  class FloatField(NumericField):
7
9
  field_type = float
10
+ type_name = FieldTypes.FLOAT
11
+
8
12
 
9
13
  @register_rule("Has at most {decimal_places:Integer} decimal places")
10
14
  def validate_max_decimal_places(self, decimal_places: int):
11
15
  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:
16
+ decimal_str = str(Decimal(str(value)).normalize())
17
+ # If no decimal point or only zeros after decimal, it has 0 decimal places
18
+ if '.' not in decimal_str:
19
+ decimal_places_count = 0
20
+ else:
21
+ decimal_places_count = len(decimal_str.split('.')[1])
22
+
23
+ if decimal_places_count > decimal_places:
15
24
  raise ValueError(f"Value must have at most {decimal_places} decimal places.")
16
25
  return value
17
- self.validators.append(validator)
26
+ return validator
@@ -1,3 +1,4 @@
1
+ from .FieldTypes import FieldTypes
1
2
  from .NumericField import NumericField
2
3
  from ..rules import register_field
3
4
 
@@ -5,3 +6,4 @@ from ..rules import register_field
5
6
  @register_field
6
7
  class IntegerField(NumericField):
7
8
  field_type = int
9
+ type_name = FieldTypes.INT
@@ -1,6 +1,7 @@
1
1
  from typing import Union
2
2
 
3
3
  from .BaseField import BaseField
4
+ from .FieldTypes import FieldTypes
4
5
  from ..rules import register_rule, register_field
5
6
 
6
7
  Numeric = Union[int, float]
@@ -9,6 +10,7 @@ Numeric = Union[int, float]
9
10
  @register_field
10
11
  class NumericField(BaseField):
11
12
  field_type = Numeric
13
+ type_name = FieldTypes.NUMERIC
12
14
 
13
15
  @register_rule("Is not zero")
14
16
  def validate_non_zero(self):
@@ -16,15 +18,15 @@ class NumericField(BaseField):
16
18
  if value == 0:
17
19
  raise ValueError("Value cannot be zero.")
18
20
  return value
19
- self.validators.append(validator)
21
+ return validator
20
22
 
21
23
  @register_rule("Is positive")
22
24
  def validate_positive(self):
23
25
  def validator(value: Numeric):
24
- if value < 0:
26
+ if value <= 0:
25
27
  raise ValueError("Value must be positive.")
26
28
  return value
27
- self.validators.append(validator)
29
+ return validator
28
30
 
29
31
  @register_rule("Is negative")
30
32
  def validate_negative(self):
@@ -32,7 +34,7 @@ class NumericField(BaseField):
32
34
  if value >= 0:
33
35
  raise ValueError("Value must be less than zero.")
34
36
  return value
35
- self.validators.append(validator)
37
+ return validator
36
38
 
37
39
  @register_rule("Is at least {min_val:Number}")
38
40
  def validate_min(self, min_val: Numeric):
@@ -40,7 +42,7 @@ class NumericField(BaseField):
40
42
  if value < min_val:
41
43
  raise ValueError(f"Value must be at least {min_val}.")
42
44
  return value
43
- self.validators.append(validator)
45
+ return validator
44
46
 
45
47
  @register_rule("Is at most {max_val:Number}")
46
48
  def validate_max(self, max_val: Numeric):
@@ -48,7 +50,7 @@ class NumericField(BaseField):
48
50
  if value > max_val:
49
51
  raise ValueError(f"Value must not exceed {max_val}.")
50
52
  return value
51
- self.validators.append(validator)
53
+ return validator
52
54
 
53
55
  @register_rule("Is greater than {threshold:Number}")
54
56
  def validate_greater_than(self, threshold: Numeric):
@@ -56,7 +58,7 @@ class NumericField(BaseField):
56
58
  if value <= threshold:
57
59
  raise ValueError(f"Value must be greater than {threshold}.")
58
60
  return value
59
- self.validators.append(validator)
61
+ return validator
60
62
 
61
63
  @register_rule("Is less than {threshold:Number}")
62
64
  def validate_less_than(self, threshold: Numeric):
@@ -64,7 +66,7 @@ class NumericField(BaseField):
64
66
  if value >= threshold:
65
67
  raise ValueError(f"Value must be less than {threshold}.")
66
68
  return value
67
- self.validators.append(validator)
69
+ return validator
68
70
 
69
71
  @register_rule("Is between {min_val:Number} and {max_val:Number}", fixed_params={"negative": False})
70
72
  @register_rule("Is not between {min_val:Number} and {max_val:Number}", fixed_params={"negative": True})
@@ -76,4 +78,4 @@ class NumericField(BaseField):
76
78
  if not condition and not negative:
77
79
  raise ValueError(f"Value must be between {min_val} and {max_val}.")
78
80
  return value
79
- self.validators.append(validator)
81
+ return validator
@@ -2,12 +2,14 @@ import re
2
2
  from typing import List
3
3
 
4
4
  from .BaseField import BaseField
5
+ from .FieldTypes import FieldTypes
5
6
  from ..rules import register_rule, register_field
6
7
 
7
8
 
8
9
  @register_field
9
10
  class StringField(BaseField):
10
11
  field_type = str
12
+ type_name = FieldTypes.STRING
11
13
 
12
14
  @register_rule("Is not empty")
13
15
  def validate_not_empty(self):
@@ -15,7 +17,7 @@ class StringField(BaseField):
15
17
  if value == "":
16
18
  raise ValueError("String cannot be empty.")
17
19
  return value
18
- self.validators.append(validator)
20
+ return validator
19
21
 
20
22
  @register_rule("Starts with {prefix:String}")
21
23
  def validate_starts_with(self, prefix: List[str]):
@@ -23,7 +25,7 @@ class StringField(BaseField):
23
25
  if not value.startswith(prefix):
24
26
  raise ValueError(f"Value must start with '{prefix}'.")
25
27
  return value
26
- self.validators.append(validator)
28
+ return validator
27
29
 
28
30
  @register_rule("Ends with {suffix:String}")
29
31
  def validate_ends_with(self, suffix: List[str]):
@@ -31,7 +33,7 @@ class StringField(BaseField):
31
33
  if not value.endswith(suffix):
32
34
  raise ValueError(f"Value must end with '{suffix}'.")
33
35
  return value
34
- self.validators.append(validator)
36
+ return validator
35
37
 
36
38
  @register_rule("Is one of {possible_values:Strings}", fixed_params={"negative": False})
37
39
  @register_rule("Is not one of {possible_values:Strings}", fixed_params={"negative": True})
@@ -43,7 +45,7 @@ class StringField(BaseField):
43
45
  if not condition and not negative:
44
46
  raise ValueError(f"Value '{value}' must be one of the possible values.")
45
47
  return value
46
- self.validators.append(validator)
48
+ return validator
47
49
 
48
50
  @register_rule("Has length between {min_val:Integer} and {max_val:Integer}")
49
51
  def validate_length_between(self, min_val: int, max_val: int):
@@ -51,7 +53,7 @@ class StringField(BaseField):
51
53
  if not (min_val < len(value) < max_val):
52
54
  raise ValueError(f"Length must be between {min_val} and {max_val} characters.")
53
55
  return value
54
- self.validators.append(validator)
56
+ return validator
55
57
 
56
58
  @register_rule("Has maximum length {max_len:Integer}")
57
59
  def validate_max_length(self, max_len: int):
@@ -59,7 +61,7 @@ class StringField(BaseField):
59
61
  if len(value) > max_len:
60
62
  raise ValueError(f"Length must not exceed {max_len} characters.")
61
63
  return value
62
- self.validators.append(validator)
64
+ return validator
63
65
 
64
66
  @register_rule("Has minimum length {min_len:Integer}")
65
67
  def validate_min_length(self, min_len: int):
@@ -67,7 +69,7 @@ class StringField(BaseField):
67
69
  if len(value) < min_len:
68
70
  raise ValueError(f"Length must be at least {min_len} characters.")
69
71
  return value
70
- self.validators.append(validator)
72
+ return validator
71
73
 
72
74
  @register_rule("Is uppercase")
73
75
  def validate_uppercase(self):
@@ -75,7 +77,7 @@ class StringField(BaseField):
75
77
  if not value.isupper():
76
78
  raise ValueError("Value must be in uppercase.")
77
79
  return value
78
- self.validators.append(validator)
80
+ return validator
79
81
 
80
82
  @register_rule("Is lowercase")
81
83
  def validate_lowercase(self):
@@ -83,7 +85,7 @@ class StringField(BaseField):
83
85
  if not value.islower():
84
86
  raise ValueError("Value must be in lowercase.")
85
87
  return value
86
- self.validators.append(validator)
88
+ return validator
87
89
 
88
90
  @register_rule("Matches regex {pattern:String}")
89
91
  def validate_matches_regex(self, pattern: str):
@@ -91,7 +93,7 @@ class StringField(BaseField):
91
93
  if not re.match(pattern, value):
92
94
  raise ValueError(f"Value does not match the required pattern {pattern}.")
93
95
  return value
94
- self.validators.append(validator)
96
+ return validator
95
97
 
96
98
  @register_rule("Is valid email")
97
99
  def validate_email(self):
@@ -101,7 +103,7 @@ class StringField(BaseField):
101
103
  if not re.match(EMAIL_REGEX, value):
102
104
  raise ValueError("Invalid email format.")
103
105
  return value
104
- self.validators.append(validator)
106
+ return validator
105
107
 
106
108
  @register_rule("Is valid URL")
107
109
  def validate_url(self):
@@ -111,7 +113,7 @@ class StringField(BaseField):
111
113
  if not re.match(URL_REGEX, value):
112
114
  raise ValueError("Invalid URL format.")
113
115
  return value
114
- self.validators.append(validator)
116
+ return validator
115
117
 
116
118
  @register_rule("Has no digits")
117
119
  def validate_no_digits(self):
@@ -119,4 +121,4 @@ class StringField(BaseField):
119
121
  if any(char.isdigit() for char in value):
120
122
  raise ValueError("Value must not contain any digits.")
121
123
  return value
122
- self.validators.append(validator)
124
+ return validator
@@ -0,0 +1,7 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class LogicalOperator(StrEnum):
5
+ AND = "AND"
6
+ OR = "OR"
7
+ NOT = "NOT"
@@ -0,0 +1,68 @@
1
+ from typing import TYPE_CHECKING, Callable, List
2
+
3
+ from .Enums import LogicalOperator
4
+ from .ProcessedRule import ProcessedRule, LogicalParsedRule
5
+
6
+ if TYPE_CHECKING: # pragma: no cover
7
+ from ..field_types import BaseField
8
+
9
+
10
+ def and_or_validator(validators: List[Callable], operator: LogicalOperator) -> Callable:
11
+ def validator(value):
12
+ exceptions = []
13
+ for validator_ in validators:
14
+ try:
15
+ validator_(value)
16
+ exceptions.append(None) # No error, validation passed
17
+ except Exception as e:
18
+ exceptions.append(str(e)) # Store error message
19
+
20
+ if operator == LogicalOperator.OR and all(exceptions):
21
+ raise ValueError(f"None of the conditions were met. Errors: {exceptions}")
22
+ if operator == LogicalOperator.AND and any(exceptions):
23
+ exceptions = list(filter(None, exceptions))
24
+ raise ValueError(f"Not all conditions were met. Errors: {exceptions}")
25
+ return value
26
+ return validator
27
+
28
+
29
+ def not_validator(validator_: Callable):
30
+ def validator(value):
31
+ try:
32
+ validator_(value)
33
+ except Exception:
34
+ return value # Validation passes if the condition fails
35
+ else:
36
+ raise ValueError("Condition was met, but expected NOT to be met.")
37
+ return validator
38
+
39
+
40
+
41
+ class LogicalRule(ProcessedRule):
42
+ operator: LogicalOperator
43
+ processed_rules: List[ProcessedRule]
44
+
45
+ def __init__(self, operator: LogicalOperator, processed_rules: List[ProcessedRule]):
46
+ if operator not in LogicalOperator:
47
+ raise TypeError(f'Logical Operator not recognised: {operator}')
48
+ if not processed_rules:
49
+ raise ValueError("Logical rules must have at least one rule.")
50
+ if operator == LogicalOperator.NOT and len(processed_rules) != 1:
51
+ raise TypeError(f'Not Operator can only contain one rule. Cotains: {len(processed_rules)}')
52
+ self.operator = operator
53
+ self.processed_rules = processed_rules
54
+
55
+ @property
56
+ def parsed_rule(self) -> LogicalParsedRule:
57
+ return {self.operator: [pr.parsed_rule for pr in self.processed_rules]}
58
+
59
+ def get_validator(self, field_instance: "BaseField") -> Callable:
60
+ if self.operator in (LogicalOperator.OR, LogicalOperator.AND):
61
+ return and_or_validator([pr.get_validator(field_instance) for pr in self.processed_rules], self.operator)
62
+ elif self.operator == LogicalOperator.NOT:
63
+ return not_validator(self.processed_rules[0].get_validator(field_instance))
64
+ else:
65
+ raise TypeError(f'Logical Operator not recognised: {self.operator}')
66
+
67
+ def get_front_end_repr(self) -> dict:
68
+ return {self.operator: [pr.get_front_end_repr() for pr in self.processed_rules]}
@@ -1,23 +1,20 @@
1
- from typing import TYPE_CHECKING, Any, Dict
1
+ from typing import TYPE_CHECKING, Any, Callable, Dict
2
2
 
3
3
  from .Rule import Rule
4
4
  from .RuleRegistry import RuleRegistry
5
+ from .ProcessedRule import ProcessedRule, MatchedParsedRule
5
6
  from .Parser.parser_utils import get_value_from_reference
6
7
 
7
- if TYPE_CHECKING:
8
- from field_types import BaseField
8
+ if TYPE_CHECKING: # pragma: no cover
9
+ from ..field_types import BaseField
9
10
 
10
11
 
11
12
  class RuleParsedValuesMismatch(Exception):
12
13
  pass
13
14
 
14
15
 
15
- class InvalidFieldTypeError(TypeError):
16
- """Raised when attempting to add a rule to an incompatible field type."""
17
-
18
-
19
- class MatchedRule(Rule):
20
- parsed_rule: str
16
+ class MatchedRule(ProcessedRule):
17
+ parsed_rule: MatchedParsedRule
21
18
  parsed_values: Dict[str, Any]
22
19
  values: Dict[str, Any]
23
20
 
@@ -48,10 +45,16 @@ class MatchedRule(Rule):
48
45
  if set(self.rule_params) != parsed_values_values:
49
46
  raise RuleParsedValuesMismatch(f"Rule Params: {self.rule_params}, Parsed Values: {parsed_values_values}")
50
47
 
51
- def add_to_instance(self, field_instance: "BaseField"):
48
+ def get_validator(self, field_instance: "BaseField") -> Callable:
52
49
  field_class = RuleRegistry.get_type(self.field_type)
53
50
  if not isinstance(field_instance, field_class):
54
- raise InvalidFieldTypeError(
55
- f"Cannot add rule to {type(field_instance).__name__}, expected {self.field_type}."
56
- )
57
- self.rule_setter(self=field_instance, **self.resolved_values)
51
+ raise TypeError(f"Cannot add rule to {type(field_instance).__name__}, expected {self.field_type}.")
52
+ return self.rule_setter(self=field_instance, **self.resolved_values)
53
+
54
+ def get_front_end_repr(self) -> dict:
55
+ return {
56
+ "rule": self.field_rule,
57
+ "parsed_rule": self.parsed_rule,
58
+ "rule_params": self.rule_params,
59
+ "parsed_values": self.parsed_values,
60
+ }
@@ -56,23 +56,3 @@ alias_parameters_types = {
56
56
  "String": parse_string,
57
57
  "Strings": parse_array_of("String", parse_string),
58
58
  }
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
- # class Store()
67
- # pass
68
-
69
-
70
- # values = {"classes": ["UNCLASSIFIED"], "min_length": 5, "max_length": 50}
71
-
72
-
73
- # alias_parser = Parser("Value in {possible_values:Strings}", extra_types=alias_parameters_types)
74
- # # print(alias_parser.parse("Value In ['UNCLASSIFIED', 'CLASSIFIED']"))
75
-
76
- # with Store(values=values) as store:
77
- # print(alias_parser.parse("Value In $values.classes"))
78
- # print(store.)
@@ -0,0 +1,24 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TYPE_CHECKING, Dict, List, Union
3
+
4
+ from .Rule import Rule
5
+ from .Enums import LogicalOperator
6
+
7
+ if TYPE_CHECKING: # pragma: no cover
8
+ from ..field_types import BaseField
9
+
10
+ MatchedParsedRule = str
11
+ LogicalParsedRule = Dict[LogicalOperator, List["ParsedRule"]]
12
+ ParsedRule = Union[MatchedParsedRule, LogicalParsedRule]
13
+
14
+
15
+ class ProcessedRule(Rule, ABC):
16
+ parsed_rule: ParsedRule
17
+
18
+ @abstractmethod
19
+ def get_validator(self, field_instance: "BaseField"):
20
+ pass # pragma: no cover
21
+
22
+ @abstractmethod
23
+ def get_front_end_repr(self) -> dict:
24
+ pass # pragma: no cover
@@ -1,64 +1,85 @@
1
1
  from itertools import chain
2
- from collections import defaultdict
3
- from typing import TYPE_CHECKING, Dict, List, Type
2
+ from typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Type
3
+
4
4
 
5
5
  from .Rule import Rule
6
6
  from ..utils.logger_config import get_logger
7
+ from ..field_types.FieldTypes import FieldTypes
7
8
 
8
9
 
9
- if TYPE_CHECKING:
10
+ if TYPE_CHECKING: # pragma: no cover
10
11
  from field_types.BaseField import BaseField
11
12
 
12
13
  logger = get_logger(__name__)
13
14
 
14
15
 
15
- class RuleRegistry:
16
- rules: Dict[str, List[Rule]] = defaultdict(list)
17
- type_map: Dict[str, Type["BaseField"]] = {}
18
-
19
- @classmethod
20
- def register_rule(cls, field_rule: str, fixed_params: dict = None):
21
- def _register(func: callable):
22
- field_type, func_name = func.__qualname__.split(".")
23
- logger.debug("Registering function '%s' for %s. Rule: %s", func_name, field_type, field_rule)
16
+ class RuleMetadata(NamedTuple):
17
+ rule: str
18
+ fixed_params: dict
24
19
 
25
- rule = Rule(field_type, field_rule, func, fixed_params)
26
- cls.rules[field_type].append(rule)
27
- logger.debug("Function '%s' Registered", func_name)
28
- return func
29
20
 
30
- return _register
21
+ class RuleRegistry:
22
+ rules: Dict[str, List[Rule]] = {}
23
+ type_map: Dict[str, Type["BaseField"]] = {}
31
24
 
32
25
  @classmethod
33
26
  def register_field(cls, field_class: Type["BaseField"]) -> Type["BaseField"]:
34
- cls.type_map[field_class.__name__] = field_class
27
+ field_type_name = field_class.type_name
28
+ cls.type_map[field_class.type_name] = field_class
29
+ cls.rules[field_class.type_name] = []
30
+
31
+ for method in field_class.__dict__.values():
32
+ metadata: RuleMetadata = getattr(method, "_rule_metadata", None)
33
+ if metadata is None:
34
+ continue
35
+ rule = Rule(
36
+ field_type=field_type_name,
37
+ field_rule=metadata.rule,
38
+ rule_setter=method,
39
+ fixed_params=metadata.fixed_params
40
+ )
41
+ cls.add_rule(field_class, rule)
35
42
  return field_class
36
43
 
37
44
  @classmethod
38
- def get_type(cls, field_type: str) -> Type["BaseField"]:
39
- return cls.type_map.get(field_type)
45
+ def add_rule(cls, field_class: Type["BaseField"], rule: Rule):
46
+ if field_class.type_name not in cls.rules:
47
+ raise ValueError(f"Field not registered: {field_class.type_name}")
48
+ cls.rules[field_class.type_name].append(rule)
49
+
50
+ @classmethod
51
+ def get_type(cls, type_name: str) -> Type["BaseField"]:
52
+ return cls.type_map.get(type_name)
40
53
 
41
54
  @classmethod
42
55
  def get_rules_for(cls, field_class: Type["BaseField"]):
43
- if field_class.__name__ == "BaseField":
44
- return cls.rules["BaseField"]
56
+ if field_class.type_name == FieldTypes.BASE:
57
+ return cls.rules[FieldTypes.BASE]
45
58
  parent_rules = list(chain.from_iterable(cls.get_rules_for(p) for p in field_class.get_parents()))
46
- return cls.rules[field_class.__name__] + parent_rules
59
+ return cls.rules[field_class.type_name] + parent_rules
47
60
 
48
61
  @classmethod
49
62
  def get_rules_definition(cls):
50
63
  return [
51
64
  {
52
- "field": field_name,
53
- "parent_field": [p.__name__ for p in field_class.get_parents()],
54
- "rules": cls.rules.get(field_name, [])
65
+ "field": name,
66
+ "parent_field": [p.type_name for p in field_class.get_parents()],
67
+ "rules": cls.rules.get(name, [])
55
68
  }
56
- for field_name, field_class in cls.type_map.items()
69
+ for name, field_class in cls.type_map.items()
57
70
  ]
58
71
 
59
72
 
60
73
  def register_rule(rule: str, fixed_params: dict = None):
61
- return RuleRegistry.register_rule(rule, fixed_params)
74
+ def _register(func: Callable):
75
+ setattr(func, "_rule_metadata",
76
+ RuleMetadata(
77
+ rule=rule,
78
+ fixed_params=fixed_params or {}
79
+ )
80
+ )
81
+ return func
82
+ return _register
62
83
 
63
84
 
64
85
  def register_field(field_class: type):
@@ -1,14 +1,20 @@
1
1
  from .Rule import Rule
2
2
  from .Parser import RuleParser
3
+ from .Enums import LogicalOperator
3
4
  from .MatchedRule import MatchedRule
5
+ from .LogicalRule import LogicalRule
6
+ from .ProcessedRule import ProcessedRule
4
7
  from .RuleRegistry import RuleRegistry, register_rule, register_field
5
8
 
6
9
 
7
10
  __all__ = [
8
11
  "Rule",
9
- "MatchedRule",
10
12
  "RuleParser",
13
+ "MatchedRule",
14
+ "LogicalRule",
15
+ "ProcessedRule",
11
16
  "RuleRegistry",
12
17
  "register_rule",
13
18
  "register_field",
19
+ "LogicalOperator",
14
20
  ]
@@ -11,7 +11,7 @@ DEFAULT_LEVEL = "INFO"
11
11
  VALID_LOG_LEVEL = ["CRITICAL", "FATAL", "ERROR", "WARN", "WARNING", "INFO", "DEBUG", "NOTSET"]
12
12
  LOG_LEVEL = environ.get("LOG_LEVEL", DEFAULT_LEVEL)
13
13
 
14
- if LOG_LEVEL not in VALID_LOG_LEVEL:
14
+ if LOG_LEVEL not in VALID_LOG_LEVEL: # pragma: no cover
15
15
  LOG_LEVEL = DEFAULT_LEVEL
16
16
 
17
17
 
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: data-sitter
3
+ Version: 0.1.6
4
+ Summary: A Python library that reads data contracts and generates Pydantic models for seamless data validation.
5
+ Author-email: Lázaro Pereira Candea <lazaro@candea.es>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: python-dotenv==1.0.1
9
+ Requires-Dist: PyYAML==6.0.2
10
+ Requires-Dist: parse_type==0.6.4
11
+ Requires-Dist: pydantic==2.10.5
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest==8.3.5; extra == "dev"
14
+ Requires-Dist: pytest-cov==6.0.0; extra == "dev"
15
+ Requires-Dist: pytest-mock==3.14.0; extra == "dev"
16
+ Requires-Dist: twine==6.1.0; extra == "dev"
17
+ Requires-Dist: build==1.2.2.post1; extra == "dev"
18
+
19
+ # Data-Sitter
20
+
21
+ ![Coverage](./coverage.svg)
22
+
23
+ ## Overview
24
+
25
+ Data-Sitter is a Python library designed to simplify data validation by converting data contracts into Pydantic models. This allows for easy and efficient validation of structured data, ensuring compliance with predefined rules and constraints.
26
+
27
+ ## Features
28
+
29
+ - Define structured data contracts in JSON format.
30
+ - Generate Pydantic models automatically from contracts.
31
+ - Enforce validation rules at the field level.
32
+ - Support for rule references within the contract.
33
+
34
+ ## Installation
35
+
36
+ ```sh
37
+ pip install data-sitter
38
+ ```
39
+
40
+ ## Development and Deployment
41
+
42
+ ### CI/CD Pipeline
43
+
44
+ The project uses GitHub Actions for continuous integration and deployment:
45
+
46
+ 1. **Pull Request Checks**
47
+ - Automatically checks if the version has been bumped in `pyproject.toml`
48
+ - Fails if the version is the same as in the main branch
49
+ - Ensures every PR includes a version update
50
+
51
+ 2. **Automatic Releases**
52
+ - When code is merged to the main branch:
53
+ - Builds the package
54
+ - Publishes to PyPI automatically
55
+ - Uses PyPI API token for secure authentication
56
+
57
+ To set up the CI/CD pipeline:
58
+
59
+ 1. Create a PyPI API token:
60
+ - Go to [PyPI Account Settings](https://pypi.org/manage/account/)
61
+ - Create a new API token with "Upload" scope
62
+ - Copy the token
63
+
64
+ 2. Add the token to GitHub:
65
+ - Go to your repository's Settings > Secrets and variables > Actions
66
+ - Create a new secret named `PYPI_API_TOKEN`
67
+ - Paste your PyPI API token
68
+
69
+ ### Setting Up Development Environment
70
+
71
+ To set up a development environment with all the necessary tools, install the package with development dependencies:
72
+
73
+ ```sh
74
+ pip install -e ".[dev]"
75
+ ```
76
+
77
+ This will install:
78
+ - The package in editable mode
79
+ - Testing tools (pytest, pytest-cov, pytest-mock)
80
+ - Build tools (build, twine)
81
+
82
+ ### Building the Package
83
+
84
+ To build the package, run:
85
+
86
+ ```sh
87
+ python -m build
88
+ ```
89
+
90
+ This will create a `dist` directory containing both a source distribution (`.tar.gz`) and a wheel (`.whl`).
91
+
92
+ ### Deploying to PyPI
93
+
94
+ To upload to PyPI:
95
+
96
+ ```sh
97
+ twine upload dist/*
98
+ ```
99
+
100
+ You'll be prompted for your PyPI username and password. For security, it's recommended to use an API token instead of your password.
101
+
102
+ ## Usage
103
+
104
+ ### Creating a Pydantic Model from a Contract
105
+
106
+ To convert a data contract into a Pydantic model, follow these steps:
107
+
108
+ ```python
109
+ from data_sitter import Contract
110
+
111
+ contract_dict = {
112
+ "name": "test",
113
+ "fields": [
114
+ {
115
+ "name": "FID",
116
+ "type": "Integer",
117
+ "rules": ["Positive"]
118
+ },
119
+ {
120
+ "name": "SECCLASS",
121
+ "type": "String",
122
+ "rules": [
123
+ "Validate Not Null",
124
+ "Value In ['UNCLASSIFIED', 'CLASSIFIED']",
125
+ ]
126
+ }
127
+ ],
128
+ }
129
+
130
+ contract = Contract.from_dict(contract_dict)
131
+ pydantic_contract = contract.pydantic_model
132
+ ```
133
+
134
+ ### Using Rule References
135
+
136
+ Data-Sitter allows you to define reusable values in the `values` key and reference them in field rules using `$values.[key]`. For example:
137
+
138
+ ```json
139
+ {
140
+ "name": "example_contract",
141
+ "fields": [
142
+ {
143
+ "name": "CATEGORY",
144
+ "type": "String",
145
+ "rules": ["Value In $values.categories"]
146
+ },
147
+ {
148
+ "name": "NAME",
149
+ "type": "String",
150
+ "rules": [
151
+ "Length Between $values.min_length and $values.max_length"
152
+ ]
153
+ }
154
+
155
+ ],
156
+ "values": {"categories": ["A", "B", "C"], "min_length": 5,"max_length": 50}
157
+ }
158
+ ```
159
+
160
+ ## Available Rules
161
+
162
+ The available validation rules can be retrieved programmatically:
163
+
164
+ ```python
165
+ from data_sitter import RuleRegistry
166
+
167
+ rules = RuleRegistry.get_rules_definition()
168
+ print(rules)
169
+ ```
170
+
171
+ ### Rule Definitions
172
+
173
+ Below are the available rules grouped by field type:
174
+
175
+ #### Base
176
+
177
+ - Is not null
178
+
179
+ #### String - (Inherits from `Base`)
180
+
181
+ - Is not empty
182
+ - Starts with {prefix:String}
183
+ - Ends with {suffix:String}
184
+ - Is not one of {possible_values:Strings}
185
+ - Is one of {possible_values:Strings}
186
+ - Has length between {min_val:Integer} and {max_val:Integer}
187
+ - Has maximum length {max_len:Integer}
188
+ - Has minimum length {min_len:Integer}
189
+ - Is uppercase
190
+ - Is lowercase
191
+ - Matches regex {pattern:String}
192
+ - Is valid email
193
+ - Is valid URL
194
+ - Has no digits
195
+
196
+ #### Numeric - (Inherits from `Base`)
197
+
198
+ - Is not zero
199
+ - Is positive
200
+ - Is negative
201
+ - Is at least {min_val:Number}
202
+ - Is at most {max_val:Number}
203
+ - Is greater than {threshold:Number}
204
+ - Is less than {threshold:Number}
205
+ - Is not between {min_val:Number} and {max_val:Number}
206
+ - Is between {min_val:Number} and {max_val:Number}
207
+
208
+ #### Integer - (Inherits from `Numeric`)
209
+
210
+ #### Float - (Inherits from `Numeric`)
211
+
212
+ - Has at most {decimal_places:Integer} decimal places
213
+
214
+ ## Contributing
215
+
216
+ Contributions are welcome! Feel free to submit issues or pull requests in the [GitHub repository](https://github.com/lcandea/data-sitter).
217
+
218
+ ## License
219
+
220
+ Data-Sitter is licensed under the MIT License.
@@ -0,0 +1,30 @@
1
+ data_sitter/Contract.py,sha256=ykeBA_gr7r7MO4FYvdDrstUzGiq7dyIIipkOZRk8qkA,4042
2
+ data_sitter/FieldResolver.py,sha256=Bh7_MTTO7E87S31dQq3tvkL9E_K-4EDlh3NJSn0eLU0,2732
3
+ data_sitter/Validation.py,sha256=5jdIQZyTrEmXZ_SJP0lq-EEFKrGbYH6z4EQ56oFR7Ck,1474
4
+ data_sitter/__init__.py,sha256=qbE-wU8ELMFwOMG4UTK0lmzn5XF2MK3rc22E8ROgypo,113
5
+ data_sitter/cli.py,sha256=SBmxNC508qt8-C4x2IS6XNZLihtfeALw34QLLYr_p_Q,1686
6
+ data_sitter/field_types/BaseField.py,sha256=2s5wJjz1NoNWvSa-69mMxVf7f5wptuxr6UGVubM6MAQ,1997
7
+ data_sitter/field_types/FieldTypes.py,sha256=ntuguQtLnVon1cB2YvG0p2c1r0zk67qw6o4qfJHxLlY,158
8
+ data_sitter/field_types/FloatField.py,sha256=gHmiSg8Eft57T_J8covrEctMEGa0zuT6R3xJP2UlyIY,945
9
+ data_sitter/field_types/IntegerField.py,sha256=Ll6eool8Rwo2pzyXQz95TjJpzdgW9ShtCK1uHmOi_pQ,213
10
+ data_sitter/field_types/NumericField.py,sha256=uJ6ZB8vJg0xsY1grrVL6k8Heygm1O84fQjEtVg1uJjw,2916
11
+ data_sitter/field_types/StringField.py,sha256=ayxo5d_9xzNR9rAWS6dHNKiRapkFdsCxMtT4K5Qb7ek,4493
12
+ data_sitter/field_types/__init__.py,sha256=GdssttQCJksGcZn7oPM53vOsqOL6R5xRiRJDEtr38Ww,293
13
+ data_sitter/rules/Enums.py,sha256=W-3vXP7NWgkgZexrn8J9EytqZoaWK3EwIX0q5O3QJKY,105
14
+ data_sitter/rules/LogicalRule.py,sha256=4HRPw62HSpmMz_Yr2en4s1eVijlPurPDBHfwSliBS3o,2794
15
+ data_sitter/rules/MatchedRule.py,sha256=DudYNI50EpeIvsFQ7cMpKI1fziBG_yfMPNUZ7OWdygo,2162
16
+ data_sitter/rules/ProcessedRule.py,sha256=5EWjTJ6wgDih1cm8uxJOghLDQs3XK_ywGTGF5hhNly8,640
17
+ data_sitter/rules/Rule.py,sha256=xE31dUwLHD3IzcmPfdqLuXS_rmw-kFdIaCKHvpIArig,2115
18
+ data_sitter/rules/RuleRegistry.py,sha256=1ZMl-5u-6DZ1AGYZA77s6DpAxeN64EFEF-XKVKeQy-U,2698
19
+ data_sitter/rules/__init__.py,sha256=SHQZYp4VNzuygP-ZPegEXwmgj-_oUqEchi9TdbasA6U,465
20
+ data_sitter/rules/Parser/RuleParser.py,sha256=7biF5N3Cf3Rf5bgB4pXUpBaZ4r5EL1I9YHvSTjdydBA,2127
21
+ data_sitter/rules/Parser/__init__.py,sha256=F8qJ7luwq0C65e7pNOzBHB2sF1lMcvIFYfDNJj6XQTc,205
22
+ data_sitter/rules/Parser/alias_parameters_parser.py,sha256=xUgOFJCm42w1eUmZOQ2OsOhCsKGHev5g4gsm_kizciA,1529
23
+ data_sitter/rules/Parser/parser_utils.py,sha256=ypI021uYJTsHAoKGShAfnhd5xQGtqqTGTHozleefsLQ,642
24
+ data_sitter/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ data_sitter/utils/logger_config.py,sha256=fBbDNZOsmDnO6TtLHI1tty4EXi2AnbrA-OgkhXcm1Aw,1235
26
+ data_sitter-0.1.6.dist-info/METADATA,sha256=H3QVZTUxe4F2FtWVEhe0JEmlgZG4n8krQCyBqp5XgdQ,5597
27
+ data_sitter-0.1.6.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
28
+ data_sitter-0.1.6.dist-info/entry_points.txt,sha256=1I7xxqFZvA78wmDx7NGavttAb8JFWM3Wxgehftx_5C4,53
29
+ data_sitter-0.1.6.dist-info/top_level.txt,sha256=Q7N21PYeqIdRbDvZQCJXhbbv0PFIf876gu1_DpInH_E,12
30
+ data_sitter-0.1.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,9 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: data-sitter
3
- Version: 0.1.4
4
- Summary: A Python library that reads data contracts and generates Pydantic models for seamless data validation.
5
- Author-email: Lázaro Pereira Candea <lazaro@candea.es>
6
- Requires-Dist: python-dotenv==1.0.1
7
- Requires-Dist: PyYAML==6.0.2
8
- Requires-Dist: parse_type==0.6.4
9
- Requires-Dist: pydantic==2.10.5
@@ -1,26 +0,0 @@
1
- data_sitter/Contract.py,sha256=ow6CdT7XRbxBy4tY5av3_L9d8qv59nw70ih-jVMKlBU,4561
2
- data_sitter/FieldResolver.py,sha256=0XSA-doOY4cM_Ikd09hUxykYZOLR5pHwkbNiTmdtweQ,1819
3
- data_sitter/Validation.py,sha256=5jdIQZyTrEmXZ_SJP0lq-EEFKrGbYH6z4EQ56oFR7Ck,1474
4
- data_sitter/__init__.py,sha256=qbE-wU8ELMFwOMG4UTK0lmzn5XF2MK3rc22E8ROgypo,113
5
- data_sitter/cli.py,sha256=1ICrtokqV5RvvWhzWKAeS5ZUSUpiviQyy2JSK71ER10,1666
6
- data_sitter/field_types/BaseField.py,sha256=mCEy9hFhwus1gW1P6ctbUCUrZ9rJMtreOvfiy9MkO5Q,1799
7
- data_sitter/field_types/FloatField.py,sha256=75zYGwI65GYIiZWgb5cwb3QoWukp3X-YcrmqhIxOe1w,675
8
- data_sitter/field_types/IntegerField.py,sha256=o__5z3bg6wsx7FIfJbBYZW5b760-WSZw_05J-OSKXR0,147
9
- data_sitter/field_types/NumericField.py,sha256=s8aEkk42HYY30citCaBjIynLGfKE7gPayvVq3fdVd2Y,2981
10
- data_sitter/field_types/StringField.py,sha256=wXlfCTPGPDr2j_lHW6LqtAFKNBoff6nk0tE5Xt02xsQ,4645
11
- data_sitter/field_types/__init__.py,sha256=GdssttQCJksGcZn7oPM53vOsqOL6R5xRiRJDEtr38Ww,293
12
- data_sitter/rules/MatchedRule.py,sha256=uHXuo7Np-Bq7IOHaHMYFmYFPRT8aYDEKCdFKcvWf4DM,1946
13
- data_sitter/rules/Rule.py,sha256=xE31dUwLHD3IzcmPfdqLuXS_rmw-kFdIaCKHvpIArig,2115
14
- data_sitter/rules/RuleRegistry.py,sha256=iqxcbzypk-_S4ukASNjEe9TD7J-f2psXWR0GVOPlK0E,2134
15
- data_sitter/rules/__init__.py,sha256=_cTO0SUkW_WW2VBx2NGd8n5TUio7gptkBr9MorW2ZZk,289
16
- data_sitter/rules/Parser/RuleParser.py,sha256=7biF5N3Cf3Rf5bgB4pXUpBaZ4r5EL1I9YHvSTjdydBA,2127
17
- data_sitter/rules/Parser/__init__.py,sha256=F8qJ7luwq0C65e7pNOzBHB2sF1lMcvIFYfDNJj6XQTc,205
18
- data_sitter/rules/Parser/alias_parameters_parser.py,sha256=jsx_JWzkA4lY2nq4hzc4fG7_nnh7yLxmVj6WIP1Mm68,1933
19
- data_sitter/rules/Parser/parser_utils.py,sha256=ypI021uYJTsHAoKGShAfnhd5xQGtqqTGTHozleefsLQ,642
20
- data_sitter/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- data_sitter/utils/logger_config.py,sha256=w9E4jWfGJnkC9tZz4qrolSqglKm4jEB8l6vjC-qfj8A,1215
22
- data_sitter-0.1.4.dist-info/METADATA,sha256=dKf83KLklS2JXoiWJCcEBGi6EqKYUcVn5XPXoL-bQBc,353
23
- data_sitter-0.1.4.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
24
- data_sitter-0.1.4.dist-info/entry_points.txt,sha256=1I7xxqFZvA78wmDx7NGavttAb8JFWM3Wxgehftx_5C4,53
25
- data_sitter-0.1.4.dist-info/top_level.txt,sha256=Q7N21PYeqIdRbDvZQCJXhbbv0PFIf876gu1_DpInH_E,12
26
- data_sitter-0.1.4.dist-info/RECORD,,