import copy
import dotenv
import json
import os
import pickle
from cryptography.fernet import Fernet
from io import StringIO
__PATH__ = os.path.dirname(os.path.abspath(__file__))
[docs]def _dict_to_dotenv(env_dict):
"""
Converts a ``dict`` to a .env formatted str.
Parameters
----------
env_dict : dict
Key value dictionary representing env variables.
Returns
-------
str
Str representing a .env formatted file structure.
Author
------
Richard Wen <rrwen.dev@gmail.com>
Example
-------
.. jupyter-execute::
from msdss_base_dotenv.tools import _dict_to_dotenv
# Create default key value env
env = dict(USER='msdss', PASSWORD='msdss123')
# Convert to dotenv file str
env_str = _dict_to_dotenv(env)
# Display results
print('env: ' + str(env))
print('env_str: ' + env_str)
"""
out = '\n'.join([k + '=' + str(v) for k, v in env_dict.items()])
return out
[docs]def clear_env_file(env_file='./.env', key_path=None):
"""
Deletes the encrypted environment file and its key if they exist.
Parameters
----------
env_file : str
Path of the encrypted environment file.
key_path : str
Path of the key file used to unlock the encrypted file. If None, this defaults to the package's directory.
Author
------
Richard Wen <rrwen.dev@gmail.com>
Example
-------
.. jupyter-execute::
from msdss_base_dotenv.tools import *
# Create default key value env
env = dict(USER='msdss', PASSWORD='msdss123')
# Save the key value env to an encrypted file
save_env_file(env)
# Remove the env file and its associated key
clear_env_file()
"""
# (clear_env_env_file) Get path of encrypted env files
env_path = os.path.abspath(env_file)
key_path = key_path if key_path is not None else os.path.join(__PATH__, '.env.key')
# (clear_env_file_remove) Remove the encrypted env files
for path in [env_path, key_path]:
if os.path.exists(path):
os.remove(path)
[docs]def del_env_var(name, env_file='./.env', key_path=None):
"""
Deletes an environmental variable using a file from :func:`msdss_base_dotenv.tools.save_env_file`.
Parameters
----------
name : str
The name of the environmental variable to be deleted.
env_file : str
Path of the environment save file.
key_path : str
Path of the key file used to unlock the save file. If None, this defaults to the package's directory.
Author
------
Richard Wen <rrwen.dev@gmail.com>
Example
-------
.. jupyter-execute::
import os
from msdss_base_dotenv.tools import *
# Clear any existing env files
clear_env_file()
# Create key value env and save it
env = dict(USER='msdss', PASSWORD='msdss123')
save_env_file(env)
# Load the saved env
load_env_file()
# Remove the password var from the saved env
del_env_var('PASSWORD')
# Load the saved env after the removal
load_env_file()
loaded_env = dict(
USER=os.environ['USER'],
PASSWORD=os.getenv('PASSWORD', None)
)
# Display the results
print('env: ' + str(env))
print('loaded_env: ' + str(loaded_env))
# Clear env files
clear_env_file()
"""
env = load_env_file(env_file=env_file, key_path=key_path, set_env=False, return_dict=True)
del os.environ[name]
del env[name]
save_env_file(env, env_file=env_file, key_path=key_path)
[docs]def env_exists(env_file='./.env', key_path=None):
"""
Checks if an environment exists using a saved environment file from :func:`msdss_base_dotenv.tools.save_env_file`.
Parameters
----------
env_file : str
Path of the environment save file.
key_path : str
Path of the key file used to unlock the save file. If None, this defaults to the package's directory.
Returns
-------
bool
Whether or not ``env_file`` and ``key_path`` exist, which define whether the env exists.
Author
------
Richard Wen <rrwen.dev@gmail.com>
Example
-------
.. jupyter-execute::
from msdss_base_dotenv.tools import *
# Clear any existing env files
clear_env_file()
# Check if env exists
exists_before = env_exists()
# Create key value env and save it
env = dict(USER='msdss', PASSWORD='msdss123')
save_env_file(env)
# Check if env exists
exists_after = env_exists()
# Display the results
print('exists_before: ' + str(exists_before))
print('exists_after: ' + str(exists_after))
# Clear env files
clear_env_file()
"""
# (env_exists_env_file) Get path of encrypted env file
env_path = os.path.abspath(env_file)
key_path = key_path if key_path is not None else os.path.join(__PATH__, '.env.key')
# (env_exists_return) Check if env file and key exists
out = os.path.isfile(env_path) and os.path.isfile(key_path)
return out
[docs]def load_env_file(env_file='./.env', key_path=None, defaults={}, set_env=True, return_dict=False):
"""
Loads a saved environment file from :func:`msdss_base_dotenv.tools.save_env_file`.
Parameters
----------
env_file : str
Path of the environment save file.
key_path : str
Path of the key file used to unlock the save file. If None, this defaults to the package's directory.
defaults : dict
Key and value pairs representing default environment values to be loaded. These will replace ones in ``env`` if they do not exist or are unset.
set_env : bool
Whether to set the ``os.environ`` with the variables in the ``env_file`` or not.
return_dict : bool
Whether to return a dictionary of the env variables or not.
Returns
-------
dict
Dictionary of the decrypted key value environment from ``env_file`` if ``return_dict`` is ``True``.
Author
------
Richard Wen <rrwen.dev@gmail.com>
Example
-------
.. jupyter-execute::
import os
from msdss_base_dotenv.tools import *
# Clear any existing env files
clear_env_file()
# Create key value env and save it with defaults
env = dict(USER='msdss', PASSWORD='msdss123')
defaults = dict(DATABASE='postgres', PASSWORD='already-set')
save_env_file(env, defaults=defaults)
# Load the saved env file
load_env_file()
loaded_env = dict(
USER=os.environ['USER'],
PASSWORD=os.environ['PASSWORD'],
DATABASE=os.environ['DATABASE']
)
# Display the results
print('env: ' + str(env))
print('defaults: ' + str(defaults))
print('loaded_env: ' + str(loaded_env))
# Clear env files
clear_env_file()
"""
# (load_env_env_file) Get path of encrypted env file
env_path = os.path.abspath(env_file)
key_path = key_path if key_path is not None else os.path.join(__PATH__, '.env.key')
# (load_env_file_load) Load env details file
with open(key_path, 'rb') as key_file, open(env_path, 'rb') as env_file:
# (load_env_file_key) Load key store object
key = pickle.load(key_file)
decrypter = Fernet(key)
# (load_env_file_encrypted) Get encrypted env details
encrypted = pickle.load(env_file)
# (load_env_file_decrypt) Decrypt env
decrypted = decrypter.decrypt(encrypted).decode('utf-8')
env = json.loads(decrypted)
# (load_env_file_set) Set env variables in environ
if set_env:
# (load_env_file_set_defaults) Set defaults first
defaults_str = _dict_to_dotenv(defaults)
dotenv.load_dotenv(stream=StringIO(defaults_str), override=True)
# (load_env_file_set_os) Set env vars in os and override defaults
env_str = _dict_to_dotenv(env)
dotenv.load_dotenv(stream=StringIO(env_str), override=True)
# (load_env_file_return) Returns a dict
if return_dict:
out = env
return out
[docs]def save_env_file(env, env_file='./.env', key_path=None, defaults={}):
"""
Saves a login file with the connection details.
Parameters
----------
env : dict
Key and value pairs representing environment values to be saved.
env_file : str
Path of the encrypted environment save file. Read/write/execute permissions will be set only for the owner of this file.
key_path : str
Path of the key file used to unlock the save file. If ``None``, this defaults to the package's directory.
defaults : dict
Key and value pairs representing default environment values to be saved. These will replace ones in ``env`` if they do not exist or are unset.
Author
------
Richard Wen <rrwen.dev@gmail.com>
Example
-------
.. jupyter-execute::
from msdss_base_dotenv.tools import *
# Clear any existing env files
clear_env_file()
# Create default key value env
env = dict(USER='msdss', PASSWORD='msdss123')
# Save the key value env to an encrypted file
save_env_file(env)
# Clear env files
clear_env_file()
"""
# (save_env_file_defaults) Set default env values
defaults = copy.deepcopy(defaults)
defaults.update(env)
env = copy.deepcopy(defaults)
# (save_env_env_file) Get path of encrypted env file
env_path = os.path.abspath(env_file)
key_path = key_path if key_path is not None else os.path.join(__PATH__, '.env.key')
# (save_env_file_key) Obtain key if exists or create one if not
key_exists = os.path.isfile(key_path)
if key_exists: # read key
with open(key_path, 'rb') as key_file:
key = pickle.load(key_file)
else: # create key
with open(key_path, 'wb') as key_file:
key = Fernet.generate_key()
pickle.dump(key, key_file)
os.chmod(key_path, 0o700) # set read/write/execute only for owner
encrypter = Fernet(key)
# (save_env_file_encrypt) Encrypt env file
out = encrypter.encrypt(bytes(json.dumps(env), encoding = 'utf-8'))
with open(env_path, 'wb') as env_file:
pickle.dump(out, env_file)
[docs]def set_env_var(name, value, env_file='./.env', key_path=None):
"""
Sets an environmental variable using a file from :func:`msdss_base_dotenv.tools.save_env_file`.
Parameters
----------
name : str
The name of the environmental variable to be set.
value : str
The value of the environmental variable to be set.
env_file : str
Path of the environment save file.
key_path : str
Path of the key file used to unlock the save file. If None, this defaults to the package's directory.
Author
------
Richard Wen <rrwen.dev@gmail.com>
Example
-------
.. jupyter-execute::
import os
from msdss_base_dotenv.tools import *
# Clear any existing env files
clear_env_file()
# Create key value env and save it
env = dict(USER='msdss')
save_env_file(env)
# Add/set a password var to the saved env
set_env_var('USER', 'msdssnew')
set_env_var('PASSWORD', 'msdss123')
# Load the saved env after the addition
load_env_file()
loaded_env = dict(
USER=os.environ['USER'],
PASSWORD=os.environ['PASSWORD']
)
# Display the results
print('env: ' + str(env))
print('loaded_env: ' + str(loaded_env))
# Clear env files
clear_env_file()
"""
env = load_env_file(env_file=env_file, key_path=key_path, set_env=False, return_dict=True)
os.environ[name] = str(value)
env[name] = os.environ[name]
save_env_file(env, env_file=env_file, key_path=key_path)