Skip to content

Commit

Permalink
Version 1.1.0 release, closes #9, closes #22
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Sep 8, 2019
1 parent a9229b7 commit 5a01e84
Show file tree
Hide file tree
Showing 10 changed files with 537 additions and 154 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
We follow semantic versioning.


## Version 1.1.0

### Features

- Adds `--strict` option to fail the command if some keys are not present

### Misc

- Updates `wemake-python-styleguide` to the latest version


## Version 1.0.0

### Breaking changes
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ This command will:

## Advanced Usage

### Multiple prefixes

```bash
$ dump-env -t .env.template -p 'SECRET_ENV_' -p 'ANOTHER_SECRET_ENV_' > .env
```
Expand All @@ -59,11 +61,32 @@ TOKEN=very secret string
VALUE=0
```

### Strict env variables

In case you want to be sure that `YOUR_VAR` exists
in your enviroment when dumping, you can use `--strict` flag:

```bash
$ dump-env --strict YOUR_VAR -p YOUR_
Missing env vars: YOUR_VAR
```

Oups! We forgot to create it! Now this will work:

```bash
$ export YOUR_VAR='abc'
$ dump-env --strict YOUR_VAR -p YOUR_
VAR=abc
```

No more forgotten template overrides or missing env vars!


## Creating secret variables in some CIs

- [travis docs](https://docs.travis-ci.com/user/environment-variables/#Defining-encrypted-variables-in-.travis.yml)
- [gitlab-ci docs](https://docs.gitlab.com/ce/ci/variables/README.html#secret-variables)
- [github actions](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables)


## Real-world usages
Expand All @@ -72,3 +95,8 @@ Projects that use this tool in production:

- [wemake-django-template](https://github.com/wemake-services/wemake-django-template/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/.gitlab-ci.yml#L24)
- [wemake-vue-template](https://github.com/wemake-services/wemake-vue-template/blob/master/template/.gitlab-ci.yml#L24)


## License

[MIT](https://github.com/sobolevn/dump-env/blob/master/LICENSE
65 changes: 49 additions & 16 deletions dump_env/cli.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,85 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import sys
from typing import NoReturn

from dump_env.dumper import dump
from dump_env.exceptions import StrictEnvException


def _create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument(
'-p',
'--prefix',
type=str,
action='append',
help='Adds prefix',
)
parser.add_argument(
'-t',
'--template',
default='',
type=str,
help='Adds template path',
)
parser.add_argument('-p', '--prefix', action='append', help='Adds prefix')
parser.add_argument(
'--strict',
type=str,
action='append',
help='Strict variables should exists in os envs',
)
return parser


def main() -> None:
def main() -> NoReturn:
"""
Runs dump-env script.
Examples:
This example will dump all environ variables::
Example:
This example will dump all environ variables:
.. code:: bash
$ dump-env
This example will dump all environ variables starting with `PIP_`::
This example will dump all environ variables starting with ``PIP_``:
.. code:: bash
$ dump-env -p 'PIP_'
This example will dump all environ variables starting with `PIP_`
and update them with variables starting with `SECRET_`::
This example will dump all environ variables starting with ``PIP_``
and update them with variables starting with ``SECRET_``:
.. code:: bash
$ dump-env -p 'PIP_' -p 'SECRET_'
This example will dump everything from `.env.template` file
and all env variables with `SECRET_` prefix into a `.env` file::
This example will dump everything from ``.env.template`` file
and all env variables with ``SECRET_`` prefix into a ``.env`` file:
.. code:: bash
$ dump-env -p 'SECRET_' -t .env.template > .env
"""
parser = _create_parser()
args = parser.parse_args()
variables = dump(args.template, args.prefix)
This example will fail if ``REQUIRED`` does not exist in environ:
for env_name, env_value in variables.items():
sys.stdout.write('{0}={1}'.format(env_name, env_value) + '\n')
.. code:: bash
$ dump-env --strict=REQUIRED
"""
args = _create_parser().parse_args()
strict_vars = set(args.strict) if args.strict else None

try:
variables = dump(args.template, args.prefix, strict_vars)
except StrictEnvException as exc:
sys.stdout.write('{0}\n'.format(str(exc)))
sys.exit(1)
else:
for env_name, env_value in variables.items():
sys.stdout.write('{0}={1}\n'.format(env_name, env_value))
sys.exit(0)
48 changes: 37 additions & 11 deletions dump_env/dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@

from collections import OrderedDict
from os import environ
from typing import Dict, List, Mapping, Optional
from typing import Dict, List, Mapping, Optional, Set

from dump_env.exceptions import StrictEnvException

Store = Mapping[str, str]


def parse(source: str) -> Store:
def _parse(source: str) -> Store:
"""
Reads the source `.env` file and load key-values.
Reads the source ``.env`` file and load key-values.
Args:
source (str): `.env` template filepath
source: ``.env`` template filepath
Returns:
Store with all keys and values.
"""
parsed_data = {}

with open(source) as env_file:
for line in env_file:
line = line.strip()
line = line.strip() # noqa: WPS440

if not line or line.startswith('#') or '=' not in line:
# Ignore comments and lines without assignment.
Expand All @@ -37,6 +40,7 @@ def parse(source: str) -> Store:


def _preload_existing_vars(prefix: str) -> Store:
"""Preloads env vars from environ with the given prefix."""
if not prefix:
# If prefix is empty just return all the env variables.
return environ
Expand All @@ -54,34 +58,56 @@ def _preload_existing_vars(prefix: str) -> Store:
return prefixed


def _assert_envs_exist(strict_keys: Set[str]) -> None:
"""Checks that all variables from strict keys do exists."""
missing_keys: List[str] = [
strict_key
for strict_key in strict_keys
if strict_key not in environ
]

if missing_keys:
raise StrictEnvException(
'Missing env vars: {0}'.format(', '.join(missing_keys)),
)


def dump(
template: str = '',
prefixes: Optional[List[str]] = None,
strict_keys: Optional[Set[str]] = None,
) -> Dict[str, str]:
"""
This function is used to dump .env files.
As a source you can use both:
1. env.template file (`''` by default)
2. env vars prefixed with some prefix (`''` by default)
1. env.template file (``''`` by default)
2. env vars prefixed with some prefix (``''`` by default)
Args:
template (str): The path of the `.env` template file,
template: The path of the `.env` template file,
use an empty string when there is no template file.
prefixes (List[str]): List of string prefixes to use only certain env
prefixes: List of string prefixes to use only certain env
variables, could be an empty string to use all available variables.
Returns:
OrderedDict: ordered key-value pairs.
Ordered key-value pairs of dumped env and template variables.
Raises:
StrictEnvException: when some variable from template is missing.
"""
if prefixes is None:
prefixes = ['']

if strict_keys:
_assert_envs_exist(strict_keys)

store: Dict[str, str] = {}

if template:
# Loading env values from template file:
store.update(parse(template))
store.update(_parse(template))

# Loading env variables from `os.environ`:
for prefix in prefixes:
Expand Down
5 changes: 5 additions & 0 deletions dump_env/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-


class StrictEnvException(Exception):
"""We use this exception when some env vars are missing from environ."""

0 comments on commit 5a01e84

Please sign in to comment.