api-response-cleaner
Turns inconsistent API responses into validated Python data using a developer-defined schema. Zero dependencies.
Why Use This Library? (Use Cases)
- Standardize third-party APIs: Normalize inconsistent field types and response formats into a predictable output schema.
- Handle legacy and inconsistent data: Support field aliases, optional fields, defaults, and explicit missing/null behavior.
- Lightweight alternative to Pydantic: A smaller, standard-library-runtime-dependency-free option for dictionary-based cleaning and validation when you don't need arbitrary object models.
- Validate nested JSON safely: Perform nested schema validation and error reporting without manual chains of
.get()calls.
Installation
pip install api-response-cleaner
Quick Start
from api_response_cleaner import clean
result = clean(
{"full_name": " Ravi ", "age": "25"},
schema={"name": {"type": str, "required": True},
"age": {"type": int, "required": True}},
aliases={"name": ["full_name"]},
)
print(result.data) # {"name": "Ravi", "age": 25}
print(result.errors) # []
Schema Reference
Define fields as dictionaries. Supported keys:
type:str,int,float,bool,list,dict,objectrequired: boolean, defaultFalsedefault: fallback value if key absentnullable: boolean, defaultFalsealiases: list of alternative keysstrip: boolean, defaultTrue(for strings)choices: iterable of allowed valuesmin,max: bounds forintandfloatmin_length,max_length: length bounds forstrandlistschema: dictionary of sub-fields (impliestypedict)items: type or spec for list elements
Shorthand: {"age": int} is equivalent to {"age": {"type": int}}.
Missing and Null Decision Table
| Input state | optional | required | default=7 | nullable=True |
|---|---|---|---|---|
| key absent | key omitted | missing_required | 7 | key omitted |
| key present, value null | key omitted | null_not_allowed | 7 | None (null preserved) |
| key present, value "25" | 25 | 25 | 25 | 25 |
| key present, value "" | conversion_failed in all four columns |
Note: nullable=True + required=True with key absent gives missing_required.
Alias and Nested Rules
- Aliases are tried in order if canonical name is missing.
- If canonical name and alias are both present and non-null, canonical wins.
- Alias keys are consumed; they are not reported as extra or unexpected.
- Errors are reported on canonical paths.
- Nested schema values must be objects (dict).
- Inner errors are collected and reported with full dotted paths (e.g.
address.city). - If an inner error occurs, the whole nested key is omitted from result data.
Structured Defaults Warning
A structured default (list or dict) is trusted. It is returned as-is (deep copied) and its contents are NOT normalized, constraint-checked, or mapped for aliases. Structured defaults must already be in their final valid form.
Constraints
| Constraint | Supported Types |
|---|---|
min, max |
int, float |
min_length, max_length |
str, list |
choices |
str, int, float, bool, object |
Conversion Rules
str: Accepts strings, ints, finite floats. Strips whitespace by default.int: Accepts ints, whole floats (e.g.,25.0), and numeric strings.float: Accepts finite floats, ints, and numeric strings.bool: Accepts booleans, ints0and1, stringstrue,false,yes,no,on,off,1,0.list: Accepts lists and tuples.dict: Accepts dicts.object: Accepts anything.
Error Codes
missing_requirednull_not_allowedconversion_failednot_in_choicestoo_smalltoo_largetoo_shorttoo_longunexpected_fieldinvalid_input
Examples
Example A — Standardizing APIs
Normalize inconsistent payloads into a predictable shape.
from api_response_cleaner import clean
schema = {
"name": {"type": str, "aliases": ["full_name"], "required": True},
"age": {"type": int, "nullable": True}
}
api_a = {"name": "Ravi", "age": 25}
api_b = {"name": "Ravi", "age": "25"}
api_c = {"full_name": "Ravi", "age": None}
for api, payload in zip(["A", "B", "C"], [api_a, api_b, api_c]):
res = clean(payload, schema)
print(f"API {api}: data={res.data}, errors={res.errors}")
Note that API C's age=None is handled exactly according to the schema (preserved because nullable=True). It is never silently converted to a zero value or a default unless explicitly configured.
Example B — Legacy field names and defaults
Define canonical fields while supporting old aliases and providing sensible defaults.
from api_response_cleaner import clean
schema = {
"theme": {"type": str, "aliases": ["ui_theme", "color_mode"], "default": "light"}
}
# 1. Canonical key works normally
print(clean({"theme": "dark"}, schema).data)
# -> {"theme": "dark"}
# 2. Alias is seamlessly mapped to the canonical key
print(clean({"ui_theme": "dark"}, schema).data)
# -> {"theme": "dark"}
# 3. Missing key falls back to the default
print(clean({}, schema).data)
# -> {"theme": "light"}
# 4. Explicit null ignores the default unless the field is nullable
res = clean({"theme": None}, schema)
print(res.errors[0].code)
# -> ErrorCode.null_not_allowed
Example C — Nested data
Safely parse nested user/address data without manual .get() chains. Invalid nested objects are omitted entirely instead of returned partially cleaned.
from api_response_cleaner import Cleaner
cleaner = Cleaner({
"address": {
"schema": {
"city": {"type": str, "required": True},
"zip": int
}
}
})
# Valid payload
res_valid = cleaner.clean({"address": {"city": "NY", "zip": "10001"}})
print(res_valid.data)
# -> {"address": {"city": "NY", "zip": 10001}}
# Invalid payload
res_invalid = cleaner.clean({"address": {"zip": "abc"}})
print("Errors:", [(e.field, e.code.value) for e in res_invalid.errors])
# -> Errors: [('address.city', 'missing_required'), ('address.zip', 'conversion_failed')]
print("Data:", res_invalid.data)
# -> Data: {}
Example D — List validation
Validate lists of typed items. Bad elements are reported by their original index, and the whole list is omitted from data if any element is invalid.
from api_response_cleaner import clean
schema = {
"scores": {"type": list, "items": int}
}
res = clean({"scores": ["1", "x", None, "3"]}, schema)
print("Errors:", [(e.field, e.code.value) for e in res.errors])
# -> Errors: [('scores[1]', 'conversion_failed'), ('scores[2]', 'null_not_allowed')]
print("Data:", res.data)
# -> Data: {}
Development Commands
pip install -e .[dev]
pytest
python -m build
twine check dist/*
Releasing
- pytest passes on Python 3.11 and the newest installed Python
- python -m build produces one sdist and one wheel
- twine check dist/* passes
- the wheel installs into a clean virtual environment and the quick-start example runs
- [project.urls], author and license holder are filled in
- the package name is confirmed available on PyPI, then upload to TestPyPI first
Metadata
Release files for api-response-cleaner 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| api_response_cleaner-0.1.0.tar.gz | 16.6 kB | Without provenance | Details for api_response_cleaner-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| api_response_cleaner-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for api_response_cleaner-0.1.0-py3-none-any.whl |
Total release size: 28.1 kB
Release files / api_response_cleaner-0.1.0.tar.gz
| Download URL | api_response_cleaner-0.1.0.tar.gz |
|---|---|
| Size | 16.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9ad8700fd47e35d3edfe0ffcf4a81449d145e8c57f21838db49994d993cf90a5
|
|
BLAKE2b-256 checksum How to use checksums |
648d07574dc23564bf958dcd76169d428d6f9c05445626448f262818fd955cb5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / api_response_cleaner-0.1.0-py3-none-any.whl
| Download URL | api_response_cleaner-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
99521daed158a9f3e6417e8eacc5eed69cbe12c9faea7ac5fda39de538d182e1
|
|
BLAKE2b-256 checksum How to use checksums |
16abe94a1abf1af649a9622ec7f3bc58e32731b7fdbc56ce6ef21c78964d5877
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Security for api-response-cleaner 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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.