Skip to main content

Pandas & PySpark data validation via JSON Schema

Project description

Norma

status

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vitalibo_norma-0.2.0.tar.gz (167.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vitalibo_norma-0.2.0-py3-none-any.whl (30.9 kB view details)

Uploaded Python 3

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

Hashes for vitalibo_norma-0.2.0.tar.gz
Algorithm Hash digest
SHA256 3681eb9aa8b11320bcb88091d190732c1abc77e2823f2ff6d125f1d94e545c63
MD5 e7c6ee211f92ab9f906bbfc9f3238b94
BLAKE2b-256 dfb43ae745aaa6b727c40b223dfc6ef4d80cd705e13254bf87314e77c624efe3

See more details on using hashes here.

Provenance

The following attestation bundles were made for vitalibo_norma-0.2.0.tar.gz:

Publisher: ci.yaml on vitalibo/norma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for vitalibo_norma-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 91446074e05a7516fa6da1b406a6cdf3cf98ebc0f79b9581dc7520af105eb535
MD5 0f3345efa0a2673c8674396b812ed7c7
BLAKE2b-256 f1c9db64a819ec8e0f0e1f376184eb2efdc225fb6985fac905c1c55cec0529dd

See more details on using hashes here.

Provenance

The following attestation bundles were made for vitalibo_norma-0.2.0-py3-none-any.whl:

Publisher: ci.yaml on vitalibo/norma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page