Module attrbox.env
Load configuration from environment files.
The file format is similar to a Bash file, but it is not as complete as python-dotenv.
Supported:
- unquoted and single-quoted key
- unquoted, single- and double-quoted value
- spaces before and after key, equal sign, and value are ignored
export
at the start of the line is ignored#
comment at the start of the line- value expansion for values in the environment
- value expansion for values only in the file (e.g., when
update_env=False
)
Unsupported:
- key without a value
- multiline value
#
comment after value- escape sequences in value
Examples
>>> loads('''
... # a comment
... normal=value
... ' quoted '=" space "
... export expanded="expanded-${normal}"
... ''')
{'normal': 'value', ' quoted ': ' space ', 'expanded': 'expanded-value'}
Global variables
var PathStr
-
Type representing a
Path
or a string to a path.
Functions
def expand(value: str, store: Optional[Mapping[str, str]] = None, *, dotted_keys: bool = False) ‑> str
-
Expand variables of the form
$var
and${var}
.A simplified form of
os.path.expandvars
.Args
value
:str
- value to expand
store
:Mapping[str, str]
, optional- valid substitutions.
If
None
,os.environ
is used. Defaults toNone
. dotted_keys
:bool
- if
True
allow${dotted.name}
to map to nested values{"dotted": {"name": "value"}}
. Defaults toFalse
.
Returns
str
- expanded value. Unknown variables are left unchanged.
Examples
Regular expansion works as expected:
>>> expand("$a ${b}", {'a': 'hello', 'b': 'world'}) 'hello world'
Unknown variables are left unchanged:
>>> expand("$a is $b", {'a': 'this'}) 'this is $b' >>> expand("no vars", {}) 'no vars'
Values are passed to
str
:>>> expand("$a", {'a': 5}) '5'
Dotted names are optionally possible:
>>> expand("${a.b}", {"a": {"b": "works"}}, dotted_keys=True) 'works'
def load(file: SupportsRead, /, *, update_env: bool = True, dotted_keys: bool = True) ‑> Dict[str, str]
-
Load an environment
file
.Args
file
:SupportsRead
- file-like (has
.read()
). update_env
:bool
, optional- If
True
, update theos.path.environ
as values are read in. Defaults toTrue
. dotted_keys
:bool
, optional- If
True
, split the key by.
and use that to create a nesteddict
. Defaults toTrue
.
Returns
Dict[str, str]
- configuration values
Examples
>>> root = Path(__file__).parent.parent.parent >>> load((root / "test/config_3.env").open()) {'section': {'key': 'value3', 'env': 'loaded'}}
def loads(text: str, /, *, update_env: bool = True, dotted_keys: bool = True) ‑> Dict[str, str]
-
Parse an environment file from a string.
Args
text
:str
- text to parse.
update_env
:bool
, optional- If
True
, update theos.path.environ
as values are read in. Defaults toTrue
. dotted_keys
:bool
, optional- If
True
, split the key by.
and use that to create a nesteddict
. Defaults toTrue
.
Returns
Dict[str, str]
- configuration values
Examples
If you don't want to update the environment:
>>> 'fake' in ENV False >>> loads('''export 'fake'=ignored ... works=not $fake''', update_env=False) {'fake': 'ignored', 'works': 'not ignored'} >>> 'fake' in ENV False
Keys with dots in them create nested dicts, but are optional:
>>> loads('section.key=value', update_env=False, dotted_keys=True) {'section': {'key': 'value'}} >>> loads('section.key=value', update_env=False, dotted_keys=False) {'section.key': 'value'}
def find_env(path: Union[pathlib._local.Path, str, ForwardRef(None)] = None, name: str = '.env') ‑> Optional[pathlib._local.Path]
-
Find the
.env
file in the ancestors of the current path.Args
path
:PathLike
, optional- A starting path to check. If
None
, starts with the current working directory. Defaults toNone
. name
:str
, optional- file name to search for. Defaults to
".env"
.
Returns
Optional[Path]
- path to environment file or
None
if it is not found.
Examples
Search from the current working directory:
>>> str(find_env()) '.../.env'
Search from a specific directory:
>>> str(find_env(".")) '.../.env'
Pass a
Path
object:>>> str(find_env(Path(__file__))) '.../.env'
Point directly to the
.env
file:>>> str(find_env(Path(__file__).parent.parent.parent / ".env")) '.../.env'
def load_env(path: Union[pathlib._local.Path, str, ForwardRef(None)] = None) ‑> Dict[str, str]
-
Load an environment file.
We recursively search for a
.env
file from the path given or the current working directory, if omitted.Args
path
:PathStr
, optional- starting path. If
None
, start from the current working directory. Defaults toNone
.
Raises
FileNotFoundError
- If not
.env
file is found.
Returns
Dict[str, str]
- configuration values
Examples
>>> load_env() # our .env doesn't have any values {}
If no
.env
can be found, aFileNotFoundError
is raised:>>> load_env("/") Traceback (most recent call last): ... FileNotFoundError: Cannot find .env file to load.
Classes
class SupportsRead (*args, **kwargs)
-
Protocol for a class that implements a
.read()
method.Expand source code
class SupportsRead(Protocol): # pylint: disable=too-few-public-methods """Protocol for a class that implements a `.read()` method.""" def read(self) -> str: """Read the contents of the file-like object.""" return "" # pragma: no cover
Ancestors
- typing.Protocol
- typing.Generic
Methods
def read(self) ‑> str
-
Read the contents of the file-like object.