furl

The easiest way to parse, build, and modify URLs in Python, without wrestling urllib.

Library
PyPI
v2.1.4
2,808stars
Unlicense

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
46/100Fair
Development Activity4
Maintenance20
Community60
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
69/100Good
Architecture68
Code Quality62
Innovation55
Learning Curve90

furl wraps Python’s standard urllib/urlparse machinery in a single object-oriented API that treats a URL’s scheme, host, port, path, query, and fragment as directly readable and writable attributes. Instead of manually splitting and rejoining query strings, developers work with a furl object whose .path.segments is a plain list and whose .args is an ordered multivalue dictionary (backed by furl’s companion orderedmultidict package), so appending a path segment, adding a repeated query parameter, or swapping a query value is a single attribute assignment rather than a string-manipulation exercise.

The library takes care of percent-encoding and decoding automatically, including Unicode hosts, paths, and query values, and correctly separates a URL fragment’s own path and query components (#path?query) since fragments can themselves contain structured data. It also infers default ports for common schemes, exposes .origin and .netloc shortcuts, and supports inline chained modification via .add(), .set(), and .remove() methods that return the furl object itself.

furl has been in continuous use since 2011, has no runtime restrictions of any kind (it’s released into the public domain via the Unlicense), and is commonly reached for whenever a codebase needs to build or rewrite URLs programmatically — constructing API request URLs, rewriting redirect targets, or normalizing links scraped from HTML — without hand-rolling string concatenation and manual urlencode calls.

What You Get

  • A furl object exposing scheme, username, password, host, port, netloc, and origin as plain readable/writable attributes
  • A Path object with a .segments list for reading and modifying path components without manual percent-decoding
  • A Query object backed by an ordered multivalue dictionary (.args/.params) that supports repeated query keys and preserves key order
  • Automatic percent-encoding and decoding, including full Unicode support for hosts, paths, and query values
  • Structured access to URL fragments, since a fragment can itself contain a path and query separated by ?
  • Chainable inline modification methods (.add(), .set(), .remove()) plus a .normalize() method for collapsing ./../redundant slashes in paths

Common Use Cases

  • Building outbound API request URLs by starting from a base URL and adding/overriding query parameters
  • Rewriting or sanitizing redirect and callback URLs (e.g. stripping tracking parameters) before storing or forwarding them
  • Normalizing links scraped from HTML or collected from user input into a canonical form for deduplication
  • Constructing paginated or filtered URLs for a web frontend by toggling individual query arguments
  • Parsing and inspecting arbitrary user-supplied URLs safely, including ones with Unicode domains or paths

Under The Hood

Architecture furl’s core lives in a single furl.py module (roughly 1,900 lines) that layers a top-level furl class over composable Path, Query, and Fragment value objects, each owning its own parsing, encoding, and decoding logic; the Query object delegates multivalue storage to omdict1D, an ordered one-dimensional multivalue dict shipped alongside it, while small cross-version shims live in compat.py and common.py. This is a modular, layered design within one file: changing how percent-encoding works inside Path or Query doesn’t ripple into the other’s code path, though the property-heavy style (many computed getters/setters on the main furl class) means a change to a shared concern like netloc composition still touches many call sites in that same large module.

Tech Stack furl is pure Python with two runtime dependencies declared in setup.py: six (for Python 2/3 compatibility shims) and orderedmultidict (the standalone package that omdict1D builds on). Packaging is classic setuptools/setup.py with no pyproject.toml; an optional icecream import is wrapped in a try/except for debug printing and silently no-ops if it isn’t installed. CI runs via tox across Python 3.8 through 3.13 plus PyPy3, defined in tox.ini and driven by a GitHub Actions workflow.

Code Quality tests/test_furl.py (2,382 lines) and tests/test_omdict1D.py (175 lines) are built on the stdlib unittest framework and are run through a custom RunTests setuptools command or via tox; the test suite is large relative to the ~2,000-line implementation and exercises encoding edge cases, Unicode hosts, fragment-path/query separation, and multivalue query behavior explicitly. Error handling favors explicit, descriptively-worded exceptions (e.g. a documented AttributeError when writing a read-only isabsolute on a URL path with a netloc) over silent failures. There are no type hints, no mypy configuration, and no enforced linter beyond a flake8 mention in tests_require; docstrings are sparse but inline comments are frequent.

API Design furl’s ergonomics come from treating every URL component as a directly readable/writable attribute and its query string as an ordered multivalue dictionary (.args), so a caller can do f.args['key'] = 'value' or del f.args['key'] instead of manually calling urlencode/parse_qs and reassembling a URL string. Chainable .add()/.set()/.remove() methods return the object itself for one-line composition, and path segments can be appended with the /= slash operator, mirroring pathlib.Path. This is a deliberate ergonomic wrapper over stdlib urllib primitives rather than a new parsing algorithm, but it removes a substantial amount of boilerplate that comes with using urllib.parse directly for anything beyond a single split/join.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search