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.
Files changed (72) hide show
  1. openlinktoken/__init__.py +15 -0
  2. openlinktoken/attributes/__init__.py +17 -0
  3. openlinktoken/attributes/attribute.py +26 -0
  4. openlinktoken/attributes/attribute_expression.py +232 -0
  5. openlinktoken/attributes/attribute_loader.py +48 -0
  6. openlinktoken/attributes/base_attribute.py +31 -0
  7. openlinktoken/attributes/combined_attribute.py +69 -0
  8. openlinktoken/attributes/general/__init__.py +17 -0
  9. openlinktoken/attributes/general/date_attribute.py +117 -0
  10. openlinktoken/attributes/general/decimal_attribute.py +86 -0
  11. openlinktoken/attributes/general/integer_attribute.py +78 -0
  12. openlinktoken/attributes/general/record_id_attribute.py +41 -0
  13. openlinktoken/attributes/general/string_attribute.py +55 -0
  14. openlinktoken/attributes/general/year_attribute.py +78 -0
  15. openlinktoken/attributes/person/__init__.py +25 -0
  16. openlinktoken/attributes/person/age_attribute.py +41 -0
  17. openlinktoken/attributes/person/birth_date_attribute.py +40 -0
  18. openlinktoken/attributes/person/birth_year_attribute.py +39 -0
  19. openlinktoken/attributes/person/canadian_postal_code_attribute.py +165 -0
  20. openlinktoken/attributes/person/first_name_attribute.py +112 -0
  21. openlinktoken/attributes/person/last_name_attribute.py +146 -0
  22. openlinktoken/attributes/person/postal_code_attribute.py +42 -0
  23. openlinktoken/attributes/person/sex_attribute.py +48 -0
  24. openlinktoken/attributes/person/social_security_number_attribute.py +119 -0
  25. openlinktoken/attributes/person/us_postal_code_attribute.py +104 -0
  26. openlinktoken/attributes/serializable_attribute.py +7 -0
  27. openlinktoken/attributes/utilities/__init__.py +7 -0
  28. openlinktoken/attributes/utilities/attribute_utilities.py +224 -0
  29. openlinktoken/attributes/validation/__init__.py +23 -0
  30. openlinktoken/attributes/validation/age_range_validator.py +41 -0
  31. openlinktoken/attributes/validation/attribute_validator.py +22 -0
  32. openlinktoken/attributes/validation/date_range_validator.py +111 -0
  33. openlinktoken/attributes/validation/not_in_validator.py +48 -0
  34. openlinktoken/attributes/validation/not_null_or_empty_validator.py +21 -0
  35. openlinktoken/attributes/validation/not_starts_with_validator.py +48 -0
  36. openlinktoken/attributes/validation/regex_validator.py +44 -0
  37. openlinktoken/attributes/validation/serializable_attribute_validator.py +26 -0
  38. openlinktoken/attributes/validation/year_range_validator.py +48 -0
  39. openlinktoken/ec_key_utils.py +207 -0
  40. openlinktoken/exchange_config.py +305 -0
  41. openlinktoken/exchange_jwe.py +107 -0
  42. openlinktoken/metadata.py +104 -0
  43. openlinktoken/tokens/__init__.py +19 -0
  44. openlinktoken/tokens/base_token_definition.py +48 -0
  45. openlinktoken/tokens/definitions/t1_token.py +39 -0
  46. openlinktoken/tokens/definitions/t2_token.py +39 -0
  47. openlinktoken/tokens/definitions/t3_token.py +39 -0
  48. openlinktoken/tokens/definitions/t4_token.py +37 -0
  49. openlinktoken/tokens/definitions/t5_token.py +37 -0
  50. openlinktoken/tokens/token.py +38 -0
  51. openlinktoken/tokens/token_definition.py +46 -0
  52. openlinktoken/tokens/token_generation_exception.py +16 -0
  53. openlinktoken/tokens/token_generator.py +201 -0
  54. openlinktoken/tokens/token_generator_result.py +33 -0
  55. openlinktoken/tokens/token_registry.py +46 -0
  56. openlinktoken/tokens/tokenizer/__init__.py +11 -0
  57. openlinktoken/tokens/tokenizer/passthrough_tokenizer.py +66 -0
  58. openlinktoken/tokens/tokenizer/sha256_tokenizer.py +72 -0
  59. openlinktoken/tokens/tokenizer/tokenizer.py +31 -0
  60. openlinktoken/tokentransformer/__init__.py +43 -0
  61. openlinktoken/tokentransformer/decrypt_token_transformer.py +83 -0
  62. openlinktoken/tokentransformer/encrypt_token_transformer.py +85 -0
  63. openlinktoken/tokentransformer/encryption_constants.py +23 -0
  64. openlinktoken/tokentransformer/hash_token_transformer.py +88 -0
  65. openlinktoken/tokentransformer/jwe_match_token_formatter.py +128 -0
  66. openlinktoken/tokentransformer/match_token_constants.py +57 -0
  67. openlinktoken/tokentransformer/no_operation_token_transformer.py +24 -0
  68. openlinktoken/tokentransformer/token_transformer.py +22 -0
  69. openlinktoken-2.0.0.dist-info/METADATA +21 -0
  70. openlinktoken-2.0.0.dist-info/RECORD +72 -0
  71. openlinktoken-2.0.0.dist-info/WHEEL +5 -0
  72. openlinktoken-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,104 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ import hashlib
4
+ import sys
5
+ from typing import Any, Dict
6
+
7
+
8
+ class Metadata:
9
+ """Handles metadata generation and management for Open Link Token."""
10
+
11
+ # Metadata keys
12
+ PLATFORM = "Platform"
13
+ PYTHON_VERSION = "PythonVersion"
14
+ VERSION = "Version"
15
+ OUTPUT_FORMAT = "OutputFormat"
16
+ ENCRYPTION_SECRET_HASH = "EncryptionSecretHash"
17
+ HASHING_SECRET_HASH = "HashingSecretHash"
18
+ BLANK_TOKENS_BY_RULE = "BlankTokensByRule"
19
+
20
+ # Metadata values
21
+ PLATFORM_PYTHON = "Python"
22
+ METADATA_FILE_EXTENSION = ".metadata.json"
23
+ SYSTEM_PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
24
+
25
+ DEFAULT_VERSION = "2.0.0"
26
+
27
+ # Output format values
28
+ OUTPUT_FORMAT_JSON = "JSON"
29
+ OUTPUT_FORMAT_CSV = "CSV"
30
+ OUTPUT_FORMAT_PARQUET = "Parquet"
31
+
32
+ def __init__(self):
33
+ """Initialize the metadata object."""
34
+ self.metadata_map: Dict[str, Any] = {}
35
+
36
+ def initialize(self) -> Dict[str, Any]:
37
+ """
38
+ Initialize metadata with system information only.
39
+ Secret hashes must be set separately using add_hashed_secret().
40
+
41
+ Returns:
42
+ The initialized metadata map
43
+ """
44
+ self.metadata_map.clear()
45
+ self.metadata_map[self.PYTHON_VERSION] = self.SYSTEM_PYTHON_VERSION
46
+ self.metadata_map[self.PLATFORM] = self.PLATFORM_PYTHON
47
+ self.metadata_map[self.VERSION] = self.DEFAULT_VERSION
48
+
49
+ return self.metadata_map
50
+
51
+ def add_hashed_secret(self, secret_key: str, secret_to_hash: str | bytes | None) -> Dict[str, Any]:
52
+ """
53
+ Set the secret and add its hash to the metadata.
54
+
55
+ Args:
56
+ secret_key: The key to use for the hashed secret in metadata
57
+ secret_to_hash: The secret to hash
58
+
59
+ Returns:
60
+ The metadata map for method chaining
61
+ """
62
+ if isinstance(secret_to_hash, bytes):
63
+ if secret_to_hash:
64
+ self.metadata_map[secret_key] = self.calculate_secure_hash(secret_to_hash)
65
+ elif secret_to_hash and secret_to_hash.strip():
66
+ self.metadata_map[secret_key] = self.calculate_secure_hash(secret_to_hash)
67
+ return self.metadata_map
68
+
69
+ @staticmethod
70
+ def calculate_secure_hash(input_str: str | bytes | None) -> str | None:
71
+ """
72
+ Calculate a secure SHA-256 hash of the given input.
73
+ The hash is returned as a hexadecimal string, or ``None`` for empty input.
74
+
75
+ Accepts both ``str`` (encoded as UTF-8) and raw ``bytes``.
76
+
77
+ Args:
78
+ input_str: The input string or raw bytes to hash.
79
+
80
+ Returns:
81
+ The SHA-256 hash as a hexadecimal string, or ``None`` when the input is empty.
82
+
83
+ Raises:
84
+ HashCalculationException: If there's an error calculating the hash.
85
+ """
86
+ if not input_str:
87
+ return None
88
+
89
+ try:
90
+ hash_input = input_str if isinstance(input_str, bytes) else input_str.encode("utf-8")
91
+ hash_object = hashlib.sha256(hash_input)
92
+ hex_string = hash_object.hexdigest()
93
+ return hex_string
94
+
95
+ except Exception as e:
96
+ raise HashCalculationException("Error calculating SHA-256 hash", e)
97
+
98
+
99
+ class HashCalculationException(Exception):
100
+ """Custom exception for hash calculation errors."""
101
+
102
+ def __init__(self, message: str, cause: Exception = None):
103
+ super().__init__(message)
104
+ self.cause = cause
@@ -0,0 +1,19 @@
1
+ """Token definitions and generation utilities for Open Link Token."""
2
+
3
+ from .base_token_definition import BaseTokenDefinition
4
+ from .token import Token
5
+ from .token_definition import TokenDefinition
6
+ from .token_generation_exception import TokenGenerationException
7
+ from .token_generator import TokenGenerator
8
+ from .token_generator_result import TokenGeneratorResult
9
+ from .token_registry import TokenRegistry
10
+
11
+ __all__ = [
12
+ "Token",
13
+ "TokenDefinition",
14
+ "BaseTokenDefinition",
15
+ "TokenGenerator",
16
+ "TokenRegistry",
17
+ "TokenGeneratorResult",
18
+ "TokenGenerationException",
19
+ ]
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import List, Set
5
+
6
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
7
+
8
+
9
+ class BaseTokenDefinition(ABC):
10
+ """
11
+ A generic interface for the token definition.
12
+ """
13
+
14
+ @abstractmethod
15
+ def get_version(self) -> str:
16
+ """
17
+ Get the version of the token definition.
18
+
19
+ Returns:
20
+ The token definition version.
21
+ """
22
+ ... # pragma: no cover
23
+
24
+ @abstractmethod
25
+ def get_token_identifiers(self) -> Set[str]:
26
+ """
27
+ Get all token identifiers. For example, a set of { T1, T2, T3, T4, T5 }.
28
+
29
+ The token identifiers are also called rule identifiers because every token is
30
+ generated from rule definition.
31
+
32
+ Returns:
33
+ A set of token identifiers.
34
+ """
35
+ ... # pragma: no cover
36
+
37
+ @abstractmethod
38
+ def get_token_definition(self, token_id: str) -> List[AttributeExpression]:
39
+ """
40
+ Get the token definition for a given token identifier.
41
+
42
+ Args:
43
+ token_id: The token/rule identifier.
44
+
45
+ Returns:
46
+ A list of token/rule definition.
47
+ """
48
+ ... # pragma: no cover
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from typing import List
4
+
5
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
6
+ from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
7
+ from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
8
+ from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
9
+ from openlinktoken.attributes.person.sex_attribute import SexAttribute
10
+ from openlinktoken.tokens.token import Token
11
+
12
+
13
+ class T1Token(Token):
14
+ """
15
+ Represents the token definition for token T1.
16
+
17
+ It is a collection of attribute expressions that are concatenated together
18
+ to get the token signature. The token signature is as follows:
19
+ U(last-name)|U(first-name-1)|U(gender)|birth-date
20
+ """
21
+
22
+ ID = "T1"
23
+
24
+ def __init__(self):
25
+ """Initialize the T1 token definition."""
26
+ self._definition = [
27
+ AttributeExpression(LastNameAttribute, "T|U"),
28
+ AttributeExpression(FirstNameAttribute, "T|S(0,1)|U"),
29
+ AttributeExpression(SexAttribute, "T|U"),
30
+ AttributeExpression(BirthDateAttribute, "T|D"),
31
+ ]
32
+
33
+ def get_identifier(self) -> str:
34
+ """Get the unique identifier for the token."""
35
+ return self.ID
36
+
37
+ def get_definition(self) -> List[AttributeExpression]:
38
+ """Get the list of attribute expressions that define the token."""
39
+ return self._definition
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from typing import List
4
+
5
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
6
+ from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
7
+ from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
8
+ from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
9
+ from openlinktoken.attributes.person.postal_code_attribute import PostalCodeAttribute
10
+ from openlinktoken.tokens.token import Token
11
+
12
+
13
+ class T2Token(Token):
14
+ """
15
+ Represents the token definition for token T2.
16
+
17
+ It is a collection of attribute expressions that are concatenated together
18
+ to get the token signature. The token signature is as follows:
19
+ U(last-name)|U(first-name)|birth-date|postal-code-3
20
+ """
21
+
22
+ ID = "T2"
23
+
24
+ def __init__(self):
25
+ """Initialize the T2 token definition."""
26
+ self._definition = [
27
+ AttributeExpression(LastNameAttribute, "T|U"),
28
+ AttributeExpression(FirstNameAttribute, "T|U"),
29
+ AttributeExpression(BirthDateAttribute, "T|D"),
30
+ AttributeExpression(PostalCodeAttribute, "T|S(0,3)|U"),
31
+ ]
32
+
33
+ def get_identifier(self) -> str:
34
+ """Get the unique identifier for the token."""
35
+ return self.ID
36
+
37
+ def get_definition(self) -> List[AttributeExpression]:
38
+ """Get the list of attribute expressions that define the token."""
39
+ return self._definition
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from typing import List
4
+
5
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
6
+ from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
7
+ from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
8
+ from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
9
+ from openlinktoken.attributes.person.sex_attribute import SexAttribute
10
+ from openlinktoken.tokens.token import Token
11
+
12
+
13
+ class T3Token(Token):
14
+ """
15
+ Represents the token definition for token T3.
16
+
17
+ It is a collection of attribute expressions that are concatenated together
18
+ to get the token signature. The token signature is as follows:
19
+ U(last-name)|U(first-name)|U(gender)|birth-date
20
+ """
21
+
22
+ ID = "T3"
23
+
24
+ def __init__(self):
25
+ """Initialize the T3 token definition."""
26
+ self._definition = [
27
+ AttributeExpression(LastNameAttribute, "T|U"),
28
+ AttributeExpression(FirstNameAttribute, "T|U"),
29
+ AttributeExpression(SexAttribute, "T|U"),
30
+ AttributeExpression(BirthDateAttribute, "T|D"),
31
+ ]
32
+
33
+ def get_identifier(self) -> str:
34
+ """Get the unique identifier for the token."""
35
+ return self.ID
36
+
37
+ def get_definition(self) -> List[AttributeExpression]:
38
+ """Get the list of attribute expressions that define the token."""
39
+ return self._definition
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from typing import List
4
+
5
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
6
+ from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
7
+ from openlinktoken.attributes.person.sex_attribute import SexAttribute
8
+ from openlinktoken.attributes.person.social_security_number_attribute import SocialSecurityNumberAttribute
9
+ from openlinktoken.tokens.token import Token
10
+
11
+
12
+ class T4Token(Token):
13
+ """
14
+ Represents the token definition for token T4.
15
+
16
+ It is a collection of attribute expressions that are concatenated together
17
+ to get the token signature. The token signature is as follows:
18
+ social-security-number|U(gender)|birth-date
19
+ """
20
+
21
+ ID = "T4"
22
+
23
+ def __init__(self):
24
+ """Initialize the T4 token definition."""
25
+ self._definition = [
26
+ AttributeExpression(SocialSecurityNumberAttribute, "T|M(\\d+)"),
27
+ AttributeExpression(SexAttribute, "T|U"),
28
+ AttributeExpression(BirthDateAttribute, "T|D"),
29
+ ]
30
+
31
+ def get_identifier(self) -> str:
32
+ """Get the unique identifier for the token."""
33
+ return self.ID
34
+
35
+ def get_definition(self) -> List[AttributeExpression]:
36
+ """Get the list of attribute expressions that define the token."""
37
+ return self._definition
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from typing import List
4
+
5
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
6
+ from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
7
+ from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
8
+ from openlinktoken.attributes.person.sex_attribute import SexAttribute
9
+ from openlinktoken.tokens.token import Token
10
+
11
+
12
+ class T5Token(Token):
13
+ """
14
+ Represents the token definition for token T5.
15
+
16
+ It is a collection of attribute expressions that are concatenated together
17
+ to get the token signature. The token signature is as follows:
18
+ U(last-name)|U(first-name-3)|U(gender)
19
+ """
20
+
21
+ ID = "T5"
22
+
23
+ def __init__(self):
24
+ """Initialize the T5 token definition."""
25
+ self._definition = [
26
+ AttributeExpression(LastNameAttribute, "T|U"),
27
+ AttributeExpression(FirstNameAttribute, "T|S(0,3)|U"),
28
+ AttributeExpression(SexAttribute, "T|U"),
29
+ ]
30
+
31
+ def get_identifier(self) -> str:
32
+ """Get the unique identifier for the token."""
33
+ return self.ID
34
+
35
+ def get_definition(self) -> List[AttributeExpression]:
36
+ """Get the list of attribute expressions that define the token."""
37
+ return self._definition
@@ -0,0 +1,38 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import List
5
+
6
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
7
+
8
+
9
+ class Token(ABC):
10
+ """
11
+ A token is a collection of attribute expressions that are concatenated
12
+ together to get the token signature and a unique identifier.
13
+ """
14
+
15
+ # Represents the value for tokens with invalid or missing data.
16
+ # This constant is a placeholder for cases where a token is either
17
+ # not provided or contains invalid information.
18
+ BLANK = "0000000000000000000000000000000000000000000000000000000000000000"
19
+
20
+ @abstractmethod
21
+ def get_identifier(self) -> str:
22
+ """
23
+ Get the unique identifier for the token.
24
+
25
+ Returns:
26
+ The unique identifier for the token.
27
+ """
28
+ pass
29
+
30
+ @abstractmethod
31
+ def get_definition(self) -> List[AttributeExpression]:
32
+ """
33
+ Get the list of attribute expressions that define the token.
34
+
35
+ Returns:
36
+ The list of attribute expressions that define the token.
37
+ """
38
+ pass
@@ -0,0 +1,46 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from typing import Dict, List, Set
4
+
5
+ from openlinktoken.attributes.attribute_expression import AttributeExpression
6
+ from openlinktoken.tokens.base_token_definition import BaseTokenDefinition
7
+
8
+
9
+ class TokenDefinition(BaseTokenDefinition):
10
+ """
11
+ Encapsulates the token definitions.
12
+
13
+ The tokens are generated using some token generation rules. This class
14
+ encapsulates the definition of those rules. Together, they are commonly
15
+ referred to as token definitions or rule definitions.
16
+
17
+ Each token/rule definition is a collection of AttributeExpression that are
18
+ concatenated together to get the token signature.
19
+ """
20
+
21
+ def __init__(self):
22
+ """
23
+ Initializes the token definitions.
24
+ Loads all token definitions using TokenRegistry.
25
+ """
26
+ from openlinktoken.tokens.token_registry import TokenRegistry
27
+
28
+ self.definitions: Dict[str, List[AttributeExpression]] = TokenRegistry.load_all_tokens()
29
+
30
+ def get_version(self) -> str:
31
+ """
32
+ Get the version of the token definition.
33
+ """
34
+ return "2.0"
35
+
36
+ def get_token_identifiers(self) -> Set[str]:
37
+ """
38
+ Get all token identifiers.
39
+ """
40
+ return set(self.definitions.keys())
41
+
42
+ def get_token_definition(self, token_id: str) -> List[AttributeExpression]:
43
+ """
44
+ Get the token definition for a given token identifier.
45
+ """
46
+ return self.definitions.get(token_id)
@@ -0,0 +1,16 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+
4
+ class TokenGenerationException(Exception):
5
+ """Exception raised when token generation fails."""
6
+
7
+ def __init__(self, message: str, cause: Exception = None):
8
+ """
9
+ Initialize the exception.
10
+
11
+ Args:
12
+ message: The error message.
13
+ cause: The underlying cause of the exception.
14
+ """
15
+ super().__init__(message)
16
+ self.cause = cause
@@ -0,0 +1,201 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ import logging
4
+ from typing import Dict, List, Optional, Set, Type
5
+
6
+ from openlinktoken.attributes.attribute import Attribute
7
+ from openlinktoken.attributes.attribute_loader import AttributeLoader
8
+ from openlinktoken.tokens.base_token_definition import BaseTokenDefinition
9
+ from openlinktoken.tokens.token import Token
10
+ from openlinktoken.tokens.token_generation_exception import TokenGenerationException
11
+ from openlinktoken.tokens.token_generator_result import TokenGeneratorResult
12
+ from openlinktoken.tokens.tokenizer.sha256_tokenizer import SHA256Tokenizer
13
+ from openlinktoken.tokens.tokenizer.tokenizer import Tokenizer
14
+ from openlinktoken.tokentransformer.token_transformer import TokenTransformer
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class TokenGenerator:
20
+ """Generates both the token signature and the token itself."""
21
+
22
+ @classmethod
23
+ def from_transformers(
24
+ cls, token_definition: BaseTokenDefinition, token_transformer_list: List[TokenTransformer]
25
+ ) -> "TokenGenerator":
26
+ """
27
+ Convenience constructor that creates a TokenGenerator with SHA256Tokenizer.
28
+
29
+ Args:
30
+ token_definition: The token definition.
31
+ token_transformer_list: A list of token transformers.
32
+
33
+ Returns:
34
+ A TokenGenerator instance with SHA256Tokenizer.
35
+ """
36
+ return cls(token_definition, SHA256Tokenizer(token_transformer_list))
37
+
38
+ def __init__(self, token_definition: BaseTokenDefinition, tokenizer: Tokenizer):
39
+ """
40
+ Initialize the token generator with an explicit tokenizer.
41
+
42
+ Args:
43
+ token_definition: The token definition.
44
+ tokenizer: Tokenizer implementation. Use PassthroughTokenizer for plain mode.
45
+ """
46
+ self.token_definition = token_definition
47
+ self.attribute_instance_map: Dict[Type[Attribute], Attribute] = {}
48
+
49
+ # Load attributes
50
+ for attribute in AttributeLoader.load():
51
+ self.attribute_instance_map[type(attribute)] = attribute
52
+
53
+ self.tokenizer = tokenizer
54
+
55
+ def _get_token_signature(
56
+ self, token_id: str, person_attributes: Dict[Type[Attribute], str], result: TokenGeneratorResult
57
+ ) -> Optional[str]:
58
+ """
59
+ Get the token signature for a given token identifier.
60
+
61
+ Populates the invalid_attributes list in the result object with the attributes
62
+ that are invalid.
63
+
64
+ Args:
65
+ token_id: The token identifier.
66
+ person_attributes: The person attributes map.
67
+ result: The token generator result.
68
+
69
+ Returns:
70
+ The token signature using the token definition for the given token identifier.
71
+ """
72
+ definition = self.token_definition.get_token_definition(token_id)
73
+
74
+ if person_attributes is None:
75
+ raise ValueError("Person attributes cannot be null.")
76
+
77
+ values = []
78
+
79
+ for attribute_expression in definition:
80
+ attribute_class = attribute_expression.attribute_class
81
+
82
+ if attribute_class not in person_attributes:
83
+ return None
84
+
85
+ attribute = self.attribute_instance_map.get(attribute_class)
86
+ if attribute is None:
87
+ return None
88
+
89
+ attribute_value = person_attributes[attribute_class]
90
+
91
+ if not attribute.validate(attribute_value):
92
+ result.invalid_attributes.add(attribute.get_name())
93
+ return None
94
+
95
+ attribute_value = attribute.normalize(attribute_value)
96
+
97
+ try:
98
+ attribute_value = attribute_expression.get_effective_value(attribute_value)
99
+ values.append(attribute_value)
100
+ except ValueError as e:
101
+ logger.error(str(e))
102
+ return None
103
+
104
+ # Filter out None and blank values, then join with '|'
105
+ filtered_values = [v for v in values if v is not None and v.strip() != ""]
106
+ return "|".join(filtered_values)
107
+
108
+ def get_all_token_signatures(self, person_attributes: Dict[Type[Attribute], str]) -> Dict[str, str]:
109
+ """
110
+ Get the token signatures for all token/rule identifiers.
111
+
112
+ This is mostly a debug/logging/test method.
113
+
114
+ Args:
115
+ person_attributes: The person attributes map.
116
+
117
+ Returns:
118
+ A map of token/rule identifier to the token signature.
119
+ """
120
+ signatures = {}
121
+
122
+ for token_id in self.token_definition.get_token_identifiers():
123
+ try:
124
+ signature = self._get_token_signature(token_id, person_attributes, TokenGeneratorResult())
125
+ if signature is not None:
126
+ signatures[token_id] = signature
127
+ except Exception as e:
128
+ logger.error(f"Error generating token signature for token id: {token_id}", exc_info=e)
129
+
130
+ return signatures
131
+
132
+ def _get_token(
133
+ self, token_id: str, person_attributes: Dict[Type[Attribute], str], result: TokenGeneratorResult
134
+ ) -> Optional[str]:
135
+ """
136
+ Get token for a given token identifier.
137
+
138
+ Args:
139
+ token_id: The token identifier.
140
+ person_attributes: The person attributes map.
141
+ result: The token generator result.
142
+
143
+ Returns:
144
+ The token using the token definition for the given token identifier.
145
+
146
+ Raises:
147
+ TokenGenerationException: In case of failure to generate the token.
148
+ """
149
+ signature = self._get_token_signature(token_id, person_attributes, result)
150
+ logger.debug(f"Token signature for token id {token_id}: {signature}")
151
+
152
+ try:
153
+ token = self.tokenizer.tokenize(signature)
154
+ # Track blank tokens by rule
155
+ if Token.BLANK == token:
156
+ result.blank_tokens_by_rule.add(token_id)
157
+ return token
158
+ except Exception as e:
159
+ logger.error(f"Error generating token for token id: {token_id}", exc_info=e)
160
+ raise TokenGenerationException("Error generating token", e)
161
+
162
+ def get_all_tokens(self, person_attributes: Dict[Type[Attribute], str]) -> TokenGeneratorResult:
163
+ """
164
+ Get the tokens for all token/rule identifiers.
165
+
166
+ Args:
167
+ person_attributes: The person attributes map.
168
+
169
+ Returns:
170
+ A TokenGeneratorResult object containing the tokens and invalid attributes.
171
+ """
172
+ result = TokenGeneratorResult()
173
+
174
+ for token_id in self.token_definition.get_token_identifiers():
175
+ try:
176
+ token = self._get_token(token_id, person_attributes, result)
177
+ if token is not None:
178
+ result.tokens[token_id] = token
179
+ except Exception as e:
180
+ logger.error(f"Error generating token for token id: {token_id}", exc_info=e)
181
+
182
+ return result
183
+
184
+ def get_invalid_person_attributes(self, person_attributes: Dict[Type[Attribute], str]) -> Set[str]:
185
+ """
186
+ Get invalid person attribute names.
187
+
188
+ Args:
189
+ person_attributes: The person attributes map.
190
+
191
+ Returns:
192
+ A set of invalid person attribute names.
193
+ """
194
+ response = set()
195
+
196
+ for attribute_class, value in person_attributes.items():
197
+ attribute = self.attribute_instance_map.get(attribute_class)
198
+ if attribute and not attribute.validate(value):
199
+ response.add(attribute.get_name())
200
+
201
+ return response