A Utility library for Dynamic Config class generation.
MIT License
A utility library for Dynamic Config class generation. Now no more repeated boilerplate code...
Before lazy_env_configurator
, config classes used to be created as below.
class BaseConfig:
APP = os.environ.get("APP")
APP_ENV = os.environ.get("APP_ENV")
# authentication related configuration
# DB Credentials
DB_USERNAME = os.environ.get("DB_USERNAME")
DB_PASSWORD = os.environ.get("DB_PASSWORD")
DB_HOST = os.environ.get("DB_HOST")
DB_PORT = os.environ.get("DB_PORT")
DB_NAME = os.environ.get("DB_NAME")
DB_DRIVER = os.environ.get("DB_DRIVER")
This use to require a lot of boiler plate and redundant code With lazy_env_configurator
this can be reduced to below:
from lazy_env_configurator import BaseEnv, BaseConfig as Config_
class BaseConfig(BaseEnv):
class Config(Config_):
envs = ('APP',
'APP_ENV',
'DB_USERNAME',
'DB_PASSWORD',
'DB_PASSWORD',
'DB_HOST',
# defaults
('DB_PORT',3306),
'DB_NAME',
'DB_DRIVER'
)
lazy_env_configurator
over Normal classesenv
variables.instance
attribute preventing need to initialization and making it behave singleton..env
files by default so you only need to focus on essentials.contained
attribute.BaseConfig
: Main Config class for library. This changes behavior of the container class.
envs
: List
or Tuple
of env
variable to be populated as attributes in container class. Elements of iterable can be a string
or tuple
with first element as attribute name and second as default
, second element Defaults to None
. Eg:
class Config(Config_):
envs = ('APP',
'APP_ENV',
'DB_USERNAME',
'DB_PASSWORD',
'DB_PASSWORD',
'DB_HOST',
# defaults
('DB_PORT',3306),
'DB_NAME',
'DB_DRIVER'
)
dot_env_path
: Path to .env
file. This can be a string or pathlib.Path
object. defaults to None
. Eg:
class Config(Config_):
dot_env_path = Path(__file__).parent/'.env'
contained
: This variable is responsible for behaviour of the container. If this is set to False
, all the env
variables read from .env
file would be populated to os.environ
and available globally. If this is set to True
, environment variables would only be contained in the container itself. This would help to create configuration containers with different env settings. It contained
is set to true and no .env
file is present, it will fallback to Environment variables. default True
. Eg:
class Config(BaseConfig):
envs = ("FOO", "APP")
dot_env_path = Path(__file__).parent / ".env.contained"
contained = True
validations
: Dict of validations to be applied to the env variables.
The key of the dict is the name of the env variable and the value is the validation options.
The validation options are the same as the pydantic field info.
This is a dictionary of key as
environment Variables
and value aslazy_env_configurator.validations:ValidationOptions
. These are all the arguments that are passed for Field Customisations. This supports auto completion.
class Abc(BaseEnv):
class Config(BaseConfig):
envs = ('dev', 'test')
validations = {
'dev': {
"alias": "dev",
"gt": 4,
"type": int,
"required": True
},
'test': {
"type": HttpUrl,
'required': False
},
}
Above would resolve dev
to int
type and validate if it is greater than 4
and is not None. Similarly, it will check it test
is not None
and is a valid Url
.
Valid Validation Options:
default
: since this is replacing the field’s default, its first argument is used...
) to indicate the field is requiredalias
: the public name of the fieldtitle
: can be any string, used in the schemadescription
: can be any string, used in the schemagt
: only applies to numbers, requires the field to be "greater than". The schemaexclusiveMinimum
validation keywordge
: only applies to numbers, requires the field to be "greater than or equal to". Theminimum
validation keywordlt
: only applies to numbers, requires the field to be "less than". The schemaexclusiveMaximum
validation keywordle
: only applies to numbers, requires the field to be "less than or equal to". Themaximum
validation keywordmultiple_of
: only applies to numbers, requires the field to be "a multiple of". ThemultipleOf
validation keywordallow_inf_nan
: only applies to numbers, allows the field to be NaN or infinity (+inf or -inf),max_digits
: only applies to Decimals, requires the field to have a maximum numberdecimal_places
: only applies to Decimals, requires the field to have at most a number of decimal placesmin_length
: only applies to strings, requires the field to have a minimum length. TheminLength
validation keywordmax_length
: only applies to strings, requires the field to have a maximum length. ThemaxLength
validation keywordallow_mutation
: a boolean which defaults to True. When False, the field raises a TypeError if the field isregex
: only applies to strings, requires the field match against a regular expressionpattern
validation keywordrepr
: show this field in the representationeagerly_validate
: If True, the env variables will be validated on class creation, else will be validated when accessed. By default, Metaclass does not validate env variables
of class creation for its lazy behaviour.
Setting this flag to True does not populate value of the elements if validation fails.
class Abc(BaseEnv):
class Config(BaseConfig):
envs = ('dev', 'test')
validations = {
'dev': {
"alias": "dev",
"gt": 4,
"type": int,
"required": True
},
'test': {
"type": HttpUrl,
'required': False
},
}
eagerly_validate = True
Above will validate all the envs on class creation and will raise and error if any mis validations found.
BaseEnv
: This class will be used as a Base Class
for all the containers. It uses EnvMeta
as metaclass to populate env
variables as attributes on Container Class. Eg:
from lazy_env_configurator import BaseEnv, BaseConfig as Config_
class BaseConfig(BaseEnv):
class Config(Config_):
envs = ('APP',
'APP_ENV',
'DB_USERNAME',
'DB_PASSWORD',
'DB_PASSWORD',
'DB_HOST',
# defaults
('DB_PORT',3306),
'DB_NAME',
'DB_DRIVER'
)
# validations for envs
validations = {
'DB_PORT': {
"type": int,
"required": True
},
'DB_HOST': {
"type": str,
'required': True
},
}
Note:
Config
class is optional. If not provided, it will not load any env variables.Config
class won't be available as an attribute on the child class.
EnvMeta
: Metaclass for populating env variables
as class attributes to the child class.
if the child class has a Config class, it will
be used to populate the env variables.
by default, the env variables are populated on first access and cached for subsequent access. This can be overriden by setting the value on the instance.
if the env variable is not set, it uses the default value provided.
This class also initializes the instance of the child class and
make it available as instance
attribute on the child class. So it can be accessed as
ChildClass.instance
.
Example:
class ABC(metaclass=EnvMeta):
# will create and populate env properties on ABC
def generate_uri(self):
return f'{self.DB_HOST}:{self.DB_PORT}'
class Config(BaseConfig):
envs = ('dev', ('test', 'test_value'),
'prd', 'DB_HOST', 'DB_PORT')
dot_env_path = Path(__file__).parent / '.env.test'
# validations for envs
validations = {
'DB_PORT': {
"type": int,
"required": True
},
'DB_HOST': {
"type": str,
'required': True
},
}
>>> # access env variables
>>> ABC.instance.dev
>>> ABC.instance.test
>>> ABC.instance.prd
Let us refer below example:
from lazy_env_configurator import BaseEnv, BaseConfig as Config_
class BaseConfig(BaseEnv):
class Config(Config_):
envs = ('APP',
'APP_ENV',
'DB_USERNAME',
'DB_PASSWORD',
'DB_PASSWORD',
'DB_HOST',
# defaults
('DB_PORT',3306),
'DB_NAME',
'DB_DRIVER'
)
# validations for envs
validations = {
'DB_PORT': {
"type": int,
"required": True
},
'DB_HOST': {
"type": str,
'required': True
},
}
# path to dotenv file, automatically detects .env
# dot_env_path = pathlib.Path(__file__).parent / ".env.sample"
# Validates of class creation if set to True
# eagerly_validate = True
# if True, Keeps env contained else propogates to global env
# contained = True
We can now use BaseConfig
class create as below.
>>> BaseConfig.instance.APP
Every class, subclassed from BaseEnv
would expose .instance
attribute which will be instance of the subclass. This instance can be used to access all the attributes on the class.
For simplicity,
metaclass
pre-initialises created class, so to make it behave as singleton.
lazy_env_configurator
uses descriptors
under the hood to dynamically populate env variables as attributes, thus making them available on demand, Lazily
.
All the Validations are done using Pydantic
Library. Validation Errors are Pydantic ValidationError
Instances that can be caught and JSON serialised if required.
Thank you for considering to help out with the source code! We welcome any contributions no matter how small they are!
If you'd like to contribute to lazy_env_configurator
, please fork, fix, commit and send a pull request for the maintainers to review and merge into the main code base.
Please make sure your contributions adhere to our coding guidelines:
master
branch.The lazy-env-configurator
source code is licensed under MIT, also included in the LICENSE
file.
To check what changed, please refer CHANGELOG.
Made with Love by @satyamsoni .
Follow me on: