├── .gitignore ├── LICENSE ├── README.md ├── defaultenv.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # defaultenv 2 | 3 | Environment and config file reader for python3. 4 | **Warrning:** slightly magic inside. This module magically read and use environment value both from '.env' file and environment itself. 5 | 6 | *Since version 0.0.6 `.env` file will be rereaded on the fly on next `env` call, so now your environment is always up to date.* 7 | 8 | ```python 9 | $ echo "MY_VAL='test'" > .env 10 | $ python 11 | >>> from defaultenv import env 12 | >>> env('PATH') 13 | '/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin' 14 | >>> env('TEST') 15 | >>> env('MY_VAL') 16 | 'test' 17 | >>> import os; os.environ['MY_VAL'] 18 | 'test' 19 | 20 | ``` 21 | 22 | `env` method may be used to check the value of variable. 23 | If variable is not defined `env` method will return `None`. 24 | If both environment variable and corresponding .env record is exist then .env have a priority. 25 | And yes, you can use `os.environ` instead of `env()`, all records from .env will be exported immidiately. 26 | 27 | For additional convinience you can use `env()` with `default` argument, for example: 28 | 29 | ```python 30 | >>> from defaultenv import env 31 | >>> env('TEST', 'no test') 32 | 'no test' 33 | >>> env('UID') 34 | '1000' 35 | >>> env('UID', int) 36 | 1000 37 | >>> env('HOME', pathlib.Path) 38 | PosixPath("/home/bobuk") 39 | >>> env('PATH', lambda x: [pathlib.Path(_) for _ in x.split(':')]) 40 | [PosixPath('/usr/local/sbin'), PosixPath('/usr/local/bin'), PosixPath('/usr/sbin'), PosixPath('/usr/bin'), 41 | PosixPath('/sbin'), PosixPath('/bin')] 42 | ``` 43 | 44 | If `default` argument for `env()` is not empty, and key what you looking for is not exists `env()` will return you value of default. 45 | But if `default` is callable (like object, lambda or function) then instead value of key from environment will be passed to this callable. 46 | My favorite is to send just `int` because it's the easiest way to convert your default to integer. 47 | 48 | Since version 0.0.2 for more convinience two classes (ENV and ENVC) was added. You can use your environment variable name without method calling. 49 | 50 | ```python 51 | $ python 52 | >>> from defaultenv import ENV 53 | >>> ENV.PATH 54 | '/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin' 55 | ``` 56 | 57 | ENV usage removing unnesesery typing of perenthesis and quotes. In this example i've save 4 keystrokes for you. But it's not very convinient to type uppercased name everytime. Let's imagine what my pinky finger is killing me. 58 | 59 | ```python 60 | $ python 61 | >>> from defaultenv import ENVC as env 62 | >>> env.shell 63 | '/usr/local/bin/zsh' 64 | >>> env.home 65 | '/home/bobuk' 66 | ``` 67 | 68 | As you can see `ENVC` convert your variable name to uppercase. 69 | For both ENVC and ENV there's a method `defaults` to add default values of callables (as for `env` above). 70 | 71 | ```python 72 | >>> from defaultenv import ENVC as env 73 | >>> env.defaults(test = 1, path = lambda x: x.split(':'), pid=int) 74 | >>> env.test 75 | 1 76 | >>> env.path 77 | ['/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin'] 78 | ``` 79 | 80 | What the difference between `os.environ.get('PATH', None)` and `env.path`? It's easy to calculate and the result is 21 (which is half of 42). 81 | 82 | Since version 0.0.9 you can use even more authomated `ENVCD` which is `ENVC` but with predefined defaults. 83 | Every path (or colon-separated paths list) will be defaulted to `PosixPath`, every digital value converted to `int`. 84 | 85 | ```python 86 | >>> from defaultenv import ENVCD as env 87 | >>> env.path 88 | [PosixPath('/usr/local/bin'), PosixPath('/usr/local/bin'), PosixPath('/usr/bin'), PosixPath('/bin'), PosixPath('/usr/sbin')] 89 | ``` -------------------------------------------------------------------------------- /defaultenv.py: -------------------------------------------------------------------------------- 1 | import typing 2 | import os, sys, ast, os.path, pathlib 3 | 4 | __env_timestamp__ = 0.0 5 | __fname__ = ".env" 6 | 7 | 8 | def read_env_file(fname:str =__fname__) -> None: 9 | if os.path.exists(fname): 10 | with open(fname, "r") as fl: 11 | cont: str = "" 12 | num: int 13 | line: str 14 | for num, line in enumerate(fl): 15 | line = cont + line.lstrip() 16 | line = line.strip() 17 | if "#" in line: 18 | line = line.split("#", 1)[0].strip() 19 | if line.endswith("\\"): 20 | cont = line[:-1] 21 | continue 22 | if len(line) > 1: 23 | if "=" not in line: 24 | sys.stderr.write( 25 | "Err in {fname} line {num}: {line}\n".format_map(locals()) 26 | ) 27 | key: str 28 | val: str 29 | key, val = line.split("=", 1) 30 | key = key.strip().upper() 31 | val = val.strip() 32 | try: 33 | val = ast.literal_eval(val) 34 | except: 35 | pass 36 | 37 | os.environ[key] = str(val) 38 | cont = "" 39 | __env_timestamp__ = os.path.getmtime(fname) 40 | 41 | 42 | def load_env_file() -> None: 43 | try: 44 | if os.path.getmtime(__fname__) > __env_timestamp__: 45 | read_env_file() 46 | except: 47 | pass 48 | 49 | 50 | def env(key:str, default: typing.Optional[typing.Any] = None): 51 | load_env_file() 52 | 53 | key = os.environ[key] if key in os.environ else "" 54 | if not key: 55 | return default(key) if callable(default) else default 56 | if default and callable(default): 57 | return default(key) 58 | return key 59 | 60 | 61 | def auto_default(val: str) -> typing.Union[str, int, typing.List[pathlib.Path], pathlib.Path]: 62 | if not val: 63 | return val 64 | if val.isdigit(): 65 | return int(val) 66 | if val.startswith("/"): 67 | if ":" in val: 68 | return [pathlib.Path(_) for _ in val.split(":")] 69 | return pathlib.Path(val) 70 | return val 71 | 72 | class EnvObj: 73 | 74 | def __init__(self, capitalize=False): 75 | self.capitalize: bool = capitalize 76 | self.default: typing.Dict = {} 77 | self.default_default = None 78 | 79 | def defaults(self, **params): 80 | self.default = params if not self.capitalize else { 81 | k.upper(): v for k, v in params.items() 82 | } 83 | return self 84 | 85 | def pretty_good_defaults(self): 86 | self.default_default = auto_default 87 | return self 88 | 89 | def __getattr__(self, name: str) -> typing.Union[str, int, typing.List[pathlib.Path], pathlib.Path]: 90 | if self.capitalize: 91 | name = name.upper() 92 | return env(name, default=self.default.get(name, self.default_default)) 93 | 94 | 95 | ENV = EnvObj() 96 | ENVC = EnvObj(capitalize=True) 97 | ENVCD = EnvObj(capitalize=True).pretty_good_defaults() 98 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | setup( 5 | name="defaultenv", 6 | version="0.0.12", 7 | py_modules=["defaultenv"], 8 | url="http://github.com/bobuk/defaultenv", 9 | author="Grigory Bakunov", 10 | author_email="thebobuk@ya.ru", 11 | description="Environment and .env file reader", 12 | long_description=open("README.md").read() if os.path.isfile("README.md") else "", 13 | long_description_content_type="text/markdown", 14 | classifiers=[ 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Development Status :: 4 - Beta", 18 | "Environment :: Other Environment", 19 | "Intended Audience :: Developers", 20 | "Operating System :: OS Independent", 21 | "Topic :: Software Development :: Libraries :: Python Modules", 22 | ], 23 | ) 24 | --------------------------------------------------------------------------------