Skip to main content

Pre-flight schema validator โ€” powered by sqlglot. Checks field names and types against BigQuery, Snowflake, Redshift, Spark, Databricks, Hive, Postgres, MySQL, DuckDB, Trino, ClickHouse, Kafka/Avro. Pure Python. No API. Works offline.

Project description

schema-preflight ๐Ÿ›ซ

PyPI Python 3.9+ License: MIT Tests

Check your schema against 12 systems simultaneously โ€” before a single byte of data is written.

Pure Python. No API server. No network calls. Works completely offline. Powered by sqlglot's community-maintained dialect databases.

You name a field 'timestamp'. Kafka accepts it. 50 million
messages later โ€” BigQuery is broken. TIMESTAMP is a reserved
keyword. The dashboard team calls at 2am.

schema-preflight catches this in milliseconds. Before you deploy.

Install

pip install schema-preflight

One dependency: sqlglot (the SQL parser powering dbt, Airflow, and hundreds of data tools).


Quickstart โ€” 3 lines

from schema_preflight import SchemaPreflight

pf = SchemaPreflight()  # no URL, no API, pure offline

report = pf.check(
    name="OrderEvent",
    source_format="avro",
    target_systems=["bigquery", "snowflake", "redshift", "spark", "postgres"],
    fields=[
        {"name": "order_id",    "type": "string"},    # โœ… safe
        {"name": "user_id",     "type": "string"},    # โœ… safe
        {"name": "timestamp",   "type": "string"},    # โŒ CRITICAL โ€” 5 systems
        {"name": "select",      "type": "integer"},   # โŒ CRITICAL โ€” 5 systems
        {"name": "total",       "type": "number"},    # โœ… safe
        {"name": "metadata",    "type": "map"},       # โŒ CRITICAL โ€” BigQuery+Redshift
        {"name": "2fa_enabled", "type": "boolean"},  # โŒ CRITICAL โ€” starts with digit
    ],
)

report.print_summary()
report.raise_if_critical()  # raises PreflightFailed if any CRITICAL found

Output:

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  Schema Preflight v2  ยท  OrderEvent
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  Status   : โŒ  FAILED โ€” fix before writing data
  Systems  : 5 checked  0 passed  5 failed
  Issues   : 22 critical  0 warning  9 info

  ๐ŸŒ Issues affecting ALL 5 target systems
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  ๐Ÿ”ด [RESERVED_KEYWORD] field: 'timestamp'
     'timestamp' is a reserved keyword in all 5 systems
     Fix : Rename to 'timestamp_value' | 'event_timestamp' | 'ts'

  ๐Ÿ”ด [RESERVED_KEYWORD] field: 'select'
     'select' is a reserved SQL keyword in all 5 systems
     Fix : Rename to 'select_value' | 'select_col' | 'event_select'

  ๐Ÿ”ด [STARTS_WITH_DIGIT] field: '2fa_enabled'
     Starts with digit โ€” invalid in all SQL systems and Avro
     Fix : Rename to '_2fa_enabled' or 'col_2fa_enabled'

  ๐Ÿ’ก Suggested renames:
     'timestamp'   โ†’  timestamp_value  |  event_timestamp  |  ts
     'select'      โ†’  select_value     |  select_col
     '2fa_enabled' โ†’  col_2fa_enabled  |  _2fa_enabled
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

All the ways to use it

Plain dicts

report = pf.check("User", fields=[
    {"name": "user_id",     "type": "string"},
    {"name": "email",       "type": "string"},
    {"name": "timestamp",   "type": "string"},   # flagged
    {"name": "created_at",  "type": "timestamp"},
], target_systems=["bigquery", "snowflake"])

SchemaField objects (IDE autocomplete + type safety)

from schema_preflight import SchemaField

fields = [
    SchemaField("user_id",  "string",  required=True),
    SchemaField("email",    "string",  required=True, max_length=255),
    SchemaField("tags",     "array",   items=SchemaField("tag", "string")),
    SchemaField("address",  "object",  fields=[
        SchemaField("city",    "string"),
        SchemaField("country", "string"),
        SchemaField("select",  "string"),  # nested reserved word โ€” still caught
    ]),
]
pf.check("User", fields).raise_if_critical()

PySpark (reads metadata โ€” no data scan)

df = spark.read.parquet("s3://raw/clickstream/")
pf.check_spark_schema(df, "ClickEvent",
    target_systems=["bigquery", "snowflake", "redshift"]
).raise_if_critical()
df.write.parquet("s3://clean/clickstream/")

pandas

df = pd.read_csv("orders.csv")
pf.check_pandas_schema(df, "Orders").raise_if_critical()

SQLAlchemy

orders = Table("orders", meta,
    Column("order_id",  String),
    Column("timestamp", String),  # flagged
)
pf.check_sqlalchemy_table(orders).raise_if_critical()

Avro schema file

import json
with open("order.avsc") as f:
    schema = json.load(f)
pf.check_avro_schema(schema).raise_if_critical()

JSON Schema (draft-07)

pf.check_json_schema({
    "title": "Order",
    "type":  "object",
    "properties": {
        "order_id":  {"type": "string"},
        "timestamp": {"type": "string"},  # flagged
    }
}).raise_if_critical()

Infer from a sample dict

sample = {"order_id": "123", "timestamp": "2025-01-01", "total": 99.99}
pf.check_dict_sample(sample, "Order").print_summary()

Quick single field check

result = pf.check_name("timestamp")
# {"safe": False, "affected_systems": ["bigquery","snowflake",...],
#  "suggestions": ["timestamp_value","event_timestamp","ts"]}

Get safe renames

pf.suggest("timestamp")
# โ†’ ["timestamp_value", "event_timestamp", "ts", "data_timestamp"]

CLI

# Full schema check
preflight check \
  --name OrderEvent \
  --format avro \
  --systems bigquery snowflake redshift spark \
  --fields order_id:string timestamp:string total:number select:integer

# Check from JSON file
preflight check --file schema.json --systems bigquery snowflake

# Single field name check
preflight check-name timestamp

# Get safe suggestions
preflight suggest select

# List all supported systems
preflight systems

# JSON output (for CI pipelines)
preflight check --json --file schema.json

Use in CI/CD (GitHub Actions)

Block PRs that introduce schema breaking changes:

- name: Schema preflight
  run: |
    pip install schema-preflight
    preflight check \
      --file schemas/order_event.json \
      --systems bigquery snowflake redshift spark
  # exits 1 and blocks merge if CRITICAL issues found

What it checks โ€” 24 edge case categories

All sourced from official system documentation.

# Check Systems
1 Reserved SQL keywords (via sqlglot โ€” 31 dialects, always current) All
2 System type name collisions (TIMESTAMP, STRING, BOOLEAN as column names) All
3 BigQuery reserved prefixes (_TABLE_, _FILE_, _PARTITION_, _CHANGE_*, _SESSION_) BigQuery
4 Names starting with a digit All
5 Special characters (spaces, hyphens, dots) All
6 Leading / trailing whitespace All
7 Empty field name All
8 Column name length limits (BQ:300, SF:255, RS:127, PG:63, MySQL:64) All
9 Duplicate names after case-folding All
10 Unicode chars in systems that forbid them BQ, Redshift, Hive
11 Double-underscore prefix (pseudo-column collision) BQ, Redshift, Postgres
12 Avro name regex [A-Za-z_][A-Za-z0-9_]* Kafka/Avro
13 Avro complex union types (more than [null, T]) Kafka/Avro
14 Array of array โ€” BigQuery hard limit BigQuery
15 Max nesting depth 15 โ€” BigQuery hard limit BigQuery
16 Max 10,000 columns including nested โ€” BigQuery hard limit BigQuery
17 Max 1,600 columns โ€” Redshift / Postgres hard limit Redshift, Postgres
18 Type mapping safety per system All
19 Decimal precision exceeds system maximum All
20 MAP type with non-string key โ€” crashes Redshift connector Redshift
21 MAP type โ€” unsupported in BigQuery (no native MAP type) BigQuery
22 Array type โ€” unsupported in Redshift Spectrum with Avro format Redshift
23 SQL function name collisions (COUNT, SUM, RANK โ€” work but cause confusion) All
24 ClickHouse case-sensitivity (ClickHouse is case-sensitive, others are not) ClickHouse

Supported systems

ID System sqlglot dialect
bigquery Google BigQuery โœ…
snowflake Snowflake โœ…
redshift Amazon Redshift โœ…
spark Apache Spark SQL โœ…
databricks Databricks / Delta Lake โœ…
hive Apache Hive โœ…
postgres PostgreSQL โœ…
mysql MySQL โœ…
duckdb DuckDB โœ…
trino Trino โœ…
clickhouse ClickHouse โœ…
kafka_avro Kafka / Avro custom

Supported field types

string integer long float double number boolean bytes date time datetime timestamp array object record map enum decimal null uuid json

Supported source formats

json avro parquet protobuf csv orc


Why this is different from every other tool

Tool What it does Gap
Confluent Schema Registry Avro compatibility for Kafka Single system, no cross-system check
AWS Glue Schema Registry Schema for AWS ecosystem AWS-only, no cross-system check
sqlglot SQL parser, 31 dialects Checks SQL queries โ€” not schema field names
buf.build Protobuf registry Protobuf only
DataHub Data contracts Requires full platform, no offline check
schema-preflight Field name + type check across all systems First offline cross-system pre-flight tool

Publish to PyPI

pip install build twine
python3 -m build
python3 -m twine upload dist/*

License

MIT ยฉ Amrutham Akshithraj

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

schema_preflight-2.0.0.tar.gz (31.5 kB view details)

Uploaded Source

Built Distribution

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

schema_preflight-2.0.0-py3-none-any.whl (29.6 kB view details)

Uploaded Python 3

File details

Details for the file schema_preflight-2.0.0.tar.gz.

File metadata

  • Download URL: schema_preflight-2.0.0.tar.gz
  • Upload date:
  • Size: 31.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for schema_preflight-2.0.0.tar.gz
Algorithm Hash digest
SHA256 5ca8cc60db1febe345f613f456c16a0139e7208bbb6d3aecf809e952404d9d47
MD5 7619c1652f1335c744587d79cbe3e455
BLAKE2b-256 3015f258e53b184c061735667d752d3870900eda7ca46016472c34f1500da0a4

See more details on using hashes here.

File details

Details for the file schema_preflight-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for schema_preflight-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3786aee79d39dce05acb006b12880f7f3a741bf78abe80c3a7e85337b8be4b0e
MD5 8ac29621b2ee75e2fb5b20edc1279530
BLAKE2b-256 0636f5fe0fef098d4424e4725905fa74a8cdeba2ccb6b0fb02da9551a2fff92a

See more details on using hashes here.

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