1import os 2import sys 3from subprocess import Popen 4from typing import Any, Dict, List 5 6try: 7 import click 8except ImportError: 9 sys.stderr.write('It seems python-dotenv is not installed with cli option. \n' 10 'Run pip install "python-dotenv[cli]" to fix this.') 11 sys.exit(1) 12 13from .main import dotenv_values, get_key, set_key, unset_key 14from .version import __version__ 15 16 17@click.group() 18@click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'), 19 type=click.Path(file_okay=True), 20 help="Location of the .env file, defaults to .env file in current working directory.") 21@click.option('-q', '--quote', default='always', 22 type=click.Choice(['always', 'never', 'auto']), 23 help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.") 24@click.option('-e', '--export', default=False, 25 type=click.BOOL, 26 help="Whether to write the dot file as an executable bash script.") 27@click.version_option(version=__version__) 28@click.pass_context 29def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None: 30 '''This script is used to set, get or unset values from a .env file.''' 31 ctx.obj = {} 32 ctx.obj['QUOTE'] = quote 33 ctx.obj['EXPORT'] = export 34 ctx.obj['FILE'] = file 35 36 37@cli.command() 38@click.pass_context 39def list(ctx: click.Context) -> None: 40 '''Display all the stored key/value.''' 41 file = ctx.obj['FILE'] 42 if not os.path.isfile(file): 43 raise click.BadParameter( 44 'Path "%s" does not exist.' % (file), 45 ctx=ctx 46 ) 47 dotenv_as_dict = dotenv_values(file) 48 for k, v in dotenv_as_dict.items(): 49 click.echo('%s=%s' % (k, v)) 50 51 52@cli.command() 53@click.pass_context 54@click.argument('key', required=True) 55@click.argument('value', required=True) 56def set(ctx: click.Context, key: Any, value: Any) -> None: 57 '''Store the given key/value.''' 58 file = ctx.obj['FILE'] 59 quote = ctx.obj['QUOTE'] 60 export = ctx.obj['EXPORT'] 61 success, key, value = set_key(file, key, value, quote, export) 62 if success: 63 click.echo('%s=%s' % (key, value)) 64 else: 65 exit(1) 66 67 68@cli.command() 69@click.pass_context 70@click.argument('key', required=True) 71def get(ctx: click.Context, key: Any) -> None: 72 '''Retrieve the value for the given key.''' 73 file = ctx.obj['FILE'] 74 if not os.path.isfile(file): 75 raise click.BadParameter( 76 'Path "%s" does not exist.' % (file), 77 ctx=ctx 78 ) 79 stored_value = get_key(file, key) 80 if stored_value: 81 click.echo(stored_value) 82 else: 83 exit(1) 84 85 86@cli.command() 87@click.pass_context 88@click.argument('key', required=True) 89def unset(ctx: click.Context, key: Any) -> None: 90 '''Removes the given key.''' 91 file = ctx.obj['FILE'] 92 quote = ctx.obj['QUOTE'] 93 success, key = unset_key(file, key, quote) 94 if success: 95 click.echo("Successfully removed %s" % key) 96 else: 97 exit(1) 98 99 100@cli.command(context_settings={'ignore_unknown_options': True}) 101@click.pass_context 102@click.option( 103 "--override/--no-override", 104 default=True, 105 help="Override variables from the environment file with those from the .env file.", 106) 107@click.argument('commandline', nargs=-1, type=click.UNPROCESSED) 108def run(ctx: click.Context, override: bool, commandline: List[str]) -> None: 109 """Run command with environment variables present.""" 110 file = ctx.obj['FILE'] 111 if not os.path.isfile(file): 112 raise click.BadParameter( 113 'Invalid value for \'-f\' "%s" does not exist.' % (file), 114 ctx=ctx 115 ) 116 dotenv_as_dict = { 117 k: v 118 for (k, v) in dotenv_values(file).items() 119 if v is not None and (override or k not in os.environ) 120 } 121 122 if not commandline: 123 click.echo('No command given.') 124 exit(1) 125 ret = run_command(commandline, dotenv_as_dict) 126 exit(ret) 127 128 129def run_command(command: List[str], env: Dict[str, str]) -> int: 130 """Run command in sub process. 131 132 Runs the command in a sub process with the variables from `env` 133 added in the current environment variables. 134 135 Parameters 136 ---------- 137 command: List[str] 138 The command and it's parameters 139 env: Dict 140 The additional environment variables 141 142 Returns 143 ------- 144 int 145 The return code of the command 146 147 """ 148 # copy the current environment variables and add the vales from 149 # `env` 150 cmd_env = os.environ.copy() 151 cmd_env.update(env) 152 153 p = Popen(command, 154 universal_newlines=True, 155 bufsize=0, 156 shell=False, 157 env=cmd_env) 158 _, _ = p.communicate() 159 160 return p.returncode 161 162 163if __name__ == "__main__": 164 cli() 165