Package: wr-profiles
Introductionwr-profilesallows declaring a profile – a collection of key-value pairs – backed by environment variables
and accessing the profile contents through dot notation with auto-complete working in your IDE.
You can also have multiple separate environments declared and switch between profiles by changing just one environment
variable (just like you normally do when working with multiple AWS accounts if you have that background).
wr-profiles are designed with testability in mind.wr-in the package name stands forWheel Reinvented. Just like all other packages whose name starts withwr-, the meaning of this library (as inmeaning of life) lies in its existence and evolution
and not in any external application of it.Supported Python VersionsPython 3.6Python 3.7ExampleDeclare the profile:fromwr_profilesimportenvvar_profile_cls@envvar_profile_clsclassWarehouseProfile:host:str="localhost"username:strpassword:strwarehouse_profile=WarehouseProfile()Set up the environments:exportWAREHOUSE_STAGING_PARENT_PROFILE="production"exportWAREHOUSE_STAGING_PASSWORD="staging-password"exportWAREHOUSE_PRODUCTION_USERNAME="production-username"exportWAREHOUSE_PRODUCTION_PASSWORD="production-password"Select the active environment:exportWAREHOUSE_PROFILE=stagingUse the profile:fromprofilesimportwarehouse_profileassertwarehouse_profile.host=="localhost"assertwarehouse_profile.username=="production-username"assertwarehouse_profile.password=="staging-password"Installationpipinstallwr-profilesIf you decide to use this library, make sure you pin the version number in your requirements file.We are following interpretation of the semantic versioning schema. Example:v2.x.a->v2.x.b- bugfix or non-breaking change, safe to upgrade.v2.x.*->v3.y.*- potentially breaking changes, feature added, minimal changes to user code may be requiredv2.*->v3.*- complete changeoverChangelogv4.2.0Addedprofile_delegatefeature.v4.1.0AddedEnvvarProfile.create_envwhich creates anEnvironmentwhich can be applied
as a context manager.User GuideConceptsProfileAprofilerepresents a set of configurablepropertiesof a single service
backed by environment variables.In your application, there can be multiple unrelated profiles each providing interface
to properties of a different service.Instances of profiles associated with the same service share the same base class and are identified byprofile_rootspecified in that base class. Is is the root from which all relevant
environment variable names are formed.Profiles of unrelated services do not share any information.
In the discussion below, different instances or kinds of profiles all relate to the same service,
e.g. sameprofile_root.Warehouse Profile (Example)In the discussion below, we will use a profile for a data warehouse access as an example.
ClassWarehouseProfiledeclares the profile and the properties it provides.
Objectwarehouse_profileis the single instance through which user must look up service’s
active configuration.fromwr_profilesimportenvvar_profile_cls@envvar_profile_clsclassWarehouseProfile:host:str="localhost"username:strpassword:strwarehouse_profile=WarehouseProfile()Profile NameIndividual instances of profiles are identified by their name (profile_nameproperty).Active ProfileTheactive profileis the profile of a service that should be used
according to the environment variables.By default, the active profile can be switched by setting a special environment variable<PROFILE_ROOT>_PROFILE. ForWarehouseProfilethat would beWAREHOUSE_PROFILE.The name of this variable can be customised by setting your class’sprofile_activating_envvar.If this variable is not set, the active profile isan empty string, and the environment variables
consulted are in the form:<PROFILE_ROOT>_<PROPERTY_NAME>For example,WAREHOUSE_HOST.If<PROFILE_ROOT>_PROFILEis set then the active profile consults environment variables in the form:<PROFILE_ROOT>_<PROFILE_NAME>_<PROPERTY_NAME>For example, ifWAREHOUSE_PROFILEis set tostagingthenhostproperty will be looked up
underWAREHOUSE_STAGING_HOST.Parent ProfileAny particular profile (for example,stagingprofile ofWarehouseProfile) can be instructed
to inherit its property values from aparent profileby setting:<PROFILE_ROOT>_<PROFILE_NAME>_PARENT_PROFILEFor example,WAREHOUSE_STAGING_PARENT_PROFILE, if set toproduction, would mean that
if environment variableWAREHOUSE_STAGING_HOSTwas not set, property value loader would
consultWAREHOUSE_PRODUCTION_HOSTinstead. And only if that variable was not present,
the default value of the property would be used.Limitation: The default profile (profile_name="") cannot be used as a parent profile.
If you specify empty string as<PROFILE_ROOT>_<PROFILE_NAME>_PARENT_PROFILEthen this
profile won”t have any parent profile. It is the same as having no value set.Live Profile vs Frozen ProfileAliveprofile always consults environment variables (os.environ) whereas
afrozenprofile does so only during instantiation and when explicitly loaded
withload()method.Common ScenariosGet Current Active ProfileCurrent active profile is always available through the instance of your profile class which is
instantiated with no arguments:warehouse_profile=WarehouseProfile()Normally you’d only need a single instance of your profile class pointing to the active profile.Get Concrete ProfileTo work with a concrete profile which may not necessarily be activated, useloadfactory method:staging=WarehouseProfile.load("staging")By default, this profile will be frozen which means it will be loaded only once during instantiation.
If you want it to always consult environment variables, passprofile_is_live=True:staging=WarehouseProfile.load("staging",profile_is_live=True)Customise Profile-Activating Environment VariableImagine you have yourWarehouseProfileand you want to use it in tests. In tests it should have different defaults.@envvar_profile_clsclassWarehouseTestProfile(WarehouseProfile):# If you don't set this, it would be "WAREHOUSE_PROFILE" which would conflict# with your non-test profile.profile_activating_envvar="WAREHOUSE_TEST_PROFILE"host:str="test-host"username:str="test-user"In your application you would then have two instances:profile=WarehouseProfile()test_profile=WarehouseTestProfile()Now you can reuse your non-test profiles in tests when it makes sense. For example, if you have set up environment
variables in the formWAREHOUSE_SANDBOX_*then this “sandbox” profile can be used in tests by just settingWAREHOUSE_TEST_PROFILEtosandbox.Note thatprofile_rootfor both profiles is the same.Activate ProfileTo activate a profile, callactivatemethod on a frozen instance of the profile without any arguments,
or,activate(profile_name)on the live current profile instance:staging.activate()# or:warehouse_profile.activate("staging")Get All Valueswarehouse_profile.to_dict()Set Environment VariablesNote that the environment variables you set normally apply only to the current process and its sub-processes
so this will have limited use – it will only make sense when you are launching sub-processes or you do this
somewhere early in the code before environment variables are loaded by other parts of your code.os.environ.update(warehouse_profile.to_envvars())Check If Property Has Non-Default Valuewarehouse_profile.has_prop_value("username")# orwarehouse_profile.has_prop_value(WarehouseProfile.username)Inspect Propertyfromwr_profilesimportEnvvarProfilePropertyassertisinstance(WarehouseProfile.username,EnvvarProfileProperty)assertWarehouseProfile.username.name=="username"assertWarehouseProfile.username.default=="default-username"Environment ObjectsStarting from version 4.1 you can create an instance ofEnvironmentwhich can then be applied onos.environor pytest’smonkeypatchfixture.Environmentis a dictionary of environment variables that neet to
be set or unset in order to apply the specified environment. The values are determined at environment
creation time.test_env=warehouse_profile.create_env(username='test',password=None)withtest_env.applied():assertwarehouse_profile.username=='test'assertos.environ['WAREHOUSE_USERNAME']=='test'assertwarehouse_profile.passwordisNoneassert'WAREHOUSE_PASSWORD'notinos.environConfig Object that Delegates to ProfileEnvironment variables are not necessarily the only source of configuration.
In a non-trivial application you probably won’t be consulting the profile object from your application code
directly. Instead, you’ll have a config object which will consult different sources including the
environment variable profile.
It is very likely that the properties defined in your profile class will
be a subset of those exposed by the config object. If you don’t want to repeat yourself, you can have your
config class extend the profile class and have the config class delegate all the attributes to the profile
class except for those implemented in the config class.profile=WarehouseProfile()classWarehouseConfig(WarehouseProfile):@propertydefprofile_delegate(self):returnprofile@propertydefusername(self):returnprofile.usernameor"anonymous"