import logging
from typing import Dict, ItemsView, Optional, Type, ValuesView
from urllib.request import urlopen
from .exceptions import TldImproperlyConfigured, TldIOError
from .helpers import project_dir
__author__ = "Artur Barseghyan"
__copyright__ = "2013-2026 Artur Barseghyan"
__license__ = "MPL-1.1 OR GPL-2.0-only OR LGPL-2.1-or-later"
__all__ = (
"BaseTLDSourceParser",
"Registry",
)
LOGGER = logging.getLogger(__name__)
[docs]
class Registry(type):
REGISTRY: Dict[str, Type["BaseTLDSourceParser"]] = {}
def __new__(mcs, name, bases, attrs): # noqa: N804
new_cls = type.__new__(mcs, name, bases, attrs)
# Here the name of the class is used as key but it could be any class
# parameter.
if getattr(new_cls, "_uid", None):
mcs.REGISTRY[new_cls._uid] = new_cls # type: ignore
return new_cls
@property
def _uid(cls) -> str:
return getattr(cls, "uid", cls.__name__)
[docs]
@classmethod
def reset(cls) -> None:
cls.REGISTRY = {}
[docs]
@classmethod
def get(
cls, key: str, default: Optional[Type["BaseTLDSourceParser"]] = None
) -> Optional[Type["BaseTLDSourceParser"]]:
return cls.REGISTRY.get(key, default)
[docs]
@classmethod
def items(cls) -> ItemsView[str, Type["BaseTLDSourceParser"]]:
return cls.REGISTRY.items()
[docs]
@classmethod
def values(cls) -> ValuesView[Type["BaseTLDSourceParser"]]:
return cls.REGISTRY.values()
# @classmethod
# def get_registry(mcs) -> Dict[str, Type]:
# return dict(mcs.REGISTRY)
#
# @classmethod
# def pop(mcs, uid) -> None:
# mcs.REGISTRY.pop(uid)
[docs]
class BaseTLDSourceParser(metaclass=Registry):
"""Base TLD source parser."""
uid: Optional[str] = None
source_url: str
local_path: str
include_private: bool = True
[docs]
@classmethod
def validate(cls):
"""Constructor."""
if not cls.uid:
raise TldImproperlyConfigured(
"The `uid` property of the TLD source parser shall be defined."
)
[docs]
@classmethod
def get_tld_names(cls, fail_silently: bool = False, retry_count: int = 0):
"""Get tld names.
:param fail_silently:
:param retry_count:
:return:
"""
cls.validate()
raise NotImplementedError(
"Your TLD source parser shall implement `get_tld_names` method."
)
[docs]
@classmethod
def update_tld_names(cls, fail_silently: bool = False) -> bool:
"""Update the local copy of the TLD file.
:param fail_silently:
:return:
"""
try:
remote_file = urlopen(cls.source_url)
local_file_abs_path = project_dir(cls.local_path)
with open(local_file_abs_path, "w", encoding="utf8") as local_file:
local_file.write(remote_file.read().decode("utf8"))
remote_file.close()
LOGGER.info(
f"Fetched '{cls.source_url}' as '{local_file_abs_path}'"
)
except Exception as err:
LOGGER.error(
f"Failed fetching '{cls.source_url}'. Reason: {str(err)}"
)
if fail_silently:
return False
raise TldIOError(err) from err
return True