""" https://docs.python.org/3.9/howto/descriptor.html#complete-practical-example In : type(c1).__dict__ Out: mappingproxy({'__module__': '__main__', 'name': <__main__.ConfigParameter at 0x7efe927a12e0>, 'mode': <__main__.ConfigParameter at 0x7efe927a1940>, 'url': <__main__.ConfigParameter at 0x7efe927a1be0>, 'delay': <__main__.ConfigParameter at 0x7efe927a1190>, 'format': <__main__.ConfigParameter at 0x7efeaae23a60>, 'maxitems': <__main__.ConfigParameter at 0x7efeaad81400>, '__init__': <function __main__.Config.__init__(self)>, '__dict__': <attribute '__dict__' of 'Config' objects>, '__weakref__': <attribute '__weakref__' of 'Config' objects>, '__doc__': None}) In : type(c1).__dict__['format'] Out: <__main__.ConfigParameter at 0x7efeaae23a60> In : type(c1).__dict__['format'].__dict__ Out: {'envvar': 'HTTP_FETCHER_FORMAT', 'description': 'File format', 'default': 'json', 'public_name': 'format', 'private_name': '_format'} Avec export HTTP_FETCHER_MODE="POST" In : c1.__dict__ Out: {'_name': None, '_mode': 'POST', '_url': None, '_delay': 3600, '_format': 'xml', '_maxitems': -1} """ import logging import os logging.basicConfig(level=logging.DEBUG) class ConfigParameter: def __init__(self, envvar=None, description=None, default=None): """ Should we define positionnal parameters or use defaut values as None or one other. """ self.envvar = envvar self.description = description 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__ """ self.public_name = name self.private_name = f"_{name}" def __get__(self, obj, objtype=None): """""" value = getattr(obj, self.private_name) logging.info("Accessing %r giving %r", self.public_name, value) return value def __set__(self, obj, value): logging.info("Updating %r to %r", self.public_name, value) setattr(obj, self.private_name, value) def set_initial_value(self, obj): logging.info("Set initial value for %r", self.public_name) self.__set__(obj, os.environ.get(self.envvar, self.default)) class Config: name = ConfigParameter( envvar="HTTP_FETCHER_NAME", description="Name of the acquisition", default=None ) mode = ConfigParameter( envvar="HTTP_FETCHER_MODE", description="http method", default="GET" ) url = ConfigParameter( envvar="HTTP_FETCHER_URL", description="URL to fetch", default=None ) delay = ConfigParameter( envvar="HTTP_FETCHER_DELAY", description="Delay between requests in seconds", default=3600, ) # format is not a Python keyword, # https://docs.python.org/3.8/reference/lexical_analysis.html#keywords format = ConfigParameter( envvar="HTTP_FETCHER_FORMAT", description="File format", default="json" ) maxitems = ConfigParameter( envvar="HTTP_FETCHER_MAXITEMS", description="Max number of fetches", default=-1 ) def __init__(self): for p in vars(type(self)): if isinstance(type(self).__dict__[p], ConfigParameter): type(self).__dict__[p].set_initial_value(self) if __name__ == "__main__": logging.debug(f"vars(Config) = {vars(Config)}") logging.debug(f"vars(vars(Config)['mode']) = {vars(vars(Config)['mode'])}") c1 = Config() logging.debug(vars(c1)) logging.debug(f"vars(Config)['mode'].__dict__ = {vars(Config)['mode'].__dict__}") c1.format = "xml" c2 = Config() c2.format = "csv" logging.debug(f"c1.format = {c1.format}, c2.format = {c2.format}")