Skip to main content

dealcode (Python)

Collision-free, random-looking codes from a counter. Python implementation of the dealcode spec.

Install

pip install dealcode

Requires Python ≥ 3.9. The only dependency is cryptography (PyCA), used for AES.

Quickstart

from dealcode import Dealcode

codec = Dealcode(key="0a1b...64-hex-chars-from-your-secret-manager")

codec.encode(0)        # '767a5b'   (6 hex chars)
codec.encode(1)        # '421163'   never collides with any other counter
codec.decode("421163") # 1

(The outputs shown are the real ones for this exact key and default config — with your own key every counter maps to a different, but equally stable, code.)

The key can be raw bytes (16/24/32 bytes are used as-is as an AES key) or any string/bytes, which are deterministically expanded to an AES-256 key. Generate one with openssl rand -hex 32 and keep it in your secret manager — the mapping is stable only while the key (and every other option) stays fixed.

Options

Dealcode(
    key,                    # bytes | str
    alphabet="hex",         # "dec" | "hex" | "base32" | "crockford" | "base36"
                            # | "base58" | "base62" | "base64url" | custom string
    min_length=6,           # codes start at this length...
    max_length=None,        # ...and grow one char at a time up to this (default: max for 2^63)
    domain="",              # namespace: same key, unrelated codes per domain
)
coupon = Dealcode(key, "crockford", domain="coupons")   # human-friendly, e.g. '7Q4WKZ'
order  = Dealcode(key, "dec", min_length=8, domain="orders")  # digits only
fixed  = Dealcode(key, "hex", min_length=16, max_length=16)   # constant-length

decode raises InvalidCodeError for malformed input — wrong length, characters outside the alphabet, or a value outside the issuable range. A well-formed code always decodes to some counter, whether or not that counter was ever issued (inherent to a permutation — see SPEC §7). Treat decode as parsing, not proof of existence: look the counter up before acting on it, and note that a one-character typo in a valid code can resolve to a different valid counter — add rate limiting (and, for human-typed flows, an existence check or your own check digit). encode raises RangeError outside [0, codec.capacity), and construction mistakes (bad key, alphabet, lengths, domain) raise ConfigError. All errors subclass DealcodeError (a ValueError).

Preset alphabets normalize obvious typing variants before decoding — hex is case-insensitive, and crockford also folds O → 0 and I/L1 — but normalization never strips anything: separators and whitespace are not removed. If you display codes grouped (XXXX-XXXX), strip the hyphens or spaces in your app before calling decode.

Using it with your database

Dealcode does not talk to your database — it only turns a counter into a code. Any source of never-repeating integers works. With PostgreSQL:

CREATE SEQUENCE order_code_seq AS bigint MINVALUE 0 START WITH 0;

CREATE TABLE orders (
  id   bigint PRIMARY KEY,          -- the counter
  code text NOT NULL UNIQUE,        -- safety net; alerts on config mistakes
  ...
);
import os

from sqlalchemy import text

from dealcode import Dealcode, InvalidCodeError

codec = Dealcode(key=os.environ["DEALCODE_KEY"], domain="orders")

def create_order(conn) -> str:
    n = conn.execute(text("SELECT nextval('order_code_seq')")).scalar_one()
    code = codec.encode(n)
    conn.execute(
        text("INSERT INTO orders (id, code) VALUES (:id, :code)"),
        {"id": n, "code": code},
    )
    return code

def find_order(conn, code: str):
    try:
        n = codec.decode(code)          # malformed codes never reach the DB
    except InvalidCodeError:
        return None
    return conn.execute(text("SELECT * FROM orders WHERE id = :id"), {"id": n}).first()

Sequences never hand out the same number twice (even across concurrent transactions and rollbacks), so codes never collide. Gaps in the sequence are invisible — codes look random anyway.

If the UNIQUE constraint on code ever fires, do not retry: it means the key/config changed for an existing namespace. Investigate.

Fixed-length cycling mode

For code shapes that must never grow — airline-PNR-style fixed-length codes — CyclingDealcode (SPEC §11) fills the entire fixed-length space, and when it is exhausted refills the same space through a different permutation instead of adding a character:

from dealcode import CyclingDealcode

pnr = CyclingDealcode(key, "crockford", length=6, domain="bookings")

code = pnr.encode(n)              # always exactly 6 chars; cycle = n // pnr.capacity
cycle = pnr.cycle_of(n)           # which cycle a counter belongs to — store it with the code
n = pnr.decode(code, cycle=3)     # the cycle is required context

Configuration mirrors Dealcode (key, alphabet, domain) with a single fixed length: 2 <= length <= 128 and 100 <= radix**length <= 2**63.

Codes repeat across cycles (the space is being reused — that's the point), so keep at most one cycle's codes live per uniqueness scope: retire or expire cycle e before issuing from e+1, index with UNIQUE(cycle, code) rather than UNIQUE(code), and store each live code's cycle — decode needs it. Decoding with a wrong (but in-range) cycle is not an error — it silently returns a different counter; the existence check on the decoded counter is what catches it.

Thread safety & performance

A Dealcode instance is immutable and thread-safe; create one per namespace at startup and reuse it. Encoding is ten AES-CBC-MAC rounds — tens of microseconds, no allocation-heavy paths, O(1) in the counter value.

Running the tests

From the repository root:

pip install -e ./python pytest    # or: export PYTHONPATH=python/src
python -m pytest python/tests

The suite covers the official NIST FF1 sample vectors, every shared cross-language vector in testvectors/, and behavioural/edge cases.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dealcode-1.0.1.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

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

dealcode-1.0.1-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

Details for the file dealcode-1.0.1.tar.gz.

File metadata

  • Download URL: dealcode-1.0.1.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dealcode-1.0.1.tar.gz
Algorithm Hash digest
SHA256 c4e7e4e6a34b8772dd326828184f3b59cc5d6f87c8da41fa33d363bc98a2e9df
MD5 c670316f267e675fb2c47a0df8e2ea37
BLAKE2b-256 58753ab446f955298bc61ff7b1dc59cdfc4a28bd61e92374509b5ed072be4e69

See more details on using hashes here.

Provenance

The following attestation bundles were made for dealcode-1.0.1.tar.gz:

Publisher: publish-pypi.yml on algorix-hq/dealcode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dealcode-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: dealcode-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 14.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dealcode-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a06ce70f3cb38d2d18cffe229059b6ac77bf60faa9bb5aa3c33817cf71e08614
MD5 5cbaef9a01e5cfa458b78bbd2b2edc25
BLAKE2b-256 e4f15f7995e9e418a15864d538a31e8f1d29153382f905bafa8e18727c176f9c

See more details on using hashes here.

Provenance

The following attestation bundles were made for dealcode-1.0.1-py3-none-any.whl:

Publisher: publish-pypi.yml on algorix-hq/dealcode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 files

1.0.0

2 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