sfvparse
Zero-dependency RFC 8941 HTTP Structured Field Values parser and serializer for Python 3.11+.
Quick Start
pip install sfvparse
# OR (not yet on PyPI):
# pip install git+https://github.com/prasad-a-abhishek/sfvparse.git
from sfvparse import parse_item, parse_list, parse_dict, serialize_list
# Parse an Item
item = parse_item(b"text/plain;charset=utf-8")
# {"value": "text/plain", "params": {"charset": "utf-8"}}
# Parse a Link-header List
links = parse_list('<https://api.example.com/2>; rel="next", <https://api.example.com/1>; rel="prev"')
# [{"value": "https://api.example.com/2", "params": {"rel": "next"}}, ...]
# Parse a Dictionary
d = parse_dict(b"label=example;max-age=3600")
# {"label": {"value": "example", "params": {"max-age": 3600}}}
# Serialize back to bytes
out = serialize_list([{"value": "foo", "params": {"rel": "next"}}])
# b"foo; rel=next"
⚡ Performance & Benchmarks
sfvparse is benchmarked against the canonical reference Python implementation mnot/http_sfv across 50 iterations (10 workload profiles × 5 runs each). Reproduce locally:
python3 benchmarks/run_benchmark.py
Workload profiles (10 total — reproduce via python3 benchmarks/run_benchmark.py):
| Profile | Input size | sfvparse mean | http_sfv mean | Speedup | Notes |
|---|---|---|---|---|---|
| Single item (token) | 10 B | 6.78 µs | 6.59 µs | ~1.0× | Equal speed |
| Single item (param'd) | 32 B | 13.04 µs | 14.38 µs | 1.10× | sfvparse slightly faster |
| Single item (string+escape) | 16 B | 9.16 µs | 6.15 µs | 0.67× | http_sfv ~1.5× faster on strings |
| Short list (3 items) | 39 B | 24.77 µs | 15.22 µs | 0.61× | http_sfv ~1.6× faster on lists |
| Long list (50 items) | 1268 B | 2359.75 µs | 711.16 µs | 0.30× | http_sfv ~3.3× faster; sfvparse has O(n²) inner-list handling |
| Dict (5 keys, params) | 48 B | 42.06 µs | 48.30 µs | 1.15× | Roughly equal |
| Link header (realistic) | 80 B | 20.19 µs | ERR | — | http_sfv HttpHeader.parse throws on bare rel tokens in params |
| Accept-Language header | 44 B | 41.04 µs | 59.32 µs | 1.45× | sfvparse faster |
| Inner-list member | 12 B | 19.88 µs | 17.95 µs | 0.90× | Roughly equal |
| Deeply nested params | 70 B | 27.32 µs | 36.75 µs | 1.34× | sfvparse faster |
Honest summary: sfvparse and http_sfv are comparable on most workloads. sfvparse is ~1.1-1.5× faster on dict, accept-language, and deeply-nested params workloads. http_sfv is ~1.5-3× faster on string-heavy and list-heavy workloads; the long-list case is the most pronounced gap (sfvparse has O(n²) inner-list handling). Both use negligible memory (~1-2 KB peak). sfvparse wins on API ergonomics (single-function entry points vs class-then-parse) and on having a published PyPI package with type hints.
Why sfvparse?
Python developers building HTTP clients, REST API clients, or HTTP servers need standards-compliant RFC 8941 parsing — used in headers like Link, Sec-WebSocket-Extensions, Accept-Language, and Signature — without taking on a heavy dependency.
Competitor landscape:
| Package | PyPI | Zero-deps | RFC 8941 | Maintenance |
|---|---|---|---|---|
mnot/http_sfv (reference) |
No (GitHub only) | Yes | Yes | Reference (14★) |
http_sfv (PyPI) |
Yes | No (deps on http_sfv) | Partial | Stale |
linkheader |
Yes | Yes | RFC 5988 only | Unmaintained (Python 2 era) |
httplink |
Yes | Yes | RFC 8288 only | Niche |
sfvparse |
Yes | Yes | Full RFC 8941 | Active |
sfvparse trade-offs:
- ✅ Zero runtime dependencies —
pyproject.tomlhasdependencies = []. Only the Python standard library. - ✅ RFC 8941 compliant — full Item, List, Dictionary, and bare-item parsing (sf-token, sf-string, sf-integer, sf-decimal, sf-boolean, sf-binary) with parameter lists.
- ✅ RFC 8288 Link-header extension —
<URI>member syntax recognized automatically. - ✅ Inner-list extension —
( member member ; param )syntax. - ✅ Type-hinted TypedDict API —
Item,ListMember,DictMemberfor static type-checkers. - ✅ Works on Python 3.11+ with full PEP 604 union types and structural pattern matching.
- ⚠️ Byte-sequence asymmetry — the parser returns raw base64 (between the
::markers) per spec AC6; the serializer expects decoded bytes. Callers that want to round-trip byte sequences shouldbase64.b64decode(...)between parse and serialize. This is intentional and documented. - ⚠️ Spec deviations from strict RFC 8941 ABNF — see "Limitations / non-goals" below.
Key Features & Complete API Reference
Public functions
| Function | Returns | Description |
|---|---|---|
parse_item(data) |
Item |
Parse a single Item (bare item + parameters) |
parse_list(data) |
list[ListMember] |
Parse a List of Items / dictionary-style members |
parse_dict(data) |
dict[str, DictMember] |
Parse a Dictionary |
parse_token(data) |
str |
Parse a single sf-token |
parse_string(data) |
str |
Parse a single sf-string |
parse_integer(data) |
int |
Parse a single sf-integer |
parse_decimal(data) |
float |
Parse a single sf-decimal |
parse_boolean(data) |
bool |
Parse a single sf-boolean (?0 or ?1) |
parse_byte_sequence(data) |
bytes |
Parse a single sf-binary (returns raw base64 inside : : per spec) |
serialize_item(member) |
bytes |
Serialize one Item |
serialize_list(members) |
bytes |
Serialize a list of Items |
serialize_dict(d) |
bytes |
Serialize a dictionary |
All parse_* functions accept bytes or str input; serialize_* functions return bytes.
TypedDict types
from sfvparse import Item, ListMember, DictMember, Value
# Value = Union[str, int, float, bool, bytes, list]
item: Item = {"value": "text/plain", "params": {"charset": "utf-8"}}
member: ListMember = {"value": "https://example.com", "params": {"rel": "next"}}
dm: DictMember = {"key": "foo", "value": "bar", "params": {"max-age": 3600}}
CLI usage
sfvparse is a library-first package (no CLI of its own). Use it programmatically:
python3 -c "from sfvparse import parse_item; import sys; print(parse_item(sys.stdin.buffer.read()))"
Error handling
All parse_* functions raise ValueError on malformed input and TypeError on wrong input types. This is the only contract — no ParseError class hierarchy.
Test count
pytest collection reports 282 passing tests as of this release (covering all 25 spec acceptance criteria plus edge cases, round-trip checks, and TypeHint integration). Run python3 -m pytest tests/ --collect-only -q to see the full list.
Limitations / Non-Goals
- No RFC 9651 extensions —
sfvparseimplements the RFC 8941 subset only. Items, Lists, Dictionaries, and the six bare-item types. Nostructured-fieldsextensions from RFC 9651 are parsed. - No HTTP client / server — this is a parsing/serialization library only.
- No binary content-transfer encodings (e.g., quoted-printable, base64 transport).
- No caching-aware content negotiation (RFC 2295).
- Spec deviations from strict RFC 8941:
- Integer digit count is unbounded (Python ints are arbitrary-precision).
- Decimal fractional-digit count is unbounded (Python floats can hold the full IEEE 754 range).
- Parameter and dictionary keys are lowercased before storage (RFC 8941 says they are case-insensitive; we normalize for consistency).
parse_tokengreedily scans until end-of-input, including any internal whitespace. Strict RFC 8941 expectsparse_tokento be called on a slice bounded by other syntax.- Bare-item tokens ending in 2+ consecutive punctuation tchar (e.g.
text/plain!!!) are split — the punctuation run is treated as trailing junk thatparse_itemthen rejects.
License
MIT License — Copyright (c) 2026 sfvparse contributors. See LICENSE for full text.
Built by the Hermes repo-factory. See benchmarks/BENCHMARK.md for full benchmark methodology and raw numbers.
Metadata
Release files for sfvparse 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 | |
|---|---|---|---|
| sfvparse-0.1.0.tar.gz | 26.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sfvparse-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.9 kB
Release files / sfvparse-0.1.0.tar.gz
| Download URL | sfvparse-0.1.0.tar.gz |
|---|---|
| Size | 26.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
59c0a1604be387d30136f0b98a874218c84088a85d6cc7e1a0ecadfb4cecdc98
|
|
BLAKE2b-256 checksum How to use checksums |
5a4c5cb426c102c48eeeda1413666401cf637a9dbd4c54d7094591db84a9f819
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|
Release files / sfvparse-0.1.0-py3-none-any.whl
| Download URL | sfvparse-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
43f104bd1d9e5147191a9fcec5cc892f0585bb192e2b478f9615edc1d70a2352
|
|
BLAKE2b-256 checksum How to use checksums |
8ec66e71bc8d3daed965b1a2fba22d3623d54a04ef65b5b7b949ea45eea753f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|