""" https://docs.python.org/3.8/reference/datamodel.html#slots __slots__ are implemented at the class level by creating descriptors (Implementing Descriptors) for each variable name. """ import collections from http import HTTPStatus import os from urllib import request from urllib.error import URLError # For validation ACCEPTED_FORMATS = ('json', 'csv', 'xml') class Config: __slots__ = ('name', 'mode', 'url', 'delay', 'format', 'maxitems') def __init__(self, defaults): # Initialisation self.name = os.environ.get(defaults['name'].envvar, defaults['name'].default) self.mode = os.environ.get(defaults['mode'].envvar, defaults['mode'].default) self.url = os.environ.get(defaults['url'].envvar, defaults['url'].default) self.delay = os.environ.get(defaults['delay'].envvar, defaults['delay'].default) self.format = os.environ.get(defaults['format'].envvar, defaults['format'].default) self.maxitems = os.environ.get(defaults['maxitems'].envvar, defaults['maxitems'].default) def valid_format(self): # Do we accept None value ? if self.format not in ACCEPTED_FORMATS: raise ValueError(f"Expected {self.format!r} to be one of {ACCEPTED_FORMATS!r}") def valid_url(self): # Do we accept None value ? try: response = request.urlopen(self.url) except URLError as e: # Avoid showing all stacktrace with URLError and OSError # https://docs.python.org/3.9/tutorial/errors.html#exception-chaining raise ValueError(f'Expected {self.url} should is triggering an error {e}') from None else: # What about moved content 301 (MOVED_PERMANENTLY) or # cached content 304 (NOT_MODIFIED) if response.status != HTTPStatus.OK: raise ValueError(f'Expected {self.url} should be a valid URL {response.status}') if __name__ == "__main__": # Using namedtuple to better manipulated defaults # https://docs.python.org/3.8/library/collections.html#collections.namedtuple # --------------------------------------------------------------------------- fparam = collections.namedtuple('FetcherParameters', ['name', 'envvar', 'description', 'default']) fetcher_defaults = { 'name': fparam(name='name', envvar='HTTP_FETCHER_NAME', description='Name of the acquisition', default=None), 'mode': fparam(name='mode', envvar='HTTP_FETCHER_MODE', description='http method', default='GET'), 'url': fparam(name='url', envvar='HTTP_FETCHER_URL', description='URL to fetch', default=None), 'delay': fparam(name='delay', envvar='HTTP_FETCHER_DELAY', description='Delay between requests in seconds', default=3600), 'format': fparam(name='format', envvar='HTTP_FETCHER_FORMAT', description='File format', default='json'), 'maxitems': fparam(name='maxitems', envvar='HTTP_FETCHER_MAXITEMS', description='Max number of fetches', default=-1) } print(f"-- vars(Config) = {vars(Config)}") c = Config(fetcher_defaults) print("-- After instanciation") for k in fetcher_defaults.keys(): print(f"c.{k} = {getattr(c, k)}, ") c.format = 'xml' c.valid_format() print("-- After format change") for k in fetcher_defaults.keys(): print(f"c.{k} = {getattr(c, k)}, ") c.url = 'https://www.twitterfountain.com/' #c.url = 'https://download.data.grandlyon.com/ws/ldata/velov.stations/all.json?maxfeatures=5&start=1' c.valid_url()