-
-
Save icaine/f0c1fe53d10f8d3607a755e357b5b3c2 to your computer and use it in GitHub Desktop.
Class for working with URLs rewritten from php Nette framework.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from urllib.parse import urlparse, quote, unquote, parse_qs | |
from socket import inet_aton | |
from struct import unpack | |
from django.utils.datastructures import MultiValueDict | |
from django.utils.http import urlencode | |
def parse_query(s): | |
return MultiValueDict(parse_qs(s)) | |
def ip2long(ip_addr): | |
try: | |
return unpack("!L", inet_aton(ip_addr))[0] | |
except OSError as e: | |
raise ValueError(e) | |
class Url: | |
""" | |
Mutable representation of a URL. | |
scheme user password host port basePath relativeUrl | |
| | | | | | | | |
/--\ /--\ /------\ /-------\ /--\/--\/----------------------------\ | |
http://john:x0y17575@nette.org:8042/en/manual.php?name=param#fragment <-- absoluteUrl | |
\__________________________/\____________/^\________/^\______/ | |
| | | | | |
authority path query fragment | |
""" | |
DEFAULT_PORTS = { | |
'http': 80, | |
'https': 443, | |
'ftp': 21, | |
} | |
def __init__(self, url=None): | |
self._scheme = self._user = self._password = \ | |
self._host = self._port = self._path = \ | |
self._query = self._fragment = None | |
if isinstance(url, Url): | |
self.scheme, \ | |
self.user, \ | |
self.password, \ | |
self.host, \ | |
self.port, \ | |
self.path, \ | |
self.query, \ | |
self.fragment = url._export() | |
else: | |
p = urlparse(url) | |
self.scheme = p.scheme or '' | |
self.port = p.port or None | |
self.host = unquote(p.hostname or '') | |
self.user = unquote(p.username or '') | |
self.password = unquote(p.password or '') | |
self.path = p.path or '' | |
self.query = p.query or MultiValueDict() | |
self.fragment = unquote(p.fragment or '') | |
@property | |
def scheme(self): | |
return self._scheme | |
@scheme.setter | |
def scheme(self, scheme): | |
self._scheme = scheme | |
@property | |
def user(self): | |
return self._user | |
@user.setter | |
def user(self, user): | |
self._user = user | |
@property | |
def password(self): | |
return self._password | |
@password.setter | |
def password(self, password): | |
self._password = password | |
@property | |
def host(self): | |
return self._host | |
@host.setter | |
def host(self, host): | |
self._host = host | |
self.path = self._path | |
def get_domain(self, level=2): | |
try: | |
ip2long(self.host) | |
except ValueError: | |
parts = [self.host] | |
else: | |
parts = self.host.split('.') | |
return '.'.join(parts[-level:] if level >= 0 else parts[:level]) | |
@property | |
def port(self): | |
return self._port or self.DEFAULT_PORTS.get(self.scheme) | |
@port.setter | |
def port(self, port): | |
self._port = port and int(port) | |
@property | |
def path(self): | |
return self._path | |
@path.setter | |
def path(self, path): | |
self._path = str(path) | |
if self.host and self._path[0:1] != '/': | |
self._path = '/{}'.format(self._path) | |
@property | |
def query_dict(self): | |
return self._query.copy() | |
@property | |
def query(self): | |
q = MultiValueDict({k: v for k, v in sorted(self._query.lists(), key=lambda i: i[0])}) | |
return urlencode(q, doseq=True) | |
@query.setter | |
def query(self, query): | |
if isinstance(query, str): | |
self._query = parse_query(query) | |
else: | |
self._query = MultiValueDict() | |
self._query.update(query) | |
def append_query(self, query): | |
self._query.update(parse_query(query) if isinstance(query, str) else query) | |
def set_query_key(self, key, val): | |
self._query[key] = val | |
def get_query_key(self, key, default=None): | |
self._query.get(key, default) | |
def del_query_key(self, *keys): | |
for key in keys: | |
try: | |
del self._query[key] | |
except KeyError: | |
pass | |
@property | |
def fragment(self): | |
return self._fragment | |
@fragment.setter | |
def fragment(self, fragment): | |
self._fragment = fragment | |
@property | |
def absolute_url(self): | |
return '{}{}{}'.format( | |
self.host_url, | |
self.base_path, | |
self.relative_url | |
) | |
@property | |
def relative_url(self): | |
query = self.query | |
return '{}{}{}'.format( | |
self.relative_path, | |
query and '?{}'.format(query), | |
self.fragment and '#{}'.format(self.fragment) | |
) | |
@property | |
def base_url(self): | |
return '{}{}'.format(self.base_path, self.relative_url) | |
@property | |
def base_path(self): | |
return '{}/'.format(self.path.rpartition('/')[0]) | |
@property | |
def relative_path(self): | |
return self.path.rpartition('/')[2] | |
@property | |
def authority(self): | |
""" Returns the [user[:pass]@]host[:port] part of URI. """ | |
if self.host == '': | |
return '' | |
user = quote(self.user) | |
if user != '': | |
if self.password != '': | |
user = '{}:{}'.format(user, quote(self.password)) | |
user = '{}@'.format(user) | |
default_port = self.DEFAULT_PORTS.get(self.scheme) | |
return '{}{}{}'.format( | |
user, | |
self.host, | |
self.port and default_port != self.port and ':{}'.format(self.port) or '' | |
) | |
@property | |
def host_url(self): | |
""" Returns scheme and authority part of URI """ | |
authority = self.authority | |
return '{}{}'.format( | |
authority and self.scheme and '{}:'.format(self.scheme), | |
authority and '//{}'.format(authority), | |
) | |
def _export(self): | |
return ( | |
self._scheme, | |
self._user, | |
self._password, | |
self._host, | |
self._port, | |
self._path, | |
self._query, | |
self._fragment, | |
) | |
def __str__(self) -> str: | |
return self.absolute_url |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment