openlinktoken 2.0.0__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.
- openlinktoken/__init__.py +15 -0
- openlinktoken/attributes/__init__.py +17 -0
- openlinktoken/attributes/attribute.py +26 -0
- openlinktoken/attributes/attribute_expression.py +232 -0
- openlinktoken/attributes/attribute_loader.py +48 -0
- openlinktoken/attributes/base_attribute.py +31 -0
- openlinktoken/attributes/combined_attribute.py +69 -0
- openlinktoken/attributes/general/__init__.py +17 -0
- openlinktoken/attributes/general/date_attribute.py +117 -0
- openlinktoken/attributes/general/decimal_attribute.py +86 -0
- openlinktoken/attributes/general/integer_attribute.py +78 -0
- openlinktoken/attributes/general/record_id_attribute.py +41 -0
- openlinktoken/attributes/general/string_attribute.py +55 -0
- openlinktoken/attributes/general/year_attribute.py +78 -0
- openlinktoken/attributes/person/__init__.py +25 -0
- openlinktoken/attributes/person/age_attribute.py +41 -0
- openlinktoken/attributes/person/birth_date_attribute.py +40 -0
- openlinktoken/attributes/person/birth_year_attribute.py +39 -0
- openlinktoken/attributes/person/canadian_postal_code_attribute.py +165 -0
- openlinktoken/attributes/person/first_name_attribute.py +112 -0
- openlinktoken/attributes/person/last_name_attribute.py +146 -0
- openlinktoken/attributes/person/postal_code_attribute.py +42 -0
- openlinktoken/attributes/person/sex_attribute.py +48 -0
- openlinktoken/attributes/person/social_security_number_attribute.py +119 -0
- openlinktoken/attributes/person/us_postal_code_attribute.py +104 -0
- openlinktoken/attributes/serializable_attribute.py +7 -0
- openlinktoken/attributes/utilities/__init__.py +7 -0
- openlinktoken/attributes/utilities/attribute_utilities.py +224 -0
- openlinktoken/attributes/validation/__init__.py +23 -0
- openlinktoken/attributes/validation/age_range_validator.py +41 -0
- openlinktoken/attributes/validation/attribute_validator.py +22 -0
- openlinktoken/attributes/validation/date_range_validator.py +111 -0
- openlinktoken/attributes/validation/not_in_validator.py +48 -0
- openlinktoken/attributes/validation/not_null_or_empty_validator.py +21 -0
- openlinktoken/attributes/validation/not_starts_with_validator.py +48 -0
- openlinktoken/attributes/validation/regex_validator.py +44 -0
- openlinktoken/attributes/validation/serializable_attribute_validator.py +26 -0
- openlinktoken/attributes/validation/year_range_validator.py +48 -0
- openlinktoken/ec_key_utils.py +207 -0
- openlinktoken/exchange_config.py +305 -0
- openlinktoken/exchange_jwe.py +107 -0
- openlinktoken/metadata.py +104 -0
- openlinktoken/tokens/__init__.py +19 -0
- openlinktoken/tokens/base_token_definition.py +48 -0
- openlinktoken/tokens/definitions/t1_token.py +39 -0
- openlinktoken/tokens/definitions/t2_token.py +39 -0
- openlinktoken/tokens/definitions/t3_token.py +39 -0
- openlinktoken/tokens/definitions/t4_token.py +37 -0
- openlinktoken/tokens/definitions/t5_token.py +37 -0
- openlinktoken/tokens/token.py +38 -0
- openlinktoken/tokens/token_definition.py +46 -0
- openlinktoken/tokens/token_generation_exception.py +16 -0
- openlinktoken/tokens/token_generator.py +201 -0
- openlinktoken/tokens/token_generator_result.py +33 -0
- openlinktoken/tokens/token_registry.py +46 -0
- openlinktoken/tokens/tokenizer/__init__.py +11 -0
- openlinktoken/tokens/tokenizer/passthrough_tokenizer.py +66 -0
- openlinktoken/tokens/tokenizer/sha256_tokenizer.py +72 -0
- openlinktoken/tokens/tokenizer/tokenizer.py +31 -0
- openlinktoken/tokentransformer/__init__.py +43 -0
- openlinktoken/tokentransformer/decrypt_token_transformer.py +83 -0
- openlinktoken/tokentransformer/encrypt_token_transformer.py +85 -0
- openlinktoken/tokentransformer/encryption_constants.py +23 -0
- openlinktoken/tokentransformer/hash_token_transformer.py +88 -0
- openlinktoken/tokentransformer/jwe_match_token_formatter.py +128 -0
- openlinktoken/tokentransformer/match_token_constants.py +57 -0
- openlinktoken/tokentransformer/no_operation_token_transformer.py +24 -0
- openlinktoken/tokentransformer/token_transformer.py +22 -0
- openlinktoken-2.0.0.dist-info/METADATA +21 -0
- openlinktoken-2.0.0.dist-info/RECORD +72 -0
- openlinktoken-2.0.0.dist-info/WHEEL +5 -0
- openlinktoken-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import unicodedata
|
|
5
|
+
from typing import Dict, Set
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AttributeUtilities:
|
|
9
|
+
"""
|
|
10
|
+
This class includes functions such as normalizing accents,
|
|
11
|
+
standardizing formats, and other attribute-related transformations.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Pattern that matches any character that is not an alphabetic character (a-z or A-Z).
|
|
15
|
+
# Used for removing or identifying non-alphabetic characters in strings.
|
|
16
|
+
NON_ALPHABETIC_PATTERN = re.compile(r"[^a-zA-Z]")
|
|
17
|
+
|
|
18
|
+
# Pattern that matches one or more whitespace characters.
|
|
19
|
+
# This includes spaces, tabs, line breaks, and other Unicode whitespace.
|
|
20
|
+
#
|
|
21
|
+
# Examples:
|
|
22
|
+
# " " -> single space
|
|
23
|
+
# "\t" -> tab
|
|
24
|
+
# "\n" -> newline
|
|
25
|
+
# "\r\n" -> carriage return + newline
|
|
26
|
+
# " " -> multiple spaces
|
|
27
|
+
WHITESPACE_PATTERN = re.compile(r"\s+")
|
|
28
|
+
|
|
29
|
+
# Characters from Latin-1 Supplement and Latin Extended blocks that do not
|
|
30
|
+
# decompose to ASCII-compatible forms with NFD alone.
|
|
31
|
+
LATIN_EXTENDED_TRANSLITERATION_MAP: Dict[str, str] = {
|
|
32
|
+
"Æ": "AE",
|
|
33
|
+
"æ": "ae",
|
|
34
|
+
"Ð": "D",
|
|
35
|
+
"ð": "d",
|
|
36
|
+
"Ø": "O",
|
|
37
|
+
"ø": "o",
|
|
38
|
+
"Þ": "TH",
|
|
39
|
+
"þ": "th",
|
|
40
|
+
"ß": "ss",
|
|
41
|
+
"ẞ": "SS",
|
|
42
|
+
"Đ": "D",
|
|
43
|
+
"đ": "d",
|
|
44
|
+
"Ħ": "H",
|
|
45
|
+
"ħ": "h",
|
|
46
|
+
"ı": "i",
|
|
47
|
+
"IJ": "IJ",
|
|
48
|
+
"ij": "ij",
|
|
49
|
+
"Ŀ": "L",
|
|
50
|
+
"ŀ": "l",
|
|
51
|
+
"Ł": "L",
|
|
52
|
+
"ł": "l",
|
|
53
|
+
"ʼn": "n",
|
|
54
|
+
"Ŋ": "NG",
|
|
55
|
+
"ŋ": "ng",
|
|
56
|
+
"Œ": "OE",
|
|
57
|
+
"œ": "oe",
|
|
58
|
+
"Ŧ": "T",
|
|
59
|
+
"ŧ": "t",
|
|
60
|
+
"ſ": "s",
|
|
61
|
+
"Ƒ": "F",
|
|
62
|
+
"ƒ": "f",
|
|
63
|
+
"DŽ": "DZ",
|
|
64
|
+
"Dž": "Dz",
|
|
65
|
+
"dž": "dz",
|
|
66
|
+
"LJ": "LJ",
|
|
67
|
+
"Lj": "Lj",
|
|
68
|
+
"lj": "lj",
|
|
69
|
+
"NJ": "NJ",
|
|
70
|
+
"Nj": "Nj",
|
|
71
|
+
"nj": "nj",
|
|
72
|
+
"DZ": "DZ",
|
|
73
|
+
"Dz": "Dz",
|
|
74
|
+
"dz": "dz",
|
|
75
|
+
"Ǽ": "AE",
|
|
76
|
+
"ǽ": "ae",
|
|
77
|
+
"Ǿ": "O",
|
|
78
|
+
"ǿ": "o",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Pattern that matches generational suffixes at the end of a string.
|
|
82
|
+
# Matches case-insensitive suffixes after a whitespace character.
|
|
83
|
+
#
|
|
84
|
+
# Matches the following types of generational suffixes:
|
|
85
|
+
# - Jr, Jr., Junior
|
|
86
|
+
# - Sr, Sr., Senior
|
|
87
|
+
# - Roman numerals (I, II, III, IV, V, VI, VII, VIII, IX, X)
|
|
88
|
+
# - Ordinal numbers (1st, 2nd, 3rd, 4th, etc.)
|
|
89
|
+
#
|
|
90
|
+
# Examples:
|
|
91
|
+
# "John Smith Jr" -> matches " Jr"
|
|
92
|
+
# "Jane Doe Sr." -> matches " Sr."
|
|
93
|
+
# "Robert Johnson III" -> matches " III"
|
|
94
|
+
# "Thomas Wilson 2nd" -> matches " 2nd"
|
|
95
|
+
GENERATIONAL_SUFFIX_PATTERN = re.compile(
|
|
96
|
+
r"(?i)\s+(jr\.?|junior|sr\.?|senior|I{1,3}|IV|V|VI{0,3}|IX|X|\d+(st|nd|rd|th))$"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# A set of common placeholder names used to identify non-identifying or
|
|
100
|
+
# placeholder text in data fields.
|
|
101
|
+
#
|
|
102
|
+
# This set includes various standard terms and phrases that are commonly used
|
|
103
|
+
# as placeholders in data records when actual values are unknown, not applicable,
|
|
104
|
+
# unavailable, or intentionally masked.
|
|
105
|
+
# These placeholders might be found in production data but don't represent
|
|
106
|
+
# actual identifying information.
|
|
107
|
+
#
|
|
108
|
+
# These values should be treated as non-identifying data and may need special
|
|
109
|
+
# handling during data processing, anonymization, or when analyzing data quality.
|
|
110
|
+
COMMON_PLACEHOLDER_NAMES: Set[str] = {
|
|
111
|
+
"Unknown", # Placeholder for unknown last names
|
|
112
|
+
"N/A", # Not applicable
|
|
113
|
+
"None", # No last name provided
|
|
114
|
+
"Test", # Commonly used in testing scenarios
|
|
115
|
+
"Sample", # Sample data placeholder
|
|
116
|
+
"Donor", # Placeholder for donor records
|
|
117
|
+
"Patient", # Placeholder for patient records
|
|
118
|
+
"Automation Test", # Placeholder for automation tests
|
|
119
|
+
"Automationtest", # Another variation of automation test
|
|
120
|
+
"patient not found", # Placeholder for cases where patient data is not found
|
|
121
|
+
"patientnotfound", # Another variation of patient not found
|
|
122
|
+
"<masked>", # Placeholder for masked data
|
|
123
|
+
"Anonymous", # Placeholder for anonymous records
|
|
124
|
+
"zzztrash", # Placeholder for test or trash data
|
|
125
|
+
"Missing", # Placeholder for missing data
|
|
126
|
+
"Unavailable", # Placeholder for unavailable data
|
|
127
|
+
"Not Available", # Placeholder for data not available
|
|
128
|
+
"NotAvailable", # Placeholder for data not available (no spaces)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def __init__(self):
|
|
132
|
+
"""Prevent instantiation of utility class."""
|
|
133
|
+
raise RuntimeError("This is a utility class and cannot be instantiated")
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def normalize_diacritics(value: str) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Removes diacritic marks from the given string.
|
|
139
|
+
|
|
140
|
+
This method performs the following steps:
|
|
141
|
+
1. Trims the input string
|
|
142
|
+
2. Normalizes the string using NFD form, which separates characters from
|
|
143
|
+
their diacritical marks
|
|
144
|
+
3. Removes all diacritical marks using Unicode category filtering
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
value: The string from which to remove diacritical marks
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
A new string with all diacritical marks removed
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
AttributeError: If value is None
|
|
154
|
+
"""
|
|
155
|
+
if value is None:
|
|
156
|
+
raise AttributeError("Value cannot be None")
|
|
157
|
+
|
|
158
|
+
trimmed_value = value.strip()
|
|
159
|
+
transliterated_value = "".join(
|
|
160
|
+
AttributeUtilities.LATIN_EXTENDED_TRANSLITERATION_MAP.get(character, character)
|
|
161
|
+
for character in trimmed_value
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Normalize to NFD (decomposed form) and filter out combining characters
|
|
165
|
+
normalized = unicodedata.normalize("NFD", transliterated_value)
|
|
166
|
+
return "".join(character for character in normalized if not unicodedata.category(character).startswith("M"))
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def remove_whitespace(value: str) -> str:
|
|
170
|
+
"""
|
|
171
|
+
Remove all whitespace characters from the string.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
value: The string from which to remove whitespace
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
String with all whitespace removed
|
|
178
|
+
"""
|
|
179
|
+
if value is None:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
return AttributeUtilities.WHITESPACE_PATTERN.sub("", value)
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def remove_non_alphabetic_characters(value: str) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Remove all non-alphabetic characters from the string.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
value: The string from which to remove non-alphabetic characters
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
String with only alphabetic characters (a-z, A-Z)
|
|
194
|
+
"""
|
|
195
|
+
if value is None:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
return AttributeUtilities.NON_ALPHABETIC_PATTERN.sub("", value)
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def remove_generational_suffix(value: str) -> str:
|
|
202
|
+
"""
|
|
203
|
+
Remove generational suffixes (Jr, Sr, III, etc.) from the string.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
value: The string from which to remove generational suffixes
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
String with generational suffixes removed
|
|
210
|
+
"""
|
|
211
|
+
if value is None:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
return AttributeUtilities.GENERATIONAL_SUFFIX_PATTERN.sub("", value).strip()
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def get_common_placeholder_names() -> Set[str]:
|
|
218
|
+
"""
|
|
219
|
+
Get a copy of the common placeholder names set.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
A copy of the set containing common placeholder names
|
|
223
|
+
"""
|
|
224
|
+
return AttributeUtilities.COMMON_PLACEHOLDER_NAMES.copy()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Validation utilities for attributes."""
|
|
2
|
+
|
|
3
|
+
from .age_range_validator import AgeRangeValidator
|
|
4
|
+
from .attribute_validator import AttributeValidator
|
|
5
|
+
from .date_range_validator import DateRangeValidator
|
|
6
|
+
from .not_in_validator import NotInValidator
|
|
7
|
+
from .not_null_or_empty_validator import NotNullOrEmptyValidator
|
|
8
|
+
from .not_starts_with_validator import NotStartsWithValidator
|
|
9
|
+
from .regex_validator import RegexValidator
|
|
10
|
+
from .serializable_attribute_validator import SerializableAttributeValidator
|
|
11
|
+
from .year_range_validator import YearRangeValidator
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AgeRangeValidator",
|
|
15
|
+
"AttributeValidator",
|
|
16
|
+
"DateRangeValidator",
|
|
17
|
+
"NotInValidator",
|
|
18
|
+
"NotNullOrEmptyValidator",
|
|
19
|
+
"NotStartsWithValidator",
|
|
20
|
+
"RegexValidator",
|
|
21
|
+
"SerializableAttributeValidator",
|
|
22
|
+
"YearRangeValidator",
|
|
23
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from openlinktoken.attributes.validation.serializable_attribute_validator import SerializableAttributeValidator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgeRangeValidator(SerializableAttributeValidator):
|
|
7
|
+
"""
|
|
8
|
+
A validator that asserts that an age value is within an acceptable range (0-120).
|
|
9
|
+
|
|
10
|
+
This validator checks that ages are:
|
|
11
|
+
- Not less than 0
|
|
12
|
+
- Not greater than 120
|
|
13
|
+
|
|
14
|
+
If the age is outside this range, the validation fails.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
MIN_AGE = 0
|
|
18
|
+
MAX_AGE = 120
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize the validator with default age range (0-120)."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def eval(self, value: str) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Validate that the age value is within acceptable range.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
value: The age string to validate
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
True if the age is within acceptable range (0-120), False otherwise
|
|
33
|
+
"""
|
|
34
|
+
if not value or not value.strip():
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
age = int(value.strip())
|
|
39
|
+
return self.MIN_AGE <= age <= self.MAX_AGE
|
|
40
|
+
except ValueError:
|
|
41
|
+
return False
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AttributeValidator(ABC):
|
|
7
|
+
"""
|
|
8
|
+
A generic interface for attribute validation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def eval(self, value: str) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Validate an attribute value.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
value: The attribute value
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
True if the attribute is valid, False otherwise
|
|
21
|
+
"""
|
|
22
|
+
pass
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from openlinktoken.attributes.validation.serializable_attribute_validator import SerializableAttributeValidator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DateRangeValidator(SerializableAttributeValidator):
|
|
10
|
+
"""
|
|
11
|
+
A generic validator that asserts that a date value is within an acceptable range.
|
|
12
|
+
|
|
13
|
+
This validator checks that dates are:
|
|
14
|
+
- Not before the specified minimum date (if provided)
|
|
15
|
+
- Not after the specified maximum date (if provided)
|
|
16
|
+
- If use_current_as_max is True, uses today's date as the maximum
|
|
17
|
+
|
|
18
|
+
If the date is outside the specified range, the validation fails.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Supported date formats for parsing
|
|
22
|
+
POSSIBLE_INPUT_FORMATS = [
|
|
23
|
+
"%Y-%m-%d",
|
|
24
|
+
"%Y/%m/%d",
|
|
25
|
+
"%m/%d/%Y",
|
|
26
|
+
"%m-%d-%Y",
|
|
27
|
+
"%d.%m.%Y",
|
|
28
|
+
# ISO 8601 timestamp formats
|
|
29
|
+
"%Y-%m-%dT%H:%M:%S.%fZ",
|
|
30
|
+
"%Y-%m-%dT%H:%M:%SZ",
|
|
31
|
+
"%Y-%m-%dT%H:%M:%S.%f%z",
|
|
32
|
+
"%Y-%m-%dT%H:%M:%S%z",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self, min_date: Optional[date] = None, max_date: Optional[date] = None, use_current_as_max: bool = False
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Initialize the validator with a date range.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
min_date: The minimum allowed date (inclusive), or None for no minimum
|
|
43
|
+
max_date: The maximum allowed date (inclusive), or None for no maximum
|
|
44
|
+
use_current_as_max: If True, uses today's date as the maximum date
|
|
45
|
+
"""
|
|
46
|
+
self._min_date = min_date
|
|
47
|
+
self._use_current_as_max = use_current_as_max
|
|
48
|
+
self._max_date = max_date
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def min_date(self) -> Optional[date]:
|
|
52
|
+
"""Get the minimum allowed date."""
|
|
53
|
+
return self._min_date
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def max_date(self) -> Optional[date]:
|
|
57
|
+
"""Get the maximum allowed date."""
|
|
58
|
+
if self._use_current_as_max:
|
|
59
|
+
return date.today()
|
|
60
|
+
return self._max_date
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def use_current_as_max(self) -> bool:
|
|
64
|
+
"""Check if using current date as maximum."""
|
|
65
|
+
return self._use_current_as_max
|
|
66
|
+
|
|
67
|
+
def eval(self, value: str) -> bool:
|
|
68
|
+
"""
|
|
69
|
+
Validate that the date value is within acceptable range.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
value: The date string to validate
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
True if the date is within acceptable range, False otherwise
|
|
76
|
+
"""
|
|
77
|
+
if not value or not value.strip():
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
parsed_date = self._parse_date(value.strip())
|
|
81
|
+
if parsed_date is None:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# Check minimum date if specified
|
|
85
|
+
if self._min_date is not None and parsed_date < self._min_date:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
# Check maximum date
|
|
89
|
+
max_date_to_check = self.max_date
|
|
90
|
+
if max_date_to_check is not None and parsed_date > max_date_to_check:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
def _parse_date(self, value: str) -> Optional[date]:
|
|
96
|
+
"""
|
|
97
|
+
Parse the date string using various supported formats.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
value: The date string to parse
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Parsed date object, or None if parsing fails
|
|
104
|
+
"""
|
|
105
|
+
for date_format in self.POSSIBLE_INPUT_FORMATS:
|
|
106
|
+
try:
|
|
107
|
+
parsed_datetime = datetime.strptime(value, date_format)
|
|
108
|
+
return parsed_datetime.date()
|
|
109
|
+
except ValueError:
|
|
110
|
+
continue
|
|
111
|
+
return None
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import Set
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.validation.serializable_attribute_validator import SerializableAttributeValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotInValidator(SerializableAttributeValidator):
|
|
9
|
+
"""
|
|
10
|
+
A Validator that asserts that the attribute value is NOT IN the list of invalid values.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, invalid_values: Set[str]):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the validator with a set of invalid values.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
invalid_values: Set of values that should be considered invalid
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: If invalid_values is None
|
|
22
|
+
"""
|
|
23
|
+
if invalid_values is None:
|
|
24
|
+
raise ValueError("Invalid values set cannot be None")
|
|
25
|
+
|
|
26
|
+
# Convert to lowercase for case-insensitive comparison
|
|
27
|
+
self._invalid_values = {value.lower() for value in invalid_values}
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def invalid_values(self) -> Set[str]:
|
|
31
|
+
"""Get the set of invalid values (in lowercase)."""
|
|
32
|
+
return self._invalid_values.copy()
|
|
33
|
+
|
|
34
|
+
def eval(self, value: str) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Validate that the attribute value is not in the list of invalid values
|
|
37
|
+
(case-insensitive comparison).
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
value: The attribute value to validate
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if the value is not in the invalid values list, False otherwise
|
|
44
|
+
"""
|
|
45
|
+
if value is None:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
return value.lower() not in self._invalid_values
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from openlinktoken.attributes.validation.serializable_attribute_validator import SerializableAttributeValidator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NotNullOrEmptyValidator(SerializableAttributeValidator):
|
|
7
|
+
"""
|
|
8
|
+
A Validator that asserts the value is NOT None and not blank.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def eval(self, value: str) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Validate that the attribute value is not None or blank.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
value: The attribute value to validate
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
True if value is not None and not blank, False otherwise
|
|
20
|
+
"""
|
|
21
|
+
return value is not None and value.strip() != ""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import Set
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.validation.serializable_attribute_validator import SerializableAttributeValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotStartsWithValidator(SerializableAttributeValidator):
|
|
9
|
+
"""
|
|
10
|
+
A Validator that asserts that the attribute value does NOT START WITH
|
|
11
|
+
any of the invalid prefixes.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, invalid_prefixes: Set[str]):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the validator with a set of invalid prefixes.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
invalid_prefixes: Set of prefixes that values should not start with
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
ValueError: If invalid_prefixes is None
|
|
23
|
+
"""
|
|
24
|
+
if invalid_prefixes is None:
|
|
25
|
+
raise ValueError("Invalid prefixes set cannot be None")
|
|
26
|
+
|
|
27
|
+
self._invalid_prefixes = invalid_prefixes.copy()
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def invalid_prefixes(self) -> Set[str]:
|
|
31
|
+
"""Get the set of invalid prefixes."""
|
|
32
|
+
return self._invalid_prefixes.copy()
|
|
33
|
+
|
|
34
|
+
def eval(self, value: str) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Validate that the attribute value does not start with any of the invalid prefixes.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
value: The attribute value to validate
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
True if the value doesn't start with any invalid prefix, False otherwise
|
|
43
|
+
"""
|
|
44
|
+
if value is None:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
trimmed_value = value.strip()
|
|
48
|
+
return not any(trimmed_value.startswith(prefix) for prefix in self._invalid_prefixes)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Pattern
|
|
5
|
+
|
|
6
|
+
from openlinktoken.attributes.validation.serializable_attribute_validator import SerializableAttributeValidator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RegexValidator(SerializableAttributeValidator):
|
|
10
|
+
"""
|
|
11
|
+
A Validator that is designed for validating with regex expressions.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, pattern: str):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the regex validator with a pattern.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
pattern: The regex pattern string to compile and use for validation
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
re.error: If the pattern is invalid
|
|
23
|
+
"""
|
|
24
|
+
if not pattern:
|
|
25
|
+
raise ValueError("Pattern cannot be None or empty")
|
|
26
|
+
|
|
27
|
+
self._compiled_pattern: Pattern[str] = re.compile(pattern)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def compiled_pattern(self) -> Pattern[str]:
|
|
31
|
+
"""Get the compiled regex pattern."""
|
|
32
|
+
return self._compiled_pattern
|
|
33
|
+
|
|
34
|
+
def eval(self, value: str) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Validate that the value matches the regex pattern.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
value: The attribute value to validate
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
True if the value matches the pattern, False otherwise
|
|
43
|
+
"""
|
|
44
|
+
return value is not None and bool(self._compiled_pattern.match(value))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.validation.attribute_validator import AttributeValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SerializableAttributeValidator(AttributeValidator, ABC):
|
|
9
|
+
"""
|
|
10
|
+
An extension of the AttributeValidator interface that is also serializable.
|
|
11
|
+
|
|
12
|
+
This interface should be implemented by validator classes that need to be
|
|
13
|
+
serialized, such as when they are part of serializable attributes or need to
|
|
14
|
+
be transmitted over a network.
|
|
15
|
+
|
|
16
|
+
Implementing classes must ensure that all their fields are serializable or
|
|
17
|
+
handled appropriately if they cannot be serialized.
|
|
18
|
+
|
|
19
|
+
Note: In Python, most objects are serializable by default with pickle,
|
|
20
|
+
but this class serves as a marker for validators that are explicitly
|
|
21
|
+
designed to be serializable.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# This is a marker interface that combines AttributeValidator and serializable capability
|
|
25
|
+
# No additional methods are required
|
|
26
|
+
pass
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from datetime import date
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.validation.serializable_attribute_validator import SerializableAttributeValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class YearRangeValidator(SerializableAttributeValidator):
|
|
9
|
+
"""
|
|
10
|
+
A validator that asserts that a year value is within an acceptable range.
|
|
11
|
+
|
|
12
|
+
This validator checks that years are:
|
|
13
|
+
- Not before 1910
|
|
14
|
+
- Not after the current year
|
|
15
|
+
|
|
16
|
+
If the year is outside this range, the validation fails.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
MIN_YEAR = 1910
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Initialize the validator with default year range (1910-current year)."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def max_year(self) -> int:
|
|
27
|
+
"""Get the maximum allowed year (current year)."""
|
|
28
|
+
return date.today().year
|
|
29
|
+
|
|
30
|
+
def eval(self, value: str) -> bool:
|
|
31
|
+
"""
|
|
32
|
+
Validate that the year value is within acceptable range.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
value: The year string to validate
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if the year is within acceptable range (1910-current year),
|
|
39
|
+
False otherwise
|
|
40
|
+
"""
|
|
41
|
+
if not value or not value.strip():
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
year = int(value.strip())
|
|
46
|
+
return self.MIN_YEAR <= year <= self.max_year
|
|
47
|
+
except ValueError:
|
|
48
|
+
return False
|