Class Config with descriptor, special initialisation, heritage working code with __mro__
The snippet can be accessed without any authentication.
Authored by
Françoise Conil
"""
https://docs.python.org/3.9/howto/descriptor.html#complete-practical-example
Bon, une mise en place naïve de l'héritage ne se passe pas très bien car ce
sont des attributs de classe et on doit les initialiser via la classe dans
laquelle ils sont définis.
"""
import logging
import os
logging.basicConfig(level=logging.DEBUG)
class ConfigEntry:
"""Stores information about a config entry"""
def __init__(self, env=None, doc=None, default=None):
"""Initialize parameter's informations.
:param env: Name of environment variable for the parameter, defaults to None
:type env: str, optional
:param doc: Description of the parameter, defaults to None
:type doc: str, optional
:param default: Default value for the parameter, defaults to None
:type default: No default type, optional
"""
self.env = env
self.doc = doc
self.default = default
def __set_name__(self, owner, name):
"""Called at the time the owning class "owner" is created.
The descriptor has been assigned to "name".
https://docs.python.org/3/howto/descriptor.html#automatic-name-notification
https://docs.python.org/3/reference/datamodel.html#object.__set_name__
:param owner: Owning class
:type owner: Config class
:param name: Name assigned to this descriptor
:type name: str
"""
self.public_name = name
self.private_name = f"_{name}"
if self.env is None:
self.env = name.upper()
def __get__(self, obj, objtype=None):
value = getattr(obj, self.private_name)
logging.debug("Accessing %r giving %r", self.public_name, value)
return value
def __set__(self, obj, value):
logging.debug("Updating %r to %r", self.public_name, value)
setattr(obj, self.private_name, value)
def set_initial_value(self, obj):
logging.debug("Set initial value for %r", self.public_name)
self.__set__(obj, os.environ.get(self.env, self.default))
class ConfigGeneral:
"""Manage configuration parameters, get values from environment
variables or default value. Value can be changed easily thanks
to descriptors lookup."""
def __init__(self):
logging.debug("ConfigGeneral.__init__")
self.config_keys = {}
cls = type(self)
logging.debug("cls.__mro__=%s", cls.__mro__)
for cls_p in cls.__mro__:
self.initialize_class_params(cls_p)
def initialize_class_params(self, cls):
logging.debug("ConfigGeneral.initialize_class_params")
if issubclass(cls, ConfigGeneral):
logging.debug("cls.__name__=%s, vars(cls)=%s", cls.__name__, vars(cls))
cls_dict = cls.__dict__
for p in vars(cls):
if isinstance(cls_dict[p], ConfigEntry):
cls_dict[p].set_initial_value(self)
self.config_keys[p] = cls
def print_config(self):
logging.info("Class parameters")
for k, cls in self.config_keys.items():
logging.info("{%s} -> {%s}", k, cls.__dict__[k].__get__(self))
class ConfigHTTP(ConfigGeneral):
"""Contains configuration information for http_fetcher"""
# HTTP parameters
name = ConfigEntry(
env="HTTP_FETCHER_NAME", doc="Name of the acquisition", default=None
)
mode = ConfigEntry(env="HTTP_FETCHER_MODE", doc="http method", default="GET")
url = ConfigEntry(env="HTTP_FETCHER_URL", doc="URL to fetch", default=None)
delay = ConfigEntry(
env="HTTP_FETCHER_DELAY",
doc="Delay between requests in seconds",
default=3600,
)
format = ConfigEntry(env="HTTP_FETCHER_FORMAT", doc="File format", default="json")
maxitems = ConfigEntry(
env="HTTP_FETCHER_MAXITEMS", doc="Max number of fetches", default=-1
)
class ConfigHTTPFetcher(ConfigHTTP):
"""Contains configuration information for http_fetcher (HTTP and
RabbitMQ data)."""
# RabbitMQ parameters
broker = ConfigEntry(
env="RABBITMQ_BROKER", doc="Hostname or IP for rabbitmq broker", default=None
)
queue = ConfigEntry(
env="RABBITMQ_QUEUE",
doc="Name of the destination queue for fetched data",
default=None,
)
if __name__ == "__main__":
c1 = ConfigHTTPFetcher()
c1.print_config()
logging.debug(f"vars(c1) = {vars(c1)}")
c1.format = "xml"
c1.print_config()
logging.debug(f"vars(c1) = {vars(c1)}")
Please register or sign in to comment