Skip to main content

framebond

Validate primary keys, foreign keys and cardinality across Pandas DataFrames. Keep original key values, row positions and index labels in inspectable issues, then export strict JSON without requiring a database.

import pandas as pd
from framebond import DataModel

customers = pd.DataFrame({"id": [1, 2]})
orders = pd.DataFrame({"id": [10, 11], "customer_id": [1, 3]})
model = DataModel().add_table("customers", customers, primary_key="id")
model.add_table("orders", orders, primary_key="id")
model.add_relation("orders.customer_id", "customers.id")
print(model.validate().explain())

This reports one orphan order referencing customer 3. Model setup and validation fit in four API calls after creating the frames.

Install

Python 3.11+ and Pandas 2.2+ are required. Until the initial PyPI release has been verified, install from a checkout:

python -m pip install .
# After the first verified PyPI release:
python -m pip install framebond

No runtime service, telemetry, database or sibling-project dependency is used.

API

API Behavior
DataModel() Create an empty model
model.add_table(name, frame, primary_key=None) Register a snapshot and optional single/composite PK; returns model
model.add_relation(child, parent, name=None, nulls="ignore", relationship="many_to_one") Declare an FK and optional child uniqueness; returns model
model.validate() Return a ValidationReport; no joined payload is materialized
report.valid True if all declared constraints hold
report.tables / report.relations Ordered tuples of typed TableResult / RelationResult
report.issues(table=None, relation=None, code=None) Fresh native DataFrame with optional exact filters
report.explain() Human-readable summary
report.to_dict() / report.to_json(indent=2) Structured JSON-native results including issues

Table names are unique nonempty strings without dots. Frames require unique flat column labels. Key columns are distinct existing strings. A primary key may be "id" or ["tenant", "id"]. Invalid configuration raises ModelError; non-DataFrame table input raises TypeError. Invalid data constraints appear in the report, allowing all failures to be reviewed together.

Tables are copied at registration. Later edits to caller frames do not change the model; create a new model to validate updated data. No transformation or in-place cleanup occurs. Snapshot copies do not recursively clone mutable objects inside unrelated payload cells; keys themselves must be hashable scalars.

Composite references and cardinality

Single-column references use "orders.customer_id". Composite references are explicit tuples of table name and ordered columns:

model.add_relation(
    ("orders", ["tenant", "customer_id"]), ("customers", ["tenant", "id"]), nulls="forbid"
)

The example assumes both named tables and columns were registered first. Child and target column counts must agree. Repeated relation names are rejected; use name=... when declaring distinct policies on the same columns.

Every FK requires unique nonnull target keys, even when those columns were not declared as a PK. relationship="many_to_one", the default, permits repeated nonnull child keys. relationship="one_to_one" additionally requires unique nonnull child keys. A stricter observed one-to-one relation satisfies the many-to-one rule. Generic one-to-many/many-to-many link validation is outside this FK API; model a junction table with two FKs instead.

observed_relationship describes nonnull uniqueness across the complete child and parent inputs, including unmatched keys. It is separate from FK membership: a relationship can have expected cardinality and still contain orphans. Parent rows need not all be referenced, and minimum participation is not enforced.

Nulls, equality and exact values

Default nulls="ignore" follows SQL MATCH SIMPLE-style behavior: any null component makes a composite child key exempt from membership checks. With nulls="forbid", every such row is a foreign_key_null issue. Null child rows are counted separately from orphans; they are never matched to null parent rows. Parent any-null keys are excluded from matching and nonnull uniqueness checks. If a parent PK is declared, its nulls still invalidate that table's PK.

Declared PKs reject any-null keys and repeated nonnull keys. duplicate_rows counts all nonnull rows participating in duplicate groups, not just excess copies; duplicate_keys counts distinct duplicated nonnull keys. Null rows are counted separately. An undeclared PK has primary_key_valid=None and zero PK issue counts, rather than an inferred constraint.

Matching uses original scalar tuples and Python hash/equality. Numeric 1 and 1.0 match; string "1" is distinct. Types are never normalized or silently cast. Containers used as key values are rejected; composite keys belong in multiple explicit columns. Object-backed adjacent large integers retain identity even beside nulls. Precision already lost while constructing a DataFrame cannot be recovered. Normalize application-specific identifiers explicitly before registration when needed.

Issues and strict JSON

Native issue columns are scope, table, relation, code, row_position, index, and key. key contains original scalar tuples. Positions are original zero-based table rows, independent of duplicate/nonstandard index labels. Issues follow constraint order and then source position within each constraint. They can be used to retrieve source records with .iloc.

Issue code Meaning
primary_key_null Declared PK has a null component
primary_key_duplicate Nonnull declared PK repeats
foreign_key_orphan Nonnull child key is absent from target
foreign_key_target_duplicate Nonnull target key repeats
foreign_key_null Child has any null component under forbid
relation_child_duplicate Nonnull child key repeats under one_to_one

RelationResult.orphan_rows includes every orphan record; missing_keys counts distinct absent nonnull keys. Counts differ when an absent reference repeats. Use report.issues(code="foreign_key_orphan") to inspect those native values.

to_dict() returns JSON-compatible structures. Integers retain their exact numeric values, missing values become null, dates/times use ISO strings and tuples become JSON arrays. Decimal/UUID/duration/byte values use explicit type tags and string/hex payloads. Nonfinite floats use tagged string values, never invalid JSON numeric literals. NumPy temporal scalars preserve temporal type and nanosecond duration values. Other supported scalar objects use a type tag and textual representation. JSON is an inspection/export format, not a full round-trip model serializer; native issue frames preserve original objects.

Alternatives, scope and architecture

dm inspired the relational-model approach. Frictionless already defines PK/FK schemas; Pandera and Great Expectations already provide strong validation tools. Pandas and Polars supply uniqueness and membership primitives. framebond focuses on a small direct-DataFrame API, exact source keys and positional issue inspection. See RESEARCH.md for comparisons, evidence and upstream licenses.

Version 0.1.0 supports in-memory Pandas snapshots. It does not implement an ORM, migrations, SQL execution, cascading changes, type coercion, generic graph-link constraints or automatic key discovery. Snapshot/index storage is proportional to input rows and key width; issue storage can also be large when many rows fail. Full issue JSON can be expensive. The small KeyBackend protocol isolates original key extraction/indexing for future adapters; the implemented backend is Pandas only. Its equality/null policies must be revisited explicitly for other backends.

Validation and benchmark

python -m pip install -e ".[dev]"
python -m pytest --cov=framebond --cov-report=term-missing
python -m ruff check .
python -m mypy src
python examples/quickstart.py
python examples/composite_and_json.py
python benchmarks/benchmark.py --rows 100000 --repeat 5

Tests use independent Counter/set references, generated composite/null keys, empty tables, duplicate indices, exact large integers, original scalar/date JSON regressions and validation with DataFrame.merge forbidden. The benchmark uses 50,000 parent and 100,000 child rows, checks equivalent manual Pandas counts, and records five timed samples after warmup plus separate traced memory. Raw results and methodology document the extra cost of snapshots, reusable indexes and native issues.

See CONTRIBUTING.md. Original code is MIT licensed with credit to upstream inspiration and no affiliation claim. Proposed 0.2.0 priorities: bounded issue samples, explicit expected dtype policies, incremental validation and a separately verified alternate backend.

Metadata

Release files for framebond 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for framebond 0.1.0
File Size Uploaded Provenance
framebond-0.1.0.tar.gz 24.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for framebond 0.1.0
File Interpreter ABI Platform
framebond-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.5 kB

Release files / framebond-0.1.0.tar.gz

Download URL framebond-0.1.0.tar.gz
Size 24.8 kB
Tags Source
SHA-256 checksum
How to use checksums
2faf5dc71beb5e4c92e7a1b9c10f51e7e77831831d286ccc43075b0c9d77512c
BLAKE2b-256 checksum
How to use checksums
a6e97c99ee4557b4ff4503d8a62f759009cc601570f7782891ccb665f91735fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / framebond-0.1.0-py3-none-any.whl

Download URL framebond-0.1.0-py3-none-any.whl
Size 11.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d134822db2abba84e3c725661d8e6d346cefc44fd851fd33f2e73c2f4f6764d9
BLAKE2b-256 checksum
How to use checksums
0e712353acfa120fd97d3636c52a8595a99c3f06032af57bcf9cf871349cc309
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for framebond 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for framebond-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page