openlinktoken-pyspark 2.0.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.
Files changed (22) hide show
  1. openlinktoken_pyspark-2.0.0/.flake8 +3 -0
  2. openlinktoken_pyspark-2.0.0/MANIFEST.in +13 -0
  3. openlinktoken_pyspark-2.0.0/PKG-INFO +220 -0
  4. openlinktoken_pyspark-2.0.0/README.md +448 -0
  5. openlinktoken_pyspark-2.0.0/dev-requirements.txt +3 -0
  6. openlinktoken_pyspark-2.0.0/examples/simple_example.py +168 -0
  7. openlinktoken_pyspark-2.0.0/notebooks/Custom_Token_Definition_Guide.ipynb +512 -0
  8. openlinktoken_pyspark-2.0.0/notebooks/Dataset_Overlap_Analysis_Guide.ipynb +417 -0
  9. openlinktoken_pyspark-2.0.0/notebooks/OpenLinkToken_PySpark_Example.ipynb +361 -0
  10. openlinktoken_pyspark-2.0.0/pyproject.toml +58 -0
  11. openlinktoken_pyspark-2.0.0/requirements.txt +13 -0
  12. openlinktoken_pyspark-2.0.0/setup.cfg +4 -0
  13. openlinktoken_pyspark-2.0.0/setup.py +83 -0
  14. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark/__init__.py +28 -0
  15. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark/notebook_helpers.py +356 -0
  16. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark/overlap_analyzer.py +392 -0
  17. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark/token_processor.py +522 -0
  18. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark.egg-info/PKG-INFO +220 -0
  19. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark.egg-info/SOURCES.txt +22 -0
  20. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark.egg-info/dependency_links.txt +1 -0
  21. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark.egg-info/requires.txt +57 -0
  22. openlinktoken_pyspark-2.0.0/src/main/openlinktoken_pyspark.egg-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ [flake8]
2
+ max-line-length = 120
3
+ exclude = .git,__pycache__,.venv,venv,build,dist
@@ -0,0 +1,13 @@
1
+ include README.md
2
+ include INSTALL.md
3
+ include requirements.txt
4
+ include dev-requirements.txt
5
+ include pyproject.toml
6
+ include .flake8
7
+ # Include repository root docs used for long_description
8
+ include ../../../README.md
9
+ include ../../../LICENSE
10
+ recursive-include notebooks *.ipynb
11
+ recursive-include examples *.py
12
+ recursive-exclude * __pycache__
13
+ recursive-exclude * *.py[co]
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: openlinktoken-pyspark
3
+ Version: 2.0.0
4
+ Summary: Open Link Token PySpark bridge for distributed token generation
5
+ Home-page: https://github.com/TruvetaPublic/OpenLinkToken
6
+ Author: Open Link Token Contributors
7
+ Project-URL: Source, https://github.com/TruvetaPublic/OpenLinkToken
8
+ Project-URL: Documentation, https://github.com/TruvetaPublic/OpenLinkToken/blob/main/README.md
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: openlinktoken==2.0.0
12
+ Requires-Dist: pycryptodome==3.23.0
13
+ Requires-Dist: jwcrypto==1.5.7
14
+ Provides-Extra: spark41
15
+ Requires-Dist: pyspark==4.1.0; extra == "spark41"
16
+ Requires-Dist: pyarrow==24.0.0; extra == "spark41"
17
+ Requires-Dist: pandas==2.2.3; python_version < "3.11" and extra == "spark41"
18
+ Requires-Dist: pandas==3.0.3; python_version >= "3.11" and extra == "spark41"
19
+ Provides-Extra: spark40
20
+ Requires-Dist: pyspark==4.0.1; extra == "spark40"
21
+ Requires-Dist: pyarrow==24.0.0; extra == "spark40"
22
+ Requires-Dist: pandas==2.2.3; python_version < "3.11" and extra == "spark40"
23
+ Requires-Dist: pandas==3.0.3; python_version >= "3.11" and extra == "spark40"
24
+ Provides-Extra: spark35
25
+ Requires-Dist: pyspark==3.5.5; extra == "spark35"
26
+ Requires-Dist: pyarrow==19.0.0; extra == "spark35"
27
+ Requires-Dist: pandas==2.2.3; python_version < "3.11" and extra == "spark35"
28
+ Requires-Dist: pandas==3.0.3; python_version >= "3.11" and extra == "spark35"
29
+ Requires-Dist: setuptools==82.0.1; python_version >= "3.12" and extra == "spark35"
30
+ Provides-Extra: spark34
31
+ Requires-Dist: pyspark==3.4.4; extra == "spark34"
32
+ Requires-Dist: pyarrow==15.0.0; extra == "spark34"
33
+ Requires-Dist: pandas==2.1.4; python_version < "3.11" and extra == "spark34"
34
+ Requires-Dist: pandas==3.0.3; python_version >= "3.11" and extra == "spark34"
35
+ Requires-Dist: setuptools==82.0.1; python_version >= "3.12" and extra == "spark34"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest==9.0.3; extra == "dev"
38
+ Requires-Dist: pytest-cov==7.1.0; extra == "dev"
39
+ Requires-Dist: flake8==7.3.0; extra == "dev"
40
+ Requires-Dist: jupyter==1.1.1; extra == "dev"
41
+ Requires-Dist: notebook==7.5.7; extra == "dev"
42
+ Requires-Dist: ipykernel==7.3.0; extra == "dev"
43
+ Dynamic: author
44
+ Dynamic: description
45
+ Dynamic: description-content-type
46
+ Dynamic: home-page
47
+ Dynamic: project-url
48
+ Dynamic: provides-extra
49
+ Dynamic: requires-dist
50
+ Dynamic: requires-python
51
+ Dynamic: summary
52
+
53
+ # Open Link Token
54
+
55
+ Privacy-preserving tokenization and matching library for secure PII-based person linkage. Open Link Token generates deterministic, cryptographically secure tokens from person attributes (name, birthdate, SSN, etc.) so datasets can be matched without exposing raw identifiers.
56
+
57
+ ## Introduction
58
+
59
+ Our approach to record linkage relies on building a set of matching tokens (or token signatures) per person which are derived from deterministic person data but preserve privacy by using cryptographically secure hashing algorithms.
60
+
61
+ - [Open Link Token](#open-link-token)
62
+ - [Introduction](#introduction)
63
+ - [Highlights](#highlights)
64
+ - [Demo](#demo)
65
+ - [Overview](#overview)
66
+ - [Why Open Link Token](#why-open-link-token)
67
+ - [Quickstart](#quickstart)
68
+ - [Key Matching Ideas](#key-matching-ideas)
69
+ - [Running Open Link Token](#running-open-link-token)
70
+ - [Security Notes](#security-notes)
71
+ - [Contributing \& Community](#contributing--community)
72
+ - [Documentation](#documentation)
73
+
74
+ ## Highlights
75
+
76
+ - Multi-language Support
77
+ - Cryptographically Secure encryption that prevents re-identification
78
+ - Enables straightforward person-matching by comparing 5 deterministic and unique hash values (after decryption), providing a high degree of confidence in matches
79
+
80
+ ## Demo
81
+
82
+ New to Open Link Token? Start with the **[PPRL Superhero Demo](demos/pprl-superhero-example/)** — a beginner-friendly, end-to-end walkthrough showing how two parties (hospital and pharmacy) can privately find matching records without exposing raw identifiers.
83
+
84
+ The demo includes:
85
+
86
+ - **Interactive Jupyter notebook** with step-by-step explanations
87
+ - **One-command runner** (`run_end_to_end.sh`) for quick execution
88
+ - Synthetic superhero dataset generation
89
+ - Token generation and overlap analysis examples
90
+
91
+ Perfect for understanding privacy-preserving record linkage concepts before diving into production use.
92
+
93
+ ## Overview
94
+
95
+ - **Multi-language parity**: Java and Python implementations produce byte-identical hash outputs (decrypted values)
96
+ - **Deterministic matching values**: Same input always produces the same cryptographically secure hash for matching
97
+ - **Privacy-preserving**: Encrypted tokens cannot be reversed to recover original person data
98
+
99
+ ## Why Open Link Token
100
+
101
+ - Practical validation and normalization for common PII-derived attributes (names, birthdates, SSN, postal codes, sex)
102
+ - Secure pipeline: SHA-256 → HMAC-SHA256 → AES-256 (or hash-only mode)
103
+ - Multiple token rules (T1–T5) to increase match confidence across varied data quality
104
+
105
+ ## Quickstart
106
+
107
+ **Self-contained executable (easiest):**
108
+
109
+ Download the binary for your platform from the [latest release](https://github.com/TruvetaPublic/OpenLinkToken/releases):
110
+
111
+ | Platform | Asset |
112
+ | ----------------------------- | ------------------------------- |
113
+ | Linux | `olt-vX.Y.Z-linux-x86_64` |
114
+ | macOS (Intel + Apple Silicon) | `olt-vX.Y.Z-macos-universal` |
115
+ | Windows | `olt-vX.Y.Z-windows-x86_64.exe` |
116
+
117
+ Each asset has a matching `.sha256` file you can use to verify the download.
118
+
119
+ ```bash
120
+ # Linux
121
+ chmod +x olt-v*-linux-x86_64
122
+ mv olt-v*-linux-x86_64 olt
123
+
124
+ # macOS — make executable, clear Gatekeeper quarantine, and rename
125
+ chmod +x olt-v*-macos-universal
126
+ xattr -d com.apple.quarantine olt-v*-macos-universal
127
+ mv olt-v*-macos-universal olt
128
+ ```
129
+
130
+ Then run:
131
+
132
+ ```bash
133
+ # Linux/macOS
134
+ # Simulate receiving the recipient's public key (in practice, your partner provides this)
135
+ ./olt generate-key-pair --name recipient
136
+ # Create the exchange config using the recipient's public key
137
+ ./olt initiate-exchange --public-key "$HOME/.openlinktoken/recipient.public.pem"
138
+ ./olt package -i ./resources/sample.csv
139
+
140
+ # Windows
141
+ # Simulate receiving the recipient's public key (in practice, your partner provides this)
142
+ .\olt.exe generate-key-pair --name recipient
143
+ # Create the exchange config using the recipient's public key
144
+ .\olt.exe initiate-exchange --public-key "$HOME/.openlinktoken/recipient.public.pem"
145
+ .\olt.exe package -i .\resources\sample.csv
146
+ ```
147
+
148
+ **Docker convenience scripts (Linux/macOS and Windows):**
149
+
150
+ ```bash
151
+ # Linux/macOS
152
+ ./run-olt.sh generate-key-pair --name recipient
153
+ ./run-olt.sh initiate-exchange --public-key "$HOME/.openlinktoken/recipient.public.pem"
154
+ ./run-olt.sh package \
155
+ -i ./resources/sample.csv -o ./resources/output.zip \
156
+ --exchange-config ./resources/openlinktoken.exchange.json \
157
+ --private-key "$HOME/.openlinktoken/recipient.private.pem"
158
+
159
+ # Windows
160
+ .\run-olt.ps1 generate-key-pair --name recipient
161
+ .\run-olt.ps1 initiate-exchange --public-key "$HOME\.openlinktoken\recipient.public.pem"
162
+ .\run-olt.ps1 package `
163
+ -i .\resources\sample.csv -o .\resources\output.zip `
164
+ --exchange-config .\resources\openlinktoken.exchange.json `
165
+ --private-key "$HOME\.openlinktoken\recipient.private.pem"
166
+ ```
167
+
168
+ **Available Commands:**
169
+
170
+ - `olt package` - Generate and encrypt tokens in one step using the exchange config
171
+ - `olt tokenize` - Generate internal hashed tokens using the exchange config, or use `--mode hash-only` for deterministic SHA-256 output without an exchange config
172
+ - `olt encrypt` - Encrypt existing hashed tokens using the exchange config
173
+ - `olt decrypt` - Decrypt encrypted tokens using the exchange config
174
+ - `olt initiate-exchange` - Create the exchange config consumed by later commands
175
+ - `olt help [command]` - Show help for a specific command
176
+
177
+ See <a href="https://truvetapublic.github.io/OpenLinkToken/quickstarts/" target="_blank" rel="noopener noreferrer">Quickstarts</a> for Python CLI and detailed setup instructions.
178
+
179
+ ## Key Matching Ideas
180
+
181
+ - **Token rules**: Five rules (T1–T5) combine attributes in different ways — see <a href="https://truvetapublic.github.io/OpenLinkToken/concepts/token-rules.html" target="_blank" rel="noopener noreferrer">Token Rules</a>
182
+ - **Normalization**: Names, dates, postal codes normalized before tokenization — see <a href="https://truvetapublic.github.io/OpenLinkToken/concepts/normalization-and-validation.html" target="_blank" rel="noopener noreferrer">Normalization and Validation</a>
183
+ - **Metadata**: Processing statistics and audit trail — see <a href="https://truvetapublic.github.io/OpenLinkToken/reference/metadata-format.html" target="_blank" rel="noopener noreferrer">Metadata Format</a>
184
+
185
+ ## Running Open Link Token
186
+
187
+ - **CLI subcommands** (`olt` binary or Docker convenience scripts):
188
+ - `tokenize` - Internal hashed token generation (`--mode hash-only` is available for deterministic SHA-256 output)
189
+ - `encrypt` - Encrypt existing hashed tokens
190
+ - `decrypt` - Decrypt encrypted tokens
191
+ - `package` - Tokenize + encrypt in one step (recommended)
192
+ - `initiate-exchange` - Create an exchange config from a partner's public key
193
+ - `generate-key-pair` - Generate an ECDH key pair
194
+ - See <a href="https://truvetapublic.github.io/OpenLinkToken/running-openlinktoken/" target="_blank" rel="noopener noreferrer">Running Open Link Token</a>
195
+ - **Docker convenience scripts**: `run-olt.sh` (Linux/macOS) and `run-olt.ps1` (Windows) wrap Docker automatically — see <a href="https://truvetapublic.github.io/OpenLinkToken/quickstarts/" target="_blank" rel="noopener noreferrer">Quickstarts</a>
196
+ - **PySpark**: Distributed processing for large datasets — see <a href="https://truvetapublic.github.io/OpenLinkToken/operations/spark-or-databricks.html" target="_blank" rel="noopener noreferrer">Spark or Databricks</a>
197
+
198
+ ## Security Notes
199
+
200
+ - **Crypto pipeline**: Token signature → SHA-256 → HMAC-SHA256 → AES-256 (or hash-only) — see <a href="https://truvetapublic.github.io/OpenLinkToken/security.html" target="_blank" rel="noopener noreferrer">Security</a>
201
+ - **`tokenize --mode hash-only`**: Deterministic SHA-256 output with no exchange config or secret. Useful for local exploration, but **not** for production or cross-organisation exchange
202
+ - **Secret management**: Handle hashing/encryption secrets securely; avoid committing secrets; prefer env/secret stores
203
+ - **Validation**: Reject placeholders and malformed attributes before tokenization
204
+
205
+ ## Contributing & Community
206
+
207
+ - <a href="https://truvetapublic.github.io/OpenLinkToken/community/contributing.html" target="_blank" rel="noopener noreferrer">Contributing Guide</a> — Branching, PR expectations, coding standards
208
+ - <a href="https://truvetapublic.github.io/OpenLinkToken/community/code-of-conduct.html" target="_blank" rel="noopener noreferrer">Code of Conduct</a>
209
+
210
+ ## Documentation
211
+
212
+ - <a href="https://truvetapublic.github.io/OpenLinkToken/" target="_blank" rel="noopener noreferrer">Documentation Index</a>
213
+ - <a href="https://truvetapublic.github.io/OpenLinkToken/quickstarts/" target="_blank" rel="noopener noreferrer">Quickstarts</a>
214
+ - <a href="https://truvetapublic.github.io/OpenLinkToken/specification.html" target="_blank" rel="noopener noreferrer">Specification</a>
215
+ - <a href="https://truvetapublic.github.io/OpenLinkToken/reference/cli.html" target="_blank" rel="noopener noreferrer">CLI Reference</a>
216
+ - <a href="https://truvetapublic.github.io/OpenLinkToken/reference/metadata-format.html" target="_blank" rel="noopener noreferrer">Metadata Format</a>
217
+
218
+ For issues or support, file an issue in this repository.
219
+
220
+ <!-- Re-run CI checks -->
@@ -0,0 +1,448 @@
1
+ # Open Link Token PySpark Bridge
2
+
3
+ A PySpark integration for the Open Link Token library, enabling distributed privacy-preserving token generation for large-scale record linkage workflows.
4
+
5
+ ## Overview
6
+
7
+ The Open Link Token PySpark Bridge provides a seamless interface between PySpark DataFrames and the Open Link Token library, allowing you to generate cryptographically secure tokens for record linkage in a distributed computing environment.
8
+
9
+ ## Features
10
+
11
+ - **Distributed Processing**: Leverage PySpark's distributed computing capabilities for large datasets
12
+ - **Simple API**: Easy-to-use interface that accepts PySpark DataFrames
13
+ - **Exchange-Config Ready**: Recommended initiate-exchange flow with direct-secret support when you manage raw secrets yourself
14
+ - **Flexible Column Names**: Supports multiple column name variants (e.g., FirstName/GivenName)
15
+ - **Jupyter Ready**: Includes example notebooks for interactive exploration
16
+
17
+ ## Installation
18
+
19
+ ### Prerequisites
20
+
21
+ - Python 3.10 or higher
22
+ - Open Link Token core library (automatically installed as dependency)
23
+ - Apache Spark or PySpark environment
24
+
25
+ ### Version Compatibility
26
+
27
+ Open Link Token PySpark supports multiple Spark versions to accommodate different Java environments and cluster configurations:
28
+
29
+ | Spark Version | PySpark Version | PyArrow Version | Pandas Version | Java Version | Installation Extra |
30
+ | ------------- | --------------- | --------------- | -------------- | ------------ | ----------------------------- |
31
+ | **4.0.x** | >=4.0.1, <5.0 | >=17.0.0, <18.0 | >=2.0.0, <2.4 | **21** | `[spark40]` **(Recommended)** |
32
+ | 4.1.x | >=4.1.0, <5.0 | >=17.0.0, <18.0 | >=2.0.0, <2.4 | **21** | `[spark41]` |
33
+ | 3.5.x | >=3.5.0, <3.6 | >=15.0.0, <20 | >=1.5, <2.3 | 8-17 | `[spark35]` |
34
+ | 3.4.x | >=3.4.0, <3.5 | >=10.0.0, <15 | >=1.5, <2.2 | 8-17 | `[spark34]` |
35
+
36
+ **Important:**
37
+
38
+ - PySpark 3.5.x and earlier are **NOT compatible** with Java 21
39
+ - If you're using Java 21, you **must** use PySpark 4.0.x+ or 4.1.x+ (Spark 4.0.x recommended)
40
+ - For managed clusters (Databricks, EMR, Azure Synapse), PySpark is typically pre-installed
41
+
42
+ ### Installation Options
43
+
44
+ #### Option 1: Spark 4.0+ (Recommended - Java 21)
45
+
46
+ For local development or clusters with Java 21:
47
+
48
+ ```bash
49
+ uv pip install openlinktoken-pyspark[spark40]
50
+ ```
51
+
52
+ #### Option 2: Spark 3.5.x (Java 8-17)
53
+
54
+ For clusters still using Java 8-17 and Spark 3.5.x:
55
+
56
+ ```bash
57
+ uv pip install openlinktoken-pyspark[spark35]
58
+ ```
59
+
60
+ #### Option 3: Spark 3.4.x (Legacy)
61
+
62
+ For older Spark 3.4.x clusters:
63
+
64
+ ```bash
65
+ uv pip install openlinktoken-pyspark[spark34]
66
+ ```
67
+
68
+ #### Option 4: Managed Cluster Environments
69
+
70
+ For environments where PySpark is pre-installed (Databricks, EMR, etc.):
71
+
72
+ ```bash
73
+ uv pip install openlinktoken-pyspark
74
+ ```
75
+
76
+ This installs only the core dependencies without PySpark. You'll use the cluster's PySpark installation.
77
+
78
+ ### Development Setup
79
+
80
+ For local development with editable install:
81
+
82
+ ```bash
83
+ # Install Open Link Token core library first
84
+ cd lib/python/openlinktoken
85
+ uv pip install -e .
86
+
87
+ # Install PySpark bridge with Spark 4.0 dependencies
88
+ cd ../openlinktoken-pyspark
89
+ uv pip install -e .[spark40,dev]
90
+ ```
91
+
92
+ The `dev` extra includes testing and notebook dependencies (pytest, jupyter, etc.).
93
+
94
+ ## Quick Start
95
+
96
+ ### Basic Usage (All Spark Versions)
97
+
98
+ The recommended production flow resolves hashing and transport secrets from an initiate-exchange config on the driver:
99
+
100
+ ```python
101
+ import sys
102
+ import os
103
+ from pyspark.sql import SparkSession
104
+ from openlinktoken_pyspark import OpenLinkTokenProcessor
105
+
106
+ # Create Spark session (PySpark 4.0.1+ handles Java 21 natively)
107
+ spark = SparkSession.builder \
108
+ .appName("OpenLinkTokenExample") \
109
+ .master("local[*]") \
110
+ .config("spark.executorEnv.PYTHONPATH", os.pathsep.join(sys.path)) \
111
+ .getOrCreate()
112
+
113
+ # Load your data
114
+ df = spark.read.csv("data.csv", header=True)
115
+
116
+ # Initialize the processor from an initiate-exchange config
117
+ processor = OpenLinkTokenProcessor.from_exchange_config(
118
+ exchange_config_path="initiate-exchange-config.json",
119
+ private_key_path="participant-private-key.pem",
120
+ )
121
+
122
+ # Generate tokens
123
+ tokens_df = processor.process_dataframe(df)
124
+
125
+ # View results
126
+ tokens_df.show()
127
+ ```
128
+
129
+ `from_exchange_config(...)` resolves the exchange config and private key on the driver, then Spark workers receive only the derived byte payloads needed for token generation.
130
+
131
+ ### Azure Key Vault Example
132
+
133
+ When your Spark job runs on Azure, keep the vault lookup on the driver and pass the resolved inputs into
134
+ `from_exchange_config(...)`. In the PySpark bridge, the direct inputs are still the initiate-exchange config JSON plus the
135
+ participant private key. The sender and recipient public keys are already embedded in the generated exchange config payload,
136
+ so you normally keep them in Key Vault for exchange creation, rotation, or validation rather than passing them directly to
137
+ `OpenLinkTokenProcessor`.
138
+
139
+ ```python
140
+ import os
141
+
142
+ from azure.identity import DefaultAzureCredential
143
+ from azure.keyvault.secrets import SecretClient
144
+ from openlinktoken_pyspark import OpenLinkTokenProcessor
145
+
146
+ vault = SecretClient(
147
+ vault_url=os.environ["AZURE_KEY_VAULT_URL"],
148
+ credential=DefaultAzureCredential(),
149
+ )
150
+
151
+ exchange_config_json = vault.get_secret("openlinktoken-initiate-exchange-config").value
152
+ participant_private_key_pem = vault.get_secret("openlinktoken-participant-private-key-pem").value
153
+
154
+ # Optional: keep the public keys in Key Vault for exchange creation/rotation or validation.
155
+ sender_public_key_pem = vault.get_secret("openlinktoken-sender-public-key-pem").value
156
+ recipient_public_key_pem = vault.get_secret("openlinktoken-recipient-public-key-pem").value
157
+
158
+ processor = OpenLinkTokenProcessor.from_exchange_config(
159
+ exchange_config_value=exchange_config_json,
160
+ private_key_value=participant_private_key_pem,
161
+ )
162
+ ```
163
+
164
+ If Azure Key Vault is your system of record for the public keys, use those values when you create or rotate the
165
+ initiate-exchange config upstream. The PySpark bridge does not take public-key arguments directly.
166
+
167
+ ### Java 8-17 Setup (PySpark 3.5.x)
168
+
169
+ If you're using Java 8-17 and cannot upgrade to Java 21:
170
+
171
+ ```python
172
+ import sys
173
+ import os
174
+ from pyspark.sql import SparkSession
175
+ from openlinktoken_pyspark import OpenLinkTokenProcessor
176
+
177
+ # Create Spark session (PySpark 3.5.x with PyArrow <20)
178
+ spark = SparkSession.builder \
179
+ .appName("OpenLinkTokenExample") \
180
+ .master("local[*]") \
181
+ .config("spark.executorEnv.PYTHONPATH", os.pathsep.join(sys.path)) \
182
+ .getOrCreate()
183
+
184
+ # Load your data
185
+ df = spark.read.csv("data.csv", header=True)
186
+
187
+ # Initialize the processor from an initiate-exchange config
188
+ processor = OpenLinkTokenProcessor.from_exchange_config(
189
+ exchange_config_path="initiate-exchange-config.json",
190
+ private_key_path="participant-private-key.pem",
191
+ )
192
+
193
+ # Generate tokens
194
+ tokens_df = processor.process_dataframe(df)
195
+
196
+ # View results
197
+ tokens_df.show()
198
+ ```
199
+
200
+ **Note:** Ensure you have PyArrow <20 installed: `uv pip install 'pyarrow>=15.0.0,<20'`
201
+
202
+ ### Databricks Example
203
+
204
+ ```python
205
+ from openlinktoken_pyspark import OpenLinkTokenProcessor
206
+
207
+ # Load data from Delta table or CSV
208
+ df = spark.read.table("my_database.person_records")
209
+
210
+ # Resolve private key material on the driver before processing
211
+ participant_private_key_pem = dbutils.secrets.get(
212
+ "openlinktoken",
213
+ "participant_private_key_pem",
214
+ )
215
+
216
+ processor = OpenLinkTokenProcessor.from_exchange_config(
217
+ exchange_config_path="/dbfs/FileStore/openlinktoken/initiate-exchange-config.json",
218
+ private_key_value=participant_private_key_pem,
219
+ )
220
+
221
+ # Generate tokens
222
+ tokens_df = processor.process_dataframe(df)
223
+
224
+ # Save results
225
+ tokens_df.write.mode("overwrite").saveAsTable("my_database.person_tokens")
226
+ ```
227
+
228
+ ### Direct Secret Usage
229
+
230
+ If you already inject raw secrets into your Spark application, the direct constructor remains supported:
231
+
232
+ ```python
233
+ from openlinktoken_pyspark import OpenLinkTokenProcessor
234
+
235
+ processor = OpenLinkTokenProcessor(
236
+ hashing_secret="your-hashing-secret",
237
+ encryption_key="0123456789abcdef0123456789abcdef",
238
+ )
239
+ ```
240
+
241
+ Use this path only when you intentionally manage raw secrets yourself. New integrations should prefer `from_exchange_config(...)`.
242
+
243
+ ## Input DataFrame Requirements
244
+
245
+ Your input DataFrame must contain the following columns (alternative names are supported):
246
+
247
+ | Standard Name | Alternative Names | Description |
248
+ | -------------------- | ---------------------------- | ------------------------------------------------------------- |
249
+ | RecordId | Id | Unique identifier (optional - auto-generated if not provided) |
250
+ | FirstName | GivenName | Person's first name |
251
+ | LastName | Surname | Person's last name |
252
+ | BirthDate | DateOfBirth | Date of birth in YYYY-MM-DD format |
253
+ | Sex | Gender | Sex/Gender (Male, Female, M, F) |
254
+ | PostalCode | ZipCode | US ZIP code or Canadian postal code |
255
+ | SocialSecurityNumber | NationalIdentificationNumber | SSN or national ID number |
256
+
257
+ ## Output Format
258
+
259
+ The output DataFrame contains:
260
+
261
+ - **RecordId**: The original record identifier
262
+ - **RuleId**: Token rule identifier (T1, T2, T3, T4, T5)
263
+ - **Token**: Encrypted match token in `olt.V1.<JWE>` format (or base64-encoded HMAC in hash-only flows)
264
+
265
+ Each input record produces multiple output rows (one per token rule).
266
+
267
+ ## Using Custom Token Definitions
268
+
269
+ You can define custom tokens using the `openlinktoken_pyspark.notebook_helpers` module and pass them to the processor:
270
+
271
+ ```python
272
+ from openlinktoken_pyspark import (
273
+ CustomTokenDefinition,
274
+ OpenLinkTokenProcessor,
275
+ TokenBuilder,
276
+ quick_token_from_exchange_config,
277
+ )
278
+
279
+ # Method 1: Using TokenBuilder
280
+ custom_token = TokenBuilder("T6") \
281
+ .add("last_name", "T|U") \
282
+ .add("first_name", "T|U") \
283
+ .add("birth_date", "T|D") \
284
+ .add("postal_code", "T|S(0,3)") \
285
+ .add("sex", "T|U") \
286
+ .build()
287
+
288
+ custom_definition = CustomTokenDefinition().add_token(custom_token)
289
+
290
+ # Create a processor with the custom definition
291
+ processor = OpenLinkTokenProcessor.from_exchange_config(
292
+ exchange_config_path="initiate-exchange-config.json",
293
+ private_key_path="participant-private-key.pem",
294
+ token_definition=custom_definition,
295
+ )
296
+
297
+ # Process DataFrame - will use T6 instead of default T1-T5
298
+ tokens_df = processor.process_dataframe(df)
299
+
300
+ # For one-off notebook exploration, the package root also exports
301
+ # create_token_generator_from_exchange_config(...) and quick_token_from_exchange_config(...)
302
+ generator = quick_token_from_exchange_config(
303
+ "T7",
304
+ [("last_name", "T|U"), ("first_name", "T|U"), ("birth_date", "T|D")],
305
+ exchange_config_path="initiate-exchange-config.json",
306
+ private_key_path="participant-private-key.pem",
307
+ )
308
+ ```
309
+
310
+ For more examples and interactive experimentation with custom tokens, see the [Custom Token Definition Guide](notebooks/Custom_Token_Definition_Guide.ipynb).
311
+
312
+ ## Example Notebooks
313
+
314
+ See the included Jupyter notebooks for complete examples:
315
+
316
+ **Basic Usage:**
317
+
318
+ ```bash
319
+ cd notebooks
320
+ jupyter notebook OpenLinkToken_PySpark_Example.ipynb
321
+ ```
322
+
323
+ **Custom Token Definitions:**
324
+
325
+ ```bash
326
+ cd notebooks
327
+ jupyter notebook Custom_Token_Definition_Guide.ipynb
328
+ ```
329
+
330
+ **Dataset Overlap Analysis:**
331
+
332
+ ```bash
333
+ cd notebooks
334
+ jupyter notebook Dataset_Overlap_Analysis_Guide.ipynb
335
+ ```
336
+
337
+ ## Dataset Overlap Analysis
338
+
339
+ The `OpenLinkTokenOverlapAnalyzer` class helps identify matching records between two tokenized datasets by decrypting tokens to deterministic values before comparison.
340
+
341
+ ### Basic Usage
342
+
343
+ ```python
344
+ from openlinktoken_pyspark import OpenLinkTokenOverlapAnalyzer
345
+
346
+ # Initialize from the same initiate-exchange config used for token generation
347
+ analyzer = OpenLinkTokenOverlapAnalyzer.from_exchange_config(
348
+ exchange_config_path="initiate-exchange-config.json",
349
+ private_key_path="participant-private-key.pem",
350
+ )
351
+
352
+ # Analyze overlap between two tokenized datasets
353
+ # Match on tokens T1 and T2 (both must match)
354
+ results = analyzer.analyze_overlap(
355
+ tokens_df1,
356
+ tokens_df2,
357
+ matching_rules=["T1", "T2"],
358
+ dataset1_name="Hospital_A",
359
+ dataset2_name="Hospital_B"
360
+ )
361
+
362
+ # Print summary
363
+ analyzer.print_summary(results)
364
+
365
+ # Access detailed results
366
+ print(f"Total records in dataset 1: {results['total_records_dataset1']}")
367
+ print(f"Matching records: {results['matching_records_dataset1']}")
368
+ print(f"Overlap percentage: {results['overlap_percentage']:.2f}%")
369
+
370
+ # Get DataFrame of matched record pairs
371
+ matches_df = results['matches']
372
+ matches_df.show()
373
+ ```
374
+
375
+ ### Compare with Multiple Rule Sets
376
+
377
+ ```python
378
+ # Compare overlap using different matching criteria
379
+ rule_sets = [
380
+ ["T1"], # Match on T1 only
381
+ ["T1", "T2"], # Match on T1 AND T2
382
+ ["T1", "T2", "T3"] # Match on T1 AND T2 AND T3
383
+ ]
384
+
385
+ results = analyzer.compare_with_multiple_rules(
386
+ tokens_df1, tokens_df2, rule_sets
387
+ )
388
+
389
+ # See how overlap changes with stricter rules
390
+ for result in results:
391
+ print(f"Rules {result['matching_rules']}: "
392
+ f"{result['overlap_percentage']:.2f}% overlap")
393
+ ```
394
+
395
+ ### Use Cases
396
+
397
+ - **Data Quality Assessment**: Identify duplicate records across datasets
398
+ - **Patient Matching**: Find matching patients between healthcare systems
399
+ - **Research Cohort Overlap**: Analyze overlap between research study populations
400
+ - **Data Sharing Analysis**: Assess data overlap before establishing data sharing agreements
401
+
402
+ ### How It Works
403
+
404
+ 1. Both datasets must contain tokenized records (RecordId, RuleId, Token columns)
405
+ 2. Matching rules specify which token types must match (e.g., ["T1", "T2"])
406
+ 3. Records are considered matching only if ALL specified token types match
407
+ 4. The analyzer provides statistics and a DataFrame of matched record pairs
408
+ 5. Uses the exchange-derived transport key that was used to generate the tokens, supporting both `olt.V1` JWE tokens and legacy encrypted token format
409
+
410
+ ## Testing
411
+
412
+ Run the test suite:
413
+
414
+ ```bash
415
+ # From the openlinktoken-pyspark directory
416
+ pytest
417
+ ```
418
+
419
+ ## Performance Considerations
420
+
421
+ - **Partitioning**: PySpark processes data in parallel across partitions. Adjust `spark.sql.shuffle.partitions` for your cluster size.
422
+ - **Memory**: Token generation is memory-efficient but ensure adequate executor memory for your data volume.
423
+ - **Secret Resolution**: Exchange configs and private keys are resolved on the driver; workers receive only the derived hashing/encryption bytes.
424
+ - **Secrets on Workers**: Derived secret bytes are still serialized to worker nodes, so ensure secure cluster configuration.
425
+
426
+ ## Architecture
427
+
428
+ The PySpark bridge uses Pandas UDFs (User Defined Functions) to efficiently process batches of records:
429
+
430
+ 1. Data is partitioned across the Spark cluster
431
+ 2. Each partition is processed by a Pandas UDF
432
+ 3. Within each batch, the Open Link Token library generates tokens
433
+ 4. Results are collected back into a PySpark DataFrame
434
+
435
+ This architecture balances the benefits of distributed computing with the cryptographic requirements of token generation.
436
+
437
+ ## Security Notes
438
+
439
+ - **Secrets Management**: Prefer initiate-exchange configs plus secure private-key delivery (for example AWS Secrets Manager or Azure Key Vault)
440
+ - **Network Security**: Ensure secure communication between Spark nodes
441
+ - **Data Privacy**: Generated tokens are cryptographically secure and cannot be reversed to original values
442
+
443
+ ## Related Documentation
444
+
445
+ - [Open Link Token Core Library](../openlinktoken/) - Python core implementation
446
+ - [Open Link Token CLI](../openlinktoken-cli/) - Python CLI with I/O support (CSV, Parquet)
447
+ - [Main Open Link Token Documentation](../../../README.md) - Project overview and setup
448
+ - [Development Guide](../../../docs/dev-guide-development.md) - Contributor documentation
@@ -0,0 +1,3 @@
1
+ # Use consolidated dev requirements from parent directory
2
+ # Install with: uv pip install -r ../dev-requirements.txt
3
+ -r ../dev-requirements.txt