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 arithmeticDecimal 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 toolhttpcneg 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

Download files

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

Source Distribution

httpcneg-0.1.0.tar.gz (27.9 kB view details)

Uploaded Source

Built Distribution

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

httpcneg-0.1.0-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file httpcneg-0.1.0.tar.gz.

File metadata

  • Download URL: httpcneg-0.1.0.tar.gz
  • Upload date:
  • Size: 27.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for httpcneg-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d6e5a9a19151d30ec0d3198ff4de68d342620fe95707b755afe23dfe8d4431f2
MD5 aa84955def7352a9eca8fa928ceff530
BLAKE2b-256 c55549a4e303171c9119be878edf5b93ce05f6ec029a4dd0da3dc6d1af7b897b

See more details on using hashes here.

File details

Details for the file httpcneg-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: httpcneg-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 12.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for httpcneg-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cd5e06e71b5831c642f72e63b7223d4ffd48a6cff14d162fbf1189dc87783707
MD5 c3662d9a18905be0e1094fba5ff1411c
BLAKE2b-256 151e3c5986e60b0b164a24dbbf22af0af5adb68c97b2985de674a3e2f0683e30

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page