Skip to main content

card_bin_data

card_bin_data is an async Python library for local BIN/IIN lookup from public CSV datasets. It normalizes multiple sources into a SQLite or PostgreSQL database and exposes typed lookup results for backend services.

Tests Coverage Downloads PyPI Python versions License Docs

Documentation: https://zylvext.github.io/card-bin-data/

The MVP is a library only. CLI commands such as card_bin_data update and card_bin_data lookup are post-MVP.

Install

Install the published library:

uv add card-bin-data

Dataset files are deployment inputs and are not bundled into wheel or source distributions. Clone or provision the upstream datasets separately and pass their explicit paths to the adapters.

The package requires Python 3.12 or newer and uses async SQLAlchemy drivers: sqlite+aiosqlite for SQLite and postgresql+asyncpg for PostgreSQL.

SQLite Quickstart

from pathlib import Path

from card_bin_data import BinData, BinDataStore, LookupStatus
from card_bin_data.sources import (
    BinlistDataAdapter,
    MarlonlpBinlistDataAdapter,
    VenelinkochevBinListDataAdapter,
)


async def run() -> None:
    store = BinDataStore.from_url("sqlite+aiosqlite:///var/lib/card_bin_data/card_bin_data.db")
    await store.init()

    await store.import_sources(
        [
            BinlistDataAdapter(Path("datasets/binlist_data/ranges.csv")),
            VenelinkochevBinListDataAdapter(Path("datasets/venelinkochev_binlist_data/bin-list-data.csv")),
            MarlonlpBinlistDataAdapter(Path("datasets/marlonlp_binlist_data/binlist-data.csv")),
        ],
    )

    client = BinData(store=store)
    result = await client.lookup("12345678")

    if result.status is LookupStatus.FOUND and result.data is not None:
        print(result.data.scheme, result.data.issuer_name)

    await store.close()

The database URL is always explicit. The library does not read a default path or environment variable on its own.

The metadata and result types at the package root remain lightweight. SQLAlchemy and the database drivers are loaded only when BinData or BinDataStore is first requested.

PostgreSQL Setup

Use PostgreSQL when several services need the same normalized dataset:

from card_bin_data import BinDataStore


store = BinDataStore.from_url("postgresql+asyncpg://card_bin_data_user@localhost/card_bin_data")
await store.init()

Treat credential-bearing URLs as secrets in application code. store.database_url masks passwords for display; keep using your original secret configuration value for connection setup.

Migrations

store.init() calls create_all and is meant for local, dev, and test databases. For production — especially PostgreSQL — let your own application own the schema through Alembic instead, so index and constraint changes can be scheduled safely.

card_bin_data ships no migrations of its own. Its tables live in a private, bind-scoped MetaData that stays isolated from your application's default metadata, so importing the package never injects the BIN/IIN tables into your schema by accident. You opt in explicitly by adding card_bin_data.db.metadata to your Alembic target_metadata:

# alembic/env.py
from card_bin_data.db import metadata as card_bin_data_metadata

# importing the metadata already registers the three card_bin_data tables,
# so no separate model import is needed.
target_metadata = [your_app_metadata, card_bin_data_metadata]

Then autogenerate and apply as usual:

alembic revision --autogenerate -m "add card_bin_data tables"
alembic upgrade head

The card_bin_data metadata uses the same Advanced Alchemy naming convention as your other models, so autogenerated constraint and index names stay consistent. If you already manage other tables with Alembic, keep your existing single migration history — the list form of target_metadata lets one environment emit DDL for both your tables and the card_bin_data tables together.

Lookup Results

BinData.lookup() returns a LookupResult with one of three statuses:

  • LookupStatus.FOUND: data contains a BinInfo value.
  • LookupStatus.NOT_FOUND: the input was valid but no record matched.
  • LookupStatus.INVALID: the input failed normalization or optional Luhn validation.

Successful results include source attribution:

result = await client.lookup("12345678")

if result.found:
    for source in result.sources:
        print(source.source_id, source.license)

Import And Update

BinDataStore owns schema initialization and write operations:

await store.init()
summary = await store.import_sources([primary_adapter, enrichment_adapter, fallback_adapter])

import_sources() runs a replace-all import inside one store-managed transaction and delegates to BinDataStore.import_sources_with_session(session, adapters). By default, provenance rows include each adapter row's raw payload. Pass store_raw_payload=False to store {} in bin_record_sources.raw_payload instead while keeping source_row_key and data-source attribution intact:

summary = await store.import_sources(
    [primary_adapter, enrichment_adapter, fallback_adapter],
    store_raw_payload=False,
)

Use the bring-your-own-session APIs when your application already owns the SQLAlchemy unit of work:

async with store.session() as session:
    result = await BinData.lookup_with_session(session, "12345678")

async with store.session_factory.begin() as session:
    summary = await BinDataStore.import_sources_with_session(session, adapters, store_raw_payload=False)

lookup() is read-only and can be shared across concurrent async tasks. The store does not keep an in-process write lock; replace-all imports rely on the caller's transaction and database locking behavior. SQLite connections use a 30 second busy timeout so overlapping writers wait before failing. If your deployment needs single-writer scheduling across processes or services, enforce that in the host application.

Source Priority And Attribution

The MVP source priority is:

  1. binlist/data as the primary source.
  2. venelinkochev/bin-list-data as enrichment.
  3. marlonlp/binlist-data as fallback.

The normalized fields use scheme for the card network. In binlist/data, brand maps to product_brand; in the two larger datasets, Brand or brand maps to scheme.

Public datasets are unofficial, can conflict, and may be stale. card_bin_data keeps row-level source attribution so callers can inspect which source rows contributed to a result. The venelinkochev and marlonlp sources are documented as CC BY 4.0 by their repositories. The final binlist/data license statement must be verified before public release claims are finalized.

PAN Safety

Lookup accepts 6-digit BINs, 8-digit IINs, and full card-number-like input. Spaces and hyphens are stripped before validation, but only safe prefixes are kept in LookupResult.query. Full input values are not stored in card_bin_data tables and are not returned in result objects.

Luhn validation is disabled by default:

result = await client.lookup(card_number_from_user, validate_luhn=True)

If validate_luhn=True is used with a short BIN/IIN, the result includes a validation warning instead of raising. card_bin_data is not a PCI validation service and does not guarantee card validity.

Documentation

See the docs in docs/:

  • docs/api.md
  • docs/backends.md
  • docs/sources.md
  • docs/security.md
  • docs/performance.md

Runnable examples live in examples/.

Metadata

Release files for card-bin-data 0.3.1

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

Source distribution (sdist)

Source distribution for card-bin-data 0.3.1
File Size Uploaded
card_bin_data-0.3.1.tar.gz 66.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for card-bin-data 0.3.1
File Interpreter ABI Platform
card_bin_data-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 103.4 kB

Release files / card_bin_data-0.3.1.tar.gz

Download URL card_bin_data-0.3.1.tar.gz
Size 66.9 kB
Tags Source
SHA-256 checksum
How to use checksums
21cca7e41e43dda931696bd8e6a929a73343e834024b29d05624d7b45012165e
BLAKE2b-256 checksum
How to use checksums
ac79a349f8e72d35fd71041b56deb93630ce50a797ced5a5b91aaf1956312ac5
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 Aug 8, 2026.

Transparency log

Release files / card_bin_data-0.3.1-py3-none-any.whl

Download URL card_bin_data-0.3.1-py3-none-any.whl
Size 36.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e9f0e6e25031bbf47e28455a5ce40adb82ff326d25b51c1f0084c0d12a426e2
BLAKE2b-256 checksum
How to use checksums
08ccd066b5e063696878220817ae5eed27e531ac32c39014f0a3b06e3df3d9c6
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 Aug 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

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