source: uri/rfc3986/_mixin.py@ 1351

Last change on this file since 1351 was 230, checked in by wouter, 3 years ago

#91 clone https://pypi.org/project/rfc3986/

File size: 13.0 KB
Line 
1"""Module containing the implementation of the URIMixin class."""
2import warnings
3
4from . import exceptions as exc
5from . import misc
6from . import normalizers
7from . import validators
8
9
10class URIMixin(object):
11 """Mixin with all shared methods for URIs and IRIs."""
12
13 __hash__ = tuple.__hash__
14
15 def authority_info(self):
16 """Return a dictionary with the ``userinfo``, ``host``, and ``port``.
17
18 If the authority is not valid, it will raise a
19 :class:`~rfc3986.exceptions.InvalidAuthority` Exception.
20
21 :returns:
22 ``{'userinfo': 'username:password', 'host': 'www.example.com',
23 'port': '80'}``
24 :rtype: dict
25 :raises rfc3986.exceptions.InvalidAuthority:
26 If the authority is not ``None`` and can not be parsed.
27 """
28 if not self.authority:
29 return {"userinfo": None, "host": None, "port": None}
30
31 match = self._match_subauthority()
32
33 if match is None:
34 # In this case, we have an authority that was parsed from the URI
35 # Reference, but it cannot be further parsed by our
36 # misc.SUBAUTHORITY_MATCHER. In this case it must not be a valid
37 # authority.
38 raise exc.InvalidAuthority(self.authority.encode(self.encoding))
39
40 # We had a match, now let's ensure that it is actually a valid host
41 # address if it is IPv4
42 matches = match.groupdict()
43 host = matches.get("host")
44
45 if (
46 host
47 and misc.IPv4_MATCHER.match(host)
48 and not validators.valid_ipv4_host_address(host)
49 ):
50 # If we have a host, it appears to be IPv4 and it does not have
51 # valid bytes, it is an InvalidAuthority.
52 raise exc.InvalidAuthority(self.authority.encode(self.encoding))
53
54 return matches
55
56 def _match_subauthority(self):
57 return misc.SUBAUTHORITY_MATCHER.match(self.authority)
58
59 @property
60 def host(self):
61 """If present, a string representing the host."""
62 try:
63 authority = self.authority_info()
64 except exc.InvalidAuthority:
65 return None
66 return authority["host"]
67
68 @property
69 def port(self):
70 """If present, the port extracted from the authority."""
71 try:
72 authority = self.authority_info()
73 except exc.InvalidAuthority:
74 return None
75 return authority["port"]
76
77 @property
78 def userinfo(self):
79 """If present, the userinfo extracted from the authority."""
80 try:
81 authority = self.authority_info()
82 except exc.InvalidAuthority:
83 return None
84 return authority["userinfo"]
85
86 def is_absolute(self):
87 """Determine if this URI Reference is an absolute URI.
88
89 See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation.
90
91 :returns: ``True`` if it is an absolute URI, ``False`` otherwise.
92 :rtype: bool
93 """
94 return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit()))
95
96 def is_valid(self, **kwargs):
97 """Determine if the URI is valid.
98
99 .. deprecated:: 1.1.0
100
101 Use the :class:`~rfc3986.validators.Validator` object instead.
102
103 :param bool require_scheme: Set to ``True`` if you wish to require the
104 presence of the scheme component.
105 :param bool require_authority: Set to ``True`` if you wish to require
106 the presence of the authority component.
107 :param bool require_path: Set to ``True`` if you wish to require the
108 presence of the path component.
109 :param bool require_query: Set to ``True`` if you wish to require the
110 presence of the query component.
111 :param bool require_fragment: Set to ``True`` if you wish to require
112 the presence of the fragment component.
113 :returns: ``True`` if the URI is valid. ``False`` otherwise.
114 :rtype: bool
115 """
116 warnings.warn(
117 "Please use rfc3986.validators.Validator instead. "
118 "This method will be eventually removed.",
119 DeprecationWarning,
120 )
121 validators = [
122 (self.scheme_is_valid, kwargs.get("require_scheme", False)),
123 (self.authority_is_valid, kwargs.get("require_authority", False)),
124 (self.path_is_valid, kwargs.get("require_path", False)),
125 (self.query_is_valid, kwargs.get("require_query", False)),
126 (self.fragment_is_valid, kwargs.get("require_fragment", False)),
127 ]
128 return all(v(r) for v, r in validators)
129
130 def authority_is_valid(self, require=False):
131 """Determine if the authority component is valid.
132
133 .. deprecated:: 1.1.0
134
135 Use the :class:`~rfc3986.validators.Validator` object instead.
136
137 :param bool require:
138 Set to ``True`` to require the presence of this component.
139 :returns:
140 ``True`` if the authority is valid. ``False`` otherwise.
141 :rtype:
142 bool
143 """
144 warnings.warn(
145 "Please use rfc3986.validators.Validator instead. "
146 "This method will be eventually removed.",
147 DeprecationWarning,
148 )
149 try:
150 self.authority_info()
151 except exc.InvalidAuthority:
152 return False
153
154 return validators.authority_is_valid(
155 self.authority,
156 host=self.host,
157 require=require,
158 )
159
160 def scheme_is_valid(self, require=False):
161 """Determine if the scheme component is valid.
162
163 .. deprecated:: 1.1.0
164
165 Use the :class:`~rfc3986.validators.Validator` object instead.
166
167 :param str require: Set to ``True`` to require the presence of this
168 component.
169 :returns: ``True`` if the scheme is valid. ``False`` otherwise.
170 :rtype: bool
171 """
172 warnings.warn(
173 "Please use rfc3986.validators.Validator instead. "
174 "This method will be eventually removed.",
175 DeprecationWarning,
176 )
177 return validators.scheme_is_valid(self.scheme, require)
178
179 def path_is_valid(self, require=False):
180 """Determine if the path component is valid.
181
182 .. deprecated:: 1.1.0
183
184 Use the :class:`~rfc3986.validators.Validator` object instead.
185
186 :param str require: Set to ``True`` to require the presence of this
187 component.
188 :returns: ``True`` if the path is valid. ``False`` otherwise.
189 :rtype: bool
190 """
191 warnings.warn(
192 "Please use rfc3986.validators.Validator instead. "
193 "This method will be eventually removed.",
194 DeprecationWarning,
195 )
196 return validators.path_is_valid(self.path, require)
197
198 def query_is_valid(self, require=False):
199 """Determine if the query component is valid.
200
201 .. deprecated:: 1.1.0
202
203 Use the :class:`~rfc3986.validators.Validator` object instead.
204
205 :param str require: Set to ``True`` to require the presence of this
206 component.
207 :returns: ``True`` if the query is valid. ``False`` otherwise.
208 :rtype: bool
209 """
210 warnings.warn(
211 "Please use rfc3986.validators.Validator instead. "
212 "This method will be eventually removed.",
213 DeprecationWarning,
214 )
215 return validators.query_is_valid(self.query, require)
216
217 def fragment_is_valid(self, require=False):
218 """Determine if the fragment component is valid.
219
220 .. deprecated:: 1.1.0
221
222 Use the Validator object instead.
223
224 :param str require: Set to ``True`` to require the presence of this
225 component.
226 :returns: ``True`` if the fragment is valid. ``False`` otherwise.
227 :rtype: bool
228 """
229 warnings.warn(
230 "Please use rfc3986.validators.Validator instead. "
231 "This method will be eventually removed.",
232 DeprecationWarning,
233 )
234 return validators.fragment_is_valid(self.fragment, require)
235
236 def normalized_equality(self, other_ref):
237 """Compare this URIReference to another URIReference.
238
239 :param URIReference other_ref: (required), The reference with which
240 we're comparing.
241 :returns: ``True`` if the references are equal, ``False`` otherwise.
242 :rtype: bool
243 """
244 return tuple(self.normalize()) == tuple(other_ref.normalize())
245
246 def resolve_with(self, base_uri, strict=False):
247 """Use an absolute URI Reference to resolve this relative reference.
248
249 Assuming this is a relative reference that you would like to resolve,
250 use the provided base URI to resolve it.
251
252 See http://tools.ietf.org/html/rfc3986#section-5 for more information.
253
254 :param base_uri: Either a string or URIReference. It must be an
255 absolute URI or it will raise an exception.
256 :returns: A new URIReference which is the result of resolving this
257 reference using ``base_uri``.
258 :rtype: :class:`URIReference`
259 :raises rfc3986.exceptions.ResolutionError:
260 If the ``base_uri`` is not an absolute URI.
261 """
262 if not isinstance(base_uri, URIMixin):
263 base_uri = type(self).from_string(base_uri)
264
265 if not base_uri.is_absolute():
266 raise exc.ResolutionError(base_uri)
267
268 # This is optional per
269 # http://tools.ietf.org/html/rfc3986#section-5.2.1
270 base_uri = base_uri.normalize()
271
272 # The reference we're resolving
273 resolving = self
274
275 if not strict and resolving.scheme == base_uri.scheme:
276 resolving = resolving.copy_with(scheme=None)
277
278 # http://tools.ietf.org/html/rfc3986#page-32
279 if resolving.scheme is not None:
280 target = resolving.copy_with(
281 path=normalizers.normalize_path(resolving.path)
282 )
283 else:
284 if resolving.authority is not None:
285 target = resolving.copy_with(
286 scheme=base_uri.scheme,
287 path=normalizers.normalize_path(resolving.path),
288 )
289 else:
290 if resolving.path is None:
291 if resolving.query is not None:
292 query = resolving.query
293 else:
294 query = base_uri.query
295 target = resolving.copy_with(
296 scheme=base_uri.scheme,
297 authority=base_uri.authority,
298 path=base_uri.path,
299 query=query,
300 )
301 else:
302 if resolving.path.startswith("/"):
303 path = normalizers.normalize_path(resolving.path)
304 else:
305 path = normalizers.normalize_path(
306 misc.merge_paths(base_uri, resolving.path)
307 )
308 target = resolving.copy_with(
309 scheme=base_uri.scheme,
310 authority=base_uri.authority,
311 path=path,
312 query=resolving.query,
313 )
314 return target
315
316 def unsplit(self):
317 """Create a URI string from the components.
318
319 :returns: The URI Reference reconstituted as a string.
320 :rtype: str
321 """
322 # See http://tools.ietf.org/html/rfc3986#section-5.3
323 result_list = []
324 if self.scheme:
325 result_list.extend([self.scheme, ":"])
326 if self.authority:
327 result_list.extend(["//", self.authority])
328 if self.path:
329 result_list.append(self.path)
330 if self.query is not None:
331 result_list.extend(["?", self.query])
332 if self.fragment is not None:
333 result_list.extend(["#", self.fragment])
334 return "".join(result_list)
335
336 def copy_with(
337 self,
338 scheme=misc.UseExisting,
339 authority=misc.UseExisting,
340 path=misc.UseExisting,
341 query=misc.UseExisting,
342 fragment=misc.UseExisting,
343 ):
344 """Create a copy of this reference with the new components.
345
346 :param str scheme:
347 (optional) The scheme to use for the new reference.
348 :param str authority:
349 (optional) The authority to use for the new reference.
350 :param str path:
351 (optional) The path to use for the new reference.
352 :param str query:
353 (optional) The query to use for the new reference.
354 :param str fragment:
355 (optional) The fragment to use for the new reference.
356 :returns:
357 New URIReference with provided components.
358 :rtype:
359 URIReference
360 """
361 attributes = {
362 "scheme": scheme,
363 "authority": authority,
364 "path": path,
365 "query": query,
366 "fragment": fragment,
367 }
368 for key, value in list(attributes.items()):
369 if value is misc.UseExisting:
370 del attributes[key]
371 uri = self._replace(**attributes)
372 uri.encoding = self.encoding
373 return uri
Note: See TracBrowser for help on using the repository browser.