mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
106 lines
3.3 KiB
Python
106 lines
3.3 KiB
Python
import re
|
|
from importlib import import_module
|
|
from typing import Tuple
|
|
|
|
PRETTY_REGEX = re.compile(r'([\w ]*?) *<(.*)> *')
|
|
|
|
# max length for domain name labels is 63 characters per RFC 1034
|
|
EMAIL_REGEX = re.compile(
|
|
r'[^\s@\u0000-\u0020"\'`,]+@'
|
|
r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z0-9]{2,63})',
|
|
re.I
|
|
)
|
|
|
|
|
|
def validate_email(value) -> Tuple[str, str]:
|
|
"""
|
|
Brutally simple email address validation. Note unlike most email address validation
|
|
* raw ip address (literal) domain parts are not allowed.
|
|
* "John Doe <local_part@domain.com>" style "pretty" email addresses are processed
|
|
* the local part check is extremely basic. This raises the possibility of unicode spoofing, but no better
|
|
solution is really possible.
|
|
* spaces are striped from the beginning and end of addresses but no error is raised
|
|
|
|
See RFC 5322 but treat it with suspicion, there seems to exist no universally acknowledged test for a valid email!
|
|
"""
|
|
m = PRETTY_REGEX.fullmatch(value)
|
|
if m:
|
|
name, value = m.groups()
|
|
else:
|
|
name = None
|
|
|
|
email = value.strip()
|
|
if not EMAIL_REGEX.fullmatch(email):
|
|
raise ValueError('Email address is not valid')
|
|
return name or email[:email.index('@')], email.lower()
|
|
|
|
|
|
def _rfc_1738_quote(text):
|
|
return re.sub(r'[:@/]', lambda m: '%{:X}'.format(ord(m.group(0))), text)
|
|
|
|
|
|
def make_dsn(*,
|
|
driver: str,
|
|
user: str=None,
|
|
password: str=None,
|
|
host: str=None,
|
|
port: str=None,
|
|
name: str=None,
|
|
query: str=None):
|
|
"""
|
|
Create a DSN from from connection settings.
|
|
|
|
Stolen approximately from sqlalchemy/engine/url.py:URL.
|
|
"""
|
|
s = driver + '://'
|
|
if user is not None:
|
|
s += _rfc_1738_quote(user)
|
|
if password is not None:
|
|
s += ':' + _rfc_1738_quote(password)
|
|
s += '@'
|
|
if host is not None:
|
|
if ':' in host:
|
|
s += '[{}]'.format(host)
|
|
else:
|
|
s += host
|
|
if port is not None:
|
|
s += ':{}'.format(int(port))
|
|
if name is not None:
|
|
s += '/' + name
|
|
query = query or {}
|
|
if query:
|
|
keys = list(query)
|
|
keys.sort()
|
|
s += '?' + '&'.join('{}={}'.format(k, query[k]) for k in keys)
|
|
return s
|
|
|
|
|
|
def import_string(dotted_path):
|
|
"""
|
|
Stolen approximately from django. Import a dotted module path and return the attribute/class designated by the
|
|
last name in the path. Raise ImportError if the import fails.
|
|
"""
|
|
try:
|
|
module_path, class_name = dotted_path.strip(' ').rsplit('.', 1)
|
|
except ValueError as e:
|
|
raise ImportError(f'"{dotted_path}" doesn\'t look like a module path') from e
|
|
|
|
module = import_module(module_path)
|
|
try:
|
|
return getattr(module, class_name)
|
|
except AttributeError as e:
|
|
raise ImportError(f'Module "{module_path}" does not define a "{class_name}" attribute') from e
|
|
|
|
|
|
def truncate(v, *, max_len=80):
|
|
"""
|
|
Truncate a value and add a unicode ellipsis (three dots) to the end if it was too long
|
|
"""
|
|
if isinstance(v, str) and len(v) > (max_len - 2):
|
|
# -3 so quote + string + … + quote has correct length
|
|
return repr(v[:(max_len - 3)] + '…')
|
|
v = repr(v)
|
|
if len(v) > max_len:
|
|
v = v[:max_len - 1] + '…'
|
|
return v
|