Makes using environment variables nicer and safer.
Project description
Envrac
Makes using environment variables nicer and safer.
Overview
Envrac is a library which solves some of the pain points of working with environment variables in Python:
- Unset variables can cause unpredictable results.
- You may need to parse values to different types.
- You can't readily discover which variables your code wants to read.
- You don't want to leak the values to logs etc.
The word "envrac" is:
- An abbreviation of environment variable reading and checking.
- A play on the French term "en vrac".
Tutorial
The tutorial takes about 3 minutes and covers all you need to know.
Installation
You can safely install and uninstall envrac - there are no third party dependencies:
pip install envrac
Experimenting
The easiest way to experiment is to open a Python shell and set environment variables using os.environ
:
>>> import os
>>> os.environ['NAME'] = 'Andy'
>>> os.environ['AGE'] = '42'
Note that environment variables are:
- Always stored as strings.
- Loaded into
os.environ
when the Python process starts. - Only set in the current process and child processes.
So variables you set this way will not affect your shell session or system.
Importing
Import env
exactly like this:
>>> from envrac import env
Note that env
is an object, not a module, so this won't work:
# DON'T DO THIS
>>> from envrac.env import *
Reading variables
Read environment variables using the method corresponding the type you want:
>>> env.str('NAME')
'Andy'
>>> env.int('AGE')
42
The simple read methods available are str
, bool
, int
, float
date
, datetime
and time
.
If a variable is not set and no default was provided, you get an error:
>>> env.str('CITY')
envrac.exceptions.EnvracUnsetVariableError:
Environment variable CITY must be set.
See envrac documentation for help.
Consistency checks
If you try to read AGE
as str
having previously read it as int
you get an error:
>>> env.str('AGE')
envrac.exceptions.EnvracSpecificationError:
Environment variable "AGE" requested differently in multiple places.
Diff:
type: str != int
See envrac documentation for help.
Environment variables are inputs to your program, and should be validated/converted the same way throughout. Envrac helps ensure this.
While experimenting you can simply clear
envrac's register:
>>> env.clear()
>>> env.str('AGE')
'42'
Default values
You can provide default values raw:
>>> from datetime import date
>>> env.date('DOB', date(2000, 1, 1))
datetime.date(2000, 1, 1)
Or as strings:
>>> env.date('DOB', '2000-01-01')
datetime.date(2000, 1, 1)
The above didn't raise an error as both dates are the same, but a different default will result in an error:
>>> env.date('DOB', '1999-09-09')
envrac.exceptions.EnvracSpecificationError:
Environment variable "AGE" requested differently in multiple places.
Diff:
default: date(2000, 1, 1) != date(1999, 9, 9)
See envrac documentation for help.
Envrac stores variable specifications but not the values:
>>> os.environ['DOB'] = '2024-06-24'
>>> env.date('DOB', '2000-01-01')
datetime.date(2024, 6, 24)
>>> del os.environ['DOB']
>>> env.date('DOB', '2000-01-01')
datetime.date(2000, 1, 1)
Parsing errors
If the value can't be parsed to that type you get an error:
>>> os.environ['AGE'] = 'fourty two'
>>> env.int('AGE')
envrac.exceptions.EnvracParsingError:
Value for environment variable "AGE" could not be parsed to type `int`.
Value: ***HIDDEN***
See envrac documentation for help.
Notice how envrac hides the value form the print out. This is to reduce the chance of accidentally leaking environment variables, which is a major security risk. You can override this behaviour in configuration.
Read different types
date, datetime and time
These use the type's fromisoformat
internally so you must use ISO format:
>>> env.date('DATE', '1999-09-10')
>>> env.date('DATETIME', '1999-09-10 16:20:00')
>>> env.date('TIME', '16:20')
bool
Boolean variables must be 1
, 0
true
or false
case insensitive:
>>> os.environ['ACTIVE'] = 'TRUE'
>>> env.bool('ACTIVE')
True
This restriction prevents arbitrary values from being interpreted as True
as would happen if you simply used bool()
:
>>> os.environ['AGE'] = '42'
>>> bool(42)
True
>>> env.bool('AGE')
Value for environment variable "AGE" could not be parsed to type `bool`.
Value: ***HIDDEN***
Try: 1/0/true/false (case insensitive)
See envrac documentation for help.
Restrict allowed values
You can specify choices:
>>> os.environ['FONT_STYLE'] = 'Arial'
>>> env.str('FONT_STYLE', choices=['BOLD', 'ITALIC'])
envrac.exceptions.EnvracChoiceError:
Environment variable "FONT_STYLE" must be one of "BOLD", "ITALIC".
value: ***HIDDEN***
See envrac documentation for help.
Or min and/or max values:
>>> os.environ['AGE'] = '100'
>>> env.int('AGE', min_val=12, max_val=45)
envrac.exceptions.EnvracRangeError:
Value for environment variable "AGE" must be in range `12` - `45`.
Value: ***HIDDEN***
See envrac documentation for help.
These options are only applicable to types for which it makes sense.
Allow None
In some cases None
is a valid value:
>>> env.str('FONT_STYLE', choices=['BOLD', 'ITALIC', None])
However there is no way to set the value to None
via the environment.
If you set None
as the default value, you will not detect unset variables, which can easily happen with a typo:
>>> os.environ['F0NT_STYLE'] = 'BOLD'
>>> env.str('FONT_STYLE', None, choices=['BOLD', 'ITALIC', None])
None
Another option is to interpret the text NULL
or NONE
as None
which you can do by adding _
to the method name:
>>> os.environ['F0NT_STYLE'] = 'NONE'
>>> env.str('FONT_STYLE', choices=['BOLD', 'ITALIC', None])
None
This protects you against unset variables:
>>> del os.environ['F0NT_STYLE']
>>> env.str('FONT_STYLE', choices=['BOLD', 'ITALIC', None])
envrac.exceptions.EnvracUnsetVariableError:
Environment variable "FONT_STYLE" must be set.
See envrac documentation for help.
All the read methods we have seen so far have a counterpart with _
suffix which will interpret NULL
or NONE
(case insensitive) as None
.
This also allows you to set a default other than None
:
>>> env.str_('FONT_STYLE', 'BOLD', choices=['BOLD', 'ITALIC', None])
'BOLD'
Of course, setting a default puts you back to being vulnerable to unset variables and typos.
Read many values at once
You can read many environment variables at once like so:
>>> os.environ['DB_NAME'] = 'users_db'
>>> os.environ['DB_PORT'] = '5432'
>>> env.dict('NAME', 'PORT:int')
{'DB_NAME': 'users_db', 'DB_PORT': 5432}
The syntax is as follows:
'FOO' # read FOO as a string
'FOO=bar # read FOO as a string, default to 'bar'
'FOO:int' # read FOO as an int
'FOO:int=0' # read FOO as an int, default to 0
'?FOO:int' # read FOO as an int, but allow 'NULL'
'?FOO:int=0' # read FOO as an int, default to 0, but allow 'NULL'
You get the same consistency checks as you would normally:
>>> env.int('AGE')
>>> env.dict('AGE:float')
envrac.exceptions.EnvracSpecificationError:
Environment variable "AGE" requested differently in multiple places.
Diff:
type: int != float
See envrac documentation for help.
The dict
method doesn't support choices, min or max values. If you need to do that:
{
**env.dict('WIDTH:int', 'HEIGHT:int'),
'COLOR' = env.str('COLOR', choices=colors)
}
Prefixes
To read multiple environment variables which use the same prefix, use the prefix
context:
>>> os.environ['USER_DB_NAME'] = 'user_db'
>>> os.environ['USER_DB_PORT'] = '5432'
>>> with env.prefix('USER_DB_'):
... env.str('NAME')
... env.int('PORT')
...
'user_db'
5432
You typically use this with the dict
method:
>>> os.environ['USER_DB_NAME'] = 'users_db'
>>> os.environ['USER_DB_PORT'] = '5432'
>>> with env.prefix('USER_DB_'):
... conn_args = env.dict('NAME', 'PORT:int')
...
{'USER_DB_NAME': 'users_db', 'USER_DB_PORT': 5432}
You can also remove the prefix from the dictionary keys:
>>> os.environ['USER_DB_NAME'] = 'users_db'
>>> os.environ['USER_DB_PORT'] = '5432'
>>> with env.prefix('USER_DB_'):
... conn_args = env.dict('NAME', 'PORT:int', drop_prefix=False)
...
{'NAME': 'users_db', 'PORT': 5432}
This only affects the returned dictionary, consistency checks look at the full variable name.
Configuration
There are two ways to configure envrac:
Using environment variables
They are all prefixed with ENVRAC_CONFIG_
:
ENVRAC_CONFIG_DISCOVERY_MODE=true
Through code
Map the environment variable to lowercase, and drop the prefix:
env.config.discovery_mode = True
Available options
Name | Type | Default | Effect |
---|---|---|---|
discovery_mode | bool | False | Suppresses Unset errors so you can discover (see below). |
print_values | bool | False | Causes values to be printed in errors and discovery. |
Note that more advanced logging which records variables may still leak the values.
Discovery
Use the print
method to print all the environment variables requested through envrac:
>>> env.int('AGE', 10)
>>> env.print()
NAME TYPE DEFAULT NULLABLE CHOICES MIN MAX
-------------------------------------------------------------
AGE int 10 False None None None
ENVRAC_DISCOVERY_MODE bool False False None None None
ENVRAC_PRINT_VALUES bool False False None None None
If your throws UnsetVariableErrors
before you reach this, set discovery_mode = True
which suppresses those errors:
>>> from envrac import env
>>> env.config.discovery_mode = True
>>> import your_code # won't throw UnsetVariableErrors
>>> env.print()
Additionally you can set print_values = True
which will show you the current raw (uncoverted) value of the environment variable:
>>> os.environ['AGE'] = 'five'
>>> env.config.print_values = True
>>> env.print()
NAME TYPE DEFAULT NULLABLE CHOICES MIN MAX RAW
-------------------------------------------------------------------
AGE int 9 False None None None five
ENVRAC_DISCOVERY_MODE bool False False None None None None
ENVRAC_PRINT_VALUES bool False False None None None None
Issues
Please raise an issue on github or submit a PR.
Licence
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file envrac-0.0.1.tar.gz
.
File metadata
- Download URL: envrac-0.0.1.tar.gz
- Upload date:
- Size: 15.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.10.12
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | e87c5551724c97ae2a4b97e86b13a474a216ac945ffe65dbde479073385a261a |
|
MD5 | 79fcf51cd5491ec8628c055ac37880e3 |
|
BLAKE2b-256 | e81b8a6cb2aee35ee1a156f39d87a7f54d8fa8a1bc8f898e0657c9de5bb4a391 |
File details
Details for the file envrac-0.0.1-py3-none-any.whl
.
File metadata
- Download URL: envrac-0.0.1-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.10.12
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 594a4f58f20148d737fbb07933f2904286af24616acf59b05fcd39f9b5f77d23 |
|
MD5 | 725a3d0dff7e6e0e15802c7ea39b0723 |
|
BLAKE2b-256 | 31c7e249c51436a54f3323b4219bfcaf037ed6f0f8a285544573044c69b10410 |