Package: confme
ConfMe: Configuration Made Easy 💖ConfMe is a simple to use, production ready application configuration management library, which takes into consideration the following three thoughts:Access to configuration values must be safe at runtime.Nomyconfig['value1']['subvalue']anymore!The configuration must be checked for consistency at startup e.g. type check, range check, ...Secrets shall be injectable from environment variablesConfMe makes all these features possible with just a few type annotations on plain Python objects.InstallationConfMe can be installed from the official python package repositorypypipip install confmeOr, if you're using pipenv:pipenv install confmeOr, if you're using poetry:poetry add confmeBasic Usage of confmeDefine your config structure as plain python objects with type annotations:fromconfmeimportBaseConfigclassDatabaseConfig(BaseConfig):host:strport:intuser:strclassMyConfig(BaseConfig):name:strdatabase:DatabaseConfigCreate a configuration yaml file with the same structure as your configuration classes have:name:"DatabaseApplication"database:host:"localhost"port:5000user:"any-db-user"Load the yaml file into your Python object structure and access it in a secure manner:my_config=MyConfig.load('config.yaml')print(f'Using database connection{my_config.database.host}'f'on port{my_config.database.port}')In the background the yaml file is parsed and mapped to the defined object structure. While mapping the values to object properties, type checks are performed. If a value is not available or is not of the correct type, an error is generated already when the configuration is loaded.Supported AnnotationsConfMe is based on pydantic and supports all annotations provided by pydantic. The most important annotations are listed and explain bellow. For the whole list, please checkoutField Types:strintfloatbooltyping.List[x]typing.Optional[x]SecretRangeEnumSecretWith the Secret annotation you can inject secrets from environment variables directly into your configuration structure. This is especially handy when you're deploying applications by using docker. Therefore, let's extend the previous example with a Secret annotation:fromconfmeimportBaseConfigfromconfme.annotationimportSecretclassDatabaseConfig(BaseConfig):...password:str=Secret('highSecurePassword')Now set the password to the defined environment variable:exporthighSecurePassword="This is my password"Load your config and check for the injected password.my_config = MyConfig.load('config.yaml')
print(f'My password is: {my_config.database.password}')RangeConfME supports OpenRange, ClosedRange and MixedRange values. The terms open and close are similar to open and closed intervals in mathematics. This means, if you want to include the lower and upper range use ClosedRange otherwise OpenRange:ClosedRange(2, 3)will include 2 and 3OpenRange(2, 3)will not include 2 and 3If you want to have a mixture of both, e.g. include 2 but exclude 3 use MixedRange:MixedRange(ge=2, lt=3)will include 2 but exclude 3fromconfmeimportBaseConfigfromconfme.annotationimportClosedRangeclassDatabaseConfig(BaseConfig):...password:int=ClosedRange(2,3)EnumIf a Python Enum is set as type annotation, ConfMe expect to find the enum value in the configuration file.fromconfmeimportBaseConfigfromenumimportEnumclassDatabaseConnection(Enum):TCP='tcp'UDP='udp'classDatabaseConfig(BaseConfig):...connection_type:DatabaseConnectionSwitching configuration based on EnvironmentA very common situation is that configurations must be changed based on the execution environment (dev, test, prod). This can be accomplished
by registering a folder with one .yaml file per environment and seting theENVenvironment variable to the value you need. An example could look
like this:Let's assume we have three environments (dev, test, prod) and one configuration file per environment in the following folder structure:project
│
└───config
│   │   my_prod_config.yaml
│   │   my_test_config.yaml
│   │   my_dev_config.yaml
│   
└───src
│   │   app.py
│   │   my_config.pyThe definition ofmy_config.pyis equivalent to the one used in the basic introduction section andapp.pyuses our configuration the
following way:# we register the folder where ConfME can find the configuration filesMyConfig.register_folder(Path(__file__).parent/'../config')...# we access the instance of the corresponding configuration file anywhere in our project.my_config=MyConfig.get()print(f'Using database connection{my_config.database.host}'f'on port{my_config.database.port}')If now one of the following environment variables (precedence in descending order):['env', 'environment', 'environ', 'stage']is
set e.g.export ENV=prodit will load the configuration file withprodin its name.Parameter overwriteIn addition to loading configuration parameters from the configuration file, they can be passed/overwritten from the command line or environment variables. Thereby, the following precedences apply (lower number means higher precedence):Command Line Arguments: Check if parameter is set as command line argument. If not go one line done...Environment Variables: Check if parameter is set as environment variable. If not go one line done...Configuration File: If value was not found in one of the previous sources, it will check in the configuration file.Overwrite Parameters from Command LineEspecially in the Data Science and Machine Learning area it is useful to pass certain parameters for experimental purposes as command line arguments. Therefore, all properties defined in the configuration classes are automatically offered as command line arguments in the following format:my_program.py:fromconfmeimportBaseConfigclassDatabaseConfig(BaseConfig):host:strport:intuser:strclassMyConfig(BaseConfig):name:intdatabase:DatabaseConfigconfig=MyConfig.load('test.yaml')When you now start your program from the command line with the++helpargument, you get the full list of all configuration options.CAVEAT!In order to not interfere with other cli tools, the prefix - was changed to +:$pythonmy_program.py--help
usage:my_program.py[+h][++nameNAME][++database.hostDATABASE.HOST][++database.portDATABASE.PORT][++database.userDATABASE.USER]optionalarguments:+h,++helpshowthishelpmessageandexitConfigurationParameters:Withtheparametersspecifiedbellow,theconfigurationvaluesfromtheconfigfilecanbeoverwritten.++nameNAME++database.hostDATABASE.HOST++database.portDATABASE.PORT++database.userDATABASE.USEROverwrite Parameters with Environment VariablesLikewise to overwriting parameters from the commandline you can also overwrite by passing environment variables. Therefore, simply set the environment variable in the same format as it would be passed as command line arguments and run your application:$exportdatabase.host=localhost
$pythonmy_programm.pyBreaking Changes in v2Pydantic is the underlying library powering ConfMe and with the update to pydantic v2 some breaking changes where
introduced. However, we tried our best to minimize the impact on your project and only passed a selection of changes
to you. Please find these documented bellow:Required, optional and nullable fieldsPydantic V2 changes some of the logic for specifying whether a field annotated as Optional is required (i.e., has no
default value) or not (i.e., has a default value of None or any other value of the corresponding type), and now more
closely matches the behavior of dataclasses. Similarly, fields annotated as Any no longer have a default value of None.The following table describes the behavior of field annotations in V2:StateField DefinitionRequired, cannot beNonef1: strNot required, cannot beNone, is'abc'by defaultf3: str = 'abc'Required, can beNonef2: Optional[str]Not required, can beNone, isNoneby defaultf3: Optional[str] = NoneNot required, can beNone, is'abc'by defaultf3: Optional[str] = 'abc'Not required, cannot beNonef4: str = 'Foobar'Required, can be any type (includingNone)f5: AnyNot required, can be any type (includingNone)f6: Any = NoneLICENSEConfMe is released under theMITlicense.