datavalue 0.1.5__tar.gz → 0.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datavalue
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Librería de tipos de datos primitivos y complejos
5
5
  Author: Specter
6
6
  Requires-Python: >=3.10
@@ -0,0 +1,3 @@
1
+ # Library import
2
+ from .classes.primitive_data import PrimitiveData
3
+ from .classes.complex_data import ComplexData
@@ -0,0 +1,253 @@
1
+ # Library import
2
+ from typing import Type, Optional, Any, Iterable
3
+ from .. import exceptions
4
+ from .primitive_data import PrimitiveData
5
+ import json
6
+
7
+ # Classes definition
8
+ class ComplexData:
9
+ def __init__(self,
10
+ data_type: Type[list] | Type[tuple] | Type[set] | Type[frozenset] | Type[dict],
11
+ value: Any,
12
+ maximum_length: Optional[int] = None, minimum_length: Optional[int] = None,
13
+ possible_values: Optional[Iterable] = None,
14
+
15
+ data_class: Optional[bool] = False
16
+ ) -> None:
17
+ # Instance properties assignment
18
+ self.data_type = data_type
19
+ self.value = value
20
+ self.maximum_length = maximum_length; self.minimum_length = minimum_length
21
+ self.possible_values = possible_values
22
+ self.data_class = data_class
23
+
24
+ # Validate constructor parameters
25
+ if self.possible_values:
26
+ # Base validation: it has to be a collection
27
+ if not isinstance(self.possible_values, (list, tuple)):
28
+ raise ValueError(f"The possible values has to be a list/tuple. Received: {type(self.possible_values).__name__}")
29
+
30
+ # Validation to collections (list, tuple, set, frozenset)
31
+ if self.data_type != dict:
32
+ if not isinstance(self.possible_values, (list, tuple)):
33
+ raise ValueError(f"Possible values for {self.data_type.__name__} must be exactly one list/tuple containing the options.")
34
+
35
+ # Specific validation for dicts
36
+ else: # self.data_type == dict
37
+ if len(self.possible_values) not in (1, 2):
38
+ raise ValueError("Possible values for dict must be 1 (keys) or 2 (keys, values) list/tuples.")
39
+
40
+ # Validate the first element
41
+ if not isinstance(self.possible_values[0], (list, tuple)):
42
+ raise ValueError("The first element of possible_values for dict must be a list/tuple of keys.")
43
+
44
+ # Validate the second element
45
+ if len(self.possible_values) == 2 and not isinstance(self.possible_values[1], (list, tuple)):
46
+ raise ValueError("The second element of possible_values for dict must be a list/tuple of value validators.")
47
+
48
+ # Execute instance data validation
49
+ if not self.data_class:
50
+ self.validate()
51
+
52
+ # Private methods
53
+ def _is_match(self, element: Any, schema: Any) -> bool:
54
+ # Validate data types
55
+ if isinstance(schema, (PrimitiveData, ComplexData)):
56
+ try:
57
+ return schema.validate(element)
58
+ except:
59
+ return False
60
+
61
+ # Validate class data types
62
+ if isinstance(schema, type):
63
+ return isinstance(element, schema)
64
+
65
+ # Validate literal values
66
+ return element == schema
67
+
68
+ def _validate_collection(self, data: Any) -> bool:
69
+ element_index: int = 0
70
+ for element in data:
71
+ if not any(self._is_match(element, validator) for validator in self.possible_values):
72
+ raise ValueError(f"[ComplexData] Element: {element}, on index: {element_index} is not allowed.")
73
+ element_index += 1
74
+
75
+ # Return results
76
+ return True
77
+
78
+ def _validate_dictionary(self, data: Any) -> bool:
79
+ # Validate keys and (if applies) values
80
+ if not isinstance(self.possible_values, (list, tuple)) or len(self.possible_values) != 2:
81
+ keys_schema = self.possible_values
82
+ values_schema = None
83
+ else:
84
+ keys_schema, values_schema = self.possible_values
85
+
86
+ for key, value in data.items():
87
+ # Validación de Claves (Debe ser un iterable de opciones)
88
+ if keys_schema:
89
+ if not any(self._is_match(key, validator) for validator in keys_schema):
90
+ raise ValueError(f"[ComplexData] Invalid key: {key}")
91
+
92
+ # Validación de Valores
93
+ if values_schema:
94
+ if not any(self._is_match(value, validator) for validator in values_schema):
95
+ raise ValueError(f"[ComplexData] Invalid value '{value}' for key '{key}'")
96
+
97
+ return True
98
+
99
+ def _serialize_recursive(self, element: Any) -> Any:
100
+ # 1. Caso: Instancias de tus clases
101
+ if isinstance(element, (PrimitiveData, ComplexData)):
102
+ return {
103
+ "__type__": element.__class__.__name__,
104
+ "content": element.to_dict()
105
+ }
106
+
107
+ # 2. Caso: Tipos de datos (clases como str, int, dict)
108
+ if isinstance(element, type):
109
+ return {"__class__": element.__name__}
110
+
111
+ # 3. Caso: Colecciones estándar (recursión profunda)
112
+ if isinstance(element, (list, tuple, set, frozenset)):
113
+ return [self._serialize_recursive(i) for i in element]
114
+
115
+ if isinstance(element, dict):
116
+ return {str(k): self._serialize_recursive(v) for k, v in element.items()}
117
+
118
+ # 4. Caso: Literales serializables (int, str, float, bool, None)
119
+ return element
120
+
121
+ def _deserialize_recursive(self, element: Any) -> Any:
122
+ SAFE_TYPES = {
123
+ "list": list, "tuple": tuple, "set": set, "frozenset": frozenset,
124
+ "dict": dict, "str": str, "int": int, "float": float, "bool": bool,
125
+ "bytes": bytes, "bytearray": bytearray
126
+ }
127
+
128
+ # A. Manejo de Diccionarios (Estructuras u Objetos Serializados)
129
+ if isinstance(element, dict):
130
+ # Caso 1: Es un objeto serializado (PrimitiveData o ComplexData)
131
+ if "__type__" in element:
132
+ obj_type = element["__type__"]
133
+ content = element["content"]
134
+
135
+ if obj_type == "PrimitiveData":
136
+ return PrimitiveData(value=None, data_type=None, data_class=True).from_dict(content)
137
+ elif obj_type == "ComplexData":
138
+ return self.from_dict(content)
139
+ else:
140
+ raise ValueError(f"Unknown serialized object type: {obj_type}")
141
+
142
+ # Caso 2: Es una referencia a un tipo de clase (__class__)
143
+ if "__class__" in element:
144
+ type_name = element["__class__"]
145
+ if type_name in SAFE_TYPES:
146
+ return SAFE_TYPES[type_name]
147
+ raise ValueError(f"Type '{type_name}' is not allowed or unknown.")
148
+
149
+ # Caso 3: Es un diccionario de datos común (recurse keys & values)
150
+ return {k: self._deserialize_recursive(v) for k, v in element.items()}
151
+
152
+ # B. Manejo de Listas (recurse items)
153
+ if isinstance(element, list):
154
+ return [self._deserialize_recursive(item) for item in element]
155
+
156
+ # C. Literales (retorno directo)
157
+ return element
158
+
159
+
160
+ # Public methods
161
+ def to_dict(self) -> dict:
162
+ return {
163
+ "DATA_TYPE":self.data_type.__name__ if hasattr(self.data_type, '__name__') else str(self.data_type),
164
+ "VALUE":self._serialize_recursive(self.value),
165
+ "MAXIMUM_LENGTH":self.maximum_length,
166
+ "MINIMUM_LENGTH":self.minimum_length,
167
+ "POSSIBLE_VALUES":self._serialize_recursive(self.possible_values) if self.possible_values is not None else None,
168
+ "DATA_CLASS":self.data_class
169
+ }
170
+
171
+
172
+ def from_dict(self, data: dict) -> 'ComplexData':
173
+ # 1. Secure types mapping
174
+ SAFE_TYPES = {
175
+ "list": list, "tuple": tuple, "set": set, "frozenset": frozenset,
176
+ "dict": dict, "str": str, "int": int, "float": float, "bool": bool,
177
+ "bytes": bytes, "bytearray": bytearray
178
+ }
179
+
180
+ # 3. Validación y Reconstrucción del Root
181
+ # Recuperamos el tipo de dato principal
182
+ raw_type = data.get("DATA_TYPE")
183
+ data_type = SAFE_TYPES.get(raw_type)
184
+ if not data_type:
185
+ raise TypeError(f"Invalid root data type: {raw_type}")
186
+
187
+ # Procesamos los possible_values con el motor recursivo
188
+ raw_possible = data.get("POSSIBLE_VALUES")
189
+ possible_values = self._deserialize_recursive(raw_possible) if raw_possible is not None else None
190
+
191
+ # Corrección de tipo para tuplas (JSON no tiene tuplas, devuelve listas)
192
+ # Si su __init__ es estricto y requiere tupla para possible_values, convertimos aquí:
193
+ if isinstance(possible_values, list) and data_type != dict:
194
+ possible_values = tuple(possible_values)
195
+
196
+ # Para dicts, mantenemos la lista de listas o convertimos según su preferencia estricta
197
+ if isinstance(possible_values, list) and data_type == dict:
198
+ # Opcional: convertir sub-listas a tuplas si su validador lo prefiere,
199
+ # aunque su validación actual acepta listas.
200
+ pass
201
+
202
+ return ComplexData(
203
+ data_type=data_type,
204
+ value=data.get("VALUE"), # Asumimos valor literal o serializable simple
205
+ maximum_length=data.get("MAXIMUM_LENGTH"),
206
+ minimum_length=data.get("MINIMUM_LENGTH"),
207
+ possible_values=possible_values,
208
+ data_class=data.get("DATA_CLASS", False)
209
+ )
210
+
211
+
212
+ def from_json(self, text_content: str) -> 'ComplexData':
213
+ try:
214
+ data = json.loads(text_content)
215
+ except json.JSONDecodeError as e:
216
+ raise ValueError(f"Invalid JSON: {e}")
217
+ return self.from_dict(data)
218
+
219
+ def to_json(self) -> str:
220
+ return json.dumps(self.to_dict(), indent=4)
221
+
222
+ def validate(self, data: Any = None) -> bool:
223
+ # Determine objective data
224
+ if data is None:
225
+ objective_data = self.value
226
+ else:
227
+ objective_data = data
228
+
229
+ # Data type validation
230
+ if not isinstance(objective_data, self.data_type):
231
+ raise exceptions.DataTypeException(
232
+ f"Incorrect data type.\nExpected: {self.data_type.__name__} - Received: {type(objective_data).__name__}"
233
+ )
234
+
235
+ # Length validation
236
+ current_length = len(objective_data)
237
+
238
+ if self.minimum_length is not None and current_length < self.minimum_length:
239
+ raise ValueError(f"Minimum length not reached: {current_length} < {self.minimum_length}")
240
+
241
+ if self.maximum_length is not None and current_length > self.maximum_length:
242
+ raise ValueError(f"Maximum length reached: {current_length} > {self.maximum_length}")
243
+
244
+ # Content validation and recurse
245
+ if self.possible_values:
246
+ # Validate dictionaries
247
+ if isinstance(objective_data, dict):
248
+ self._validate_dictionary(objective_data)
249
+ else:
250
+ self._validate_collection(objective_data)
251
+
252
+ # Return results
253
+ return True
@@ -2,6 +2,7 @@
2
2
  import re
3
3
  from typing import Type, Optional, Any, Iterable, Union
4
4
  from .. import exceptions
5
+ import json
5
6
 
6
7
  # Classes definition
7
8
  class PrimitiveData:
@@ -54,7 +55,62 @@ class PrimitiveData:
54
55
  "REGULAR_EXPRESSION":self.regular_expression,
55
56
  "DATA_CLASS":self.data_class
56
57
  }
58
+
59
+ def to_json(self) -> str:
60
+ return json.dumps(self.to_dict(), indent=4)
61
+
62
+
63
+ def from_dict(self, data: dict) -> 'PrimitiveData':
64
+ # Expected keys definition
65
+ expected_keys = {
66
+ "DATA_TYPE", "VALUE", "MAXIMUM_LENGTH", "MINIMUM_LENGTH",
67
+ "MAXIMUM_SIZE", "MINIMUM_SIZE", "POSSIBLE_VALUES",
68
+ "REGULAR_EXPRESSION", "DATA_CLASS"
69
+ }
70
+
71
+ # Verify unknown keys on the table
72
+ unknown_keys = set(data.keys()) - expected_keys
73
+ if unknown_keys:
74
+ raise ValueError(f"Unknown keys in data structure: {unknown_keys}")
75
+
76
+ # Secure type mapping
77
+ type_mapping = {
78
+ "str": str,
79
+ "int": int,
80
+ "float": float,
81
+ "bool": bool,
82
+ "bytes": bytes,
83
+ "bytearray": bytearray,
84
+ "NoneType": type(None)
85
+ }
86
+
87
+ type_str = data.get("DATA_TYPE")
88
+ real_type = type_mapping.get(type_str)
89
+
90
+ if real_type is None and type_str != "None":
91
+ raise TypeError(f"Unsupported or unsafe data type for deserialization: {type_str}")
92
+
93
+ # return instance result
94
+ return PrimitiveData(
95
+ data_type=real_type,
96
+ value=data.get("VALUE"),
97
+ maximum_length=data.get("MAXIMUM_LENGTH"),
98
+ minimum_length=data.get("MINIMUM_LENGTH"),
99
+ maximum_size=data.get("MAXIMUM_SIZE"),
100
+ minimum_size=data.get("MINIMUM_SIZE"),
101
+ possible_values=data.get("POSSIBLE_VALUES"),
102
+ regular_expression=data.get("REGULAR_EXPRESSION"),
103
+ data_class=data.get("DATA_CLASS", False)
104
+ )
57
105
 
106
+ def from_json(self, text_content: str) -> 'PrimitiveData':
107
+ try:
108
+ data_table = json.loads(text_content)
109
+ except json.JSONDecodeError as Error:
110
+ raise ValueError(f"Invalid JSON format: {Error}")
111
+
112
+ return self.from_dict(data_table)
113
+
58
114
  def validate(self, data: Optional[Any] = None) -> bool:
59
115
  # Define the data to validate
60
116
  if data is None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datavalue
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Librería de tipos de datos primitivos y complejos
5
5
  Author: Specter
6
6
  Requires-Python: >=3.10
@@ -6,6 +6,6 @@ datavalue.egg-info/SOURCES.txt
6
6
  datavalue.egg-info/dependency_links.txt
7
7
  datavalue.egg-info/top_level.txt
8
8
  datavalue/classes/__init__.py
9
+ datavalue/classes/complex_data.py
9
10
  datavalue/classes/primitive_data.py
10
- datavalue/exceptions/__init__.py
11
- tests/test.py
11
+ datavalue/exceptions/__init__.py
@@ -0,0 +1 @@
1
+ datavalue
@@ -4,11 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "datavalue"
7
- version = "0.1.5"
7
+ version = "0.1.7"
8
8
  description = "Librería de tipos de datos primitivos y complejos"
9
9
  readme = "README.md"
10
10
  authors = [{ name="Specter" }]
11
11
  requires-python = ">=3.10"
12
12
 
13
- [tool.setuptools]
14
- packages = { find = {} }
13
+ [tool.setuptools.packages.find]
14
+ include = ["datavalue*"]
15
+ exclude = ["docs*"]
@@ -1,2 +0,0 @@
1
- # Library import
2
- from .classes.primitive_data import PrimitiveData
@@ -1,4 +0,0 @@
1
- datavalue
2
- dist
3
- docs
4
- tests
@@ -1,62 +0,0 @@
1
- # Library import
2
- from datavalue import PrimitiveData
3
-
4
- # Phone number validation
5
- print("="*10)
6
- print("Validating phone number...")
7
- phone_number = PrimitiveData(
8
- data_type=str,
9
- value="+34600111222",
10
- minimum_length=7, # Minimum character length
11
- maximum_length=15, # Maximum character length
12
- minimum_size=None, # Not validate the minimum number
13
- maximum_size=None, # Not validate the maximum number,
14
- possible_values=None, # Not specify obligatory possible values
15
- regular_expression=r"^\+[1-9]\d{6,14}$"
16
- )
17
-
18
- print("Phone number validated sucessfully:", phone_number.value)
19
-
20
- # Connection port validation
21
- print("="*10)
22
- print("Validating connection port...")
23
- connection_port = PrimitiveData(
24
- data_type=int,
25
- value=45321,
26
- minimum_length=1, # Maximum digits
27
- maximum_length=5, # Minimum digits
28
- minimum_size=1, # Minimum numerical value
29
- maximum_size=65535, # Maximum numerical value
30
- possible_values=None, # Possible options
31
- regular_expression=None # Regular expression applied
32
- )
33
- print("Connection port validated:", connection_port.value)
34
-
35
- # Transport protocol validation
36
- print("="*10)
37
- print("Validating transport protocol...")
38
- transport_protocol = PrimitiveData(
39
- data_type=str,
40
- value="TCP",
41
- maximum_length=None,
42
- minimum_length=None,
43
- minimum_size=None,
44
- maximum_size=None,
45
- possible_values=("TCP", "UDP"),
46
- regular_expression=None
47
- )
48
- print("Transport protocol validated:", transport_protocol.value)
49
-
50
- # Validating IPv4 address
51
- print("="*10)
52
- print("Validating IPv4 address...")
53
- ip_address = PrimitiveData(
54
- data_type=str,
55
- value="192.168.0.1",
56
- maximum_length=15,
57
- minimum_length=7,
58
- maximum_size=None, minimum_size=None,
59
- possible_values=None,
60
- regular_expression=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
61
- )
62
- print("IPv4 address validated:", ip_address.value)
File without changes
File without changes