You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
5.3 KiB
148 lines
5.3 KiB
5 years ago
|
"""Module containing the implementation of the IRIReference class."""
|
||
|
# -*- coding: utf-8 -*-
|
||
|
# Copyright (c) 2014 Rackspace
|
||
|
# Copyright (c) 2015 Ian Stapleton Cordasco
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||
|
# implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
from collections import namedtuple
|
||
|
|
||
|
from . import compat
|
||
|
from . import exceptions
|
||
|
from . import misc
|
||
|
from . import normalizers
|
||
|
from . import uri
|
||
|
|
||
|
|
||
|
try:
|
||
|
import idna
|
||
|
except ImportError: # pragma: no cover
|
||
|
idna = None
|
||
|
|
||
|
|
||
|
class IRIReference(namedtuple('IRIReference', misc.URI_COMPONENTS),
|
||
|
uri.URIMixin):
|
||
|
"""Immutable object representing a parsed IRI Reference.
|
||
|
|
||
|
Can be encoded into an URIReference object via the procedure
|
||
|
specified in RFC 3987 Section 3.1
|
||
|
|
||
|
.. note::
|
||
|
The IRI submodule is a new interface and may possibly change in
|
||
|
the future. Check for changes to the interface when upgrading.
|
||
|
"""
|
||
|
|
||
|
slots = ()
|
||
|
|
||
|
def __new__(cls, scheme, authority, path, query, fragment,
|
||
|
encoding='utf-8'):
|
||
|
"""Create a new IRIReference."""
|
||
|
ref = super(IRIReference, cls).__new__(
|
||
|
cls,
|
||
|
scheme or None,
|
||
|
authority or None,
|
||
|
path or None,
|
||
|
query,
|
||
|
fragment)
|
||
|
ref.encoding = encoding
|
||
|
return ref
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
"""Compare this reference to another."""
|
||
|
other_ref = other
|
||
|
if isinstance(other, tuple):
|
||
|
other_ref = self.__class__(*other)
|
||
|
elif not isinstance(other, IRIReference):
|
||
|
try:
|
||
|
other_ref = self.__class__.from_string(other)
|
||
|
except TypeError:
|
||
|
raise TypeError(
|
||
|
'Unable to compare {0}() to {1}()'.format(
|
||
|
type(self).__name__, type(other).__name__))
|
||
|
|
||
|
# See http://tools.ietf.org/html/rfc3986#section-6.2
|
||
|
return tuple(self) == tuple(other_ref)
|
||
|
|
||
|
def _match_subauthority(self):
|
||
|
return misc.ISUBAUTHORITY_MATCHER.match(self.authority)
|
||
|
|
||
|
@classmethod
|
||
|
def from_string(cls, iri_string, encoding='utf-8'):
|
||
|
"""Parse a IRI reference from the given unicode IRI string.
|
||
|
|
||
|
:param str iri_string: Unicode IRI to be parsed into a reference.
|
||
|
:param str encoding: The encoding of the string provided
|
||
|
:returns: :class:`IRIReference` or subclass thereof
|
||
|
"""
|
||
|
iri_string = compat.to_str(iri_string, encoding)
|
||
|
|
||
|
split_iri = misc.IRI_MATCHER.match(iri_string).groupdict()
|
||
|
return cls(
|
||
|
split_iri['scheme'], split_iri['authority'],
|
||
|
normalizers.encode_component(split_iri['path'], encoding),
|
||
|
normalizers.encode_component(split_iri['query'], encoding),
|
||
|
normalizers.encode_component(split_iri['fragment'], encoding),
|
||
|
encoding,
|
||
|
)
|
||
|
|
||
|
def encode(self, idna_encoder=None): # noqa: C901
|
||
|
"""Encode an IRIReference into a URIReference instance.
|
||
|
|
||
|
If the ``idna`` module is installed or the ``rfc3986[idna]``
|
||
|
extra is used then unicode characters in the IRI host
|
||
|
component will be encoded with IDNA2008.
|
||
|
|
||
|
:param idna_encoder:
|
||
|
Function that encodes each part of the host component
|
||
|
If not given will raise an exception if the IRI
|
||
|
contains a host component.
|
||
|
:rtype: uri.URIReference
|
||
|
:returns: A URI reference
|
||
|
"""
|
||
|
authority = self.authority
|
||
|
if authority:
|
||
|
if idna_encoder is None:
|
||
|
if idna is None: # pragma: no cover
|
||
|
raise exceptions.MissingDependencyError(
|
||
|
"Could not import the 'idna' module "
|
||
|
"and the IRI hostname requires encoding"
|
||
|
)
|
||
|
|
||
|
def idna_encoder(name):
|
||
|
if any(ord(c) > 128 for c in name):
|
||
|
try:
|
||
|
return idna.encode(name.lower(),
|
||
|
strict=True,
|
||
|
std3_rules=True)
|
||
|
except idna.IDNAError:
|
||
|
raise exceptions.InvalidAuthority(self.authority)
|
||
|
return name
|
||
|
|
||
|
authority = ""
|
||
|
if self.host:
|
||
|
authority = ".".join([compat.to_str(idna_encoder(part))
|
||
|
for part in self.host.split(".")])
|
||
|
|
||
|
if self.userinfo is not None:
|
||
|
authority = (normalizers.encode_component(
|
||
|
self.userinfo, self.encoding) + '@' + authority)
|
||
|
|
||
|
if self.port is not None:
|
||
|
authority += ":" + str(self.port)
|
||
|
|
||
|
return uri.URIReference(self.scheme,
|
||
|
authority,
|
||
|
path=self.path,
|
||
|
query=self.query,
|
||
|
fragment=self.fragment,
|
||
|
encoding=self.encoding)
|