from __future__ import annotations
import dataclasses
import urllib.parse
from abc import ABCMeta
from limits.errors import ConfigurationError
SCHEMES: dict[str, StorageRegistry] = {}
class StorageRegistry(ABCMeta):
def __new__(
mcs, name: str, bases: tuple[type, ...], dct: dict[str, str | list[str]]
) -> StorageRegistry:
storage_scheme = dct.get("STORAGE_SCHEME", None)
cls = super().__new__(mcs, name, bases, dct)
if storage_scheme:
if isinstance(storage_scheme, str): # noqa
schemes = [storage_scheme]
else:
schemes = storage_scheme
for scheme in schemes:
SCHEMES[scheme] = cls
return cls
@dataclasses.dataclass
class StorageURIOptions:
scheme: str
username: str | None
password: str | None
locations: list[tuple[str, int]]
path: str | None
query: dict[str, list[str]]
@property
def empty(self) -> bool:
"""
whether this is just a scheme:// uri without any information
that might be useful when constructing the actual storage
instance
"""
return bool(
self.username is None
and self.password is None
and not (self.locations or self.path or self.query)
)
def parse_storage_uri(uri: str) -> StorageURIOptions:
parsed = urllib.parse.urlparse(uri)
sep = parsed.netloc.find("@") + 1
locations = []
try:
if parsed.netloc[sep:]:
for loc in parsed.netloc[sep:].split(","):
sub = urllib.parse.urlparse(f"fake://{loc.strip()}")
hostname, port = sub.hostname, sub.port
if hostname is None or port is None:
raise ConfigurationError(f"Missing host or port in location {loc}")
locations.append((hostname, port))
except ValueError as err:
raise ConfigurationError(f"Unable to parse storage uri {uri}") from err
return StorageURIOptions(
parsed.scheme,
parsed.username,
parsed.password,
locations,
parsed.path,
urllib.parse.parse_qs(parsed.query),
)