openlinktoken 2.0.0__tar.gz → 2.1.0__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.
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/PKG-INFO +3 -2
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/pyproject.toml +3 -2
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/requirements.txt +4 -1
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/setup.py +1 -2
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/__init__.py +1 -1
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/__init__.py +4 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/attribute_expression.py +24 -1
- openlinktoken-2.1.0/src/main/openlinktoken/attributes/attribute_field.py +58 -0
- openlinktoken-2.1.0/src/main/openlinktoken/attributes/field_registry.py +137 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/metadata.py +1 -1
- openlinktoken-2.1.0/src/main/openlinktoken/tokens/definitions/field_ids.py +17 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t1_token.py +5 -4
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t2_token.py +5 -4
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t3_token.py +5 -4
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t4_token.py +4 -3
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t5_token.py +4 -3
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_generator.py +151 -22
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken.egg-info/PKG-INFO +3 -2
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken.egg-info/SOURCES.txt +3 -0
- openlinktoken-2.1.0/src/main/openlinktoken.egg-info/requires.txt +3 -0
- openlinktoken-2.0.0/src/main/openlinktoken.egg-info/requires.txt +0 -2
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/MANIFEST.in +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/dev-requirements.txt +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/setup.cfg +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/attribute_loader.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/base_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/combined_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/__init__.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/date_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/decimal_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/integer_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/record_id_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/string_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/year_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/__init__.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/age_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/birth_date_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/birth_year_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/canadian_postal_code_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/first_name_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/last_name_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/postal_code_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/sex_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/social_security_number_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/us_postal_code_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/serializable_attribute.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/utilities/__init__.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/utilities/attribute_utilities.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/__init__.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/age_range_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/attribute_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/date_range_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/not_in_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/not_null_or_empty_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/not_starts_with_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/regex_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/serializable_attribute_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/year_range_validator.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/ec_key_utils.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/exchange_config.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/exchange_jwe.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/__init__.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/base_token_definition.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_definition.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_generation_exception.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_generator_result.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_registry.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/tokenizer/__init__.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/tokenizer/passthrough_tokenizer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/tokenizer/sha256_tokenizer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/tokenizer/tokenizer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/__init__.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/decrypt_token_transformer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/encrypt_token_transformer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/encryption_constants.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/hash_token_transformer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/jwe_match_token_formatter.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/match_token_constants.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/no_operation_token_transformer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/token_transformer.py +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken.egg-info/dependency_links.txt +0 -0
- {openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openlinktoken
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Open Link Token Python core library for record linkage
|
|
5
5
|
Home-page: https://github.com/TruvetaPublic/OpenLinkToken
|
|
6
6
|
Author: Open Link Token Contributors
|
|
@@ -8,8 +8,9 @@ Project-URL: Source, https://github.com/TruvetaPublic/OpenLinkToken
|
|
|
8
8
|
Project-URL: Documentation, https://github.com/TruvetaPublic/OpenLinkToken/blob/main/README.md
|
|
9
9
|
Requires-Python: >=3.10
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
|
-
Requires-Dist: cryptography==
|
|
11
|
+
Requires-Dist: cryptography==49.0.0
|
|
12
12
|
Requires-Dist: jwcrypto==1.5.7
|
|
13
|
+
Requires-Dist: PyYAML==6.0.3
|
|
13
14
|
Dynamic: author
|
|
14
15
|
Dynamic: description
|
|
15
16
|
Dynamic: description-content-type
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "openlinktoken"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.1.0"
|
|
4
4
|
requires-python = ">=3.10"
|
|
5
5
|
dynamic = [
|
|
6
6
|
"authors",
|
|
@@ -10,8 +10,9 @@ dynamic = [
|
|
|
10
10
|
"urls",
|
|
11
11
|
]
|
|
12
12
|
dependencies = [
|
|
13
|
-
"cryptography==
|
|
13
|
+
"cryptography==49.0.0",
|
|
14
14
|
"jwcrypto==1.5.7",
|
|
15
|
+
"PyYAML==6.0.3",
|
|
15
16
|
]
|
|
16
17
|
|
|
17
18
|
[build-system]
|
|
@@ -15,10 +15,9 @@ try:
|
|
|
15
15
|
except FileNotFoundError:
|
|
16
16
|
# Fallback to a short description if README is unavailable
|
|
17
17
|
long_description = "Open Link Token Python implementation for record linkage."
|
|
18
|
-
|
|
19
18
|
setup(
|
|
20
19
|
name="openlinktoken",
|
|
21
|
-
version="2.
|
|
20
|
+
version="2.1.0",
|
|
22
21
|
author="Open Link Token Contributors",
|
|
23
22
|
description="Open Link Token Python core library for record linkage",
|
|
24
23
|
long_description=long_description,
|
|
@@ -2,16 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
from .attribute import Attribute
|
|
4
4
|
from .attribute_expression import AttributeExpression
|
|
5
|
+
from .attribute_field import AttributeField
|
|
5
6
|
from .attribute_loader import AttributeLoader
|
|
6
7
|
from .base_attribute import BaseAttribute
|
|
7
8
|
from .combined_attribute import CombinedAttribute
|
|
9
|
+
from .field_registry import FieldRegistry
|
|
8
10
|
from .serializable_attribute import SerializableAttribute
|
|
9
11
|
|
|
10
12
|
__all__ = [
|
|
11
13
|
"Attribute",
|
|
12
14
|
"AttributeExpression",
|
|
15
|
+
"AttributeField",
|
|
13
16
|
"AttributeLoader",
|
|
14
17
|
"BaseAttribute",
|
|
15
18
|
"CombinedAttribute",
|
|
19
|
+
"FieldRegistry",
|
|
16
20
|
"SerializableAttribute",
|
|
17
21
|
]
|
|
@@ -32,16 +32,39 @@ class AttributeExpression:
|
|
|
32
32
|
|
|
33
33
|
EXPRESSION_PATTERN = re.compile(r"\s*(?P<expr>[^ (]+)(?:\((?P<args>[^\)]+)\))?")
|
|
34
34
|
|
|
35
|
-
def __init__(
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
attribute_class: Type["Attribute"],
|
|
38
|
+
expressions: Optional[str],
|
|
39
|
+
field_id: Optional[str] = None,
|
|
40
|
+
):
|
|
36
41
|
"""
|
|
37
42
|
Initialize the AttributeExpression.
|
|
38
43
|
|
|
39
44
|
Args:
|
|
40
45
|
attribute_class: The class of the attribute being processed.
|
|
41
46
|
expressions: The string of expressions to apply.
|
|
47
|
+
field_id: Optional field identifier for field-based lookup. When None,
|
|
48
|
+
the attribute class is used directly as the identity key (legacy behavior).
|
|
42
49
|
"""
|
|
43
50
|
self.attribute_class = attribute_class
|
|
44
51
|
self.expressions = expressions
|
|
52
|
+
self.field_id = field_id
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def of(cls, field_id: str, attribute_class: Type["Attribute"], expressions: Optional[str]) -> "AttributeExpression":
|
|
56
|
+
"""
|
|
57
|
+
Create an attribute expression using a field ID for identity.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
field_id: Unique field identifier from the FieldRegistry.
|
|
61
|
+
attribute_class: The class of the attribute providing normalization and validation.
|
|
62
|
+
expressions: The string of expressions to apply.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
A new AttributeExpression with field_id set.
|
|
66
|
+
"""
|
|
67
|
+
return cls(attribute_class, expressions, field_id=field_id)
|
|
45
68
|
|
|
46
69
|
def get_effective_value(self, value: Optional[str]) -> str:
|
|
47
70
|
"""
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Type
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from openlinktoken.attributes.attribute import Attribute
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AttributeField:
|
|
10
|
+
"""
|
|
11
|
+
Represents a named field slot in a person record.
|
|
12
|
+
|
|
13
|
+
An AttributeField separates two concerns that were previously conflated:
|
|
14
|
+
- Field identity: "which value from the person record?" (the field_id)
|
|
15
|
+
- Attribute behavior: "how to normalize/validate?" (the attribute_class)
|
|
16
|
+
|
|
17
|
+
This allows multiple fields to share the same attribute type (e.g., two fields
|
|
18
|
+
both using StringAttribute normalization) while remaining distinct keys in the
|
|
19
|
+
person attributes map.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, field_id: str, attribute_class: Type["Attribute"]):
|
|
23
|
+
"""
|
|
24
|
+
Create an attribute field with the given identity and attribute type.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
field_id: Unique field identifier (e.g., "LastName", "MotherLastName").
|
|
28
|
+
attribute_class: The attribute class providing normalization and validation behavior.
|
|
29
|
+
"""
|
|
30
|
+
if field_id is None:
|
|
31
|
+
raise ValueError("field_id must not be None")
|
|
32
|
+
if attribute_class is None:
|
|
33
|
+
raise ValueError("attribute_class must not be None")
|
|
34
|
+
self._field_id = field_id
|
|
35
|
+
self._attribute_class = attribute_class
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def field_id(self) -> str:
|
|
39
|
+
"""Get the unique field identifier."""
|
|
40
|
+
return self._field_id
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def attribute_class(self) -> Type["Attribute"]:
|
|
44
|
+
"""Get the attribute class providing normalization and validation behavior."""
|
|
45
|
+
return self._attribute_class
|
|
46
|
+
|
|
47
|
+
def __eq__(self, other: object) -> bool:
|
|
48
|
+
if self is other:
|
|
49
|
+
return True
|
|
50
|
+
if not isinstance(other, AttributeField):
|
|
51
|
+
return False
|
|
52
|
+
return self._field_id == other._field_id
|
|
53
|
+
|
|
54
|
+
def __hash__(self) -> int:
|
|
55
|
+
return hash(self._field_id)
|
|
56
|
+
|
|
57
|
+
def __repr__(self) -> str:
|
|
58
|
+
return f"AttributeField(field_id='{self._field_id}', attribute_class={self._attribute_class.__name__})"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Optional, Set, Type
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.attribute import Attribute
|
|
6
|
+
from openlinktoken.attributes.attribute_field import AttributeField
|
|
7
|
+
from openlinktoken.attributes.attribute_loader import AttributeLoader
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FieldRegistry:
|
|
11
|
+
"""
|
|
12
|
+
Registry mapping field identifiers to their corresponding Attribute instances.
|
|
13
|
+
|
|
14
|
+
The FieldRegistry is the resolution layer that connects field identifiers
|
|
15
|
+
(string keys used in person-attribute maps) to the attribute instances that provide
|
|
16
|
+
normalization and validation behavior.
|
|
17
|
+
|
|
18
|
+
Built-in attributes are auto-registered using their canonical name as the field ID.
|
|
19
|
+
Config-driven fields can register additional mappings (e.g., "MotherLastName" → StringAttribute).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, fields: Dict[str, AttributeField], field_to_attribute: Dict[str, Attribute]):
|
|
23
|
+
"""
|
|
24
|
+
Initialize a FieldRegistry with the given mappings.
|
|
25
|
+
|
|
26
|
+
Use FieldRegistry.create_default() or FieldRegistry.Builder for construction.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
fields: Mapping of field ID to AttributeField.
|
|
30
|
+
field_to_attribute: Mapping of field ID to Attribute instance.
|
|
31
|
+
"""
|
|
32
|
+
self._fields: Dict[str, AttributeField] = dict(fields)
|
|
33
|
+
self._field_to_attribute: Dict[str, Attribute] = dict(field_to_attribute)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def create_default(cls) -> "FieldRegistry":
|
|
37
|
+
"""
|
|
38
|
+
Create a default registry populated with all built-in attributes.
|
|
39
|
+
|
|
40
|
+
Each attribute is registered using its canonical name (from get_name()) as the field ID.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
A new registry with built-in attribute registrations.
|
|
44
|
+
"""
|
|
45
|
+
builder = cls.Builder()
|
|
46
|
+
for attribute in AttributeLoader.load():
|
|
47
|
+
builder.register(attribute.get_name(), type(attribute), attribute)
|
|
48
|
+
return builder.build()
|
|
49
|
+
|
|
50
|
+
def get_attribute(self, field_id: str) -> Optional[Attribute]:
|
|
51
|
+
"""
|
|
52
|
+
Resolve the attribute instance for a given field ID.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
field_id: The field identifier.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The attribute if registered, None otherwise.
|
|
59
|
+
"""
|
|
60
|
+
return self._field_to_attribute.get(field_id)
|
|
61
|
+
|
|
62
|
+
def get_field(self, field_id: str) -> Optional[AttributeField]:
|
|
63
|
+
"""
|
|
64
|
+
Resolve the attribute field definition for a given field ID.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
field_id: The field identifier.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The AttributeField if registered, None otherwise.
|
|
71
|
+
"""
|
|
72
|
+
return self._fields.get(field_id)
|
|
73
|
+
|
|
74
|
+
def get_field_ids(self) -> Set[str]:
|
|
75
|
+
"""
|
|
76
|
+
Return all registered field IDs.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
A set of field identifiers.
|
|
80
|
+
"""
|
|
81
|
+
return set(self._fields.keys())
|
|
82
|
+
|
|
83
|
+
def size(self) -> int:
|
|
84
|
+
"""
|
|
85
|
+
Return the number of registered fields.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The registry size.
|
|
89
|
+
"""
|
|
90
|
+
return len(self._fields)
|
|
91
|
+
|
|
92
|
+
class Builder:
|
|
93
|
+
"""Builder for constructing a FieldRegistry with custom registrations."""
|
|
94
|
+
|
|
95
|
+
def __init__(self):
|
|
96
|
+
self._fields: Dict[str, AttributeField] = {}
|
|
97
|
+
self._field_to_attribute: Dict[str, Attribute] = {}
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_defaults(cls) -> "FieldRegistry.Builder":
|
|
101
|
+
"""
|
|
102
|
+
Create a builder pre-populated with all built-in attribute registrations.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A new builder with defaults loaded.
|
|
106
|
+
"""
|
|
107
|
+
builder = cls()
|
|
108
|
+
for attribute in AttributeLoader.load():
|
|
109
|
+
builder.register(attribute.get_name(), type(attribute), attribute)
|
|
110
|
+
return builder
|
|
111
|
+
|
|
112
|
+
def register(
|
|
113
|
+
self, field_id: str, attribute_class: Type[Attribute], attribute: Attribute
|
|
114
|
+
) -> "FieldRegistry.Builder":
|
|
115
|
+
"""
|
|
116
|
+
Register a field ID with its attribute class and instance.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
field_id: The unique field identifier.
|
|
120
|
+
attribute_class: The attribute class providing behavior.
|
|
121
|
+
attribute: The attribute instance for normalization/validation.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
This builder for chaining.
|
|
125
|
+
"""
|
|
126
|
+
self._fields[field_id] = AttributeField(field_id, attribute_class)
|
|
127
|
+
self._field_to_attribute[field_id] = attribute
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def build(self) -> "FieldRegistry":
|
|
131
|
+
"""
|
|
132
|
+
Build an immutable FieldRegistry from the current registrations.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The constructed registry.
|
|
136
|
+
"""
|
|
137
|
+
return FieldRegistry(self._fields, self._field_to_attribute)
|
|
@@ -22,7 +22,7 @@ class Metadata:
|
|
|
22
22
|
METADATA_FILE_EXTENSION = ".metadata.json"
|
|
23
23
|
SYSTEM_PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
24
24
|
|
|
25
|
-
DEFAULT_VERSION = "2.
|
|
25
|
+
DEFAULT_VERSION = "2.1.0"
|
|
26
26
|
|
|
27
27
|
# Output format values
|
|
28
28
|
OUTPUT_FORMAT_JSON = "JSON"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FieldIds:
|
|
5
|
+
"""
|
|
6
|
+
Field identifiers referenced by the built-in T1-T5 token definitions.
|
|
7
|
+
|
|
8
|
+
Centralizing these values avoids duplicating the same field-id strings across
|
|
9
|
+
multiple token definition modules.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
LAST_NAME = "LastName"
|
|
13
|
+
FIRST_NAME = "FirstName"
|
|
14
|
+
SEX = "Sex"
|
|
15
|
+
BIRTH_DATE = "BirthDate"
|
|
16
|
+
POSTAL_CODE = "PostalCode"
|
|
17
|
+
SOCIAL_SECURITY_NUMBER = "SocialSecurityNumber"
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t1_token.py
RENAMED
|
@@ -7,6 +7,7 @@ from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttrib
|
|
|
7
7
|
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
8
8
|
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
9
9
|
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
10
|
+
from openlinktoken.tokens.definitions.field_ids import FieldIds
|
|
10
11
|
from openlinktoken.tokens.token import Token
|
|
11
12
|
|
|
12
13
|
|
|
@@ -24,10 +25,10 @@ class T1Token(Token):
|
|
|
24
25
|
def __init__(self):
|
|
25
26
|
"""Initialize the T1 token definition."""
|
|
26
27
|
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"),
|
|
28
|
+
AttributeExpression(LastNameAttribute, "T|U", field_id=FieldIds.LAST_NAME),
|
|
29
|
+
AttributeExpression(FirstNameAttribute, "T|S(0,1)|U", field_id=FieldIds.FIRST_NAME),
|
|
30
|
+
AttributeExpression(SexAttribute, "T|U", field_id=FieldIds.SEX),
|
|
31
|
+
AttributeExpression(BirthDateAttribute, "T|D", field_id=FieldIds.BIRTH_DATE),
|
|
31
32
|
]
|
|
32
33
|
|
|
33
34
|
def get_identifier(self) -> str:
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t2_token.py
RENAMED
|
@@ -7,6 +7,7 @@ from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttrib
|
|
|
7
7
|
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
8
8
|
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
9
9
|
from openlinktoken.attributes.person.postal_code_attribute import PostalCodeAttribute
|
|
10
|
+
from openlinktoken.tokens.definitions.field_ids import FieldIds
|
|
10
11
|
from openlinktoken.tokens.token import Token
|
|
11
12
|
|
|
12
13
|
|
|
@@ -24,10 +25,10 @@ class T2Token(Token):
|
|
|
24
25
|
def __init__(self):
|
|
25
26
|
"""Initialize the T2 token definition."""
|
|
26
27
|
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"),
|
|
28
|
+
AttributeExpression(LastNameAttribute, "T|U", field_id=FieldIds.LAST_NAME),
|
|
29
|
+
AttributeExpression(FirstNameAttribute, "T|U", field_id=FieldIds.FIRST_NAME),
|
|
30
|
+
AttributeExpression(BirthDateAttribute, "T|D", field_id=FieldIds.BIRTH_DATE),
|
|
31
|
+
AttributeExpression(PostalCodeAttribute, "T|S(0,3)|U", field_id=FieldIds.POSTAL_CODE),
|
|
31
32
|
]
|
|
32
33
|
|
|
33
34
|
def get_identifier(self) -> str:
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t3_token.py
RENAMED
|
@@ -7,6 +7,7 @@ from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttrib
|
|
|
7
7
|
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
8
8
|
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
9
9
|
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
10
|
+
from openlinktoken.tokens.definitions.field_ids import FieldIds
|
|
10
11
|
from openlinktoken.tokens.token import Token
|
|
11
12
|
|
|
12
13
|
|
|
@@ -24,10 +25,10 @@ class T3Token(Token):
|
|
|
24
25
|
def __init__(self):
|
|
25
26
|
"""Initialize the T3 token definition."""
|
|
26
27
|
self._definition = [
|
|
27
|
-
AttributeExpression(LastNameAttribute, "T|U"),
|
|
28
|
-
AttributeExpression(FirstNameAttribute, "T|U"),
|
|
29
|
-
AttributeExpression(SexAttribute, "T|U"),
|
|
30
|
-
AttributeExpression(BirthDateAttribute, "T|D"),
|
|
28
|
+
AttributeExpression(LastNameAttribute, "T|U", field_id=FieldIds.LAST_NAME),
|
|
29
|
+
AttributeExpression(FirstNameAttribute, "T|U", field_id=FieldIds.FIRST_NAME),
|
|
30
|
+
AttributeExpression(SexAttribute, "T|U", field_id=FieldIds.SEX),
|
|
31
|
+
AttributeExpression(BirthDateAttribute, "T|D", field_id=FieldIds.BIRTH_DATE),
|
|
31
32
|
]
|
|
32
33
|
|
|
33
34
|
def get_identifier(self) -> str:
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t4_token.py
RENAMED
|
@@ -6,6 +6,7 @@ from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
|
6
6
|
from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
|
|
7
7
|
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
8
8
|
from openlinktoken.attributes.person.social_security_number_attribute import SocialSecurityNumberAttribute
|
|
9
|
+
from openlinktoken.tokens.definitions.field_ids import FieldIds
|
|
9
10
|
from openlinktoken.tokens.token import Token
|
|
10
11
|
|
|
11
12
|
|
|
@@ -23,9 +24,9 @@ class T4Token(Token):
|
|
|
23
24
|
def __init__(self):
|
|
24
25
|
"""Initialize the T4 token definition."""
|
|
25
26
|
self._definition = [
|
|
26
|
-
AttributeExpression(SocialSecurityNumberAttribute, "T|M(\\d+)"),
|
|
27
|
-
AttributeExpression(SexAttribute, "T|U"),
|
|
28
|
-
AttributeExpression(BirthDateAttribute, "T|D"),
|
|
27
|
+
AttributeExpression(SocialSecurityNumberAttribute, "T|M(\\d+)", field_id=FieldIds.SOCIAL_SECURITY_NUMBER),
|
|
28
|
+
AttributeExpression(SexAttribute, "T|U", field_id=FieldIds.SEX),
|
|
29
|
+
AttributeExpression(BirthDateAttribute, "T|D", field_id=FieldIds.BIRTH_DATE),
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
def get_identifier(self) -> str:
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/definitions/t5_token.py
RENAMED
|
@@ -6,6 +6,7 @@ from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
|
6
6
|
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
7
7
|
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
8
8
|
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
9
|
+
from openlinktoken.tokens.definitions.field_ids import FieldIds
|
|
9
10
|
from openlinktoken.tokens.token import Token
|
|
10
11
|
|
|
11
12
|
|
|
@@ -23,9 +24,9 @@ class T5Token(Token):
|
|
|
23
24
|
def __init__(self):
|
|
24
25
|
"""Initialize the T5 token definition."""
|
|
25
26
|
self._definition = [
|
|
26
|
-
AttributeExpression(LastNameAttribute, "T|U"),
|
|
27
|
-
AttributeExpression(FirstNameAttribute, "T|S(0,3)|U"),
|
|
28
|
-
AttributeExpression(SexAttribute, "T|U"),
|
|
27
|
+
AttributeExpression(LastNameAttribute, "T|U", field_id=FieldIds.LAST_NAME),
|
|
28
|
+
AttributeExpression(FirstNameAttribute, "T|S(0,3)|U", field_id=FieldIds.FIRST_NAME),
|
|
29
|
+
AttributeExpression(SexAttribute, "T|U", field_id=FieldIds.SEX),
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
def get_identifier(self) -> str:
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_generator.py
RENAMED
|
@@ -5,6 +5,7 @@ from typing import Dict, List, Optional, Set, Type
|
|
|
5
5
|
|
|
6
6
|
from openlinktoken.attributes.attribute import Attribute
|
|
7
7
|
from openlinktoken.attributes.attribute_loader import AttributeLoader
|
|
8
|
+
from openlinktoken.attributes.field_registry import FieldRegistry
|
|
8
9
|
from openlinktoken.tokens.base_token_definition import BaseTokenDefinition
|
|
9
10
|
from openlinktoken.tokens.token import Token
|
|
10
11
|
from openlinktoken.tokens.token_generation_exception import TokenGenerationException
|
|
@@ -35,13 +36,20 @@ class TokenGenerator:
|
|
|
35
36
|
"""
|
|
36
37
|
return cls(token_definition, SHA256Tokenizer(token_transformer_list))
|
|
37
38
|
|
|
38
|
-
def __init__(
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
token_definition: BaseTokenDefinition,
|
|
42
|
+
tokenizer: Tokenizer,
|
|
43
|
+
field_registry: Optional[FieldRegistry] = None,
|
|
44
|
+
):
|
|
39
45
|
"""
|
|
40
46
|
Initialize the token generator with an explicit tokenizer.
|
|
41
47
|
|
|
42
48
|
Args:
|
|
43
49
|
token_definition: The token definition.
|
|
44
50
|
tokenizer: Tokenizer implementation. Use PassthroughTokenizer for plain mode.
|
|
51
|
+
field_registry: Optional custom field registry for field-ID-based lookups.
|
|
52
|
+
When None, a default registry is created from built-in attributes.
|
|
45
53
|
"""
|
|
46
54
|
self.token_definition = token_definition
|
|
47
55
|
self.attribute_instance_map: Dict[Type[Attribute], Attribute] = {}
|
|
@@ -51,19 +59,20 @@ class TokenGenerator:
|
|
|
51
59
|
self.attribute_instance_map[type(attribute)] = attribute
|
|
52
60
|
|
|
53
61
|
self.tokenizer = tokenizer
|
|
62
|
+
self.field_registry = field_registry or FieldRegistry.create_default()
|
|
54
63
|
|
|
55
64
|
def _get_token_signature(
|
|
56
65
|
self, token_id: str, person_attributes: Dict[Type[Attribute], str], result: TokenGeneratorResult
|
|
57
66
|
) -> Optional[str]:
|
|
58
67
|
"""
|
|
59
|
-
Get the token signature
|
|
68
|
+
Get the token signature using a class-keyed person attributes map.
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
.. deprecated::
|
|
71
|
+
Use :meth:`_get_token_signature_via_field_id` with a field-ID-keyed map instead.
|
|
63
72
|
|
|
64
73
|
Args:
|
|
65
74
|
token_id: The token identifier.
|
|
66
|
-
person_attributes: The person attributes map.
|
|
75
|
+
person_attributes: The person attributes map, keyed by attribute class.
|
|
67
76
|
result: The token generator result.
|
|
68
77
|
|
|
69
78
|
Returns:
|
|
@@ -107,12 +116,13 @@ class TokenGenerator:
|
|
|
107
116
|
|
|
108
117
|
def get_all_token_signatures(self, person_attributes: Dict[Type[Attribute], str]) -> Dict[str, str]:
|
|
109
118
|
"""
|
|
110
|
-
Get the token signatures for all token/rule identifiers.
|
|
119
|
+
Get the token signatures for all token/rule identifiers using a class-keyed map.
|
|
111
120
|
|
|
112
|
-
|
|
121
|
+
.. deprecated::
|
|
122
|
+
Use :meth:`get_all_token_signatures_via_field_id` with a field-ID-keyed map instead.
|
|
113
123
|
|
|
114
124
|
Args:
|
|
115
|
-
person_attributes: The person attributes map.
|
|
125
|
+
person_attributes: The person attributes map, keyed by attribute class.
|
|
116
126
|
|
|
117
127
|
Returns:
|
|
118
128
|
A map of token/rule identifier to the token signature.
|
|
@@ -133,18 +143,10 @@ class TokenGenerator:
|
|
|
133
143
|
self, token_id: str, person_attributes: Dict[Type[Attribute], str], result: TokenGeneratorResult
|
|
134
144
|
) -> Optional[str]:
|
|
135
145
|
"""
|
|
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.
|
|
146
|
+
Get token for a given token identifier using a class-keyed person attributes map.
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Raises:
|
|
147
|
-
TokenGenerationException: In case of failure to generate the token.
|
|
148
|
+
.. deprecated::
|
|
149
|
+
Use :meth:`get_all_tokens_via_field_id` with a field-ID-keyed map instead.
|
|
148
150
|
"""
|
|
149
151
|
signature = self._get_token_signature(token_id, person_attributes, result)
|
|
150
152
|
logger.debug(f"Token signature for token id {token_id}: {signature}")
|
|
@@ -161,10 +163,13 @@ class TokenGenerator:
|
|
|
161
163
|
|
|
162
164
|
def get_all_tokens(self, person_attributes: Dict[Type[Attribute], str]) -> TokenGeneratorResult:
|
|
163
165
|
"""
|
|
164
|
-
Get the tokens for all token/rule identifiers.
|
|
166
|
+
Get the tokens for all token/rule identifiers using a class-keyed person attributes map.
|
|
167
|
+
|
|
168
|
+
.. deprecated::
|
|
169
|
+
Use :meth:`get_all_tokens_via_field_id` with a field-ID-keyed ``Dict[str, str]`` map instead.
|
|
165
170
|
|
|
166
171
|
Args:
|
|
167
|
-
person_attributes: The person attributes map.
|
|
172
|
+
person_attributes: The person attributes map, keyed by attribute class.
|
|
168
173
|
|
|
169
174
|
Returns:
|
|
170
175
|
A TokenGeneratorResult object containing the tokens and invalid attributes.
|
|
@@ -185,8 +190,11 @@ class TokenGenerator:
|
|
|
185
190
|
"""
|
|
186
191
|
Get invalid person attribute names.
|
|
187
192
|
|
|
193
|
+
.. deprecated::
|
|
194
|
+
Use field-ID-keyed person attributes with :meth:`get_all_tokens_via_field_id` instead.
|
|
195
|
+
|
|
188
196
|
Args:
|
|
189
|
-
person_attributes: The person attributes map.
|
|
197
|
+
person_attributes: The person attributes map, keyed by attribute class.
|
|
190
198
|
|
|
191
199
|
Returns:
|
|
192
200
|
A set of invalid person attribute names.
|
|
@@ -199,3 +207,124 @@ class TokenGenerator:
|
|
|
199
207
|
response.add(attribute.get_name())
|
|
200
208
|
|
|
201
209
|
return response
|
|
210
|
+
|
|
211
|
+
# ===== Primary API =====
|
|
212
|
+
|
|
213
|
+
def _get_token_signature_via_field_id(
|
|
214
|
+
self, token_id: str, person_attributes: Dict[str, str], result: TokenGeneratorResult
|
|
215
|
+
) -> Optional[str]:
|
|
216
|
+
"""
|
|
217
|
+
Get the token signature for a given token identifier.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
token_id: The token identifier.
|
|
221
|
+
person_attributes: Person attributes keyed by field ID (e.g., "LastName" → "Smith").
|
|
222
|
+
result: The token generator result.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
The token signature, or None if required fields are missing or invalid.
|
|
226
|
+
"""
|
|
227
|
+
definition = self.token_definition.get_token_definition(token_id)
|
|
228
|
+
|
|
229
|
+
if person_attributes is None:
|
|
230
|
+
raise ValueError("Person attributes cannot be null.")
|
|
231
|
+
|
|
232
|
+
values = []
|
|
233
|
+
|
|
234
|
+
for attribute_expression in definition:
|
|
235
|
+
resolved_field_id = self._resolve_field_id(attribute_expression)
|
|
236
|
+
if resolved_field_id is None or resolved_field_id not in person_attributes:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
attribute = self._resolve_attribute(attribute_expression, resolved_field_id)
|
|
240
|
+
if attribute is None:
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
attribute_value = person_attributes[resolved_field_id]
|
|
244
|
+
|
|
245
|
+
if not attribute.validate(attribute_value):
|
|
246
|
+
result.invalid_attributes.add(attribute.get_name())
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
attribute_value = attribute.normalize(attribute_value)
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
attribute_value = attribute_expression.get_effective_value(attribute_value)
|
|
253
|
+
values.append(attribute_value)
|
|
254
|
+
except ValueError as e:
|
|
255
|
+
logger.error(str(e))
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
filtered_values = [v for v in values if v is not None and v.strip() != ""]
|
|
259
|
+
return "|".join(filtered_values)
|
|
260
|
+
|
|
261
|
+
def get_all_tokens_via_field_id(self, person_attributes: Dict[str, str]) -> TokenGeneratorResult:
|
|
262
|
+
"""
|
|
263
|
+
Get the tokens for all token/rule identifiers.
|
|
264
|
+
|
|
265
|
+
This is the preferred API. It natively supports multiple fields sharing the same
|
|
266
|
+
attribute type (e.g., "MotherLastName" and "FatherLastName" both backed by StringAttribute).
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
person_attributes: Person attributes keyed by field ID (e.g., "LastName" → "Smith").
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
A TokenGeneratorResult object containing the tokens and invalid attributes.
|
|
273
|
+
"""
|
|
274
|
+
result = TokenGeneratorResult()
|
|
275
|
+
|
|
276
|
+
for token_id in self.token_definition.get_token_identifiers():
|
|
277
|
+
try:
|
|
278
|
+
signature = self._get_token_signature_via_field_id(token_id, person_attributes, result)
|
|
279
|
+
logger.debug(f"Token signature for token id {token_id}: {signature}")
|
|
280
|
+
try:
|
|
281
|
+
token = self.tokenizer.tokenize(signature)
|
|
282
|
+
if Token.BLANK == token:
|
|
283
|
+
result.blank_tokens_by_rule.add(token_id)
|
|
284
|
+
if token is not None:
|
|
285
|
+
result.tokens[token_id] = token
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Error generating token for token id: {token_id}", exc_info=e)
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error(f"Error generating token for token id: {token_id}", exc_info=e)
|
|
290
|
+
|
|
291
|
+
return result
|
|
292
|
+
|
|
293
|
+
def get_all_token_signatures_via_field_id(self, person_attributes: Dict[str, str]) -> Dict[str, str]:
|
|
294
|
+
"""
|
|
295
|
+
Get the token signatures for all token/rule identifiers. Mostly useful for debugging.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
person_attributes: Person attributes keyed by field ID.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
A map of token/rule identifier to the token signature.
|
|
302
|
+
"""
|
|
303
|
+
signatures = {}
|
|
304
|
+
|
|
305
|
+
for token_id in self.token_definition.get_token_identifiers():
|
|
306
|
+
try:
|
|
307
|
+
signature = self._get_token_signature_via_field_id(token_id, person_attributes, TokenGeneratorResult())
|
|
308
|
+
if signature is not None:
|
|
309
|
+
signatures[token_id] = signature
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.error(f"Error generating token signature for token id: {token_id}", exc_info=e)
|
|
312
|
+
|
|
313
|
+
return signatures
|
|
314
|
+
|
|
315
|
+
def _resolve_field_id(self, expression) -> Optional[str]:
|
|
316
|
+
"""Resolve the effective field ID from an AttributeExpression."""
|
|
317
|
+
if expression.field_id is not None:
|
|
318
|
+
return expression.field_id
|
|
319
|
+
# Legacy fallback: derive field ID from attribute class name
|
|
320
|
+
attribute = self.attribute_instance_map.get(expression.attribute_class)
|
|
321
|
+
return attribute.get_name() if attribute else None
|
|
322
|
+
|
|
323
|
+
def _resolve_attribute(self, expression, resolved_field_id: str) -> Optional[Attribute]:
|
|
324
|
+
"""Resolve the attribute instance for an expression and field ID."""
|
|
325
|
+
# Try field registry first
|
|
326
|
+
from_registry = self.field_registry.get_attribute(resolved_field_id)
|
|
327
|
+
if from_registry is not None:
|
|
328
|
+
return from_registry
|
|
329
|
+
# Fallback to class-based lookup
|
|
330
|
+
return self.attribute_instance_map.get(expression.attribute_class)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openlinktoken
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Open Link Token Python core library for record linkage
|
|
5
5
|
Home-page: https://github.com/TruvetaPublic/OpenLinkToken
|
|
6
6
|
Author: Open Link Token Contributors
|
|
@@ -8,8 +8,9 @@ Project-URL: Source, https://github.com/TruvetaPublic/OpenLinkToken
|
|
|
8
8
|
Project-URL: Documentation, https://github.com/TruvetaPublic/OpenLinkToken/blob/main/README.md
|
|
9
9
|
Requires-Python: >=3.10
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
|
-
Requires-Dist: cryptography==
|
|
11
|
+
Requires-Dist: cryptography==49.0.0
|
|
12
12
|
Requires-Dist: jwcrypto==1.5.7
|
|
13
|
+
Requires-Dist: PyYAML==6.0.3
|
|
13
14
|
Dynamic: author
|
|
14
15
|
Dynamic: description
|
|
15
16
|
Dynamic: description-content-type
|
|
@@ -18,9 +18,11 @@ src/main/openlinktoken.egg-info/top_level.txt
|
|
|
18
18
|
src/main/openlinktoken/attributes/__init__.py
|
|
19
19
|
src/main/openlinktoken/attributes/attribute.py
|
|
20
20
|
src/main/openlinktoken/attributes/attribute_expression.py
|
|
21
|
+
src/main/openlinktoken/attributes/attribute_field.py
|
|
21
22
|
src/main/openlinktoken/attributes/attribute_loader.py
|
|
22
23
|
src/main/openlinktoken/attributes/base_attribute.py
|
|
23
24
|
src/main/openlinktoken/attributes/combined_attribute.py
|
|
25
|
+
src/main/openlinktoken/attributes/field_registry.py
|
|
24
26
|
src/main/openlinktoken/attributes/serializable_attribute.py
|
|
25
27
|
src/main/openlinktoken/attributes/general/__init__.py
|
|
26
28
|
src/main/openlinktoken/attributes/general/date_attribute.py
|
|
@@ -60,6 +62,7 @@ src/main/openlinktoken/tokens/token_generation_exception.py
|
|
|
60
62
|
src/main/openlinktoken/tokens/token_generator.py
|
|
61
63
|
src/main/openlinktoken/tokens/token_generator_result.py
|
|
62
64
|
src/main/openlinktoken/tokens/token_registry.py
|
|
65
|
+
src/main/openlinktoken/tokens/definitions/field_ids.py
|
|
63
66
|
src/main/openlinktoken/tokens/definitions/t1_token.py
|
|
64
67
|
src/main/openlinktoken/tokens/definitions/t2_token.py
|
|
65
68
|
src/main/openlinktoken/tokens/definitions/t3_token.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/attribute_loader.py
RENAMED
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/base_attribute.py
RENAMED
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/combined_attribute.py
RENAMED
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/general/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/person/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/utilities/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/attributes/validation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/base_token_definition.py
RENAMED
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_definition.py
RENAMED
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/token_generator_result.py
RENAMED
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/tokenizer/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokens/tokenizer/tokenizer.py
RENAMED
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken/tokentransformer/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlinktoken-2.0.0 → openlinktoken-2.1.0}/src/main/openlinktoken.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|