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.
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:
| Mode | Needs keys | What it computes | When to use |
|---|---|---|---|
names_only | no | Column-name diff + row counts. Skips type inference. | Cheapest "same shape?" check. |
schema | no | Column-name + dtype + logical-type diff. | You only care about structure. |
rowcount | no | Schema + total row counts on each side. | Quick freshness signal after a load. |
hash | optional | Single xxhash64 fingerprint per side. | Yes/no answer, no per-row detail. |
sampled | yes | Keyed compare on a random sample. | Spot-check a huge table. |
keyed (default) | yes | Schema + counts + per-row diff. | The full reconciliation report. |
profile | no | Schema + 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.
| Parameter | Type | Default | Notes |
|---|---|---|---|
left | Source | — | Any Source subclass instance. |
right | Source | — | Any Source subclass instance. |
keys | Iterable[str] | None | None | Required for keyed and sampled modes. |
compare_mode | str | "keyed" | One of the seven modes above. |
columns | list[str] | None | None | Restrict to this column set. |
exclude_columns | list[str] | None | None | Drop these columns before comparing. |
tolerances | dict[str, float] | None | None | Per-column absolute tolerance for numeric diffs. |
chunk_size | int | None | None | Streaming chunk hint for SQL sources. |
partition | PartitionSpec | None | None | Split work by column for parallelism / huge tables. |
config | ReconConfig | None | None | Bundle of normalization + comparison rules. |
ReconConfig fields
| Field | Type | Default | Effect |
|---|---|---|---|
trim_strings | bool | False | Strip leading/trailing whitespace. |
case_sensitive | bool | True | When False, "ACTIVE" == "active". |
null_equals_empty | bool | False | Treat "" and NULL as equal. |
decimal_scale | int | None | None | Round numeric columns to N decimal places. |
tolerances | dict[str, float] | {} | Absolute tolerance per numeric column. |
columns | list[str] | None | None | Restrict the comparison to these columns. |
exclude_columns | list[str] | [] | Drop these columns before comparing. |
column_mapping | dict[str, str] | {} | Rename right-side columns to match left. |
ignore_column_order | bool | True | Treat column-order changes as schema diff when False. |
row_hash | bool | False | Per-row 64-bit hash instead of column-by-column compare. |
ReconResult fields
| Field | Type | Notes |
|---|---|---|
status | Status | MATCH, MISMATCH, or ERROR. |
row_count_left / row_count_right | int | Total rows scanned from each side. |
schema_match | bool | True if column names & types align. |
data_match | bool | True if no missing/extra/changed rows. |
missing_in_left / missing_in_right | int | Keys present on only one side. |
changed_rows | int | Keys on both sides whose payload differs. |
exit_code | int | 0 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 GitHub.
- LinkedIn: nikhilesh-shinde-2666a851
- Contribution: Dedicated Contribution to the Open Source Community
Links: PyPI package · GitHub source · Report an issue · Changelog