URI API
URI (pure URI paths), URIPath (URI paths with I/O and scheme dispatch),
Source and Query. The built-in schemes are documented under Schemes.
uripath
pathlib-style paths for URIs.
QueryLike = 'str | Query | _ty.Mapping[str, str | None | _ty.Sequence[str | None]] | _ty.Sequence[tuple[str, str | None]]'
module-attribute
What URI.with_query() accepts in place of a Query: query text (taken as
already percent-encoded), a mapping or a sequence of pairs, or a Query; None
removes the query.
URILike = 'str | URI | os.PathLike'
module-attribute
Query(pairs=(), *, separator=None, encoding=None)
A URI query as an immutable value: the percent-encoded text and the
(name, value) pairs it stands for.
Build one from pairs or a mapping (a sequence value repeats the name, a
None value is a bare name) with Query(...), which encodes them so that
to_pairs() returns what was given, or from text with Query.parse(),
which keeps the text byte for byte: it is what URI.query holds, as
received, and what is sent. str(query) is that text.
Two queries are equal, and hash alike, when they decode to the same pairs,
whatever their spelling (a=%41 and a=A); a query is never equal to a
str, and has no length, items or iteration. to_pairs() and to_dict()
follow RFC 3986, not application/x-www-form-urlencoded: only %XX
escapes are decoded and a + stays a plus (urllib.parse.parse_qsl reads
form text), and an escape that is not valid in encoding survives as a
surrogate escape instead of raising.
separator splits the text into terms (default &) and encoding is the
charset of the escapes (default utf-8); both say how the text is read
and are not part of the value.
Source code in src/uripath/query.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
ENCODING = 'utf-8'
class-attribute
instance-attribute
SEPARATOR = '&'
class-attribute
instance-attribute
encoding
property
The charset the escapes of the text are read in.
separator
property
What splits the text into terms.
parse(text, *, separator=SEPARATOR, encoding=ENCODING)
classmethod
The query that text spells, percent-encoded as received and kept
byte for byte. Any text parses; TypeError for what is not a str.
Source code in src/uripath/query.py
120 121 122 123 124 125 126 127 128 129 130 | |
to_dict(*, single=False)
The pairs by name: each name maps to the list of its values, or with
single to its last value.
Source code in src/uripath/query.py
147 148 149 150 151 152 153 154 155 156 | |
to_pairs()
The decoded (name, value) pairs in order; value is None for a
name with no =.
Source code in src/uripath/query.py
142 143 144 145 | |
Source
Bases: NamedTuple
A URI's scheme/userinfo/host/port -- everything before the path.
Falsy (bool(source) is False) when every field is empty/None. A host of
"" is an authority with no host (file:///x) and None is no authority
(file:/x): they render differently and name the same endpoint.
host
instance-attribute
port
instance-attribute
scheme
instance-attribute
userinfo
instance-attribute
get_scheme_cls(schemesmap=None)
The URIPath subclass registered for this scheme, or URIPath
itself. An explicit schemesmap is the complete set of classes to
choose from: a scheme missing from it gives URIPath, and neither
plugins nor the global registry are consulted.
Source code in src/uripath/source/_source.py
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
is_local()
Whether host resolves to this machine.
The answer depends on the host alone (userinfo, port and scheme play
no part) and is remembered for _LOCAL_TTL seconds, at most
_LOCAL_CACHE_SIZE hosts: it comes from a DNS/hosts-file lookup and
the machine's interfaces, so it is neither free nor forever.
The hostname->address step uses netimps.resolve() (default
backend chain: dnspython, then the OS resolver via
getaddrinfo() -- hosts file, NSS, DNS, OS cache -- then
nslookup as a last resort), trying both "a"/"aaaa" record
types and treating host as local if ANY resolved address is.
The "is this address MINE" comparison uses
netimps.is_local_address(), which enumerates the real network
interfaces (netimps.get_interfaces()), so VMs, containers, VPN
interfaces and additional NICs count.
host as a bare IP-literal str (e.g. a directly-constructed
Source(..., host="::1", ...), bypassing _decode_host()'s usual
bracket-literal parsing) is handled by netimps.try_parse()
without going through resolution at all.
Source code in src/uripath/source/_source.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | |
keys()
Source code in src/uripath/source/_source.py
149 150 | |
parse(text, *, strict=True)
classmethod
The Source a URI string names. With strict (the default) a
path, query or fragment is a URISyntaxError that names the component
and not the text, which may carry a password. A port outside
0-65535, or a ':' followed by anything but digits in a name that is
not a bracketed address, is a URISyntaxError as well. TypeError
for what is not a str.
Source code in src/uripath/source/_source.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
parsed_userinfo()
(user, password), "" for a part that is absent. The split is
the first colon of the userinfo as it was written (an escaped colon
in the user name stays in the name).
Source code in src/uripath/source/_source.py
157 158 159 160 161 162 163 164 | |
reveal()
The text of this source with its password, which str() leaves out:
the credentialed round trip, as URI.as_uri(sanitize=False) is for a
URI. Named as an action because the result carries the secret; it
belongs in a connection, never in a log or a message.
Source code in src/uripath/source/_source.py
98 99 100 101 102 103 104 | |
try_parse(text, default=None, *, strict=True)
classmethod
parse(), but default (None) for text that is not a source.
TypeError still for what is not a str.
Source code in src/uripath/source/_source.py
138 139 140 141 142 143 144 145 146 147 | |
URI(*uris, **options)
Bases: Pathname
A pure (no I/O) RFC 3986 URI, lazily parsed into source (scheme/
userinfo/host/port), path, query, and fragment on first access.
Join semantics (multiple constructor args, or /) are pathlib-
joinpath-like, not RFC 3986 reference resolution -- see
_load_parts's docstring and docs/divergences.md.
Source code in src/uripath/_uri.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
fragment
property
normalized_path
property
Return the normalized path using posixpath rules.
parent
property
The logical parent of the path.
parts
property
The tuple of URI components: (source, path, query, fragment).
path
property
query
property
The query exactly as received: still percent-encoded, so an
escaped &, = or + inside a value survives the round trip. Use
Query.to_pairs()/to_dict() for decoded pairs.
segments
property
source
property
stem
property
suffix
property
as_posix()
Source code in src/uripath/_uri.py
986 987 988 989 990 991 992 993 994 995 996 997 | |
as_uri(sanitize=False)
Source code in src/uripath/_uri.py
680 681 682 683 684 685 686 687 | |
host_fspath()
Return .path for any scheme whose path component is a
filesystem path on the URI's own host (see _host_filesystem_path),
for building a command line that runs on that host (e.g. via a
remote executor). Unlike __fspath__, this never falls back to
treating the path as local -- it raises NotImplementedError for
schemes with no host-filesystem meaning (http:, s3:, ...).
Source code in src/uripath/_uri.py
662 663 664 665 666 667 668 669 670 671 | |
is_absolute()
True if the path is absolute: it starts with "/", with or without
a source (as PurePosixPath("/a") is absolute).
Source code in src/uripath/_uri.py
861 862 863 864 | |
is_local()
Return True if the URI points to a local resource.
Source code in src/uripath/_uri.py
914 915 916 | |
is_relative_to(other)
Return True if the path is relative to another path or False.
Source code in src/uripath/_uri.py
866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 | |
joinpath(*args)
Combine this URI with segments; a str is a decoded path (see
URI._join_arg), anything else is joined as the constructor joins
it -- pass a URI for a scheme-aware join.
Source code in src/uripath/_uri.py
963 964 965 966 967 968 969 970 971 972 973 974 975 | |
relative_to(other, *, walk_up=False)
Source code in src/uripath/_uri.py
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 | |
with_fragment(fragment)
Return a new URI with the fragment replaced.
Source code in src/uripath/_uri.py
782 783 784 | |
with_path(path)
Return a new URI with the path replaced; under an authority a
relative path gets the leading / the constructor gives it.
Source code in src/uripath/_uri.py
764 765 766 767 768 769 770 771 | |
with_query(query)
Return a new URI with the query replaced.
A str is taken in the percent-encoded form .query returns and is
sent as given; a mapping or a sequence of pairs is encoded by
Query; None removes the query.
Source code in src/uripath/_uri.py
773 774 775 776 777 778 779 780 | |
with_segments(*segments)
Return a new URI with the path segments replaced.
The segments are the spelling segments returns (a leading "" is
the root), joined with /; an element may also be bytes, a
PurePath, a Pathname or an os.PathLike, whose posix text is
used.
Source code in src/uripath/_uri.py
752 753 754 755 756 757 758 759 760 761 762 | |
with_source(source)
Return a new URI with the source replaced.
Source code in src/uripath/_uri.py
748 749 750 | |
URIPath(*uris, **options)
Bases: URI, Path
URI + Path (I/O) + scheme dispatch. URIPath(...) constructs
the concrete subclass registered for the URI's scheme (via __SCHEMES)
-- e.g. URIPath("http://...") returns an HTTPURIPath. Subclass this
and set __SCHEMES to add a new scheme (Track B of extending this
library; see docs/guides/extending.md): defining the class registers
it, a name another class holds raises ValueError unless the class
statement passes replace=True, and schemes() / scheme_class() read
the registry. Implement the I/O surface
(_listdir or _scandir, stat, _open, ...) documented in
docs/guides/extending.md. Prefer overriding _scandir() over _listdir() when the
listing call already returns type/size/mtime metadata (PROPFIND, MLSD,
listdir_attr, an S3 list page, ...) -- walk()/glob() then answer
is_dir() on the results for free, without a stat request per entry.
Source code in src/uripath/_uri.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
backend
property
The connection or session state backend instance.
iterdir()
Source code in src/uripath/schemes/_path.py
623 624 625 626 | |
scheme_class(name)
classmethod
The class registered for the scheme name (case-insensitive),
importing its module or loading its entry point first when needed.
Raises UnsupportedSchemeError naming the registered schemes when there is
none, and ImportError for a built-in module that cannot be
imported and for a scheme another distribution provides that is not
installed (the error names what to install).
Source code in src/uripath/schemes/_path.py
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
schemes()
classmethod
The registered scheme names, sorted: those of every class defined
so far, every built-in scheme and every entry point of the
uripath.schemes group, whether or not its module is imported
yet. The schemes another distribution provides are listed too.
Source code in src/uripath/schemes/_path.py
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
with_backend(backend)
Return a new path instance sharing the same backend state.
Source code in src/uripath/schemes/_path.py
480 481 482 | |
with_source(source)
Source code in src/uripath/schemes/_path.py
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 | |
URIPathError
Bases: Exception
The base of the errors uripath raises.
URISyntaxError
Bases: URIPathError, ValueError
Text that is not a URI, or not the part of one that was asked for: a port
that is not 0-65535, a malformed host, a zip: URI without the scheme of
its archive. The message names what is wrong and never the text, which can
carry a password.
UnsupportedSchemeError
Bases: URIPathError, ValueError
A scheme that no class registered. The message lists the registered ones.
as_path(text)
The path text names: a URIPath when it is a URI, a LocalPath when
it is a local path.
text is a URI when it starts with an RFC 3986 scheme and either holds
:// or the scheme is one a class registers (data:, zip:, file:,
sftp:, ...). Anything else is local: C:/data (a one-letter scheme is a
drive), 12:30.txt, notes:draft and ./a:b. A scheme another
distribution provides gives a URIPath whose first I/O raises the
ImportError that names the distribution to install.
Raises TypeError for what is not a str. Nothing is read or connected
to.
Source code in src/uripath/_pathtext.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
copy_stream(source, target, *, exclusive=False, chunk_size=_CHUNK_SIZE)
Copy the bytes of source to target, chunk_size at a time, never
holding the whole object in memory.
Each end is a binary stream (anything with read() or write()), a path
object, or text that as_path() turns into a path. A path is opened here
and closed when the copy ends; a stream stays open and is the caller's.
The target stream is flushed at the end.
exclusive refuses a target path that exists (FileExistsError), as an
exclusive create does: there is no check-then-write race. A target stream
has nothing to refuse.
A target stream whose reader went away (a pipe whose other end closed,
which Windows reports as OSError(EINVAL)) raises OutputClosedError.
The same error from the connection of a path is not translated.
Source code in src/uripath/_transfer.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
uripath.schemes.DerivedBackend
Marks a backend class whose instances only ever come from
_initbackend(), for a backend that cannot be weakly referenced and so
cannot be recorded in _DERIVED_BACKENDS (a tuple, say).
__slots__ = ()
class-attribute
instance-attribute
uripath.schemes.DelegatingURIPath(*uris, **options)
Bases: URIPath
A URIPath whose I/O is done by a core path that needs no URI.
A scheme names the hooks it hands over in _DELEGATED and builds the core
path for its URI in _make_core(); everything about URIs (parsing,
joining, backends, the text of the path) stays here. The core path is made
once per instance and used for every call, and an OSError it raises names
this path, not the core one.
Source code in src/uripath/_uri.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
__slots__ = ('_core',)
class-attribute
instance-attribute
__init_subclass__(**kwargs)
Source code in src/uripath/schemes/_delegate.py
71 72 73 74 75 | |
stat(*, follow_symlinks=True)
Source code in src/uripath/schemes/_delegate.py
110 111 112 113 114 115 | |
uripath.source
Source (the scheme, userinfo, host and port of a URI) and the helpers that
read and compose the text of one.
ERRORS = 'surrogateescape'
module-attribute
SAFE_PATH = _SUB_DELIMS + ':@/'
module-attribute
Source
Bases: NamedTuple
A URI's scheme/userinfo/host/port -- everything before the path.
Falsy (bool(source) is False) when every field is empty/None. A host of
"" is an authority with no host (file:///x) and None is no authority
(file:/x): they render differently and name the same endpoint.
host
instance-attribute
port
instance-attribute
scheme
instance-attribute
userinfo
instance-attribute
get_scheme_cls(schemesmap=None)
The URIPath subclass registered for this scheme, or URIPath
itself. An explicit schemesmap is the complete set of classes to
choose from: a scheme missing from it gives URIPath, and neither
plugins nor the global registry are consulted.
Source code in src/uripath/source/_source.py
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
is_local()
Whether host resolves to this machine.
The answer depends on the host alone (userinfo, port and scheme play
no part) and is remembered for _LOCAL_TTL seconds, at most
_LOCAL_CACHE_SIZE hosts: it comes from a DNS/hosts-file lookup and
the machine's interfaces, so it is neither free nor forever.
The hostname->address step uses netimps.resolve() (default
backend chain: dnspython, then the OS resolver via
getaddrinfo() -- hosts file, NSS, DNS, OS cache -- then
nslookup as a last resort), trying both "a"/"aaaa" record
types and treating host as local if ANY resolved address is.
The "is this address MINE" comparison uses
netimps.is_local_address(), which enumerates the real network
interfaces (netimps.get_interfaces()), so VMs, containers, VPN
interfaces and additional NICs count.
host as a bare IP-literal str (e.g. a directly-constructed
Source(..., host="::1", ...), bypassing _decode_host()'s usual
bracket-literal parsing) is handled by netimps.try_parse()
without going through resolution at all.
Source code in src/uripath/source/_source.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | |
keys()
Source code in src/uripath/source/_source.py
149 150 | |
parse(text, *, strict=True)
classmethod
The Source a URI string names. With strict (the default) a
path, query or fragment is a URISyntaxError that names the component
and not the text, which may carry a password. A port outside
0-65535, or a ':' followed by anything but digits in a name that is
not a bracketed address, is a URISyntaxError as well. TypeError
for what is not a str.
Source code in src/uripath/source/_source.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
parsed_userinfo()
(user, password), "" for a part that is absent. The split is
the first colon of the userinfo as it was written (an escaped colon
in the user name stays in the name).
Source code in src/uripath/source/_source.py
157 158 159 160 161 162 163 164 | |
reveal()
The text of this source with its password, which str() leaves out:
the credentialed round trip, as URI.as_uri(sanitize=False) is for a
URI. Named as an action because the result carries the secret; it
belongs in a connection, never in a log or a message.
Source code in src/uripath/source/_source.py
98 99 100 101 102 103 104 | |
try_parse(text, default=None, *, strict=True)
classmethod
parse(), but default (None) for text that is not a source.
TypeError still for what is not a str.
Source code in src/uripath/source/_source.py
138 139 140 141 142 143 144 145 146 147 | |
compose_host(host)
Encode a host for composition: an IPv6 address is bracketed, and so is a
bare (non-bracketed) string that parses as one, as a manually constructed
Source(..., host="::1", ...) holds it. Deliberately not netimps'
join_host/FQDN: those would reject names this composes. An IPv6 zone is
written as RFC 6874 says.
Source code in src/uripath/source/_compose.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
compose_uri(scheme, userinfo, host, port, path, query, fragment)
The URI text of already-decoded components, percent-encoding each as
its part of RFC 3986 asks. None leaves a component out; a path that
cannot sit beside the authority given (or beside none) is a ValueError,
as is an invalid scheme or port.
This is direct string assembly, with no re-validation of every component
(a scheme regex, authority re-parsing, IP-literal detection), which only
matters for untrusted input. It replicates only what changes the output
string (IP-literal bracketing, the "./" escape of a colon in the first
segment); the tests compare it with uritools.uricompose() on generated
(scheme, userinfo, host, port, path, query, fragment) combinations.
Source code in src/uripath/source/_compose.py
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | |
fold_host(host)
host as a comparison or a key reads it: a name in lower case, since
host names are case-insensitive (RFC 3986 3.2.2), and an address as it is.
Source.host keeps the spelling it was given, so anything that compares
hosts or uses one as a key folds it at that place.
Source code in src/uripath/source/_parse.py
135 136 137 138 139 140 | |
is_drive(segment)
Whether a path segment is a Windows drive ("C:").
Source code in src/uripath/source/_parse.py
143 144 145 | |
percent_encode(text, safe)
text percent-encoded as UTF-8, keeping the characters of safe.
A decoded path may hold escaped bytes (ERRORS) and, from a Windows
listing, a lone surrogate; neither may make the composer raise.
Source code in src/uripath/source/_compose.py
51 52 53 54 55 56 57 58 | |
same_authority(a, b)
Whether two sources name the same endpoint (scheme, userinfo, host,
port), treating "" and None alike and a host name without regard to
case. The test for whether a backend, connection or rename may cross from
one path to another.
Source code in src/uripath/source/_source.py
231 232 233 234 235 236 | |
uripath.query
QueryLike = 'str | Query | _ty.Mapping[str, str | None | _ty.Sequence[str | None]] | _ty.Sequence[tuple[str, str | None]]'
module-attribute
What URI.with_query() accepts in place of a Query: query text (taken as
already percent-encoded), a mapping or a sequence of pairs, or a Query; None
removes the query.
Query(pairs=(), *, separator=None, encoding=None)
A URI query as an immutable value: the percent-encoded text and the
(name, value) pairs it stands for.
Build one from pairs or a mapping (a sequence value repeats the name, a
None value is a bare name) with Query(...), which encodes them so that
to_pairs() returns what was given, or from text with Query.parse(),
which keeps the text byte for byte: it is what URI.query holds, as
received, and what is sent. str(query) is that text.
Two queries are equal, and hash alike, when they decode to the same pairs,
whatever their spelling (a=%41 and a=A); a query is never equal to a
str, and has no length, items or iteration. to_pairs() and to_dict()
follow RFC 3986, not application/x-www-form-urlencoded: only %XX
escapes are decoded and a + stays a plus (urllib.parse.parse_qsl reads
form text), and an escape that is not valid in encoding survives as a
surrogate escape instead of raising.
separator splits the text into terms (default &) and encoding is the
charset of the escapes (default utf-8); both say how the text is read
and are not part of the value.
Source code in src/uripath/query.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
ENCODING = 'utf-8'
class-attribute
instance-attribute
SEPARATOR = '&'
class-attribute
instance-attribute
encoding
property
The charset the escapes of the text are read in.
separator
property
What splits the text into terms.
parse(text, *, separator=SEPARATOR, encoding=ENCODING)
classmethod
The query that text spells, percent-encoded as received and kept
byte for byte. Any text parses; TypeError for what is not a str.
Source code in src/uripath/query.py
120 121 122 123 124 125 126 127 128 129 130 | |
to_dict(*, single=False)
The pairs by name: each name maps to the list of its values, or with
single to its last value.
Source code in src/uripath/query.py
147 148 149 150 151 152 153 154 155 156 | |
to_pairs()
The decoded (name, value) pairs in order; value is None for a
name with no =.
Source code in src/uripath/query.py
142 143 144 145 | |