PK!J22confect/__init__.py__version__ = '0.1.0' from .conf_ import Conf from .error import (ConfGroupExistsError, FrozenConfGroupError, FrozenConfPropError, UnknownConfError) conf = Conf() __all__ = [ Conf, conf, FrozenConfPropError, FrozenConfGroupError, UnknownConfError, ConfGroupExistsError ] PK!e5M&M&confect/conf_.pyimport importlib import logging import weakref from contextlib import contextmanager from copy import deepcopy from confect.error import (ConfGroupExistsError, FrozenConfGroupError, FrozenConfPropError, UnknownConfError) logger = logging.getLogger(__name__) def _get_obj_attr(obj, attr): return object.__getattribute__(obj, attr) class Conf: '''Configuration Manager >>> conf = Conf() >>> conf.add_group('dummy', opt1=3, opt2='some string') >>> conf.dummy.opt1 3 >>> conf.dummy.opt2 = 'other string' Traceback (most recent call last): ... confect.error.FrozenConfPropError: Configuration properties are frozen. Configuration properties can only be changed globally by loading configuration file through ``Conf.load_conf_file()`` and ``Conf.load_conf_module()``. And it can be changed locally in the context created by `Conf.local_env()`. ''' # noqa __slots__ = ('_is_setting_imported', '_is_frozen', '_conf_depot', '_conf_groups', '__weakref__', ) def __init__(self): from confect.conf_depot import ConfDepot self._is_setting_imported = False self._is_frozen = True self._conf_depot = ConfDepot() self._conf_groups = {} def add_group(self, name, **default_properties): '''Add new configuration group and all property names with default values >>> conf = Conf() Add new group and properties through context manager >>> with conf.add_group('yummy') as yummy: ... yummy.kind='seafood' ... yummy.name='fish' >>> conf.yummy.name 'fish' Add new group and properties through function call >>> conf.add_group('dummy', num_prop=3, str_prop='some string') >>> conf.dummy.num_prop 3 ''' if name in self._conf_groups: raise ConfGroupExistsError( f'configuration group {name!r} already exists') with self._mutable_conf_ctx(): group = ConfGroup(weakref.proxy(self), name, default_properties) self._conf_groups[name] = group if not default_properties: return group._default_setter() def _backup(self): return deepcopy(self._conf_groups) def _restore(self, conf_groups): self._conf_groups = conf_groups @contextmanager def local_env(self): '''Return a context manager that makes this Conf mutable temporarily. All configuration properties will be restored upon completion of the block. >>> conf = Conf() >>> conf.add_group('dummy', opt1=3, opt2='some string') >>> with conf.local_env(): ... conf.dummy.opt1 = 5 ... conf.dummy.opt1 5 >>> conf.dummy.opt1 3 ''' # noqa conf_groups_backup = self._backup() with self._mutable_conf_ctx(): yield self._restore(conf_groups_backup) @contextmanager def _mutable_conf_ctx(self): self._is_frozen = False yield self._is_frozen = True @contextmanager def _confect_c_ctx(self): import confect confect.c = self._conf_depot yield del confect.c def __contains__(self, group_name): return group_name in self._conf_groups def __getitem__(self, group_name): return getattr(self, group_name) def __getattr__(self, group_name): conf_groups = _get_obj_attr(self, '_conf_groups') conf_depot = _get_obj_attr(self, '_conf_depot') if group_name not in conf_groups: raise UnknownConfError( f'Unknown configuration group {group_name!r}') conf_group = conf_groups[group_name] if group_name in conf_depot: conf_depot_group = conf_depot[group_name] conf_group._update_from_conf_depot_group(conf_depot_group) del conf_depot[group_name] return conf_group def __setattr__(self, name, value): if name in self.__slots__: object.__setattr__(self, name, value) else: raise FrozenConfGroupError( 'Configuration groups are frozen. ' 'Call `confect.add_group()` for ' 'registering new configuration group.' ) def __dir__(self): return self._conf_groups.__dir__() def __deepcopy__(self, memo): cls = type(self) new_self = cls.__new__(cls) new_self._is_setting_imported = self._is_setting_imported new_self._is_frozen = self._is_frozen new_self._conf_depot = deepcopy(self._conf_depot) new_self._conf_groups = deepcopy(self._conf_groups) for group in new_self._conf_groups.values(): group._conf = weakref.proxy(new_self) return new_self def load_conf_file(self, path): '''Load python configuration file through file path. All configuration groups and properties should be added through Conf.add_group() in your source code. Otherwise, it won't be accessable even if it is in configuration file. >>> conf = Conf() >>> conf.load_conf_file('path/to/conf.py') # doctest: +SKIP Configuration file example ``` from confect import c c.yammy.kind = 'seafood' c.yammy.name = 'fish' ``` ''' # noqa from pathlib import Path if not isinstance(path, Path): path = Path(path) with self._mutable_conf_ctx(): with self._confect_c_ctx(): exec(path.open('r').read()) def load_conf_module(self, module_name): '''Load python configuration file through import. The module should be importable either through PYTHONPATH or was install as a package. All configuration groups and properties should be added through Conf.add_group() in your source code. Otherwise, it won't be accessable even if it is in configuration file. >>> conf = Conf() >>> conf.load_conf_file('path/to/conf.py') # doctest: +SKIP Configuration file example ``` from confect import c c.yammy.kind = 'seafood' c.yammy.name = 'fish' ``` ''' # noqa with self._mutable_conf_ctx(): with self._confect_c_ctx(): importlib.import_module(module_name) def set_conf_file(self, path): ''' ''' def set_conf_module(self, module_name): pass class ConfGroupDefaultSetter: __slots__ = '_conf_group' def __init__(self, conf_group): self._conf_group = conf_group def __getattr__(self, property_name): return getattr(self._conf_group, property_name) def __setattr__(self, property_name, value): if property_name in self.__slots__: object.__setattr__(self, property_name, value) else: self._conf_group._defaults[property_name] = value class ConfGroup: __slots__ = '_conf', '_name', '_properties', '_is_mutable', '_defaults' def __init__(self, conf: Conf, name: str, default_properties: dict): self._conf = conf self._name = name self._is_mutable = False self._defaults = deepcopy(default_properties) self._properties = {} def __getattr__(self, property_name): properties = _get_obj_attr(self, '_properties') defaults = _get_obj_attr(self, '_defaults') if property_name in properties: return properties[property_name] elif property_name in defaults: return defaults[property_name] else: raise UnknownConfError( f'Unknown {property_name!r} property in ' f'configuration group {self._name!r}') def __setattr__(self, property_name, value): if property_name in self.__slots__: object.__setattr__(self, property_name, value) elif self._is_mutable: self._properties[property_name] = value elif property_name not in self._defaults: raise UnknownConfError( f'Unknown {property_name!r} property in ' 'configuration group {self.name!r}') elif self._conf._is_frozen: raise FrozenConfPropError( 'Configuration properties are frozen.\n' 'Configuration properties can only be changed globally by ' 'loading configuration file through ' '``Conf.load_conf_file()`` and ``Conf.load_conf_module()``.\n' 'And it can be changed locally in the context ' 'created by `Conf.local_env()`.' ) else: self._properties[property_name] = value def __dir__(self): return self._properties.__dir__() @contextmanager def _mutable_ctx(self): self._is_mutable = True yield self self._is_mutable = False @contextmanager def _default_setter(self): yield ConfGroupDefaultSetter(self) def _update_from_conf_depot_group(self, conf_depot_group): with self._mutable_ctx(): for conf_property, value in conf_depot_group._items(): setattr(self, conf_property, value) def __deepcopy__(self, memo): cls = type(self) new_self = cls.__new__(cls) new_self._conf = self._conf # Don't need to copy conf new_self._name = self._name new_self._is_mutable = self._is_mutable new_self._defaults = deepcopy(self._defaults) new_self._properties = deepcopy(self._properties) return new_self PK!_ confect/conf_depot.py def _get_obj_attr(obj, attr): return object.__getattribute__(obj, attr) class ConfDepot: __slots__ = '_depot_groups' def __init__(self): self._depot_groups = {} def __delitem__(self, group_name): del self._depot_groups[group_name] def __getitem__(self, group_name): return self._depot_groups[group_name] def __getattr__(self, group_name): depot_groups = _get_obj_attr(self, '_depot_groups') if group_name not in depot_groups: conf_depot_group = ConfDepotGroup() depot_groups[group_name] = conf_depot_group return conf_depot_group return depot_groups[group_name] def __setattr__(self, name, value): if name in self.__slots__: object.__setattr__(self, name, value) else: raise TypeError( 'Adding property to first level of ConfDepot is forbidding.\n' # noqa 'In configuration file, all configuration properties should be in some configuration group.\n' # noqa 'Configuration group would be created automatically when needed.\n' # noqa 'In the following example, `yummy` is the group name and `kind` is the property name.\n' # noqa '>>> c.yummy.kind = "seafood"' ) def __contains__(self, group_name): return group_name in self._depot_groups class ConfDepotGroup: __slots__ = '_depot_properties' def __init__(self): self._depot_properties = {} def _items(self): return self._depot_properties.items() def __getitem__(self, property_name): return self._depot_properties[property_name] def __setitem__(self, property_name): return self._depot_properties[property_name] def __getattr__(self, property_name): depot_properties = _get_obj_attr(self, '_depot_properties') if property_name not in depot_properties: raise AttributeError( f'ConfDepotGroup object has no property {property_name!r}' ) return depot_properties[property_name] def __setattr__(self, name, value): if name in self.__slots__: object.__setattr__(self, name, value) else: self._depot_properties[name] = value PK!confect/error.py class UnknownConfError(AttributeError, KeyError): pass class FrozenConfPropError(TypeError): pass class FrozenConfGroupError(TypeError): pass class ConfGroupExistsError(ValueError): pass PK!HW"TTconfect-0.1.0.dist-info/WHEEL A н#J."jm)Afb~ ڡ5 G7hiޅF4+-3ڦ/̖?XPK!HzԘ confect-0.1.0.dist-info/METADATARKnA )|I%@D""&bT>T)6Q-fβ޳}A5 (&Nò\hI!#4/\k1.G"JA1WA:pM BO?v ՀPJN(6hhɲ@$ .RF?{*ՙTlN$$]U-K7\۪^.ŋUwΆ\〦b=:iH$®!G }6SE5|BW޴SfbVBgq^:(f/3I)qÔna6˚֘-DXFn1~}ڬѵCԻ=W/ݱTxR~J7`)?߲EHiA r(|}&31c ׽QkJ0ho܎&*sOigmpIz_rmy%~||_4;FT|:J`hwxA2 KP%iڇUFYTp_bx[@eۂFN~rab+[|ՠȶuZR1fv\[?8pԴ{ZZWbXc-HiD_J@FnwDmBD=$WiV O_OS7 oPK!J22confect/__init__.pyPK!e5M&M&cconfect/conf_.pyPK!_ 'confect/conf_depot.pyPK!1confect/error.pyPK!HW"TT2confect-0.1.0.dist-info/WHEELPK!HzԘ 2confect-0.1.0.dist-info/METADATAPK!H mqR{4confect-0.1.0.dist-info/RECORDPK 6