openlinktoken-pyspark 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_pyspark/__init__.py +28 -0
- openlinktoken_pyspark/notebook_helpers.py +356 -0
- openlinktoken_pyspark/overlap_analyzer.py +392 -0
- openlinktoken_pyspark/token_processor.py +522 -0
- openlinktoken_pyspark-2.0.0.dist-info/METADATA +500 -0
- openlinktoken_pyspark-2.0.0.dist-info/RECORD +8 -0
- openlinktoken_pyspark-2.0.0.dist-info/WHEEL +5 -0
- openlinktoken_pyspark-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Open Link Token PySpark Bridge - Distributed token generation for PySpark DataFrames.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "2.0.0"
|
|
7
|
+
|
|
8
|
+
from openlinktoken_pyspark.notebook_helpers import (
|
|
9
|
+
CustomTokenDefinition,
|
|
10
|
+
TokenBuilder,
|
|
11
|
+
create_token_generator,
|
|
12
|
+
create_token_generator_from_exchange_config,
|
|
13
|
+
quick_token,
|
|
14
|
+
quick_token_from_exchange_config,
|
|
15
|
+
)
|
|
16
|
+
from openlinktoken_pyspark.overlap_analyzer import OpenLinkTokenOverlapAnalyzer
|
|
17
|
+
from openlinktoken_pyspark.token_processor import OpenLinkTokenProcessor
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"CustomTokenDefinition",
|
|
21
|
+
"TokenBuilder",
|
|
22
|
+
"OpenLinkTokenProcessor",
|
|
23
|
+
"OpenLinkTokenOverlapAnalyzer",
|
|
24
|
+
"create_token_generator",
|
|
25
|
+
"create_token_generator_from_exchange_config",
|
|
26
|
+
"quick_token",
|
|
27
|
+
"quick_token_from_exchange_config",
|
|
28
|
+
]
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Notebook helpers for convenient token definition and experimentation.
|
|
4
|
+
This module provides a simplified API for creating custom tokens in Jupyter notebooks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Mapping, Optional, Type, Union
|
|
9
|
+
|
|
10
|
+
from openlinktoken.attributes.attribute import Attribute
|
|
11
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
12
|
+
from openlinktoken.attributes.general.record_id_attribute import RecordIdAttribute
|
|
13
|
+
from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
|
|
14
|
+
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
15
|
+
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
16
|
+
from openlinktoken.attributes.person.postal_code_attribute import PostalCodeAttribute
|
|
17
|
+
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
18
|
+
from openlinktoken.attributes.person.social_security_number_attribute import SocialSecurityNumberAttribute
|
|
19
|
+
from openlinktoken.exchange_config import derive_transport_encryption_key, resolve_exchange_config_inputs
|
|
20
|
+
from openlinktoken.tokens.base_token_definition import BaseTokenDefinition
|
|
21
|
+
from openlinktoken.tokens.token import Token
|
|
22
|
+
from openlinktoken.tokens.token_generator import TokenGenerator
|
|
23
|
+
from openlinktoken.tokentransformer.encrypt_token_transformer import EncryptTokenTransformer
|
|
24
|
+
from openlinktoken.tokentransformer.hash_token_transformer import HashTokenTransformer
|
|
25
|
+
|
|
26
|
+
# Convenient attribute name mappings
|
|
27
|
+
ATTRIBUTE_MAP = {
|
|
28
|
+
"first_name": FirstNameAttribute,
|
|
29
|
+
"last_name": LastNameAttribute,
|
|
30
|
+
"birth_date": BirthDateAttribute,
|
|
31
|
+
"sex": SexAttribute,
|
|
32
|
+
"postal_code": PostalCodeAttribute,
|
|
33
|
+
"ssn": SocialSecurityNumberAttribute,
|
|
34
|
+
"record_id": RecordIdAttribute,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TokenBuilder:
|
|
39
|
+
"""
|
|
40
|
+
A fluent builder for creating custom tokens with minimal code.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> token = TokenBuilder("T6") \\
|
|
44
|
+
... .add("last_name", "T|U") \\
|
|
45
|
+
... .add("first_name", "T|U") \\
|
|
46
|
+
... .add("birth_date", "T|D") \\
|
|
47
|
+
... .add("postal_code", "T|S(0,3)") \\
|
|
48
|
+
... .add("sex", "T|U") \\
|
|
49
|
+
... .build()
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, token_id: str):
|
|
53
|
+
"""
|
|
54
|
+
Initialize the token builder.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
token_id: The identifier for the token (e.g., "T6", "T7").
|
|
58
|
+
"""
|
|
59
|
+
self.token_id = token_id
|
|
60
|
+
self.expressions: List[AttributeExpression] = []
|
|
61
|
+
|
|
62
|
+
def add(self, attribute: Union[str, Type[Attribute]], expression: str = "T") -> "TokenBuilder":
|
|
63
|
+
"""
|
|
64
|
+
Add an attribute expression to the token definition.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
attribute: Either an attribute class or a string name (e.g., "first_name", "last_name").
|
|
68
|
+
expression: The transformation expression (e.g., "T|U", "T|S(0,3)|U").
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Self for method chaining.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> builder.add("last_name", "T|U").add("first_name", "T|S(0,3)|U")
|
|
75
|
+
"""
|
|
76
|
+
if isinstance(attribute, str):
|
|
77
|
+
if attribute not in ATTRIBUTE_MAP:
|
|
78
|
+
raise ValueError(f"Unknown attribute: {attribute}. Available: {', '.join(ATTRIBUTE_MAP.keys())}")
|
|
79
|
+
attribute_class = ATTRIBUTE_MAP[attribute]
|
|
80
|
+
else:
|
|
81
|
+
attribute_class = attribute
|
|
82
|
+
|
|
83
|
+
self.expressions.append(AttributeExpression(attribute_class, expression))
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def build(self) -> Token:
|
|
87
|
+
"""
|
|
88
|
+
Build the custom token.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
A Token instance with the configured expressions.
|
|
92
|
+
"""
|
|
93
|
+
token_id = self.token_id
|
|
94
|
+
expressions = self.expressions
|
|
95
|
+
|
|
96
|
+
class CustomToken(Token):
|
|
97
|
+
"""Dynamically created custom token."""
|
|
98
|
+
|
|
99
|
+
ID = token_id
|
|
100
|
+
|
|
101
|
+
def __init__(self):
|
|
102
|
+
self._definition = expressions
|
|
103
|
+
|
|
104
|
+
def get_identifier(self):
|
|
105
|
+
return self.ID
|
|
106
|
+
|
|
107
|
+
def get_definition(self):
|
|
108
|
+
return self._definition
|
|
109
|
+
|
|
110
|
+
return CustomToken()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CustomTokenDefinition(BaseTokenDefinition):
|
|
114
|
+
"""
|
|
115
|
+
A custom token definition that can include multiple custom tokens.
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
>>> definition = CustomTokenDefinition()
|
|
119
|
+
>>> definition.add_token(t6_token)
|
|
120
|
+
>>> definition.add_token(t7_token)
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self):
|
|
124
|
+
"""Initialize an empty custom token definition."""
|
|
125
|
+
self.tokens: Dict[str, Token] = {}
|
|
126
|
+
|
|
127
|
+
def add_token(self, token: Token) -> "CustomTokenDefinition":
|
|
128
|
+
"""
|
|
129
|
+
Add a custom token to the definition.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
token: The token to add.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Self for method chaining.
|
|
136
|
+
"""
|
|
137
|
+
token_id = token.get_identifier()
|
|
138
|
+
self.tokens[token_id] = token
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
def get_version(self) -> str:
|
|
142
|
+
"""Get the version of the token definition."""
|
|
143
|
+
return "2.0-custom"
|
|
144
|
+
|
|
145
|
+
def get_token_identifiers(self) -> set:
|
|
146
|
+
"""Get all token identifiers."""
|
|
147
|
+
return set(self.tokens.keys())
|
|
148
|
+
|
|
149
|
+
def get_token_definition(self, token_id: str) -> List[AttributeExpression]:
|
|
150
|
+
"""Get the token definition for a given token identifier."""
|
|
151
|
+
token = self.tokens.get(token_id)
|
|
152
|
+
if token:
|
|
153
|
+
return token.get_definition()
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def create_token_generator(
|
|
158
|
+
hashing_secret: Union[str, bytes, None],
|
|
159
|
+
encryption_key: Union[str, bytes],
|
|
160
|
+
token_definition: Optional[BaseTokenDefinition] = None,
|
|
161
|
+
) -> TokenGenerator:
|
|
162
|
+
"""
|
|
163
|
+
Create a token generator with the specified secrets and token definition.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
hashing_secret: The secret used for HMAC-SHA256 hashing.
|
|
167
|
+
encryption_key: The 32-character key used for AES-256 encryption.
|
|
168
|
+
token_definition: Optional custom token definition. If None, uses default tokens.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
A configured TokenGenerator instance.
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
>>> # Use default tokens (T1-T5)
|
|
175
|
+
>>> generator = create_token_generator("my-hash-secret", "my-32-character-encryption-key!")
|
|
176
|
+
>>>
|
|
177
|
+
>>> # Use custom tokens
|
|
178
|
+
>>> custom_def = CustomTokenDefinition().add_token(t6_token)
|
|
179
|
+
>>> generator = create_token_generator("secret", "key123...", custom_def)
|
|
180
|
+
"""
|
|
181
|
+
if token_definition is None:
|
|
182
|
+
from openlinktoken.tokens.token_definition import TokenDefinition
|
|
183
|
+
|
|
184
|
+
token_definition = TokenDefinition()
|
|
185
|
+
|
|
186
|
+
token_transformers = [HashTokenTransformer(hashing_secret), EncryptTokenTransformer(encryption_key)]
|
|
187
|
+
|
|
188
|
+
return TokenGenerator.from_transformers(token_definition, token_transformers)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def create_token_generator_from_exchange_config(
|
|
192
|
+
exchange_config_path: Union[str, Path, None] = None,
|
|
193
|
+
exchange_config_value: Union[str, bytes, Mapping[str, Any], None] = None,
|
|
194
|
+
private_key_path: Union[str, Path, None] = None,
|
|
195
|
+
private_key_env: Optional[str] = None,
|
|
196
|
+
private_key_value: Union[str, bytes, None] = None,
|
|
197
|
+
token_definition: Optional[BaseTokenDefinition] = None,
|
|
198
|
+
) -> TokenGenerator:
|
|
199
|
+
"""
|
|
200
|
+
Create a token generator from an exchange config and private-key inputs.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
exchange_config_path: Optional exchange-config path. Uses the default path when omitted.
|
|
204
|
+
exchange_config_value: Optional in-memory exchange-config JSON or decoded mapping.
|
|
205
|
+
private_key_path: Optional private-key PEM path.
|
|
206
|
+
private_key_env: Optional environment-variable name containing private-key PEM text.
|
|
207
|
+
private_key_value: Optional in-memory private-key PEM text or bytes.
|
|
208
|
+
token_definition: Optional custom token definition. If None, uses default tokens.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
A configured TokenGenerator instance using the exchange hashing secret and transport key.
|
|
212
|
+
"""
|
|
213
|
+
exchange = resolve_exchange_config_inputs(
|
|
214
|
+
exchange_config_path=exchange_config_path,
|
|
215
|
+
exchange_config_value=exchange_config_value,
|
|
216
|
+
private_key_path=private_key_path,
|
|
217
|
+
private_key_env=private_key_env,
|
|
218
|
+
private_key_value=private_key_value,
|
|
219
|
+
)
|
|
220
|
+
return create_token_generator(
|
|
221
|
+
hashing_secret=exchange.hashing_secret,
|
|
222
|
+
encryption_key=derive_transport_encryption_key(exchange),
|
|
223
|
+
token_definition=token_definition,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def quick_token(
|
|
228
|
+
token_id: str,
|
|
229
|
+
attributes: List[tuple],
|
|
230
|
+
hashing_secret: Union[str, bytes, None],
|
|
231
|
+
encryption_key: Union[str, bytes],
|
|
232
|
+
) -> TokenGenerator:
|
|
233
|
+
"""
|
|
234
|
+
Create a custom token and generator in one quick call.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
token_id: The identifier for the new token (e.g., "T6").
|
|
238
|
+
attributes: List of (attribute_name, expression) tuples.
|
|
239
|
+
hashing_secret: The secret used for HMAC-SHA256 hashing.
|
|
240
|
+
encryption_key: The 32-character key used for AES-256 encryption.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
A TokenGenerator configured with the custom token.
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
>>> generator = quick_token(
|
|
247
|
+
... "T6",
|
|
248
|
+
... [
|
|
249
|
+
... ("last_name", "T|U"),
|
|
250
|
+
... ("first_name", "T|U"),
|
|
251
|
+
... ("birth_date", "T|D"),
|
|
252
|
+
... ("postal_code", "T|S(0,3)"),
|
|
253
|
+
... ("sex", "T|U")
|
|
254
|
+
... ],
|
|
255
|
+
... "my-hashing-secret",
|
|
256
|
+
... "my-32-character-encryption-key!"
|
|
257
|
+
... )
|
|
258
|
+
"""
|
|
259
|
+
builder = TokenBuilder(token_id)
|
|
260
|
+
for attr_name, expr in attributes:
|
|
261
|
+
builder.add(attr_name, expr)
|
|
262
|
+
|
|
263
|
+
token = builder.build()
|
|
264
|
+
definition = CustomTokenDefinition().add_token(token)
|
|
265
|
+
|
|
266
|
+
return create_token_generator(hashing_secret, encryption_key, definition)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def quick_token_from_exchange_config(
|
|
270
|
+
token_id: str,
|
|
271
|
+
attributes: List[tuple],
|
|
272
|
+
exchange_config_path: Union[str, Path, None] = None,
|
|
273
|
+
exchange_config_value: Union[str, bytes, Mapping[str, Any], None] = None,
|
|
274
|
+
private_key_path: Union[str, Path, None] = None,
|
|
275
|
+
private_key_env: Optional[str] = None,
|
|
276
|
+
private_key_value: Union[str, bytes, None] = None,
|
|
277
|
+
) -> TokenGenerator:
|
|
278
|
+
"""
|
|
279
|
+
Create a custom token generator from exchange-config inputs in one quick call.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
token_id: The identifier for the new token (e.g., "T6").
|
|
283
|
+
attributes: List of (attribute_name, expression) tuples.
|
|
284
|
+
exchange_config_path: Optional exchange-config path. Uses the default path when omitted.
|
|
285
|
+
exchange_config_value: Optional in-memory exchange-config JSON or decoded mapping.
|
|
286
|
+
private_key_path: Optional private-key PEM path.
|
|
287
|
+
private_key_env: Optional environment-variable name containing private-key PEM text.
|
|
288
|
+
private_key_value: Optional in-memory private-key PEM text or bytes.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
A TokenGenerator configured with the custom token and exchange-derived secrets.
|
|
292
|
+
"""
|
|
293
|
+
builder = TokenBuilder(token_id)
|
|
294
|
+
for attr_name, expr in attributes:
|
|
295
|
+
builder.add(attr_name, expr)
|
|
296
|
+
|
|
297
|
+
token = builder.build()
|
|
298
|
+
definition = CustomTokenDefinition().add_token(token)
|
|
299
|
+
|
|
300
|
+
return create_token_generator_from_exchange_config(
|
|
301
|
+
exchange_config_path=exchange_config_path,
|
|
302
|
+
exchange_config_value=exchange_config_value,
|
|
303
|
+
private_key_path=private_key_path,
|
|
304
|
+
private_key_env=private_key_env,
|
|
305
|
+
private_key_value=private_key_value,
|
|
306
|
+
token_definition=definition,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# Convenience function to list available attributes
|
|
311
|
+
def list_attributes() -> Dict[str, Type[Attribute]]:
|
|
312
|
+
"""
|
|
313
|
+
Get a dictionary of available attribute names and their classes.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Dictionary mapping attribute names to their classes.
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
>>> attrs = list_attributes()
|
|
320
|
+
>>> print(attrs.keys())
|
|
321
|
+
dict_keys(['first_name', 'last_name', 'birth_date', 'sex', 'postal_code', 'ssn', 'record_id'])
|
|
322
|
+
"""
|
|
323
|
+
return ATTRIBUTE_MAP.copy()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def expression_help() -> str:
|
|
327
|
+
"""
|
|
328
|
+
Get help text about expression syntax.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
A string describing the expression syntax.
|
|
332
|
+
"""
|
|
333
|
+
return """
|
|
334
|
+
Expression Syntax Guide:
|
|
335
|
+
========================
|
|
336
|
+
|
|
337
|
+
Components (separated by |):
|
|
338
|
+
- T : Trim whitespace
|
|
339
|
+
- U : Convert to uppercase
|
|
340
|
+
- S(start,end): Substring (e.g., S(0,3) for first 3 characters)
|
|
341
|
+
- D : Format as date (yyyy-MM-dd)
|
|
342
|
+
- M(regex) : Match regular expression
|
|
343
|
+
- R(old,new) : Replace string
|
|
344
|
+
|
|
345
|
+
Examples:
|
|
346
|
+
- "T|U" : Trim and uppercase (e.g., " john " → "JOHN")
|
|
347
|
+
- "T|S(0,3)|U" : Trim, take first 3 chars, uppercase (e.g., "john" → "JOH")
|
|
348
|
+
- "T|D" : Trim and format as date (e.g., "1990-01-15" → "1990-01-15")
|
|
349
|
+
- "T|S(0,5)" : Trim and take first 5 chars (e.g., "98101-1234" → "98101")
|
|
350
|
+
|
|
351
|
+
Common Patterns:
|
|
352
|
+
- Names: "T|U" or "T|S(0,3)|U"
|
|
353
|
+
- Dates: "T|D"
|
|
354
|
+
- Postal codes: "T|S(0,3)" or "T|S(0,5)"
|
|
355
|
+
- Gender/Sex: "T|U"
|
|
356
|
+
"""
|