Skip to content

Instantly share code, notes, and snippets.

@icaine

icaine/urls.py Secret

Created April 2, 2021 20:41
Show Gist options
  • Save icaine/f0c1fe53d10f8d3607a755e357b5b3c2 to your computer and use it in GitHub Desktop.
Save icaine/f0c1fe53d10f8d3607a755e357b5b3c2 to your computer and use it in GitHub Desktop.
Class for working with URLs rewritten from php Nette framework.
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