Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rfc3986/_mixin.py: 67%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Module containing the implementation of the URIMixin class."""
3import typing as t
4import warnings
6from . import exceptions as exc
7from . import misc
8from . import normalizers
9from . import uri
10from . import validators
11from ._typing_compat import Self as _Self
14class _AuthorityInfo(t.TypedDict):
15 """A typed dict for the authority info triple: userinfo, host, and port."""
17 userinfo: t.Optional[str]
18 host: t.Optional[str]
19 port: t.Optional[str]
22class URIMixin:
23 """Mixin with all shared methods for URIs and IRIs."""
25 if t.TYPE_CHECKING:
26 scheme: t.Optional[str]
27 authority: t.Optional[str]
28 path: t.Optional[str]
29 query: t.Optional[str]
30 fragment: t.Optional[str]
31 encoding: str
33 def authority_info(self) -> _AuthorityInfo:
34 """Return a dictionary with the ``userinfo``, ``host``, and ``port``.
36 If the authority is not valid, it will raise a
37 :class:`~rfc3986.exceptions.InvalidAuthority` Exception.
39 :returns:
40 ``{'userinfo': 'username:password', 'host': 'www.example.com',
41 'port': '80'}``
42 :rtype: dict
43 :raises rfc3986.exceptions.InvalidAuthority:
44 If the authority is not ``None`` and can not be parsed.
45 """
46 if not self.authority:
47 return {"userinfo": None, "host": None, "port": None}
49 match = self._match_subauthority()
51 if match is None:
52 # In this case, we have an authority that was parsed from the URI
53 # Reference, but it cannot be further parsed by our
54 # misc.SUBAUTHORITY_MATCHER. In this case it must not be a valid
55 # authority.
56 raise exc.InvalidAuthority(self.authority.encode(self.encoding))
58 # We had a match, now let's ensure that it is actually a valid host
59 # address if it is IPv4
60 matches = match.groupdict()
61 host = matches.get("host")
63 if (
64 host
65 and misc.IPv4_MATCHER.match(host)
66 and not validators.valid_ipv4_host_address(host)
67 ):
68 # If we have a host, it appears to be IPv4 and it does not have
69 # valid bytes, it is an InvalidAuthority.
70 raise exc.InvalidAuthority(self.authority.encode(self.encoding))
72 return matches
74 def _match_subauthority(self) -> t.Optional[t.Match[str]]:
75 return misc.SUBAUTHORITY_MATCHER.match(self.authority)
77 @property
78 def _validator(self) -> validators.Validator:
79 v = getattr(self, "_cached_validator", None)
80 if v is not None:
81 return v
82 self._cached_validator = validators.Validator().require_presence_of(
83 "scheme"
84 )
85 return self._cached_validator
87 @property
88 def host(self) -> t.Optional[str]:
89 """If present, a string representing the host."""
90 try:
91 authority = self.authority_info()
92 except exc.InvalidAuthority:
93 return None
94 return authority["host"]
96 @property
97 def port(self) -> t.Optional[str]:
98 """If present, the port extracted from the authority."""
99 try:
100 authority = self.authority_info()
101 except exc.InvalidAuthority:
102 return None
103 return authority["port"]
105 @property
106 def userinfo(self) -> t.Optional[str]:
107 """If present, the userinfo extracted from the authority."""
108 try:
109 authority = self.authority_info()
110 except exc.InvalidAuthority:
111 return None
112 return authority["userinfo"]
114 def is_absolute(self) -> bool:
115 """Determine if this URI Reference is an absolute URI.
117 See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation.
119 :returns: ``True`` if it is an absolute URI, ``False`` otherwise.
120 :rtype: bool
121 """
122 return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit()))
124 def is_valid(self, **kwargs: bool) -> bool:
125 """Determine if the URI is valid.
127 .. deprecated:: 1.1.0
129 Use the :class:`~rfc3986.validators.Validator` object instead.
131 :param bool require_scheme: Set to ``True`` if you wish to require the
132 presence of the scheme component.
133 :param bool require_authority: Set to ``True`` if you wish to require
134 the presence of the authority component.
135 :param bool require_path: Set to ``True`` if you wish to require the
136 presence of the path component.
137 :param bool require_query: Set to ``True`` if you wish to require the
138 presence of the query component.
139 :param bool require_fragment: Set to ``True`` if you wish to require
140 the presence of the fragment component.
141 :returns: ``True`` if the URI is valid. ``False`` otherwise.
142 :rtype: bool
143 """
144 warnings.warn(
145 "Please use rfc3986.validators.Validator instead. "
146 "This method will be eventually removed.",
147 DeprecationWarning,
148 )
149 validators = [
150 (self.scheme_is_valid, kwargs.get("require_scheme", False)),
151 (self.authority_is_valid, kwargs.get("require_authority", False)),
152 (self.path_is_valid, kwargs.get("require_path", False)),
153 (self.query_is_valid, kwargs.get("require_query", False)),
154 (self.fragment_is_valid, kwargs.get("require_fragment", False)),
155 ]
156 return all(v(r) for v, r in validators)
158 def authority_is_valid(self, require: bool = False) -> bool:
159 """Determine if the authority component is valid.
161 .. deprecated:: 1.1.0
163 Use the :class:`~rfc3986.validators.Validator` object instead.
165 :param bool require:
166 Set to ``True`` to require the presence of this component.
167 :returns:
168 ``True`` if the authority is valid. ``False`` otherwise.
169 :rtype:
170 bool
171 """
172 warnings.warn(
173 "Please use rfc3986.validators.Validator instead. "
174 "This method will be eventually removed.",
175 DeprecationWarning,
176 )
177 try:
178 self.authority_info()
179 except exc.InvalidAuthority:
180 return False
182 return validators.authority_is_valid(
183 self.authority,
184 host=self.host,
185 require=require,
186 )
188 def scheme_is_valid(self, require: bool = False) -> bool:
189 """Determine if the scheme component is valid.
191 .. deprecated:: 1.1.0
193 Use the :class:`~rfc3986.validators.Validator` object instead.
195 :param str require: Set to ``True`` to require the presence of this
196 component.
197 :returns: ``True`` if the scheme is valid. ``False`` otherwise.
198 :rtype: bool
199 """
200 warnings.warn(
201 "Please use rfc3986.validators.Validator instead. "
202 "This method will be eventually removed.",
203 DeprecationWarning,
204 )
205 return validators.scheme_is_valid(self.scheme, require)
207 def path_is_valid(self, require: bool = False) -> bool:
208 """Determine if the path component is valid.
210 .. deprecated:: 1.1.0
212 Use the :class:`~rfc3986.validators.Validator` object instead.
214 :param str require: Set to ``True`` to require the presence of this
215 component.
216 :returns: ``True`` if the path is valid. ``False`` otherwise.
217 :rtype: bool
218 """
219 warnings.warn(
220 "Please use rfc3986.validators.Validator instead. "
221 "This method will be eventually removed.",
222 DeprecationWarning,
223 )
224 return validators.path_is_valid(self.path, require)
226 def query_is_valid(self, require: bool = False) -> bool:
227 """Determine if the query component is valid.
229 .. deprecated:: 1.1.0
231 Use the :class:`~rfc3986.validators.Validator` object instead.
233 :param str require: Set to ``True`` to require the presence of this
234 component.
235 :returns: ``True`` if the query is valid. ``False`` otherwise.
236 :rtype: bool
237 """
238 warnings.warn(
239 "Please use rfc3986.validators.Validator instead. "
240 "This method will be eventually removed.",
241 DeprecationWarning,
242 )
243 return validators.query_is_valid(self.query, require)
245 def fragment_is_valid(self, require: bool = False) -> bool:
246 """Determine if the fragment component is valid.
248 .. deprecated:: 1.1.0
250 Use the Validator object instead.
252 :param str require: Set to ``True`` to require the presence of this
253 component.
254 :returns: ``True`` if the fragment is valid. ``False`` otherwise.
255 :rtype: bool
256 """
257 warnings.warn(
258 "Please use rfc3986.validators.Validator instead. "
259 "This method will be eventually removed.",
260 DeprecationWarning,
261 )
262 return validators.fragment_is_valid(self.fragment, require)
264 def normalized_equality(self, other_ref: "uri.URIReference") -> bool:
265 """Compare this URIReference to another URIReference.
267 :param URIReference other_ref: (required), The reference with which
268 we're comparing.
269 :returns: ``True`` if the references are equal, ``False`` otherwise.
270 :rtype: bool
271 """
272 return tuple(self.normalize()) == tuple(other_ref.normalize())
274 def resolve_with( # noqa: C901
275 self,
276 base_uri: t.Union[str, "uri.URIReference"],
277 strict: bool = False,
278 ) -> _Self:
279 """Use an absolute URI Reference to resolve this relative reference.
281 Assuming this is a relative reference that you would like to resolve,
282 use the provided base URI to resolve it.
284 See http://tools.ietf.org/html/rfc3986#section-5 for more information.
286 :param base_uri: Either a string or URIReference. It must be an
287 absolute URI or it will raise an exception.
288 :returns: A new URIReference which is the result of resolving this
289 reference using ``base_uri``.
290 :rtype: :class:`URIReference`
291 :raises rfc3986.exceptions.ResolutionError:
292 If the ``base_uri`` does not at least have a scheme.
293 """
294 if not isinstance(base_uri, URIMixin):
295 base_uri = type(self).from_string(base_uri)
297 if t.TYPE_CHECKING:
298 base_uri = t.cast(uri.URIReference, base_uri)
300 try:
301 self._validator.validate(base_uri)
302 except exc.ValidationError:
303 raise exc.ResolutionError(base_uri)
305 # This is optional per
306 # http://tools.ietf.org/html/rfc3986#section-5.2.1
307 base_uri = base_uri.normalize()
309 # The reference we're resolving
310 resolving = self
312 if not strict and resolving.scheme == base_uri.scheme:
313 resolving = resolving.copy_with(scheme=None)
315 # http://tools.ietf.org/html/rfc3986#page-32
316 if resolving.scheme is not None:
317 target = resolving.copy_with(
318 path=normalizers.normalize_path(resolving.path)
319 )
320 else:
321 if resolving.authority is not None:
322 target = resolving.copy_with(
323 scheme=base_uri.scheme,
324 path=normalizers.normalize_path(resolving.path),
325 )
326 else:
327 if resolving.path is None:
328 if resolving.query is not None:
329 query = resolving.query
330 else:
331 query = base_uri.query
332 target = resolving.copy_with(
333 scheme=base_uri.scheme,
334 authority=base_uri.authority,
335 path=base_uri.path,
336 query=query,
337 )
338 else:
339 if resolving.path.startswith("/"):
340 path = normalizers.normalize_path(resolving.path)
341 else:
342 path = normalizers.normalize_path(
343 misc.merge_paths(base_uri, resolving.path)
344 )
345 target = resolving.copy_with(
346 scheme=base_uri.scheme,
347 authority=base_uri.authority,
348 path=path,
349 query=resolving.query,
350 )
351 return target
353 def unsplit(self) -> str:
354 """Create a URI string from the components.
356 :returns: The URI Reference reconstituted as a string.
357 :rtype: str
358 """
359 # See http://tools.ietf.org/html/rfc3986#section-5.3
360 result_list: list[str] = []
361 if self.scheme:
362 result_list.extend([self.scheme, ":"])
363 if self.authority:
364 result_list.extend(["//", self.authority])
365 if self.path:
366 result_list.append(self.path)
367 if self.query is not None:
368 result_list.extend(["?", self.query])
369 if self.fragment is not None:
370 result_list.extend(["#", self.fragment])
371 return "".join(result_list)
373 def copy_with(
374 self,
375 scheme: t.Optional[str] = misc.UseExisting,
376 authority: t.Optional[str] = misc.UseExisting,
377 path: t.Optional[str] = misc.UseExisting,
378 query: t.Optional[str] = misc.UseExisting,
379 fragment: t.Optional[str] = misc.UseExisting,
380 ) -> _Self:
381 """Create a copy of this reference with the new components.
383 :param str scheme:
384 (optional) The scheme to use for the new reference.
385 :param str authority:
386 (optional) The authority to use for the new reference.
387 :param str path:
388 (optional) The path to use for the new reference.
389 :param str query:
390 (optional) The query to use for the new reference.
391 :param str fragment:
392 (optional) The fragment to use for the new reference.
393 :returns:
394 New URIReference with provided components.
395 :rtype:
396 URIReference
397 """
398 attributes = {
399 "scheme": scheme,
400 "authority": authority,
401 "path": path,
402 "query": query,
403 "fragment": fragment,
404 }
405 for key, value in list(attributes.items()):
406 if value is misc.UseExisting:
407 del attributes[key]
408 uri: _Self = self._replace(**attributes)
409 uri.encoding = self.encoding
410 return uri