open-source · Python data reconciliation library · pip install fastrecon

fastrecon: Python Data Reconciliation Library for SQL, CSV, Parquet & ETL Testing

fastrecon is a free, high-performance Python data reconciliation library for comparing two tabular datasets — SQL tables, SQL query results, CSV and DAT files, Parquet, JSON, Excel, XML, Avro, ORC, fixed-width and mainframe files — through a single DuckDB + Polars + PyArrow engine. Use it for ETL testing, source-to-target validation, data migration checks, CI/CD data validation and automated data quality pipelines.

pip install fastrecon
31combinations
7compare modes
13source types
6output formats

How to install fastrecon from PyPI

The fastrecon Python package is published on PyPI and installs in seconds. It bundles DuckDB, Polars and PyArrow — the engines that power fast, memory-efficient tabular comparison. No database server required.

pip install fastrecon

# optional extras — install only what you need
pip install "fastrecon[postgres]"      # PostgreSQL via psycopg2
pip install "fastrecon[snowflake]"     # Snowflake connector
pip install "fastrecon[databricks]"    # Databricks SQL connector
pip install "fastrecon[all-files]"     # Excel, Avro, ORC, XML support

Quickest possible smoke test — compare two CSV files and print the result:

import fastrecon as fr

result = fr.compare(
    left=fr.CsvFile("left.csv"),
    right=fr.CsvFile("right.csv"),
    keys=["id"],
)
print(result.to_text())
print("exit:", result.exit_code)   # 0 MATCH · 1 MISMATCH · 2 ERROR

Complete reconciliation matrix — all 31 combinations

Every cell is a real, runnable example on this page. Click a cell to jump to the worked example. All source-type combinations are covered.

Left ↓ / Right → CSV DAT Fixed Width Parquet JSON Excel XML Avro ORC SQL Table SQL Query
CSV 01 → 02 → 03 → 04 → 05 → 06 → 07 → 08 → 09 → 10 → 11 →
DAT 12 → 13 → 14 →
Parquet 15 → 16 → 17 →
JSON 18 → 19 → 20 →
Excel 21 → 22 → 23 →
XML 24 → 25 → 26 →
Avro 27 →
ORC 28 →
SQL Table 29 → 31 →
SQL Query 30 →

31 runnable combinations covering every source-type pair. Cells with — are symmetric duplicates.

Supported data sources

Anything you reconcile is wrapped in a Source. The factory fastrecon.source(path) picks one for you based on the file extension; otherwise instantiate the class directly.

File sources

CsvFile

CsvFile(path, options={}) — DuckDB read_csv_auto. Use options={"delim":"|"} for pipe-delimited DAT files.

ParquetFile

ParquetFile(path) — columnar input. Supports glob patterns (e.g. "data/*.parquet").

JsonFile

JsonFile(path, options={}) — JSON or newline-delimited JSON (NDJSON); options forwarded to DuckDB read_json.

ExcelFile

ExcelFile(path, sheet_name=None, has_header=True) — .xlsx workbooks; default sheet is the first one.

FixedWidthFile

FixedWidthFile(path, columns=[], skip_rows=0, trim_values=False, encoding="utf-8") — legacy fixed-width records. columns is a list of (name, start, length) tuples.

XmlFile

XmlFile(path, row_path="./row", columns={}, namespaces=None, encoding=None) — XML with explicit XPath mapping.

AvroFile

AvroFile(path, batch_size=100000, flatten_unions=True) — Apache Avro.

OrcFile

OrcFile(path, columns=None) — Apache ORC; columns projects a subset.

MainframeFile

MainframeFile(path, fields=[], record_length=None, encoding="cp037", ...) — EBCDIC/COBOL mainframe records.

Database sources

SqlTable

SqlTable(conn, table, schema=None, chunk_size=50000, streaming=True) — any SQLAlchemy URI (postgresql://, mysql+pymysql://, sqlite:///, etc.).

SqlQuery

SqlQuery(conn, query, chunk_size=50000, streaming=True) — arbitrary SELECT against a SQLAlchemy URI.

OdbcQuery

OdbcQuery(conn_str, query, chunk_size=50000) — pyodbc-backed (SQL Server, Snowflake ODBC, …).

JdbcQuery

JdbcQuery(jdbc_url, driver_class, driver_jar="", query="", user=None, password=None, properties={}) — JDBC via JayDeBeApi.

PostgresSource

PostgresSource(conn, table=None, query=None, schema=None, ...) — zero-copy via DuckDB postgres_scanner.

Compare modes

The compare_mode argument trades depth for speed. From cheapest to richest:

ModeNeeds keysWhat it computesWhen to use
names_onlynoColumn-name diff + row counts. Skips type inference.Cheapest "same shape?" check.
schemanoColumn-name + dtype + logical-type diff.You only care about structure.
rowcountnoSchema + total row counts on each side.Quick freshness signal after a load.
hashoptionalSingle xxhash64 fingerprint per side.Yes/no answer, no per-row detail.
sampledyesKeyed compare on a random sample.Spot-check a huge table.
keyed (default)yesSchema + counts + per-row diff.The full reconciliation report.
profilenoSchema + per-column stats (min/max/distinct/null counts).Drift detection without row-level keys.

ReconConfig

All normalization and comparison rules live on ReconConfig. Pass an instance via compare(..., config=cfg).

cfg = fr.ReconConfig(
    case_sensitive=False,         # "ACTIVE" == "active"
    trim_strings=True,            # strip whitespace before comparing
    null_equals_empty=True,       # "" == NULL
    decimal_scale=2,              # round numerics before compare
    timestamp_tz="UTC",           # normalize tz-aware timestamps
    tolerances={"amount": 0.01},  # absolute tolerance per column
    columns=["id", "amount"],     # restrict the comparison
    exclude_columns=["updated_at"],
    column_mapping={"customer_id": "id"},  # right -> left
    ignore_column_order=True,
    sample_limit=100,
    sample_size_keyed=1000,
    chunk_size=None,
    infer_logical_types=True,
    infer_sample_size=10000,
    fast_path=True,
    row_hash=False,
)

Worked examples — all 31 source combinations

Each card shows a complete, runnable Python snippet for that source-type pair. Use the filter chips or the search box to narrow down.

API reference

fastrecon.compare(left, right, *, keys=None, compare_mode="keyed", columns=None, exclude_columns=None, tolerances=None, chunk_size=None, partition=None, config=None) → ReconResult

The single user-facing entry point. left and right are any combination of Source instances. Returns a ReconResult dataclass.

ParameterTypeDefaultNotes
leftSourceAny Source subclass instance.
rightSourceAny Source subclass instance.
keysIterable[str] | NoneNoneRequired for keyed and sampled modes.
compare_modestr"keyed"One of the seven modes above.
columnslist[str] | NoneNoneRestrict to this column set.
exclude_columnslist[str] | NoneNoneDrop these columns before comparing.
tolerancesdict[str, float] | NoneNonePer-column absolute tolerance for numeric diffs.
chunk_sizeint | NoneNoneStreaming chunk hint for SQL sources.
partitionPartitionSpec | NoneNoneSplit work by column for parallelism / huge tables.
configReconConfig | NoneNoneBundle of normalization + comparison rules.

ReconConfig fields

FieldTypeDefaultEffect
trim_stringsboolFalseStrip leading/trailing whitespace.
case_sensitiveboolTrueWhen False, "ACTIVE" == "active".
null_equals_emptyboolFalseTreat "" and NULL as equal.
decimal_scaleint | NoneNoneRound numeric columns to N decimal places.
tolerancesdict[str, float]{}Absolute tolerance per numeric column.
columnslist[str] | NoneNoneRestrict the comparison to these columns.
exclude_columnslist[str][]Drop these columns before comparing.
column_mappingdict[str, str]{}Rename right-side columns to match left.
ignore_column_orderboolTrueTreat column-order changes as schema diff when False.
row_hashboolFalsePer-row 64-bit hash instead of column-by-column compare.

ReconResult fields

FieldTypeNotes
statusStatusMATCH, MISMATCH, or ERROR.
row_count_left / row_count_rightintTotal rows scanned from each side.
schema_matchboolTrue if column names & types align.
data_matchboolTrue if no missing/extra/changed rows.
missing_in_left / missing_in_rightintKeys present on only one side.
changed_rowsintKeys on both sides whose payload differs.
exit_codeint0 MATCH, 1 MISMATCH, 2 ERROR — for shell/CI use.

Renderers: to_text(), to_json(), to_html(), to_junit(), to_csv(), to_dict(), summary().

Frequently asked questions about fastrecon

What is fastrecon?

fastrecon is a free, open-source Python data reconciliation library for comparing two tabular datasets. It supports SQL tables, SQL queries, CSV, DAT (pipe-delimited), Parquet, JSON/NDJSON, Excel (.xlsx), XML, Avro, ORC, fixed-width and mainframe files through a unified DuckDB + Polars + PyArrow engine. Any combination of those 11 source types works with a single fr.compare() call.

How do I install fastrecon?

Run pip install fastrecon. For database-specific connectors use extras: pip install "fastrecon[postgres]" for PostgreSQL, "fastrecon[snowflake]" for Snowflake, "fastrecon[databricks]" for Databricks, or "fastrecon[all-files]" for Excel, Avro, ORC and XML support.

How do I compare a CSV file against a SQL table in Python?

Use fr.compare(left=fr.CsvFile("source.csv"), right=fr.SqlTable(conn="postgresql://...", table="my_table"), keys=["id"]). fastrecon streams the SQL table through DuckDB and diffs it row by row against the CSV. The same API works for all 31 source-type combinations. See example 10 →

Can fastrecon compare Parquet files against SQL databases?

Yes. fr.compare(left=fr.ParquetFile("data.parquet"), right=fr.SqlTable(conn=..., table="my_table"), keys=["id"]) diffs the Parquet file against the SQL table. The same API covers Parquet vs SQL Query too. See example 16 →

Which source types can fastrecon compare?

fastrecon supports 11 source types: CSV, DAT (pipe-delimited via CsvFile with options={"delim":"|"}), Fixed Width, Parquet, JSON/NDJSON, Excel (.xlsx), XML, Avro, ORC, SQL Table (any SQLAlchemy URI), and SQL Query. The reconciliation matrix shows all 31 distinct pair combinations.

What compare modes does fastrecon support?

fastrecon has 7 compare modes: keyed (full row-level diff, default), hash (single xxhash64 fingerprint per side), schema (column names and types only), rowcount (schema + counts), names_only (cheapest shape check), sampled (keyed diff on a random sample), and profile (per-column min/max/distinct/null statistics). See the modes table →

Can I ignore certain columns or allow small numeric differences?

Yes. Pass exclude_columns=["updated_at", "load_ts"] to drop audit columns, and tolerances={"amount": 0.01} to allow a ±0.01 absolute difference on numeric columns. Both can be passed directly to compare() or bundled in a ReconConfig object for reuse. See example 32 →

What output formats does fastrecon support?

ReconResult exposes six renderers: to_text() for a human-readable terminal report, to_json() for structured output, to_html() for browser reports, to_junit() for JUnit XML (Jenkins, GitHub Actions, etc.), to_csv() for spreadsheet export, and to_dict() for programmatic access. See example 35 →

Does fastrecon work with Snowflake, Databricks, or BigQuery?

Yes. Any database with a SQLAlchemy driver works with SqlTable and SqlQuery. Install the matching extra: pip install "fastrecon[snowflake]" for Snowflake or "fastrecon[databricks]" for Databricks. PostgreSQL, MySQL, SQL Server and SQLite work out of the box. ODBC and JDBC sources are also supported via OdbcQuery and JdbcQuery.

Is fastrecon suitable for ETL testing and CI/CD pipelines?

Yes. fastrecon is designed for automated ETL testing and data pipeline validation. result.exit_code returns 0 for MATCH, 1 for MISMATCH and 2 for ERROR — making it trivial to fail a CI step. The to_junit() renderer produces JUnit XML that integrates with Jenkins, GitHub Actions, GitLab CI and other CI/CD systems. Failures are always returned as a ReconResult rather than raised as exceptions, so one bad comparison never aborts a batch run.

How do I compare two DAT pipe-delimited files in Python?

Use CsvFile with a delim option: fr.compare(left=fr.CsvFile("source.dat", options={"delim":"|"}), right=fr.CsvFile("target.dat", options={"delim":"|"}), keys=["id"]). The same approach works for any delimiter. See example 12 →

What Python version does fastrecon require?

fastrecon requires Python 3.9 or later. It is tested on Python 3.9, 3.10, 3.11, and 3.12 on Linux, macOS and Windows.

About fastrecon

fastrecon is a free, open-source Python data reconciliation library developed by Nikhil Shinde as a contribution to the open-source community. Contributions, bug reports and feature requests are welcome on .

Nikhil Shinde
Nikhil Shinde
Creator & Lead Developer
DuckDB Polars PyArrow SQLAlchemy Python 3.9+ MIT License

Links: PyPI package · GitHub source · Report an issue · Changelog