Origin-aware values for Python
Project description
Origpy
Origpy is a small Python library for values that remember where they came
from. Wrap a value with track(...), transform it with normal Python
functions, and inspect its origin later without dragging in a framework.
It is useful when a script, notebook, service, or data cleanup job needs a lightweight answer to: "why does this value have this value?"
Install
pip install origpy
Origpy supports Python 3.11 and newer. It has no runtime dependencies outside the standard library.
Quick Start
from origpy import track
port = track(5432, source_type="config", source="settings.json")
dsn = port.map(lambda value: f"postgres://localhost:{value}/app")
print(dsn.value)
# postgres://localhost:5432/app
print(dsn.lineage())
# - transform:__main__.<lambda> fp=sha256:...
# - config:settings.json fp=sha256:...
Track is the only wrapper you need to know. It exposes:
item.valueitem.originitem.map(fn)item.combine(other, fn)item.tag(*tags)item.redact()item.lineage()
Why This Exists
Python code often builds important values from files, environment variables, API responses, and small transformations. By the time a value reaches the part of the program that uses it, the trail is usually gone.
Origpy keeps that trail next to the value. It does not try to be a database lineage system, a config framework, or a policy engine. It is deliberately just a value wrapper with safe metadata and a few practical helpers.
Config Provenance
This is the shape Origpy is best at: a value assembled from a file plus a small runtime override.
from pathlib import Path
from origpy import from_env, read_json
base = read_json("service.json", base_dir=Path("/app/config"))
endpoint = base.map(lambda data: data["service"]["endpoint"], source="service.endpoint")
timeout = from_env(
"SERVICE_TIMEOUT",
default=base.value["service"]["timeout_seconds"],
sensitive=False,
redacted=False,
)
public_config = endpoint.combine(
timeout,
lambda url, seconds: {"endpoint": url, "timeout_seconds": int(seconds)},
source="runtime config",
)
print(public_config.value)
print(public_config.lineage())
Environment values are sensitive and redacted by default. Opt out only for values you are comfortable showing, such as a timeout or feature flag.
For a complete runnable version, see examples/config_provenance.py.
Traced Logic
@traced is useful when the interesting operation is already a normal
function. Tracked inputs are unwrapped before the function runs, and their
origins become parents of the result.
from origpy import track, traced
@traced(label="invoice total")
def calculate_invoice(subtotal_cents: int, tax_rate: float, discount_cents: int = 0) -> dict[str, int]:
taxable = max(subtotal_cents - discount_cents, 0)
tax_cents = round(taxable * tax_rate)
return {"total_cents": taxable + tax_cents, "tax_cents": tax_cents}
subtotal = track(12_000, source_type="cart", source="cart:8421")
discount = track(1_500, source_type="promotion", source="spring-credit")
invoice = calculate_invoice(subtotal, 0.0825, discount_cents=discount)
print(invoice.value)
print(invoice.lineage())
See examples/traced_logic.py for the full example.
API Response Cleanup
from origpy import track
payload = track(
{"id": "ord_1001", "amount": "42.50", "currency": "usd", "status": "PAID"},
source_type="api",
source="GET /v1/orders/ord_1001",
tags=("orders",),
)
normalized = payload.map(
lambda order: {
"order_id": order["id"],
"amount_cents": int(float(order["amount"]) * 100),
"currency": order["currency"].upper(),
},
source="normalize_order",
)
total = normalized.map(lambda order: order["amount_cents"], source="amount_cents")
currency = normalized.map(lambda order: order["currency"], source="currency")
summary = total.combine(currency, lambda cents, code: f"{code} {cents / 100:.2f}")
print(summary.value)
# USD 42.50
The lineage is handy when a cleaned field looks wrong and you need to see
which response and transform produced it. A fuller version lives in
examples/api_response_cleanup.py.
Redaction
Redaction is explicit. It hides values and origin details from repr, lineage
rendering, JSON serialization, and the CLI.
from origpy import from_env
credential = from_env("PAYMENT_API_TOKEN", default="<not-set>")
print(credential)
# Track(value=<redacted>, origin=Origin(source_type='env', source='<redacted>', ...))
The raw value still exists at .value; Origpy is not a secret vault. The point
is to avoid accidental leakage while debugging or serializing metadata.
Safe Serialization
Origpy uses a small explicit JSON format. There is no pickle, no eval, and no
code execution during deserialization.
from origpy import from_json, to_json, track
report = track({"job": "daily-settlement", "records": 128}, source_type="job", source="settlement:daily")
payload = to_json(report)
loaded = from_json(payload)
assert loaded.value == report.value
assert loaded.origin == report.origin
Redacted values stay redacted:
from origpy import from_env, to_json
safe_payload = to_json(from_env("PAYMENT_API_TOKEN", default="<not-set>"))
assert "PAYMENT_API_TOKEN" not in safe_payload
Serialization is intentionally strict: duplicate JSON keys, non-string object
keys, NaN, infinity, reserved Origpy keys in user data, oversized payloads,
and deeply nested structures are rejected.
See examples/serialization_roundtrip.py.
CLI Inspection
The CLI is intentionally tiny. It inspects an Origpy JSON blob without printing raw tracked values.
origpy inspect tracked.json
Example output:
Track(value=<dict len=2>, origin=Origin(source_type='job', source='settlement:daily'))
- job:settlement:daily fp=sha256:...
For a redacted blob, the source and value are hidden. See
examples/cli_inspection.py.
When To Use It
Use Origpy for:
- config provenance in scripts and small services
- debugging transformed API or JSON payloads
- notebook and batch-job reproducibility notes
- lightweight audit trails around derived values
Reach for something else when you need:
- a distributed lineage database
- access control or secret storage
- a workflow engine
- automatic tracking of every Python operation
Security Notes
Origpy is defensive by default:
- no pickle,
eval, orexec - strict JSON deserialization
- duplicate JSON keys are rejected
- file and CLI reads are bounded
base_dirprevents path escapes in file helpers- redacted parent provenance stays hidden
- lineage and unwrapping are cycle-safe
reprand CLI output avoid raw values
The one thing Origpy cannot protect you from is deliberately printing
.value. If a tracked value is sensitive, treat .value as sensitive too.
Runnable Examples
The source repository's examples/ directory contains short scripts that are
also covered by the test suite:
config_provenance.pyapi_response_cleanup.pytraced_logic.pyserialization_roundtrip.pycli_inspection.py
Run one with:
PYTHONPATH=src python examples/config_provenance.py
On Windows PowerShell:
$env:PYTHONPATH = "src"
python examples/config_provenance.py
Development
pip install -e .
python -m unittest
Build a release locally with:
python -m build
License
origpy is published by thundersl under the MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file origpy-0.1.3.tar.gz.
File metadata
- Download URL: origpy-0.1.3.tar.gz
- Upload date:
- Size: 20.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
873e8a40c643875139146d06a4927c0102c1814701b9518a43954c669ba7da0f
|
|
| MD5 |
061a277c142764c8febbcb8c24fb3de3
|
|
| BLAKE2b-256 |
76e6756f4c6e3eb906d076e317e1c8762ee7c3cab1dea51ab30eb99e0b98daad
|
File details
Details for the file origpy-0.1.3-py3-none-any.whl.
File metadata
- Download URL: origpy-0.1.3-py3-none-any.whl
- Upload date:
- Size: 19.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3511ef2b36d29bf8cc0c159ee92c2deed83dcf8e7d735668b9f86151a95d936
|
|
| MD5 |
888b654656bcab6f57986d52ca56273f
|
|
| BLAKE2b-256 |
907199baa24954169afb1de7d7b42f2b4e5f40f7aa71c681792ad3b23c9d1fdd
|