Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
14e1ee5
Add built-in methods for PII detection
ghanse Jul 27, 2025
b359636
Added configuration profiles and improved docs
ghanse Jul 27, 2025
9ed05be
Improve docs
ghanse Jul 27, 2025
7eb84a3
Update contains_pii message and add tests
ghanse Jul 28, 2025
0a09308
Update check function
ghanse Jul 28, 2025
a934e2f
Refactor
ghanse Jul 29, 2025
4069478
Merge branch 'main' into pii_detection
mwojtyczka Jul 29, 2025
3021eda
Refactor
ghanse Jul 29, 2025
5cca074
Refactor
ghanse Jul 30, 2025
b4ef3fa
Merge branch 'main' into pii_detection
ghanse Jul 30, 2025
014c94f
Refactor and fix tests
ghanse Jul 30, 2025
8c0192e
Fmt
ghanse Jul 30, 2025
4a6941b
Change config and presidio log level
ghanse Jul 31, 2025
3f37058
Update return types
ghanse Jul 31, 2025
7971463
Fix check func message
ghanse Jul 31, 2025
4dbc5ae
Fmt and python version
ghanse Jul 31, 2025
042c189
Pandas UDF
ghanse Jul 31, 2025
0c7699a
Fix UDF creation and execution
ghanse Jul 31, 2025
2a0155f
Test
ghanse Jul 31, 2025
42b4cb8
Test
ghanse Jul 31, 2025
dd99534
Test
ghanse Jul 31, 2025
3cab031
Revert
ghanse Jul 31, 2025
9478d70
Run tests on 3.11
ghanse Aug 1, 2025
68cafba
Run tests on 3.11
ghanse Aug 1, 2025
1254a0f
Fix tests
ghanse Aug 1, 2025
8531171
Merge branch 'main' into pii_detection
mwojtyczka Aug 1, 2025
434e9b6
Add perf warning and docs disclaimer
ghanse Aug 1, 2025
d60aed5
Install required modules at cluster level
ghanse Aug 1, 2025
855e2fa
Install required modules
ghanse Aug 1, 2025
ca240f2
Use environments with Databricks connect
ghanse Aug 2, 2025
5684598
Use environments with Databricks connect
ghanse Aug 2, 2025
9b95655
Use environments with Databricks connect
ghanse Aug 3, 2025
8e86cae
Use environments with Databricks connect
ghanse Aug 3, 2025
9dce240
Use environments with Databricks connect
ghanse Aug 3, 2025
3225454
Merge branch 'main' into pii_detection
mwojtyczka Aug 5, 2025
8b6fc71
Merge branch 'main' into pii_detection
mwojtyczka Aug 6, 2025
b5bead5
Added configuration profiles and improved docs
ghanse Jul 27, 2025
1a19e29
Limit PII detection environments
ghanse Aug 6, 2025
9bf02cc
Update install path
ghanse Aug 6, 2025
5db2e75
Fix fmt, pyproject file, and actions
ghanse Aug 6, 2025
6f3a667
Merge branch 'main' into pii_detection
ghanse Aug 6, 2025
8514538
Fix tests and pin dependencies
ghanse Aug 7, 2025
1310543
Pin numpy
ghanse Aug 7, 2025
aa58326
Unpin numpy
ghanse Aug 7, 2025
a129366
Pin numpy
ghanse Aug 7, 2025
3769317
Update docs
ghanse Aug 7, 2025
e1def09
Update demo
ghanse Aug 8, 2025
b854ac9
Format
ghanse Aug 8, 2025
45dae55
Update docs/dqx/docs/reference/quality_rules.mdx
mwojtyczka Aug 8, 2025
b679aaf
Update src/databricks/labs/dqx/pii/pii_detection_funcs.py
mwojtyczka Aug 8, 2025
cf804f0
Merge branch 'main' into pii_detection
mwojtyczka Aug 8, 2025
7feb2b8
Add dev documentation and tests
ghanse Aug 8, 2025
d591ed6
Revert dependency change
ghanse Aug 8, 2025
7b1e982
Fix issue with config validation
ghanse Aug 8, 2025
809141c
Remove unused default variable
ghanse Aug 8, 2025
c3efb21
Add e2e tests
ghanse Aug 8, 2025
f96b612
Update tests and documentation
ghanse Aug 8, 2025
61b25bf
Update actions
ghanse Aug 8, 2025
ce4b971
Update e2e tests
ghanse Aug 8, 2025
0fea1e8
Test
ghanse Aug 8, 2025
d554911
Validate dependency installation
ghanse Aug 8, 2025
b4bd87c
Fmt
ghanse Aug 8, 2025
111a0a7
Fmt
ghanse Aug 8, 2025
d03ff35
Fix formatting
ghanse Aug 8, 2025
98efabf
Clean-up error type and tests
ghanse Aug 10, 2025
abe135a
Fix tests
ghanse Aug 10, 2025
284d6d2
Rename to does_not_contain_pii
ghanse Aug 11, 2025
4a02c75
updated docs in demo
mwojtyczka Aug 12, 2025
7e554a0
updated docs
mwojtyczka Aug 12, 2025
6b5bf4c
Merge branch 'main' into pii_detection
mwojtyczka Aug 13, 2025
876d4f0
removed pandas types
mwojtyczka Aug 13, 2025
917af13
refactor
mwojtyczka Aug 13, 2025
2a3e63c
updated streaming demo to use volume as path to avoid issues with aut…
mwojtyczka Aug 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Install hatch
run: pip install hatch==1.9.4
Expand Down Expand Up @@ -90,7 +90,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Install hatch
run: pip install hatch==1.9.4
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Install hatch
run: pip install hatch==1.9.4
Expand Down Expand Up @@ -156,7 +156,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Install hatch
run: pip install hatch==1.9.4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/downstreams.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Install toolchain
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Install hatch
run: pip install hatch==1.9.4
Expand Down Expand Up @@ -78,7 +78,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'
Comment thread
mwojtyczka marked this conversation as resolved.

- name: Install hatch
run: pip install hatch==1.9.4
Expand Down Expand Up @@ -111,7 +111,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Install hatch
run: pip install hatch==1.9.4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
python-version: '3.10'
python-version: '3.11'

- name: Build wheels
run: |
Expand Down
212 changes: 127 additions & 85 deletions demos/dqx_demo_pii_detection.py
Original file line number Diff line number Diff line change
@@ -1,144 +1,186 @@
# Databricks notebook source
# MAGIC %md
# MAGIC # Using DQX for PII Detection
# MAGIC Increased regulation makes Databricks customers responsible for any Personally Identifiable Information (PII) stored in Unity Catalog. While [Lakehouse Monitoring](https://docs.databricks.com/aws/en/lakehouse-monitoring/data-classification#discover-sensitive-data) can identify sensitive data in-place, many customers need to proactively quarantine or anonymize PII before writing the data to Delta.
# MAGIC Increased regulation makes Databricks customers responsible for any Personally Identifiable Information (PII) stored in Unity Catalog. Companies need to be able to perform PII detection for data at-rest and in-transit to proactively quarantine or anonymize PII before persisting the data.
# MAGIC
# MAGIC [Databricks Labs' DQX project](https://databrickslabs.github.io/dqx/) provides in-flight data quality monitoring for Spark `DataFrames`. Customers can apply checks, get row-level metadata, and quarantine failing records. Workloads can use DQX's built-in checks or custom user-defined functions.
# MAGIC
# MAGIC In this notebook, we'll use DQX with a custom function to detect PII in JSON strings.
# MAGIC DQX provides in-flight data quality monitoring for Spark `DataFrames`. You can apply checks, get row-level metadata, and quarantine failing records. Workloads can also use DQX's built-in functions to check `DataFrames` for PII.

# COMMAND ----------

# MAGIC %md
# MAGIC ## Prerequisites
# MAGIC This notebook uses [Presidio](https://microsoft.github.io/presidio/) to detect PII in strings. To run this notebook:
# MAGIC - Use DBR 15.4LTS
# MAGIC - Install [SpaCy](https://spacy.io/usage/models#download) as a cluster-scoped library
# MAGIC # Install DQX with PII extras
# MAGIC
# MAGIC To enable PII detection quality checking, DQX has to be installed with `pii` extras:
# MAGIC
# MAGIC `%pip install databricks-labs-dqx[pii]`

# COMMAND ----------

dbutils.widgets.text("test_library_ref", "", "Test Library Ref")

if dbutils.widgets.get("test_library_ref") != "":
%pip install '{dbutils.widgets.get("test_library_ref")}'
%pip install 'databricks-labs-dqx[pii] @ {dbutils.widgets.get("test_library_ref")}'
else:
%pip install databricks-labs-dqx

%pip install presidio_analyzer numpy==1.23.5
%pip install databricks-labs-dqx[pii]

# COMMAND ----------

dbutils.library.restartPython()

# COMMAND ----------

import json
import pandas as pd

from pyspark.sql.functions import concat_ws, col, lit, pandas_udf
from pyspark.sql import Column
from presidio_analyzer import AnalyzerEngine
from databricks.sdk import WorkspaceClient
from databricks.labs.dqx.engine import DQEngine
from databricks.labs.dqx.rule import DQRowRule
from databricks.labs.dqx.check_funcs import make_condition
from databricks.labs.dqx.pii.nlp_engine_config import NLPEngineConfig
from databricks.labs.dqx.pii.pii_detection_funcs import does_not_contain_pii

# COMMAND ----------

# MAGIC %md
# MAGIC ## Creating the Presidio analyzer
# MAGIC First, we'll use Presidio's `AnalyzerEngine` to define a function that checks values for PII. For any PII detected, the `entity_mapping` will contain the type of PII identified and a confidence score.
# MAGIC ## Detecting PII with DQX
# MAGIC DQX supports built-in PII detection using Presidio's `AnalyzerEngine` to define a function that checks values for PII. For any PII detected, the `entity_mapping` will contain the type of PII identified and a confidence score.

# COMMAND ----------

# Create the Presidio analyzer:
analyzer = AnalyzerEngine()

# Get the list of entities to download the model:
entities = analyzer.get_supported_entities()

# Create a wrapper function to generate the entity mapping results:
def get_entity_mapping(data: str) -> str | None:
if data:
# Run the Presidio analyzer to detect PII in the string:
results = analyzer.analyze(
text=data,
entities=["PERSON", "EMAIL_ADDRESS"],
language='en',
score_threshold=0.5,
)
if results != []:
output = []
# Validate and return the results:
for result in results:
# Ignore if the result is low confidence:
if result.score < 0.5:
continue
# Append the result to the output:
output.append({
"entity_type": result.entity_type,
"start": int(result.start),
"end": int(result.end),
"score": float(result.score),
})
if output != []:
# Return the results as JSON:
return json.dumps(output)
return None
# Define the DQX rule:
checks = [
DQRowRule(
criticality="error",
check_func=does_not_contain_pii,
column="val",
name="does_not_contain_pii",
)
]

# Initialize the DQX engine:
dq_engine = DQEngine(WorkspaceClient())

# Create some sample data:
data = [
["My name is John Smith"],
["The sky is blue, road runner"],
["Jane Smith sent an email to sara@info.com"],
[None],
]
df = spark.createDataFrame(data, "val string")

# Run the checks and display the output:
checked_df = dq_engine.apply_checks(df, checks)
display(checked_df)

# COMMAND ----------

# MAGIC %md
# MAGIC ## Creating a Pandas UDF
# MAGIC We can call `get_entity_mapping` on DataFrame rows using a Pandas user-defined function. This provides good performance with batched execution over the arriving records.
# MAGIC ## Configuring the PII detection settings
# MAGIC DQX supports several configurable settings which control PII detection:
# MAGIC - `threshold` controls the specificity of the PII detection (higher values give more specificity with less sensitivity)
# MAGIC - `entities` specifies which [entity types](https://microsoft.github.io/presidio/supported_entities/) are marked as PII
# MAGIC - `language` sets the detection language
# MAGIC - `nlp_engine_config` sets various properties of the Presidio analyzer's [named entity recognition model](https://microsoft.github.io/presidio/samples/python/ner_model_configuration/)

# COMMAND ----------

# Register a pandas UDF to run the analyzer:
@pandas_udf('string')
def contains_pii(batch: pd.Series) -> pd.Series:
# Apply `get_entity_mapping` to each value:
return batch.map(get_entity_mapping)
# Use a built-in NLP configuration for detecting PII:
nlp_engine_config = NLPEngineConfig.SPACY_MEDIUM

checks = [
# Define a PII check with a lower threshold (more sensitivity):
DQRowRule(
criticality="error",
check_func=does_not_contain_pii,
check_func_kwargs={"threshold": 0.5},
column="val",
name="does_not_contain_pii_lower_threshold",
),
# Define a PII check with a subset of named entities:
DQRowRule(
criticality="error",
check_func=does_not_contain_pii,
check_func_kwargs={
"entities": ["EMAIL_ADDRESS"],
},
column="val",
name="contains_email_address_data",
),
# Define a PII check with a built-in named-entity recognizer (SpaCy medium):
DQRowRule(
criticality="error",
check_func=does_not_contain_pii,
check_func_kwargs={
"entities": ["PERSON", "LOCATION"],
"nlp_engine_config": NLPEngineConfig.SPACY_MEDIUM
},
column="val",
name="contains_person_or_address_data",
),
# Define a PII check with a built-in named-entity recognizer (SpaCy medium):
DQRowRule(
criticality="error",
check_func=does_not_contain_pii,
check_func_kwargs={
"entities": ["PERSON", "LOCATION"],
"nlp_engine_config": NLPEngineConfig.SPACY_MEDIUM
},
column="val",
name="contains_person_or_address_data",
),
]

# Initialize the DQX engine:
dq_engine = DQEngine(WorkspaceClient())

# Create some sample data:
data = [
["My name is John Smith and I live at 123 Main St New York, NY 07008"],
["The sky is blue, road runner"],
["Jane Smith sent an email to sara@info.com"],
[None],
]
df = spark.createDataFrame(data, "val string")

# Run the checks and display the output:
checked_df = dq_engine.apply_checks(df, checks)
display(checked_df)

# COMMAND ----------

# MAGIC %md
# MAGIC ## Making and applying a DQX condition
# MAGIC Once our Presidio algorithm can be called as a Spark UDF, we can use DQX's `make_condition` to implement a custom check that identifies PII and generates row-level metadata about the `json` keys and types of PII identified.
# MAGIC ## Using a custom named entity recognizer
# MAGIC DQX supports custom named entity recognizers passed as Python dictionaries. All dependencies must be pre-loaded for use with DQX's built-in PII detection checks.
# MAGIC
# MAGIC ***WARNING:** Using custom named entity recognizers can significantly degrade performance of quality checking at scale. Sample data or use smaller models for best performance. Run checks on non-serverless compute when using large named entity recognizers.*

# COMMAND ----------

def does_not_contain_pii(column: str) -> Column:
# Define a PII detection expression calling the pandas UDF:
pii_info = contains_pii(col(column))

# Return the DQX condition that uses the PII detection expression:
return make_condition(
pii_info.isNotNull(),
concat_ws(
' ',
lit(column),
lit('contains pii with the following info:'),
pii_info
),
f'{column}_contains_pii'
)
# Define the NLP configuration:
nlp_engine_config = {
"nlp_engine_name": "spacy",
"models": [{"lang_code": "en", "model_name": "en_core_web_md"}]
}

# Define the DQX rule:
checks = [
DQRowRule(criticality='error', check_func=does_not_contain_pii, column='val')
# Define a PII check with a custom named-entity recognizer (Stanford De-Identifier Base):
DQRowRule(
criticality="error",
check_func=does_not_contain_pii,
check_func_kwargs={"nlp_engine_config": nlp_engine_config},
column="val",
name="contains_pii_custom_recognizer",
),
]

# Initialize the DQX engine:
dq_engine = DQEngine(WorkspaceClient())

# Create some sample data:
data = [
['My name is John Smith'],
['The sky is blue, road runner'],
['Jane Smith sent an email to sara@info.com']
["My name is John Smith and I live at 123 Main St New York, NY 07008"],
["The sky is blue, road runner"],
["Jane Smith sent an email to sara@info.com"],
[None],
]
df = spark.createDataFrame(data, 'val string')
df = spark.createDataFrame(data, "val string")

# Run the checks and display the output:
checked_df = dq_engine.apply_checks(df, checks)
Expand Down
1 change: 0 additions & 1 deletion demos/dqx_streaming_demo_diy.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@

from databricks.labs.dqx.engine import DQEngine
from databricks.sdk import WorkspaceClient
from pyspark.sql import DataFrame

dq_engine = DQEngine(WorkspaceClient())

Expand Down
Loading
Loading