Package: glorpen-config
Config framework for Your projects - with validation, interpolation and value normalization!Full documentation:https://glorpen-config.readthedocs.io/Official repositoriesGitHub:https://github.com/glorpen/glorpen-configBitBucket:https://bitbucket.org/glorpen/glorpen-configFeaturesYou can:create custom fields for custom datadefine configuration schema inside Python appconvert configuration values to Python objectsvalidate configurationuse interpolation to fill config valuesset default valuesLoading dataglorpen.config.Configusesglorpen.config.loadersto allow loading data from different sources.Loaders should accept:path,filepathconstructor argumentfile-like object,fileobjconstructor argumentAdditionally you can just passdictdata to config withglorpen.config.Config.load_dataorglorpen.config.Config.finalize.InterpolationYou can reuse values from config with{{ path.to.value }}notation, eg:project:path:"/tmp"cache_path:"{{project.path}}/cache"String interpolation currently can be used only withglorpen.config.fields.simple.Stringfields.Normalization and validationEach field type has own normalization rules, eg. forglorpen.config.fields.log.LogLevel:logging:DEBUGconfig.get("logging")would yield value10as islogging.DEBUG.Additionally it will raiseglorpen.config.exceptions.ValidationErrorif invalid level name is given.Default valuesEach field can have default value. If no value is given in config but default one is set, it will be used instead.Default values adhere to same interpolation and normalization rules - each default value is denormalized and then passed to normalizers.
That way complex object can still profit from config interpolation. There should not be any real impact on performance as it is done only once.Example usageYour first step should be defining configuration schema:importloggingimportglorpen.config.fields.simpleasffromglorpen.config.fields.logimportLogLevelproject_path="/tmp/project"spec=f.Dict({"project_path":f.Path(default=project_path),"project_cache_path":f.Path(default="{{ project_path }}/cache"),"logging":LogLevel(default=logging.INFO),"database":f.String(),"sources":f.Dict({"some_param":f.String(),"some_path":f.Path(),}),"maybe_string":f.Variant([f.String(),f.Number()])})Example yaml config:logging:"DEBUG"database:"mysql://...."sources:some_param:"someparam"some_path:"/tmp"maybe_string:12Then you can createglorpen.config.Configinstance:fromglorpen.configimportConfigimportglorpen.config.loadersasloadersloader=loaders.YamlLoader(filepath=config_path)cfg=Config(loader=loader,spec=spec).finalize()cfg.get("sources.some_param")#=> 'some param'cfg.get("project_path")#=> '/tmp/project'cfg.get("project_cache_path")#=> '/tmp/project/cache'cfg.get("logging")#=> 10cfg.get("maybe_string")#=> 12Creating custom fieldsCustom field class should extendglorpen.config.fields.base.Fieldorglorpen.config.fields.base.FieldWithDefault.glorpen.config.fields.base.Field.make_resolvablemethod should register normalizer functions which later will be called in registration order.
Each value returned by normalizer is passed to next one. After chain end value is returned as config value.Returnedglorpen.config.fields.base.ResolvableObjectinstance is resolved before passing it to next normalizer.If value passed to normalizator is invalid it should raiseglorpen.config.exceptions.ValidationError.
Sometimes value can be lazy loaded - it is represented asglorpen.config.fields.base.ResolvableObject.
You can get real value by usingglorpen.config.fields.base.resolve(value, config).classMyValue(object):def__init__(self,value):super(MyValue,self).__init__()self.value=valueclassMyField(Field):defto_my_value(self,value,config):returnMyValue(value)defis_value_supported(self,value):returnTruedefmake_resolvable(self,r):r.on_resolve(self.to_my_value)The last thing is to use prepared custom field in configuration spec.