├── .bumpversion.cfg ├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── envreader ├── __init__.py ├── env_reader.py ├── exceptions.py ├── field.py ├── field_getter.py └── version.py ├── requirements-dev.txt ├── requirements-package.txt ├── requirements-test.txt ├── setup.py └── tests ├── conftest.py ├── test_default.py ├── test_fields.py ├── test_json.py ├── test_simple.py ├── test_simple_annotations.py └── test_transform.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.0.0 3 | commit = True 4 | tag = True 5 | tag_name = v{new_version} 6 | 7 | [bumpversion:file:envreader/version.py] 8 | 9 | [bumpversion:file:setup.py] 10 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: PUBLISH 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | 10 | 11 | publish: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Set up Python 3.11 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: 3.11 21 | 22 | - name: Install dependencies 23 | run: | 24 | python3 -m pip install --upgrade pip 25 | pip3 install -r requirements-package.txt 26 | 27 | - name: Build 28 | run: | 29 | python3 setup.py sdist bdist_wheel 30 | 31 | - name: Publish 32 | env: 33 | PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} 34 | PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 35 | run: | 36 | python3 -m twine upload -u $PYPI_USERNAME -p $PYPI_PASSWORD dist/* 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: TEST 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | ~ 9 | 10 | jobs: 11 | 12 | 13 | test-flake8: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | 29 | - name: Install dependencies 30 | run: | 31 | python3 -m pip install --upgrade pip 32 | pip3 install -r requirements-test.txt 33 | 34 | - name: Lint with flake8 35 | run: | 36 | # stop the build if there are Python syntax errors or undefined names 37 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 38 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 39 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 40 | 41 | test-pytest: 42 | runs-on: ubuntu-latest 43 | 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 48 | 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - name: Set up Python ${{ matrix.python-version }} 53 | uses: actions/setup-python@v5 54 | with: 55 | python-version: ${{ matrix.python-version }} 56 | 57 | - name: Install dependencies 58 | run: | 59 | python3 -m pip install --upgrade pip 60 | pip3 install -r requirements-test.txt 61 | 62 | - name: Test with pytest 63 | run: | 64 | python3 -m pytest 65 | 66 | test-package: 67 | runs-on: ubuntu-latest 68 | 69 | strategy: 70 | fail-fast: false 71 | matrix: 72 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 73 | 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | - name: Set up Python ${{ matrix.python-version }} 78 | uses: actions/setup-python@v5 79 | with: 80 | python-version: ${{ matrix.python-version }} 81 | 82 | - name: Install dependencies 83 | run: | 84 | python3 -m pip install --upgrade pip 85 | pip3 install -r requirements-package.txt 86 | 87 | - name: Test build and setup 88 | run: | 89 | python3 setup.py sdist bdist_wheel 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Environments 88 | .env 89 | .venv 90 | env/ 91 | venv/ 92 | ENV/ 93 | env.bak/ 94 | venv.bak/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project. 3 | 4 | 5 | ## [Unreleased] 6 | 7 | 8 | ## [1.0.0] - 2024-02-05 9 | 10 | ### Added 11 | - Support for python 3.11 12 | - Support for python 3.12 13 | ### Removed 14 | - Support for python 3.6 15 | 16 | 17 | ## [0.0.5] - 2022-02-16 18 | 19 | ### Added 20 | - Added support for PEP 563: Postponed Evaluation of Annotations 21 | 22 | 23 | ## [0.0.4] - 2021-02-03 24 | 25 | ### Added 26 | - Added list, tuple and dict types, assuming json-input. 27 | 28 | 29 | ## [0.0.3] - 2020-10-13 30 | 31 | ### Added 32 | - Added "yes" and "y" variants for the bool type. 33 | 34 | 35 | ## [0.0.1] - 2020-10-12 36 | 37 | ### Added 38 | - Initial release. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2024 Vd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EnvReader 2 | 3 | Environment variables parser with types! Yes! 4 | 5 | Every time when you make new service you need some class to receive, validate and store environment variables. 6 | 7 | With this package it’ll be easy and funny. 8 | 9 | Just make a class with typed fields and... that’s it. 10 | 11 | ### Requirements 12 | 13 | Python 3.7 and above. There's no additional dependencies. 14 | 15 | ### Installation 16 | 17 | `pip install envreader` 18 | 19 | ### Simple usage 20 | 21 | ```python 22 | from envreader import EnvReader 23 | 24 | 25 | class MyEnv(EnvReader): 26 | PATH: str 27 | LIST: list 28 | NONE_EXIST: int = 1234 # Variable with default value 29 | 30 | 31 | e = MyEnv() 32 | 33 | print(e.PATH) 34 | # /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin 35 | 36 | print(e.LIST) 37 | # [1, 2, 3, 4] 38 | 39 | print(e.NONE_EXIST) 40 | # 1234 41 | ``` 42 | 43 | ### Use with transform functions 44 | 45 | I don’t want to make a giant validation library like wonderful **Pydantic**. Thus EnvReader supports only simple types( 46 | bool, str, int, float, list, tuple and dict) by default. This is enough in most cases. 47 | 48 | Transform functions allows using EnvReader for more complex cases. 49 | 50 | ```python 51 | from typing import List 52 | from envreader import EnvReader, Field 53 | 54 | 55 | class MyEnv(EnvReader): 56 | PATH: List[str] = Field(transform=lambda x: x.split(":")) 57 | 58 | 59 | e = MyEnv() 60 | 61 | print(e.PATH) 62 | # ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'] 63 | ``` 64 | 65 | ### Using static methods as a transform functions. 66 | 67 | You may store all your helper functions inside the same class. But don’t forget to add @staticmethod decorator. 68 | 69 | ```python 70 | from typing import List 71 | from envreader import EnvReader, Field 72 | 73 | 74 | class MyEnv(EnvReader): 75 | @staticmethod 76 | def trans(x: str) -> List[str]: 77 | return x.split(':') 78 | 79 | PATH: List[str] = Field(transform=trans) 80 | 81 | 82 | e = MyEnv() 83 | 84 | print(e.PATH) 85 | # ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'] 86 | ``` 87 | 88 | ### Make your environment variables self-documented. 89 | 90 | Documentation is in great demand for all good applications, right? 91 | 92 | ```python 93 | from envreader import EnvReader, Field 94 | 95 | 96 | class MyEnv(EnvReader): 97 | PATH: str = Field("/sbin", description="Application path", example="/usr/bin:/bin:/usr/sbin:/sbin") 98 | 99 | 100 | e = MyEnv() 101 | 102 | print(e.help()) 103 | # PATH 104 | # Application path 105 | # Example: /usr/bin:/bin:/usr/sbin:/sbin 106 | # Default: /sbin 107 | ``` 108 | 109 | ### Error handling? Easy. 110 | 111 | ```python 112 | import sys 113 | from envreader import EnvReader, EnvMissingError 114 | 115 | 116 | class MyEnv(EnvReader): 117 | SOME_VAR: str 118 | 119 | 120 | try: 121 | e = MyEnv() 122 | except EnvMissingError as e: 123 | print(f"Missing required env var {e.field}") 124 | sys.exit(-1) 125 | # Missing required env var SOME_VAR 126 | ``` 127 | 128 | #### Enjoy! 129 | -------------------------------------------------------------------------------- /envreader/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | from .env_reader import EnvReader 7 | from .exceptions import EnvMissingError, EnvTransformError 8 | from .field import Field 9 | from .version import version 10 | -------------------------------------------------------------------------------- /envreader/env_reader.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | import json 6 | from typing import Any, List 7 | 8 | from .field import Field 9 | from .field_getter import FieldGetter 10 | 11 | 12 | class EnvReader: 13 | __transforms = { 14 | str: lambda x: str(x), 15 | int: lambda x: int(str(x)), 16 | float: lambda x: float(str(x)), 17 | bool: lambda x: str(x).strip().lower() in ('1', 'true', 'ok', 'on', 'yes', 'y', 'enable', 'enabled'), 18 | list: lambda x: list(json.loads(x) if isinstance(x, str) else x), 19 | tuple: lambda x: tuple(json.loads(x) if isinstance(x, str) else x), 20 | dict: lambda x: dict(json.loads(x) if isinstance(x, str) else x) 21 | } 22 | __transforms = {**__transforms, **{k.__name__: v for k, v in __transforms.items()}} 23 | 24 | def __new__(cls: Any, *args, cached: bool = True, populate: bool = True, **kwargs): 25 | _attrs = {} 26 | 27 | for attr, attr_type in cls.__annotations__.items(): 28 | _field: Field = getattr(cls, attr, None) 29 | 30 | if not isinstance(_field, Field): 31 | _field = Field(... if _field is None else _field, alias=attr) 32 | 33 | if attr_type not in cls.__transforms and not _field.transform: 34 | raise TypeError(f"Unsupported type `{attr_type}` for field `{attr}`. " 35 | f"Supported field types is `{list(cls.__transforms.keys())}`. " 36 | f"Please provide transform function!") 37 | 38 | _field_getter = FieldGetter( 39 | default=_field.default, 40 | alias=_field.alias or attr, 41 | transform=_field.transform or cls.__transforms[attr_type], 42 | description=_field.description, 43 | example=_field.example, 44 | cached=cached 45 | ) 46 | 47 | if populate: 48 | _field_getter.get_value() 49 | 50 | _attrs[attr] = _field_getter 51 | 52 | return super().__new__(type(cls.__name__, (cls,), _attrs)) 53 | 54 | def fields(self) -> List[Field]: 55 | values = type(self).__dict__.values() 56 | return [i for i in values if isinstance(i, FieldGetter)] 57 | 58 | def help(self) -> str: 59 | return "\n\n".join([i.help() for i in self.fields()]) 60 | -------------------------------------------------------------------------------- /envreader/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | class EnvError(ValueError): 7 | def __init__(self, field: str, msg: str): 8 | self.__field = field 9 | super().__init__(msg) 10 | 11 | @property 12 | def field(self) -> str: 13 | return self.__field 14 | 15 | 16 | class EnvMissingError(EnvError): 17 | pass 18 | 19 | 20 | class EnvTransformError(EnvError): 21 | pass 22 | -------------------------------------------------------------------------------- /envreader/field.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | import os 7 | from typing import Any, Optional, Callable 8 | 9 | from .exceptions import EnvMissingError, EnvTransformError 10 | 11 | 12 | class Field: 13 | def __init__(self, default=..., *, alias: Optional[str] = None, transform: Optional[Callable] = None, 14 | description: Optional[str] = None, example: Optional[str] = None): 15 | self.__default = default 16 | self.__alias = alias 17 | self.__description = description 18 | self.__example = example 19 | self.__transform = transform.__func__ if isinstance(transform, staticmethod) else transform 20 | 21 | if self.__transform and not callable(self.__transform): 22 | raise TypeError("Unsupported transform value! Use callable object, function, lambda or staticmethod!") 23 | 24 | @property 25 | def default(self) -> Any: 26 | return self.__default 27 | 28 | @property 29 | def alias(self) -> Optional[str]: 30 | return self.__alias 31 | 32 | @property 33 | def transform(self) -> Optional[Callable]: 34 | return self.__transform 35 | 36 | @property 37 | def description(self) -> Optional[str]: 38 | return self.__description 39 | 40 | @property 41 | def example(self) -> Optional[str]: 42 | return self.__example 43 | 44 | def get_value(self) -> Any: 45 | val = os.environ.get(self.__alias) 46 | 47 | if val is None and self.__default is ...: 48 | raise EnvMissingError(self.__alias, f"Required environment variable `{self.__alias}` was not found!") 49 | 50 | if val is None: 51 | val = self.__transform(self.__default) 52 | val = self.__default 53 | 54 | try: 55 | return self.__transform(val) 56 | except Exception: 57 | raise EnvTransformError(self.__alias, 58 | f"Cannot transform field `{self.__alias}`! Probably it have wrong format!") 59 | 60 | def help(self) -> str: 61 | res = self.__alias 62 | if self.__description: 63 | res = f"{res}\n {self.__description}" 64 | if self.__example or self.__default is not ...: 65 | res = f"{res}\n Example: {self.__example or self.__default}" 66 | if self.__default is not ...: 67 | res = f"{res}\n Default: {self.__default}" 68 | 69 | return res 70 | -------------------------------------------------------------------------------- /envreader/field_getter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | from typing import Callable, Optional 7 | 8 | from .field import Field 9 | 10 | 11 | class FieldGetter(Field): 12 | def __init__(self, default, alias: str, transform: Callable, description: Optional[str], 13 | example: Optional[str], cached: bool): 14 | 15 | super().__init__(default, alias=alias, transform=transform, description=description, example=example) 16 | self.__cached = cached 17 | self.__value = None 18 | 19 | def get_value(self): 20 | if self.__value: 21 | return self.__value 22 | 23 | val = super().get_value() 24 | 25 | if self.__cached: 26 | self.__value = val 27 | 28 | return val 29 | 30 | def __get__(self, obj, cls=None): 31 | return self.get_value() 32 | 33 | def __set__(self, obj, value): 34 | raise AttributeError("You can't change an env variable from here! Use os.environ for this.") 35 | -------------------------------------------------------------------------------- /envreader/version.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | def version(): 7 | return "1.0.0" 8 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | bump2version 2 | -------------------------------------------------------------------------------- /requirements-package.txt: -------------------------------------------------------------------------------- 1 | twine 2 | setuptools 3 | wheel 4 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | flake8 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | from os.path import join, dirname 7 | 8 | import setuptools 9 | 10 | setuptools.setup( 11 | name='envreader', 12 | version='1.0.0', 13 | author='Vd', 14 | author_email='vd@vd2.org', 15 | url='https://github.com/vd2org/envreader', 16 | license='MIT', 17 | description='Environment variables parser with types!', 18 | long_description=open(join(dirname(__file__), 'README.md')).read(), 19 | long_description_content_type='text/markdown', 20 | packages=setuptools.find_packages(), 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Intended Audience :: Developers', 24 | 'Intended Audience :: Education', 25 | 'Intended Audience :: Information Technology', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Natural Language :: English', 28 | 'Operating System :: OS Independent', 29 | 'Programming Language :: Python :: 3.7', 30 | 'Programming Language :: Python :: 3.8', 31 | 'Programming Language :: Python :: 3.9', 32 | 'Programming Language :: Python :: 3.10', 33 | 'Programming Language :: Python :: 3.11', 34 | 'Programming Language :: Python :: 3.12', 35 | 'Programming Language :: Python :: 3 :: Only', 36 | 'Topic :: Utilities', 37 | 'Topic :: Software Development', 38 | 'Topic :: Software Development :: Libraries', 39 | 'Topic :: Software Development :: Libraries :: Python Modules', 40 | ], 41 | ) 42 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info.major == 3 and sys.version_info.minor < 7: 4 | collect_ignore = ["test_simple_annotations.py"] 5 | -------------------------------------------------------------------------------- /tests/test_default.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | import os 7 | 8 | from envreader import EnvReader, Field 9 | 10 | 11 | def test_default(): 12 | os.environ['VAR_EXIST_1'] = "some_string" 13 | os.environ['VAR_EXIST_2'] = "some_string" 14 | 15 | class MyEnv(EnvReader): 16 | VAR_STR: str = Field("some_string") 17 | VAR_INT_1: int = 123456 18 | VAR_INT_2: int = "123456" 19 | VAR_EXIST_1: str = "XXXXXX" 20 | VAR_EXIST_2: str 21 | 22 | e = MyEnv() 23 | 24 | assert e.VAR_STR == "some_string" 25 | assert e.VAR_INT_1 == 123456 26 | assert e.VAR_INT_2 == 123456 27 | assert e.VAR_EXIST_1 == "some_string" 28 | assert e.VAR_EXIST_2 == "some_string" 29 | 30 | fields = {i.alias: i for i in e.fields()} 31 | 32 | assert fields['VAR_STR'].default == "some_string" 33 | assert fields['VAR_INT_1'].default == 123456 34 | assert fields['VAR_INT_2'].default == "123456" 35 | assert fields['VAR_EXIST_1'].default == "XXXXXX" 36 | assert fields['VAR_EXIST_2'].default is ... 37 | -------------------------------------------------------------------------------- /tests/test_fields.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | import os 7 | 8 | from envreader import EnvReader, Field 9 | 10 | 11 | def test_fields(): 12 | os.environ['VAR_STR_R'] = "some_string" 13 | os.environ['VAR_INT_R'] = "1234" 14 | 15 | class MyEnv(EnvReader): 16 | VAR_STR: str = Field(..., alias="VAR_STR_R", description="description", example="example") 17 | VAR_INT: int = Field(123, alias="VAR_INT_R", description="int", example="123") 18 | 19 | e = MyEnv() 20 | 21 | assert e.VAR_STR == "some_string" 22 | assert e.VAR_INT == 1234 23 | 24 | fields = {i.alias: i for i in e.fields()} 25 | 26 | assert fields['VAR_STR_R'].get_value() == "some_string" 27 | assert fields['VAR_STR_R'].alias == "VAR_STR_R" 28 | assert fields['VAR_STR_R'].default is ... 29 | assert fields['VAR_STR_R'].description == "description" 30 | assert fields['VAR_STR_R'].example == "example" 31 | 32 | assert fields['VAR_STR_R'].help() == "VAR_STR_R\n description\n Example: example" 33 | 34 | assert fields['VAR_INT_R'].get_value() == 1234 35 | assert fields['VAR_INT_R'].alias == "VAR_INT_R" 36 | assert fields['VAR_INT_R'].default == 123 37 | assert fields['VAR_INT_R'].description == "int" 38 | assert fields['VAR_INT_R'].example == "123" 39 | 40 | assert fields['VAR_INT_R'].help() == "VAR_INT_R\n int\n Example: 123\n Default: 123" 41 | 42 | assert e.help() == "VAR_STR_R\n description\n Example: example\n\n" \ 43 | "VAR_INT_R\n int\n Example: 123\n Default: 123" 44 | -------------------------------------------------------------------------------- /tests/test_json.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | import os 7 | 8 | from envreader import EnvReader 9 | 10 | 11 | def test_json(): 12 | os.environ['VAR_DICT'] = '{"a": "text", "b": 1, "c": 2.5}' 13 | os.environ['VAR_LIST'] = '[1, 2, 3, 4, "text"]' 14 | os.environ['VAR_TUPLE'] = '[9, 8, 7, 6, "text"]' 15 | 16 | class MyEnv(EnvReader): 17 | VAR_DICT: dict 18 | VAR_LIST: list 19 | VAR_TUPLE: tuple 20 | 21 | e = MyEnv() 22 | 23 | assert e.VAR_DICT == {"a": "text", "b": 1, "c": 2.5} 24 | assert e.VAR_LIST == [1, 2, 3, 4, "text"] 25 | assert e.VAR_TUPLE == (9, 8, 7, 6, "text") 26 | -------------------------------------------------------------------------------- /tests/test_simple.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | import os 7 | 8 | from envreader import EnvReader 9 | 10 | 11 | def test_simple(): 12 | os.environ['VAR_STR'] = "some_string" 13 | os.environ['VAR_INT'] = "123456" 14 | os.environ['VAR_FLOAT'] = "123.456" 15 | os.environ['VAR_BOOL_OK'] = "ok" 16 | os.environ['VAR_BOOL_TRUE'] = "True" 17 | os.environ['VAR_BOOL_ON'] = "on" 18 | os.environ['VAR_BOOL_1'] = "1" 19 | os.environ['VAR_BOOL_FALSE'] = "False" 20 | 21 | class MyEnv(EnvReader): 22 | VAR_STR: str 23 | VAR_INT: int 24 | VAR_FLOAT: float 25 | VAR_BOOL_OK: bool 26 | VAR_BOOL_TRUE: bool 27 | VAR_BOOL_ON: bool 28 | VAR_BOOL_1: bool 29 | VAR_BOOL_FALSE: bool 30 | 31 | e = MyEnv() 32 | 33 | assert e.VAR_STR == "some_string" 34 | assert e.VAR_INT == 123456 35 | assert e.VAR_FLOAT == 123.456 36 | assert e.VAR_BOOL_OK == True 37 | assert e.VAR_BOOL_TRUE == True 38 | assert e.VAR_BOOL_ON == True 39 | assert e.VAR_BOOL_1 == True 40 | assert e.VAR_BOOL_FALSE == False 41 | -------------------------------------------------------------------------------- /tests/test_simple_annotations.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | from __future__ import annotations 5 | 6 | import os 7 | 8 | from envreader import EnvReader 9 | 10 | 11 | def test_simple(): 12 | os.environ['VAR_STR'] = "some_string" 13 | os.environ['VAR_INT'] = "123456" 14 | os.environ['VAR_FLOAT'] = "123.456" 15 | os.environ['VAR_BOOL_OK'] = "ok" 16 | os.environ['VAR_BOOL_TRUE'] = "True" 17 | os.environ['VAR_BOOL_ON'] = "on" 18 | os.environ['VAR_BOOL_1'] = "1" 19 | os.environ['VAR_BOOL_FALSE'] = "False" 20 | 21 | class MyEnv(EnvReader): 22 | VAR_STR: str 23 | VAR_INT: int 24 | VAR_FLOAT: float 25 | VAR_BOOL_OK: bool 26 | VAR_BOOL_TRUE: bool 27 | VAR_BOOL_ON: bool 28 | VAR_BOOL_1: bool 29 | VAR_BOOL_FALSE: bool 30 | 31 | e = MyEnv() 32 | 33 | assert e.VAR_STR == "some_string" 34 | assert e.VAR_INT == 123456 35 | assert e.VAR_FLOAT == 123.456 36 | assert e.VAR_BOOL_OK == True 37 | assert e.VAR_BOOL_TRUE == True 38 | assert e.VAR_BOOL_ON == True 39 | assert e.VAR_BOOL_1 == True 40 | assert e.VAR_BOOL_FALSE == False 41 | -------------------------------------------------------------------------------- /tests/test_transform.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2024 by Vd. 2 | # This file is part of EnvReader, the modern environment variables processor. 3 | # EnvReader is released under the MIT License (see LICENSE). 4 | 5 | 6 | import os 7 | from typing import Tuple 8 | 9 | from envreader import EnvReader, Field 10 | 11 | 12 | def test_transform(): 13 | os.environ['VAR_LIST_STR'] = "a, b, c, d" 14 | os.environ['VAR_LIST_INT'] = "1, 2, 3, 4, 5, 6" 15 | 16 | class MyEnv(EnvReader): 17 | @staticmethod 18 | def transform_list_int(x: str) -> Tuple[int, ...]: 19 | return tuple([int(x.strip()) for x in x.split(',')]) 20 | 21 | VAR_LIST_STR: Tuple[str] = Field(transform=lambda x: tuple([x.strip() for x in x.split(',')])) 22 | VAR_LIST_INT: Tuple[int] = Field(transform=transform_list_int) 23 | 24 | e = MyEnv() 25 | 26 | assert e.VAR_LIST_STR == ('a', 'b', 'c', 'd') 27 | assert e.VAR_LIST_INT == (1, 2, 3, 4, 5, 6) 28 | --------------------------------------------------------------------------------