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.
- {datavalue-0.1.5 → datavalue-0.1.7}/PKG-INFO +1 -1
- datavalue-0.1.7/datavalue/__init__.py +3 -0
- datavalue-0.1.7/datavalue/classes/complex_data.py +253 -0
- {datavalue-0.1.5 → datavalue-0.1.7}/datavalue/classes/primitive_data.py +56 -0
- {datavalue-0.1.5 → datavalue-0.1.7}/datavalue.egg-info/PKG-INFO +1 -1
- {datavalue-0.1.5 → datavalue-0.1.7}/datavalue.egg-info/SOURCES.txt +2 -2
- datavalue-0.1.7/datavalue.egg-info/top_level.txt +1 -0
- {datavalue-0.1.5 → datavalue-0.1.7}/pyproject.toml +4 -3
- datavalue-0.1.5/datavalue/__init__.py +0 -2
- datavalue-0.1.5/datavalue.egg-info/top_level.txt +0 -4
- datavalue-0.1.5/tests/test.py +0 -62
- {datavalue-0.1.5 → datavalue-0.1.7}/README.md +0 -0
- {datavalue-0.1.5 → datavalue-0.1.7}/datavalue/classes/__init__.py +0 -0
- {datavalue-0.1.5 → datavalue-0.1.7}/datavalue/exceptions/__init__.py +0 -0
- {datavalue-0.1.5 → datavalue-0.1.7}/datavalue.egg-info/dependency_links.txt +0 -0
- {datavalue-0.1.5 → datavalue-0.1.7}/setup.cfg +0 -0
|
@@ -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:
|
|
@@ -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.
|
|
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
|
-
|
|
13
|
+
[tool.setuptools.packages.find]
|
|
14
|
+
include = ["datavalue*"]
|
|
15
|
+
exclude = ["docs*"]
|
datavalue-0.1.5/tests/test.py
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|