Package: caep
CAEPConfiguration library that supports loading configuration from ini, environment variables
and arguments into apydanticschema.With the pydantic schema you will have a fully typed configuration object that is parsed
at load time.Change log1.1.0Support list/set/dict defaults, so you can now do:intlist:List[int]=Field([0,1,2],description="List of ints")The previous way to defined defaults using strings are still supported, but will fail type checking with the pydantic mypy plugin, and will be removed in a later version:intlist:List[int]=Field("0,1,2",description="List of ints")1.0.0Support for pydantic 2.x. It is advised to migrate models with these changes:Usemin_lengthinstead ofmin_sizePydantic has builtin support for size of list, dictionaries and sets usingmin_lengthso you should changeintlist:List[int]=Field(description="Space separated list of ints",min_size=1)tointlist:List[int]=Field(description="Space separated list of ints",min_length=1)Migratesplitandkv_splittojson_schema_extraDo not usesplitandkv_splitdirectly on the field, but put them in a dictionaryjson_schema_extra. E.g. changeintlist:List[int]=Field(description="Space separated list of ints",split=" ")tointlist:List[int]=Field(description="Space separated list of ints",json_schema_extra={"split":" "})and changedict_int:Dict[str,int]=Field(description="Int Dict split by slash and dash",split="-",kv_split="/")todict_int:Dict[str,int]=Field(description="Int Dict split by slash and dash",json_schema_extra={"split":"-","kv_split":"/"},)Migrateroot_validatortomodel_validatorroot_validatorare stil supported, but it is advised to migrate tomodel_validator. Example using helper functionraise_if_some_and_not_all:@model_validator(mode="after")# type: ignoredefcheck_arguments(cls,m:"ExampleConfig")->"ExampleConfig":"""If one argument is set, they should all be set"""caep.raise_if_some_and_not_all(m.__dict__,["username","password","parent_id"])returnmExample#!/usr/bin/env python3fromtypingimportListfrompydanticimportBaseModel,FieldimportcaepclassConfig(BaseModel):text:str=Field(description="Required String Argument")number:int=Field(default=1,description="Integer with default value")switch:bool=Field(description="Boolean with default value")intlist:List[int]=Field(description="Space separated list of ints",json_schema_extra={"split":" "})# Config/section options below will only be used if loading configuration# from ini file (under ~/.config)config=caep.load(Config,"CAEP Example","caep",# Find .ini file under ~/.config/caep"caep.ini",# Find .ini file name caep.ini"section",# Load settings from [section] (default to [DEFAULT])print(config)Sample output with aintlistread from environment andswitchfrom command line:$exportINTLIST="1 2 3"$./example.py--text"My value"--switchtext='My value'number=1switch=Trueintlist=[1,2,3]Load config without ini supportSpecifying configuration location, name and section is optional and can be skipped if you
do not want to support loading ini files from$XDG_CONFIG_HOME:# Only load arguments from environment and command lineconfig=caep.load(Config,"CAEP Example",)With the code above you can still specify a ini file with--config <ini-file>, and use
environment variables and command line arguments.Pydantic field typesPydantic fields should be defined usingFieldand include thedescriptionparameter
to specify help text for the commandline.Unless theFieldhas adefaultvalue, it is a required field that needs to be
specified in the environment, configuration file or on the command line.Many of the types described inhttps://docs.pydantic.dev/usage/types/should be supported, but not all of them are tested. However,  nested schemas
arenotsupported.Tested types:strStandard string argument.intValues parsed as integer.floatValue parsed as float.pathlib.PathValue parsed as Path.ipaddress.IPv4AddressValues parsed and validated as IPv4Address.ipaddress.IPv4NetworkValues parsed and validated as IPv4Network.boolValue parsed as booleans. Booleans will default to False, if no default value is set.
Examples:FieldInputConfigurationenable: bool = Field(description="Enable")Falseenable: bool = Field(value=False, description="Enable")yesTrueenable: bool = Field(value=False, description="Enable")trueTruedisable: bool = Field(value=True, description="Disable")Truedisable: bool = Field(value=True, description="Disable")yesFalsedisable: bool = Field(value=True, description="Disable")trueFalseList[str](list[str]for python >= 3.9)List of strings, split by specified character (default = comma, argument=split).Some examples:FieldInputConfigurationList[int] = Field(description="Ints", json_schema_extra={"split": " "})1 2[1, 2]List[str] = Field(description="Strs")ab,bc["ab", "bc"]The argumentmin_length(pydantic builtin) can be used to specify the minimum size of the list:FieldInputConfigurationList[str] = Field(description="Strs", min_length=1)``Raises ValidationErrorSet[str](set[str]for python >= 3.9)Set, split by specified character (default = comma, argument=split).Some examples:FieldInputConfigurationSet[int] = Field(description="Ints", json_schema_extra={"split": " "})1 2 2{1, 2}Set[str] = Field(description="Strs")ab,ab,xy{"ab", "xy"}The argumentmin_lengthcan be used to specify the minimum size of the set:FieldInputConfigurationSet[str] = Field(description="Strs", min_length=1)``Raises ValidationErrorDict[str, <TYPE>](dict[str, <TYPE>]for python >= 3.9)Dictioray of strings, split by specified character (default = comma, argument=splitfor
splitting items and colon for splitting key/value).Some examples:FieldInputConfigurationDict[str, str] = Field(description="Dict")x:a,y:b{"x": "a", "y": "b"}Dict[str, int] = Field(description="Dict of ints")a b c:1, d e f:2{"a b c": 1, "d e f": 2}The argumentmin_lengthcan be used to specify the minimum numer of keys in the dictionary:FieldInputConfigurationDict[str, str] = Field(description="Strs", min_length=1)``Raises ValidationErrorConfigurationArguments are parsed in two phases. First, it will look for the optional argument--configwhich can be used to specify an alternative location for the ini file. If not--configargument
is given it will look for an optional ini file in the following locations
(~/.config has presedence)ifconfig_idandconfig_nameis specified:~/.config/<CONFIG_ID>/<CONFIG_FILE_NAME>(or directory specified by$XDG_CONFIG_HOME)/etc/<CONFIG_FILE_NAME>The ini file can contain a[DEFAULT]section that will be used for all configurations.
In addition it can have a section that corresponds with<SECTION_NAME>(if specified) that for
specific configuration, that will over override config from[DEFAULT]Environment variablesThe configuration step will also look for environment variables in uppercase and
with-replaced with_. For the example below it will lookup the following environment
variables:$NUMBER$BOOL$STR_ARGThe configuration presedence are (from lowest to highest):argparse defaultini fileenvironment variablecommand line argumentValidationXDGHelper functions to useXDG Base Directoriesare included incaep.xdg:It will look upXDGenvironment variables like$XDG_CONFIG_HOMEand use
defaults if not specified.get_xdg_dirGeneric function to get aXDGdirectory.The following example with will return a path object to ~/.config/myprog
(if$XDG_CONFIG_HOMEis not set) and create the directoy if it does not
exist.get_xdg_dir("myprog","XDG_CONFIG_HOME",".config",True)get_config_dirShortcut forget_xdg_dir("CONFIG").get_cache_dirShortcut forget_xdg_dir("CACHE").CAEP Legacy usagePrior to version0.1.0the recommend usage was to add parser objects manually. This is
still supported, but with this approach you will not get the validation from pydantic:>>>importcaep>>>importargparse>>>parser=argparse.ArgumentParser("test argparse")>>>parser.add_argument('--number',type=int,default=1)>>>parser.add_argument('--bool',action='store_true')>>>parser.add_argument('--str-arg')>>>args=caep.config.handle_args(parser,<CONFIG_ID>,<CONFIG_FILE_NAME>,<SECTION_NAME>)Helper Functionsraise_if_some_and_not_allRaise ArgumentError if some of the specified entries in the dictionary has non
false values but not allclassExampleConfig(BaseModel):username:Optional[str]=Field(description="Username")password:Optional[str]=Field(description="Password")parent_id:Optional[str]=Field(description="Parent ID")@model_validator(mode="after")# type: ignoredefcheck_arguments(cls,m:"ExampleConfig")->"ExampleConfig":"""If one argument is set, they should all be set"""caep.raise_if_some_and_not_all(m.__dict__,["username","password","parent_id"])returnmscript_nameReturn first external module that called this function, directly, or indirectly