Pandas & PySpark data validation via JSON Schema
Project description
Norma
Norma is a data validation framework designed for Pandas and PySpark DataFrames, leveraging JSON Schema or a Python API to enforce data integrity. Unlike traditional validation libraries that halt execution on errors, Norma introduces a resilient, non-intrusive approach that captures validation details in a dedicated errors column while resetting erroneous values to null.
Install
Install with pip:
pip install vitalibo.norma
Quick Start
First, create a validation schema from JSON Schema:
{
"$id": "https://example.com/address.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 2
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 120
},
"sex": {
"type": "string",
"enum": [
"M",
"F"
]
},
"email": {
"type": "string",
"pattern": "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
}
},
"required": [
"name",
"age",
"email"
]
}
from norma.schema import Schema
schema = Schema.from_json_schema({... see above ...})
alternatively, build the schema with the Python API:
from norma.schema import Column, Schema
from norma import rules
schema = Schema({
'name': Column(str, min_length=2),
'age': Column(int, ge=0, le=120),
'sex': Column(str, rules=rules.isin(['M', 'F'])),
'email': Column(str, pattern=r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')
})
Now, we are ready to validate our data. Let's create a Pandas DataFrame with some invalid data.
import pandas as pd
df = pd.DataFrame({
'name': ['John', 'Rian', 'Alice'],
'age': [42, 130, 25],
'sex': ['M', 'F', 'X'],
'email': ['john@spring.ep', 'ryna@spring.ep', 'alice.spring@ep']
})
or, you can create the same example as a PySpark DataFrame:
from pyspark.sql import SparkSession
spark_session = SparkSession.builder.getOrCreate()
df = spark_session.createDataFrame([
('John', 42, 'M', 'john@spring.ep'),
('Rian', 130, 'F', 'ryna@spring.ep'),
('Alice', 25, 'X', 'alice.spring@ep')
], ['name', 'age', 'sex', 'email'])
Finally, validate the dataframe:
actual = schema.validate(df)
Output:
For Pandas DataFrame we have:
name age sex email errors
0 John 42 M john@spring.ep {}
1 Rian <NA> F ryna@spring.ep {'age': {'details': [{'type': 'less_than_equal', 'msg': 'Input should be less than or equal to 120'}], 'original': '130'}}
2 Alice 25 <NA> <NA> {'sex': {'details': [{'type': 'enum', 'msg': 'Input should be "M" or "F"'}], 'original': '"X"'}, 'email': {'details': [{'type': 'string_pattern_mismatch', 'msg': 'String should match pattern "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"'}], 'original': '"alice.spring@ep"'}}
and dtypes:
name string[python]
age Int64
sex string[python]
email string[python]
errors object
dtype: object
Similarly, for PySpark DataFrame:
+-----+----+----+--------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|name |age |sex |email |errors |
+-----+----+----+--------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|John |42 |M |john@spring.ep|{} |
|Rian |NULL|F |ryna@spring.ep|{age -> {[{less_than_equal, Input should be less than or equal to 120}], 130}} |
|Alice|25 |NULL|NULL |{email -> {[{string_pattern_mismatch, String should match pattern "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"}], "alice.spring@ep"}, sex -> {[{enum, Input should be "M" or "F"}], "X"}}|
+-----+----+----+--------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
and schema:
root
|-- name: string (nullable = true)
|-- age: integer (nullable = true)
|-- sex: string (nullable = true)
|-- email: string (nullable = true)
|-- errors: map (nullable = false)
| |-- key: string
| |-- value: struct (valueContainsNull = true)
| | |-- details: array (nullable = false)
| | | |-- element: struct (containsNull = true)
| | | | |-- type: string (nullable = true)
| | | | |-- msg: string (nullable = true)
| | |-- original: string (nullable = true)
Supported validation rules
Norma supports a variety of validation rules, including:
| rule | pandas | pandas[object] | pandas[array] | pyspark | pyspark[object] | pyspark[array] |
|---|---|---|---|---|---|---|
required |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
equal_to |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
not_equal_to |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
greater_than |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
greater_than_equal |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
less_than |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
less_than_equal |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
multiple_of |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
min_length |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
max_length |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
pattern |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
isin |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
notin |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
extra_forbidden |
✅ | ✅ | ➖ | ✅ | ✅ | ➖ |
unique_items |
✅ | ➖ | ✅ | ✅ | ➖ | ✅ |
max_items |
✅ | ➖ | ✅ | ✅ | ➖ | ✅ |
min_items |
✅ | ➖ | ✅ | ✅ | ➖ | ✅ |
int_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
float_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
str_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
bool_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
date_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
time_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
datetime_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
duration_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
uuid_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
ipv4_address |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
ipv6_address |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
uri_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
object_parsing |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
array_parsing |
✅ | ✅ | ❌ | ✅ | ✅ | ❌ |
✅ - supported ❌ - not supported ➖ - not applicable
Errors
The error format is a dictionary where the key is the column name, and value is a structure with two fields: details
and original. The details field is a list of details about the error, and the original field is the original value
that caused the error.
{
"name": "Alice",
"age": 25,
"sex": null,
"email": null,
"errors": {
"sex": {
"details": [
{
"type": "enum",
"msg": "Input should be \"M\" or \"F\""
}
],
"original": "\"X\""
},
"email": {
"details": [
{
"type": "string_pattern_mismatch",
"msg": "String should match pattern \"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$\""
}
],
"original": "\"alice.spring@ep\""
}
}
}
Special case, when DataFrame has array validation rules, the error format is slightly different.
In this case, the details array has an additional loc field that indicates the indexes of the array elements that
failed validation and incorrect values are replaced with null in an array.
{
"tags": [
null,
"tag1",
null
],
"errors": {
"tags[]": {
"details": [
{
"loc": [
0,
2
],
"type": "enum",
"msg": "Input should be \"tag1\", \"tag2\" or \"tag3\""
}
],
"original": "[\"tag0\",\"tag1\",\"tag4\"]"
}
}
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vitalibo_norma-0.2.0.tar.gz.
File metadata
- Download URL: vitalibo_norma-0.2.0.tar.gz
- Upload date:
- Size: 167.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3681eb9aa8b11320bcb88091d190732c1abc77e2823f2ff6d125f1d94e545c63
|
|
| MD5 |
e7c6ee211f92ab9f906bbfc9f3238b94
|
|
| BLAKE2b-256 |
dfb43ae745aaa6b727c40b223dfc6ef4d80cd705e13254bf87314e77c624efe3
|
Provenance
The following attestation bundles were made for vitalibo_norma-0.2.0.tar.gz:
Publisher:
ci.yaml on vitalibo/norma
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vitalibo_norma-0.2.0.tar.gz -
Subject digest:
3681eb9aa8b11320bcb88091d190732c1abc77e2823f2ff6d125f1d94e545c63 - Sigstore transparency entry: 2239170333
- Sigstore integration time:
-
Permalink:
vitalibo/norma@c021b5c825059de43a2314fcfb1ffcda751ee63d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/vitalibo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yaml@c021b5c825059de43a2314fcfb1ffcda751ee63d -
Trigger Event:
push
-
Statement type:
File details
Details for the file vitalibo_norma-0.2.0-py3-none-any.whl.
File metadata
- Download URL: vitalibo_norma-0.2.0-py3-none-any.whl
- Upload date:
- Size: 30.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91446074e05a7516fa6da1b406a6cdf3cf98ebc0f79b9581dc7520af105eb535
|
|
| MD5 |
0f3345efa0a2673c8674396b812ed7c7
|
|
| BLAKE2b-256 |
f1c9db64a819ec8e0f0e1f376184eb2efdc225fb6985fac905c1c55cec0529dd
|
Provenance
The following attestation bundles were made for vitalibo_norma-0.2.0-py3-none-any.whl:
Publisher:
ci.yaml on vitalibo/norma
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vitalibo_norma-0.2.0-py3-none-any.whl -
Subject digest:
91446074e05a7516fa6da1b406a6cdf3cf98ebc0f79b9581dc7520af105eb535 - Sigstore transparency entry: 2239170682
- Sigstore integration time:
-
Permalink:
vitalibo/norma@c021b5c825059de43a2314fcfb1ffcda751ee63d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/vitalibo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yaml@c021b5c825059de43a2314fcfb1ffcda751ee63d -
Trigger Event:
push
-
Statement type: