Hyperlink API

Hyperlink provides Pythonic URL parsing, construction, and rendering.

Usage is straightforward:

>>> from hyperlink import URL
>>> url = URL.from_text(u'http://github.com/mahmoud/hyperlink?utm_source=docs')
>>> url.host
u'github.com'
>>> secure_url = url.replace(scheme=u'https')
>>> secure_url.get('utm_source')[0]
u'docs'

As seen here, the API revolves around the lightweight and immutable URL type, documented below.

Creation

Before you can work with URLs, you must create URLs. There are two ways to create URLs, from parts and from text.

class hyperlink.URL(scheme=None, host=None, path=(), query=(), fragment=u'', port=None, rooted=None, userinfo=u'', uses_netloc=None)[source]

From blogs to billboards, URLs are so common, that it’s easy to overlook their complexity and power. With hyperlink’s URL type, working with URLs doesn’t have to be hard.

URLs are made of many parts. Most of these parts are officially named in RFC 3986 and this diagram may prove handy in identifying them:

 foo://user:pass@example.com:8042/over/there?name=ferret#nose
 \_/   \_______/ \_________/ \__/\_________/ \_________/ \__/
  |        |          |        |      |           |        |
scheme  userinfo     host     port   path       query   fragment

While from_text() is used for parsing whole URLs, the URL constructor builds a URL from the individual components, like so:

>>> from hyperlink import URL
>>> url = URL(scheme=u'https', host=u'example.com', path=[u'hello', u'world'])
>>> print(url.to_text())
https://example.com/hello/world

The constructor runs basic type checks. All strings are expected to be decoded (unicode in Python 2). All arguments are optional, defaulting to appropriately empty values. A full list of constructor arguments is below.

Parameters:
  • scheme (unicode) – The text name of the scheme.
  • host (unicode) – The host portion of the network location
  • port (int) – The port part of the network location. If None or no port is passed, the port will default to the default port of the scheme, if it is known. See the SCHEME_PORT_MAP and register_default_port() for more info.
  • path (tuple) – A tuple of strings representing the slash-separated parts of the path.
  • query (tuple) – The query parameters, as a dictionary or as an iterable of key-value pairs.
  • fragment (unicode) – The fragment part of the URL.
  • rooted (bool) – Whether or not the path begins with a slash.
  • userinfo (unicode) – The username or colon-separated username:password pair.
  • uses_netloc (bool) – Indicates whether two slashes appear between the scheme and the host (http://eg.com vs mailto:e@g.com). Set automatically based on scheme.

All of these parts are also exposed as read-only attributes of URL instances, along with several useful methods.

classmethod URL.from_text(text)[source]

Whereas the URL constructor is useful for constructing URLs from parts, from_text() supports parsing whole URLs from their string form:

>>> URL.from_text(u'http://example.com')
URL.from_text(u'http://example.com')
>>> URL.from_text(u'?a=b&x=y')
URL.from_text(u'?a=b&x=y')

As you can see above, it’s also used as the repr() of URL objects. The natural counterpart to to_text(). This method only accepts text, so be sure to decode those bytestrings.

Parameters:text (unicode) – A valid URL string.
Returns:The structured object version of the parsed string.
Return type:URL

Note

Somewhat unexpectedly, URLs are a far more permissive format than most would assume. Many strings which don’t look like URLs are still valid URLs. As a result, this method only raises URLParseError on invalid port and IPv6 values in the host portion of the URL.

Transformation

Once a URL is created, some of the most common tasks are to transform it into other URLs and text.

URL.to_text(with_password=False)[source]

Render this URL to its textual representation.

By default, the URL text will not include a password, if one is set. RFC 3986 considers using URLs to represent such sensitive information as deprecated. Quoting from RFC 3986, section 3.2.1:

“Applications should not render as clear text any data after the first colon (“:”) character found within a userinfo subcomponent unless the data after the colon is the empty string (indicating no password).”
Parameters:with_password (bool) – Whether or not to include the password in the URL text. Defaults to False.
Returns:The serialized textual representation of this URL, such as u"http://example.com/some/path?some=query".
Return type:str

The natural counterpart to URL.from_text().

URL.to_uri()[source]

Make a new URL instance with all non-ASCII characters appropriately percent-encoded. This is useful to do in preparation for sending a URL over a network protocol.

For example:

>>> URL.from_text(u'https://ايران.com/foo⇧bar/').to_uri()
URL.from_text(u'https://xn--mgba3a4fra.com/foo%E2%87%A7bar/')
Returns:A new instance with its path segments, query parameters, and hostname encoded, so that they are all in the standard US-ASCII range.
Return type:URL
URL.to_iri()[source]

Make a new URL instance with all but a few reserved characters decoded into human-readable format.

Percent-encoded Unicode and IDNA-encoded hostnames are decoded, like so:

>>> url = URL.from_text(u'https://xn--mgba3a4fra.example.com/foo%E2%87%A7bar/')
>>> print(url.to_iri().to_text())
https://ايران.example.com/foo⇧bar/

Note

As a general Python issue, “narrow” (UCS-2) builds of Python may not be able to fully decode certain URLs, and the in those cases, this method will return a best-effort, partially-decoded, URL which is still valid. This issue does not affect any Python builds 3.4+.

Returns:A new instance with its path segments, query parameters, and hostname decoded for display purposes.
Return type:URL
URL.replace(scheme=Sentinel('_UNSET'), host=Sentinel('_UNSET'), path=Sentinel('_UNSET'), query=Sentinel('_UNSET'), fragment=Sentinel('_UNSET'), port=Sentinel('_UNSET'), rooted=Sentinel('_UNSET'), userinfo=Sentinel('_UNSET'), uses_netloc=Sentinel('_UNSET'))[source]

URL objects are immutable, which means that attributes are designed to be set only once, at construction. Instead of modifying an existing URL, one simply creates a copy with the desired changes.

If any of the following arguments is omitted, it defaults to the value on the current URL.

Parameters:
  • scheme (unicode) – The text name of the scheme.
  • host (unicode) – The host portion of the network location
  • port (int) – The port part of the network location.
  • path (tuple) – A tuple of strings representing the slash-separated parts of the path.
  • query (tuple) – The query parameters, as a tuple of key-value pairs.
  • query – The query parameters, as a dictionary or as an iterable of key-value pairs.
  • fragment (unicode) – The fragment part of the URL.
  • rooted (bool) – Whether or not the path begins with a slash.
  • userinfo (unicode) – The username or colon-separated username:password pair.
  • uses_netloc (bool) – Indicates whether two slashes appear between the scheme and the host (http://eg.com vs mailto:e@g.com)
Returns:

a copy of the current URL, with new values for

parameters passed.

Return type:

URL

URL.normalize(scheme=True, host=True, path=True, query=True, fragment=True, userinfo=True, percents=True)[source]

Return a new URL object with several standard normalizations applied:

  • Decode unreserved characters (RFC 3986 2.3)
  • Uppercase remaining percent-encoded octets (RFC 3986 2.1)
  • Convert scheme and host casing to lowercase (RFC 3986 3.2.2)
  • Resolve any “.” and “..” references in the path (RFC 3986 6.2.2.3)
  • Ensure an ending slash on URLs with an empty path (RFC 3986 6.2.3)
  • Encode any stray percent signs (%) in percent-encoded fields (path, query, fragment, userinfo) (RFC 3986 2.4)

All are applied by default, but normalizations can be disabled per-part by passing False for that part’s corresponding name.

Parameters:
  • scheme (bool) – Convert the scheme to lowercase
  • host (bool) – Convert the host to lowercase
  • path (bool) – Normalize the path (see above for details)
  • query (bool) – Normalize the query string
  • fragment (bool) – Normalize the fragment
  • userinfo (bool) – Normalize the userinfo
  • percents (bool) – Encode isolated percent signs for any percent-encoded fields which are being normalized (defaults to True).
>>> url = URL.from_text(u'Http://example.COM/a/../b/./c%2f?%61%')
>>> print(url.normalize().to_text())
http://example.com/b/c%2F?a%25

Query Parameters

CRUD operations on the query string multimap.

URL.get(name)[source]

Get a list of values for the given query parameter, name:

>>> url = URL.from_text(u'?x=1&x=2')
>>> url.get('x')
[u'1', u'2']
>>> url.get('y')
[]

If the given name is not set, an empty list is returned. A list is always returned, and this method raises no exceptions.

Parameters:name (unicode) – The name of the query parameter to get.
Returns:
A list of all the values associated with the key, in
string form.
Return type:list
URL.add(name, value=None)[source]

Make a new URL instance with a given query argument, name, added to it with the value value, like so:

>>> URL.from_text(u'https://example.com/?x=y').add(u'x')
URL.from_text(u'https://example.com/?x=y&x')
>>> URL.from_text(u'https://example.com/?x=y').add(u'x', u'z')
URL.from_text(u'https://example.com/?x=y&x=z')
Parameters:
  • name (unicode) – The name of the query parameter to add. The part before the =.
  • value (unicode) – The value of the query parameter to add. The part after the =. Defaults to None, meaning no value.
Returns:

A new URL instance with the parameter added.

Return type:

URL

URL.set(name, value=None)[source]

Make a new URL instance with the query parameter name set to value. All existing occurences, if any are replaced by the single name-value pair.

>>> URL.from_text(u'https://example.com/?x=y').set(u'x')
URL.from_text(u'https://example.com/?x')
>>> URL.from_text(u'https://example.com/?x=y').set(u'x', u'z')
URL.from_text(u'https://example.com/?x=z')
Parameters:
  • name (unicode) – The name of the query parameter to set. The part before the =.
  • value (unicode) – The value of the query parameter to set. The part after the =. Defaults to None, meaning no value.
Returns:

A new URL instance with the parameter set.

Return type:

URL

URL.remove(name)[source]

Make a new URL instance with all occurrences of the query parameter name removed. No exception is raised if the parameter is not already set.

Parameters:name (unicode) – The name of the query parameter to remove.
Returns:A new URL instance with the parameter removed.
Return type:URL

Attributes

URLs have many parts, and URL objects have many attributes to represent them.

URL.absolute

Whether or not the URL is “absolute”. Absolute URLs are complete enough to resolve to a network resource without being relative to a base URI.

>>> URL.from_text(u'http://wikipedia.org/').absolute
True
>>> URL.from_text(u'?a=b&c=d').absolute
False

Absolute URLs must have both a scheme and a host set.

URL.scheme

The scheme is a string, and the first part of an absolute URL, the part before the first colon, and the part which defines the semantics of the rest of the URL. Examples include “http”, “https”, “ssh”, “file”, “mailto”, and many others. See register_scheme() for more info.

URL.host

The host is a string, and the second standard part of an absolute URL. When present, a valid host must be a domain name, or an IP (v4 or v6). It occurs before the first slash, or the second colon, if a port is provided.

URL.port

The port is an integer that is commonly used in connecting to the host, and almost never appears without it.

When not present in the original URL, this attribute defaults to the scheme’s default port. If the scheme’s default port is not known, and the port is not provided, this attribute will be set to None.

>>> URL.from_text(u'http://example.com/pa/th').port
80
>>> URL.from_text(u'foo://example.com/pa/th').port
>>> URL.from_text(u'foo://example.com:8042/pa/th').port
8042

Note

Per the standard, when the port is the same as the schemes default port, it will be omitted in the text URL.

URL.path

A tuple of strings, created by splitting the slash-separated hierarchical path. Started by the first slash after the host, terminated by a “?”, which indicates the start of the query string.

URL.query

Tuple of pairs, created by splitting the ampersand-separated mapping of keys and optional values representing non-hierarchical data used to identify the resource. Keys are always strings. Values are strings when present, or None when missing.

For more operations on the mapping, see get(), add(), set(), and delete().

URL.fragment

A string, the last part of the URL, indicated by the first “#” after the path or query. Enables indirect identification of a secondary resource, like an anchor within an HTML page.

URL.userinfo

The colon-separated string forming the username-password combination.

URL.user

The user portion of userinfo.

URL.rooted

Whether or not the path starts with a forward slash (/).

This is taken from the terminology in the BNF grammar, specifically the “path-rootless”, rule, since “absolute path” and “absolute URI” are somewhat ambiguous. path does not contain the implicit prefixed "/" since that is somewhat awkward to work with.

Low-level functions

A couple of notable helpers used by the URL type.

class hyperlink.URLParseError[source]

Exception inheriting from ValueError, raised when failing to parse a URL. Mostly raised on invalid ports and IPv6 addresses.

hyperlink.register_scheme(text, uses_netloc=True, default_port=None)[source]

Registers new scheme information, resulting in correct port and slash behavior from the URL object. There are dozens of standard schemes preregistered, so this function is mostly meant for proprietary internal customizations or stopgaps on missing standards information. If a scheme seems to be missing, please file an issue!

Parameters:
  • text (unicode) – Text representing the scheme. (the ‘http’ in ‘http://hatnote.com’)
  • uses_netloc (bool) – Does the scheme support specifying a network host? For instance, “http” does, “mailto” does not. Defaults to True.
  • default_port (int) – The default port, if any, for netloc-using schemes.