Class Config with descriptor and validator
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
"""
from abc import ABC, abstractmethod
import collections
from http import HTTPStatus
import logging
import os
from urllib import request
logging.basicConfig(level=logging.INFO)
class ConfigParameter():
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/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)
class ConfigValidParameter(ABC):
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/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)
self.validate(value)
setattr(obj, self.private_name, value)
@abstractmethod
def validate(self, value):
pass
class OneOf(ConfigValidParameter):
def __init__(self, options):
self.options = set(options)
def validate(self, value):
# Do we accept None value ?
if value is not in self.options:
raise ValueError(f'Expected {value!r} to be one of {self.options!r}')
class ValidURL(ConfigValidParameter):
def validate(self, value):
# Do we accept None value ?
response = request.urlopen(value)
if response.status != HTTPStatus.OK:
raise ValueError(f'Expected {value} should be a valid URL {response.status}')
class Config:
name = ConfigParameter()
mode = ConfigParameter()
url = ValidURL()
delay = ConfigParameter()
# format is not a Python keyword, https://docs.python.org/3.8/reference/lexical_analysis.html#keywords
format = OneOf('json', 'csv', 'xml')
maxitems = ConfigParameter()
def __init__(self, defaults):
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)
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(vars(c))
c.format = 'xml'
Please register or sign in to comment