Skip to main content

priorityheader

Parse, merge and build the HTTP Priority field and PRIORITY_UPDATE frames (RFC 9218), zero dependencies and typed.

Install

pip install priorityheader

Read a request

from priorityheader import Priority, parse_priority

print(parse_priority("u=1, i"))
# Priority(urgency=1, incremental=True)

# Absent members take their defaults: urgency 3, not incremental.
print(parse_priority(None))
# Priority(urgency=3, incremental=False)

# Unknown members, an urgency outside 0..7 and wrong types are ignored.
print(parse_priority("u=9, i, vendor-hint=?1"))
# Priority(urgency=3, incremental=True)

# A field that breaks the structured field grammar is ignored as a whole.
print(parse_priority("u=1, i=?2"))
# Priority(urgency=3, incremental=False)

# Several field lines, or the raw octets of a frame, are read too.
print(parse_priority(["u=2", b"i"]))
# Priority(urgency=2, incremental=True)

parse_priority(value, strict=True) raises PriorityError instead of falling back when the field does not parse; the error carries the offset where reading stopped. Out of range and mistyped members are still ignored, because RFC 9218 says a receiver must ignore them once the dictionary parses.

Merge a response into a request

In a request an absent member means its default. In a response it means the server has no opinion, so the client value stands (RFC 9218 section 8).

from priorityheader import Priority, merge_priority, parse_params

print(merge_priority("u=5, i", "u=1"))
# Priority(urgency=1, incremental=True)

params = parse_params("u=1")
print(params)
# PriorityParams(urgency=1, incremental=None)
print(params.over(Priority(5, True)))
# Priority(urgency=1, incremental=True)

Build a field value

from priorityheader import PriorityParams, Token, build_params, build_priority

# Request form: members at their default are left out.
print(repr(build_priority()))
# ''
print(build_priority(urgency=0, incremental=True))
# u=0, i
print(build_priority(1, extensions={"vendor-hint": Token("viewport")}))
# u=1, vendor-hint=viewport

# Response form: exactly the members given, defaults included.
print(build_params(PriorityParams(urgency=3, incremental=False)))
# u=3, i=?0

Both builders check every key and value, so the result always parses back to what was given: an urgency outside 0..7 or an extension that cannot be written raises PriorityError, and a wrong type raises TypeError. A carriage return or line feed can never reach the output.

PRIORITY_UPDATE frames

from priorityheader import decode_h2_update, decode_h3_update, encode_h2_update, encode_h3_update

frame = encode_h2_update(5, "u=0")
print(frame.hex())
# 00000710000000000000000005753d30
update = decode_h2_update(frame)
print(update)
# PriorityUpdate(element_id=5, field_value='u=0', target='stream', protocol='h2')
print(update.priority())
# Priority(urgency=0, incremental=False)

push = decode_h3_update(encode_h3_update(3, "u=6", target="push"))
print(push.target, push.priority().urgency)
# push 6

The decoders raise FrameError, whose code names the connection error the RFCs assign: PROTOCOL_ERROR or FRAME_SIZE_ERROR for HTTP/2, H3_FRAME_ERROR or H3_ID_ERROR for HTTP/3, and None for bytes that are not a single PRIORITY_UPDATE frame. update.priority(strict=True) raises a FrameError with PROTOCOL_ERROR or H3_GENERAL_PROTOCOL_ERROR when the carried value does not parse, the error RFC 9218 allows a server to close the connection with. Stream state and stream limits belong to the connection and stay with the caller.

Command line

$ priorityheader "u=1, i"
{
  "urgency": 1,
  "incremental": true,
  "extensions": {},
  "canonical": "u=1, i"
}
$ curl -sI https://example.com | priorityheader --response
$ priorityheader --request "u=5, i" "u=1"

Without a value the field lines come from standard input, either bare values or a message head. --strict exits with status 1 on a value that does not parse; usage errors exit with 2.

API

Name What it does
parse_priority(value, *, strict=False) Resolved Priority of a request or frame field
parse_params(value, *, strict=False) Members present in a field, None when absent
merge_priority(request, response) Response members over the request (section 8)
build_priority(urgency=3, incremental=False, *, extensions=None) Request form, defaults left out
build_params(params) Exactly the members of a PriorityParams
encode_h2_update, decode_h2_update HTTP/2 PRIORITY_UPDATE frame (type 0x10)
encode_h3_update, decode_h3_update HTTP/3 PRIORITY_UPDATE frames (0xF0700, 0xF0701)
Priority Named tuple of urgency and incremental
PriorityParams Immutable members of a field, with over() and resolve()
PriorityUpdate Decoded frame: element_id, field_value, target, protocol, priority()
PriorityError A ValueError with the offset of a strict parse failure
FrameError A PriorityError with the RFC error code
Token, Date, DisplayString Structured field types for extension values
ExtensionValue, FieldInput Type aliases for extension values and field input
DEFAULT_URGENCY, MIN_URGENCY, MAX_URGENCY 3, 0 and 7
H2_FRAME_TYPE, H3_REQUEST_FRAME_TYPE, H3_PUSH_FRAME_TYPE Frame type numbers

Why

RFC 9218 replaced the HTTP/2 dependency tree with two small signals: an urgency from 0 (first) to 7 (background) and a flag saying the response is useful in pieces. They travel as a structured field dictionary in the Priority field, end to end, and in PRIORITY_UPDATE frames, hop by hop. The rules are short but easy to get subtly wrong: a field that fails to parse must be dropped as a whole, unknown or out of range members are ignored, an absent member means the default in a request but "no change" in a response, and a response that wants the default urgency has to say u=3 out loud.

The structured field machinery usually lives inside large servers and HTTP stacks. This library is the focused piece: a parser checked against the shared structured field test corpus, a builder that refuses anything it cannot write back, the section 8 merge, and the frame codecs, using only the standard library.

Development

python scripts/fetch_upstream.py downloads the RFCs, the IANA registry and the structured field test corpus the tests compare against; they are not part of the repository and their tests skip until fetched. scripts/check.ps1 runs format, lint, types and tests.

License

MIT. See LICENSE.

Metadata

Release files for priorityheader 0.2.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 priorityheader 0.2.0
File Size Uploaded Provenance
priorityheader-0.2.0.tar.gz 39.0 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 60.9 kB

Release files / priorityheader-0.2.0.tar.gz

Download URL priorityheader-0.2.0.tar.gz
Size 39.0 kB
Tags Source
SHA-256 checksum
How to use checksums
7f388a3f5e2e7bf7339590bd0e5f917d0510cf5708a5ba73fe865a105521d60c
BLAKE2b-256 checksum
How to use checksums
877f33dce4154d83c4a4c20c07d0939cc734f6ddfb559dcf49b98d99eb2306db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release files / priorityheader-0.2.0-py3-none-any.whl

Download URL priorityheader-0.2.0-py3-none-any.whl
Size 21.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b96c1e85c29200689c3c7f36c3cc23c748b2652fb4ce70d01e76b4640bc2046c
BLAKE2b-256 checksum
How to use checksums
5737afd796a5a21273a9dc0859b97c79b17ff86d079d70e3997fd1fa3b84fe28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Security for priorityheader 0.2.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.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

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