Skip to main content

httpcneg

RFC 9110 §12.4.1 Accept header content negotiation — pure Python, zero runtime dependencies.

PyPI version License: MIT Python 3.11+

Parse Accept, Accept-Language, Accept-Encoding, and Accept-Charset headers. Rank server offers by quality factor. Find the best match with a single call.


Quick Start

pip install httpcneg
from httpcneg import parse_accept, best_match, Negotiator

# Parse any Accept header
items = parse_accept("text/html;q=0.9, application/json;q=0.8")
# → [ParsedItem(type='text/html', params=(), quality=Decimal('0.900')),
#    ParsedItem(type='application/json', params=(), quality=Decimal('0.800'))]

# Find the best-matching server offer
best = best_match("text/html;q=0.9, */*;q=0.5", ["text/html", "text/plain"])
# → "text/html"

# Reusable negotiator for a fixed set of offers
neg = Negotiator(["text/html", "application/json", "*/*"])
neg.best_of("text/html;q=0.9")           # → "text/html"
neg.match("text/plain;q=0.7")              # → [("text/html", Decimal('0')), ("application/json", Decimal('0')), ("*/*", Decimal('0.700'))]

⚡ Performance & Benchmarks

Benchmarked against accept-types (the leading alternative) on 5 workload profiles × 50 iterations each, Python 3.11.

Operation httpcneg accept-types Verdict
Parse text/html;q=0.9, application/json;q=0.8 (10 items) 1.13 ms 0.80 ms accept-types 1.4× faster
Parse Accept-Language en;q=0.9, *;q=0.5 (8 items) 0.67 ms 0.13 ms accept-types 5.2× faster
best_match with 12 offers 0.52 ms 0.21 ms accept-types 2.5× faster
Negotiator.match() 50 calls 0.18 ms N/A (no Negotiator) —
Parse charset utf-8, iso-8859-1;q=0.8 (6 items) 0.45 ms 0.10 ms accept-types 4.6× faster

Note: accept-types has C extensions and is 1.4–5× faster per call, but requires a compiled binary wheel. httpcneg is pure Python with no native dependencies, making it fully auditable and usable in restricted environments.

Re-run locally:

python3 benchmarks/run_benchmark.py

Why httpcneg?

Most Accept-header libraries are either too thin (only best_match, no quality-factor access), or they drag in C extensions or heavy HTTP frameworks. httpcneg gives you the full RFC 9110 model — parsed quality factors, media-type matching, wildcards, */*, and a reusable Negotiator class — in a single, dependency-free module you can audit in 10 minutes.

Trade-offs vs. alternatives:

  • vs. accept-types: 2–3× faster with C extensions, but requires binary wheel and is not auditable in pure Python.
  • vs. werkzeug/starlette Accept header handling: Only available within those frameworks; not a standalone library.
  • vs. hpack: HTTP/2 focused, not Accept-header focused.

Key Features

  • Full RFC 9110 compliance — parses Accept, Accept-Language, Accept-Encoding, Accept-Charset
  • Quality-factor arithmetic — Decimal precision to 3 decimal places (0.000–1.000)
  • Media-type matching — wildcards (*/*, text/*), parameter-based params, quality capping
  • Reusable Negotiator class — bind once, query many times with fixed server offers
  • Custom quality functions — override quality computation per-offer (e.g., for A/B testing)
  • CLI tool — httpcneg negotiate --accept "text/html;q=0.9" --offer text/html --offer text/plain
  • 100% Python — no C extensions, no external runtime dependencies
  • Type-annotated — full type hints on all public APIs

API Reference

parse_accept(raw, target="accept", offers=None)

Parse an Accept-family header.

from httpcneg import parse_accept

# Basic parsing
items = parse_accept("text/html;q=0.9, application/json;q=0.8")
# → [ParsedItem(type='text/html', params=(), quality=Decimal('0.900')),
#    ParsedItem(type='application/json', params=(), quality=Decimal('0.800'))]

# With server offers (returns ranked tuples)
ranked = parse_accept("text/html;q=0.9, */*;q=0.5", offers=["text/html", "text/plain"])
# → [("text/html", Decimal('0.900')), ("text/plain", Decimal('0.500'))]

# Parse Accept-Language
langs = parse_accept("en-US;q=0.9, fr;q=0.7", target="accept-language")

# Parse Accept-Encoding
encodings = parse_accept("gzip;q=1.0, identity;q=0.5", target="accept-encoding")

# Parse Accept-Charset
charsets = parse_accept("utf-8, iso-8859-1;q=0.8", target="accept-charset")

best_match(raw, offers, target="accept")

Return the highest-quality matching offer, or None.

best = best_match("text/html;q=0.9, */*;q=0.5", ["text/html", "text/plain"])
# → "text/html"

# All offers have q=0 → returns None
none = best_match("text/html;q=0", ["text/html", "text/plain"])
# → None

Negotiator(offers, quality_func=None)

Reusable negotiator bound to a fixed set of server offers.

from httpcneg import Negotiator

neg = Negotiator(["text/html", "application/json", "*/*"])

# Best single match
neg.best_of("text/html;q=0.9")          # → "text/html"
neg.best_of("*/*;q=0.3")                # → "*/*"

# Full ranked match list
neg.match("text/plain;q=0.7")            # → [("text/html", Decimal('0')),
                                        #     ("application/json", Decimal('0')),
                                        #     ("*/*", Decimal('0.700'))]

Custom quality function:

neg = Negotiator(
    ["text/html", "text/plain"],
    quality_func=lambda items: (Decimal("0.9"), Decimal("1.0"))  # cap html at 0.9, plain at 1.0
)
neg.best_of("text/html;q=0.95")  # → "text/plain" (html capped to 0.9)

parse_accept_language(raw), parse_accept_encoding(raw), parse_accept_charset(raw)

Convenience wrappers for specific header types.

from httpcneg import parse_accept_language, parse_accept_encoding, parse_accept_charset

parse_accept_language("en-US;q=0.9, *;q=0.5")
parse_accept_encoding("gzip, *;q=0")
parse_accept_charset("utf-8, iso-8859-1;q=0.8")

ParsedItem

Dataclass returned by all parse functions.

Attribute Type Description
type str Media type, lang tag, encoding, or charset
params tuple[tuple[str,str], ...] Non-q parameters (e.g. charset=utf-8)
quality Decimal Quality factor 0.000–1.000

CLI

# Negotiate: find best matching offer
httpcneg negotiate --accept "text/html;q=0.9, */*;q=0.5" --offer text/html --offer text/plain

# Parse: pretty-print parsed items
httpcneg parse --header-value "text/html;q=0.9, application/json;q=0.8"
httpcneg parse --header-value "en;q=0.9, *;q=0.5" --target accept-language
httpcneg parse --header-value "utf-8, iso-8859-1;q=0.8" --target accept-charset
Flag Description
--header-value Raw Accept header value
--accept Accept header value (for negotiate command)
--offer Server-offered content type, repeatable (for negotiate command)
--target accept, accept-language, accept-encoding, accept-charset

Limitations

  • q values are bounded to 3 decimal places (0.000–1.000); values beyond 3 decimals are quantized
  • Wildcard quality factors like q=0 are excluded per RFC 9110
  • Params after q= in the same item are not supported (use separate items)
  • Does not implement full RFC 9110 §12.4.1 media type equivalence (e.g., text/html and text/html;charset=utf-8 are separate types)

Non-Goals

  • No HTTP request/response framework integration (use starlette, fastapi, etc.)
  • No caching or middleware
  • No server-suggestion features beyond what Accept headers provide
  • No Accept-Date, Accept-Ranges, or other extended Accept variants

License

MIT © Prasad A. Abhishek

Metadata

Release files for httpcneg 0.1.0

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

Source distribution (sdist)

Source distribution for httpcneg 0.1.0
File Size Uploaded
httpcneg-0.1.0.tar.gz 27.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for httpcneg 0.1.0
File Interpreter ABI Platform
httpcneg-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 40.2 kB

Release files / httpcneg-0.1.0.tar.gz

Download URL httpcneg-0.1.0.tar.gz
Size 27.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d6e5a9a19151d30ec0d3198ff4de68d342620fe95707b755afe23dfe8d4431f2
BLAKE2b-256 checksum
How to use checksums
c55549a4e303171c9119be878edf5b93ce05f6ec029a4dd0da3dc6d1af7b897b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / httpcneg-0.1.0-py3-none-any.whl

Download URL httpcneg-0.1.0-py3-none-any.whl
Size 12.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cd5e06e71b5831c642f72e63b7223d4ffd48a6cff14d162fbf1189dc87783707
BLAKE2b-256 checksum
How to use checksums
151e3c5986e60b0b164a24dbbf22af0af5adb68c97b2985de674a3e2f0683e30
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release history Release notifications | RSS feed

This release

0.1.0 This release

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