├── .github ├── dependabot.yml └── workflows │ └── python_package.yml ├── .gitignore ├── LICENSE ├── README.md ├── coverage.xml ├── examples ├── __init__.py ├── config.env ├── config.json ├── config.toml ├── config.yaml ├── example_env.py ├── example_json.py ├── example_toml.py └── example_yaml.py ├── konfik └── __init__.py ├── makefile ├── poetry.lock ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── scripts └── update.py └── tests ├── __init__.py ├── conftest.py └── test_konfik.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "pip" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/workflows/python_package.yml: -------------------------------------------------------------------------------- 1 | name: Konfik Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macos-latest] 17 | python-version: [3.6, 3.7, 3.8, 3.9] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install the Dependencies 26 | run: | 27 | echo "Upgrading pip ...." 28 | python -m pip install --upgrade pip 29 | 30 | echo "Installing poetry..." 31 | pip install poetry 32 | 33 | echo "Installing the dependencies..." 34 | poetry install 35 | 36 | - name: Check Black Formatting 37 | run: | 38 | echo "Checking black formatting..." 39 | poetry run black --check . 40 | 41 | - name: Check Isort Formatting 42 | run: | 43 | echo "Checking Isort formatting..." 44 | poetry run isort --profile black --check . 45 | 46 | - name: Run the tests & Generate coverage report 47 | run: | 48 | poetry run pytest -v -s --cov=./ --cov-report=xml 49 | 50 | - name: Upload coverage to Codecov 51 | uses: codecov/codecov-action@v1 52 | with: 53 | token: ${{ secrets.CODECOV_TOKEN }} 54 | file: ./coverage.xml 55 | name: codecov-umbrella 56 | fail_ci_if_error: false 57 | verbose: true 58 | -------------------------------------------------------------------------------- /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | # coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Redowan Delowar 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 |
2 | 3 | konfik-logo 4 | 5 | >> The Strangely Familiar Config Parser << 6 |

7 | ![Codecov](https://img.shields.io/codecov/c/github/rednafi/konfik?color=pink&style=flat-square&logo=appveyor) 8 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square&logo=appveyor)](https://github.com/python/black) 9 | [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square&logo=appveyor)](./LICENSE) 10 |

11 | 12 | 13 | **Konfik** is a simple configuration parser that helps you access your config variables using dot (.) notation. 14 | This lets you to do this — 15 | 16 | ```python 17 | foo_bar_bazz = config.FOO.BAR.BAZZ 18 | ``` 19 | 20 | — instead of this — 21 | 22 | ```python 23 | foo_bar_bazz = config["FOO"]["BAR"]["BAZZ"] 24 | ``` 25 | 26 | Konfik currently supports **TOML**, **YAML**, **DOTENV** and **JSON** configuration formats. 27 |
28 | 29 | ## ⚙️ Installation 30 | 31 | Install Konfik via pip: 32 | 33 | ``` 34 | pip install konfik 35 | ``` 36 | 37 | 38 | ## 💡 Usage 39 | 40 | Let's see how you can parse a TOML config file and access the configuration variables. For demonstration, we'll be using the following `config.toml` file: 41 | 42 | ```toml 43 | # Contents of `config.toml` 44 | 45 | title = "TOML Example" 46 | 47 | [owner] 48 | name = "Tom Preston-Werner" 49 | dob = 1979-05-27T07:32:00-08:00 # First class dates 50 | 51 | [servers] 52 | [servers.alpha] 53 | ip = "10.0.0.1" 54 | dc = "eqdc10" 55 | 56 | [servers.beta] 57 | ip = "10.0.0.2" 58 | dc = "eqdc10" 59 | 60 | [clients] 61 | data = [ ["gamma", "delta"], [1, 2] ] 62 | ``` 63 | 64 | Load the above config file and access the variables using dot notation: 65 | 66 | ```python 67 | from pathlib import Path 68 | from konfik import Konfik 69 | 70 | # Define the config path 71 | BASE_DIR = Path(__file__).parent 72 | CONFIG_PATH_TOML = BASE_DIR / "config.toml" 73 | 74 | # Initialize the konfik class 75 | konfik = Konfik(config_path=CONFIG_PATH_TOML) 76 | 77 | # Print the config file as a Python dict 78 | konfik.show_config() 79 | 80 | # Get the config dict from the konfik class 81 | config = konfik.config 82 | 83 | # Access and print the variables 84 | print(config.title) 85 | print(config.owner) 86 | print(config.owner.dob) 87 | print(config.database.ports) 88 | print(config.servers.alpha.ip) 89 | print(config.clients) 90 | ``` 91 | 92 | The `.show_config()` method will print your entire config file as a colorized Python dictionary object like this: 93 | 94 | ```python 95 | { 96 | 'title': 'TOML Example', 97 | 'owner': { 98 | 'name': 'Tom Preston-Werner', 99 | 'dob': datetime.datetime(1979, 5, 27, 7, 32, tzinfo=) 101 | }, 102 | 'database': { 103 | 'server': '192.168.1.1', 104 | 'ports': [8001, 8001, 8002], 105 | 'connection_max': 5000, 106 | 'enabled': True 107 | }, 108 | 'servers': { 109 | 'alpha': {'ip': '10.0.0.1', 'dc': 'eqdc10'}, 110 | 'beta': {'ip': '10.0.0.2', 'dc': 'eqdc10'} 111 | }, 112 | 'clients': {'data': [['gamma', 'delta'], [1, 2]]} 113 | } 114 | ``` 115 | 116 | Konfik also exposes a few command-line options for you to introspect your config file and variables. Run: 117 | 118 | ``` 119 | konfik --help 120 | ``` 121 | 122 | This will reveal the options associated with the CLI tool: 123 | 124 | ``` 125 | Konfik -- The strangely familiar config parser ⚙️ 126 | 127 | usage: konfik [-h] [--path PATH] [--show] [--show-literal] [--var VAR] [--version] 128 | 129 | optional arguments: 130 | -h, --help show this help message and exit 131 | --path PATH add config file path 132 | --show print config as a dict 133 | --show-literal print config file content literally 134 | --var VAR print config variable 135 | --version print konfik-cli version number 136 | ``` 137 | 138 | To inspect the value of a specific variable in a `./config.toml` file you can run: 139 | 140 | ``` 141 | konfik --path=config.toml --var=servers.alpha.ip 142 | ``` 143 | 144 |
145 | ✨ 🍰 ✨ 146 |
147 | -------------------------------------------------------------------------------- /coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /home/rednafi/code/personal/konfik/konfik 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rednafi/konfik/1c8792e549f4263b02e8cd039293957f94a274e6/examples/__init__.py -------------------------------------------------------------------------------- /examples/config.env: -------------------------------------------------------------------------------- 1 | # This is an example .env document 2 | 3 | TITLE = 'DOTENV_EXAMPLE' 4 | NAME = 'TOM' 5 | DOB = '1994-03-24T07:32:00-08:00' 6 | SERVER = '192.168.1.1' 7 | PORT = '8001' 8 | CONNECTION_MAX = '5000' 9 | ENABLED = 'True' 10 | IP = '10.0.0.1' 11 | DC = 'eqdc10' 12 | -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JSON Example", 3 | "owner": { 4 | "name": "Tom Preston-Werner", 5 | "dob": "1979-05-27" 6 | }, 7 | "database": { 8 | "server": "192.168.1.1", 9 | "ports": [ 10 | 8001, 11 | 8001, 12 | 8002 13 | ], 14 | "connection_max": 5000, 15 | "enabled": true 16 | }, 17 | "servers": { 18 | "alpha": { 19 | "ip": "10.0.0.1", 20 | "dc": "eqdc10" 21 | }, 22 | "beta": { 23 | "ip": "10.0.0.2", 24 | "dc": "eqdc10" 25 | } 26 | }, 27 | "clients": { 28 | "data": [ 29 | [ 30 | "gamma", 31 | "delta" 32 | ], 33 | [ 34 | 1, 35 | 2 36 | ] 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/config.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML document. 2 | 3 | title = "TOML Example" 4 | 5 | [owner] 6 | name = "Tom Preston-Werner" 7 | dob = 1979-05-27T07:32:00-08:00 8 | 9 | [database] 10 | server = "192.168.1.1" 11 | ports = [ 8001, 8001, 8002 ] 12 | connection_max = 5000 13 | enabled = true 14 | 15 | [servers] 16 | [servers.alpha] 17 | ip = "10.0.0.1" 18 | dc = "eqdc10" 19 | 20 | [servers.beta] 21 | ip = "10.0.0.2" 22 | dc = "eqdc10" 23 | 24 | [clients] 25 | data = [ ["gamma", "delta"], [1, 2] ] 26 | -------------------------------------------------------------------------------- /examples/config.yaml: -------------------------------------------------------------------------------- 1 | title: YAML Example 2 | owner: 3 | name: Tom Preston-Werner 4 | dob: 1979-05-27T15:32:00.000Z 5 | database: 6 | server: 192.168.1.1 7 | ports: 8 | - 8001 9 | - 8001 10 | - 8002 11 | connection_max: 5000 12 | enabled: true 13 | servers: 14 | alpha: 15 | ip: 10.0.0.1 16 | dc: eqdc10 17 | beta: 18 | ip: 10.0.0.2 19 | dc: eqdc10 20 | clients: 21 | data: 22 | - - gamma 23 | - delta 24 | - - 1 25 | - 2 26 | -------------------------------------------------------------------------------- /examples/example_env.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from konfik import Konfik 4 | 5 | # Define the config paths 6 | BASE_DIR = Path(__file__).parent 7 | CONFIG_PATH_ENV = BASE_DIR / "config.env" 8 | 9 | # Initialize the Konfik class 10 | konfik = Konfik(config_path=CONFIG_PATH_ENV) 11 | 12 | # Serialize and print the dotenv config file 13 | konfik.show_config() 14 | 15 | # Access the variables in the config files via dot notation 16 | config = konfik.config 17 | 18 | # Access and print the variables in env file 19 | print(config.TITLE) 20 | print(config.NAME) 21 | print(config.DOB) 22 | print(config.SERVER) 23 | print(config.PORT) 24 | -------------------------------------------------------------------------------- /examples/example_json.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from konfik import Konfik 4 | 5 | # Define the config path 6 | BASE_DIR = Path(__file__).parent 7 | CONFIG_PATH_JSON = BASE_DIR / "config.json" 8 | 9 | # Initialize the Konfik class 10 | konfik = Konfik(config_path=CONFIG_PATH_JSON) 11 | 12 | # Serialize and print the confile file 13 | konfik.show_config() 14 | 15 | # Get the configuration dictionary from the konfik class 16 | config = konfik.config 17 | 18 | # Access and print the variables in toml file 19 | print(config.title) 20 | print(config.owner) 21 | print(config.owner.dob) 22 | print(config.database.ports) 23 | print(config.servers.alpha.ip) 24 | print(config.clients) 25 | -------------------------------------------------------------------------------- /examples/example_toml.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from konfik import Konfik 4 | 5 | # Define the config path 6 | BASE_DIR = Path(__file__).parent 7 | CONFIG_PATH_TOML = BASE_DIR / "config.toml" 8 | 9 | # Initialize the konfik class 10 | konfik = Konfik(config_path=CONFIG_PATH_TOML) 11 | 12 | # Serialize and print the confile file 13 | konfik.show_config() 14 | 15 | # Get the configuration dictionary from the konfik class 16 | config = konfik.config 17 | 18 | # Access and print the variables 19 | print(config.title) 20 | print(config.owner) 21 | print(config.owner.dob) 22 | print(config.database.ports) 23 | print(config.servers.alpha.ip) 24 | print(config.clients) 25 | -------------------------------------------------------------------------------- /examples/example_yaml.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from konfik import Konfik 4 | 5 | # Define the config path 6 | BASE_DIR = Path(__file__).parent 7 | CONFIG_PATH_YAML = BASE_DIR / "config.yaml" 8 | 9 | # Initialize the konfik class 10 | konfik = Konfik(config_path=CONFIG_PATH_YAML) 11 | 12 | # Serialize and print the confile file 13 | konfik.show_config() 14 | 15 | # Get the configuration dictionary from the konfik class 16 | config = konfik.config 17 | 18 | # Access and print the variables 19 | print(config.title) 20 | print(config.owner) 21 | print(config.owner.dob) 22 | print(config.database.ports) 23 | print(config.servers.alpha.ip) 24 | print(config.clients) 25 | -------------------------------------------------------------------------------- /konfik/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import operator 4 | import sys 5 | import traceback 6 | from functools import reduce 7 | from pprint import pformat 8 | 9 | import pkg_resources 10 | import toml 11 | import yaml 12 | from dotenv import dotenv_values, find_dotenv 13 | from pygments import highlight 14 | from pygments.formatters import TerminalFormatter 15 | from pygments.lexers import PythonLexer, get_lexer_by_name 16 | 17 | __all__ = ["Konfik"] 18 | 19 | __version__ = pkg_resources.get_distribution("konfik").version 20 | 21 | 22 | class Colorize: 23 | """Colorize tracebacks, variables and config literals.""" 24 | 25 | def __init__(self): 26 | self.formatter = TerminalFormatter() 27 | sys.excepthook = self.colorize_traceback 28 | 29 | def colorize_traceback(self, type, value, tb): 30 | """Colorize exception tracebacks.""" 31 | 32 | lexer = get_lexer_by_name("py3tb") 33 | tbtext = "".join(traceback.format_exception(type, value, tb)) 34 | 35 | # Error needs to go to stderr 36 | sys.stderr.write(highlight(tbtext, lexer, self.formatter)) 37 | 38 | def colorize_config(self, config_str, config_ext): 39 | """Colorize config literals.""" 40 | 41 | lexer_map = { 42 | "toml": "toml", 43 | "json": "json", 44 | "env": "bash", 45 | "yaml": "yaml", 46 | } 47 | 48 | lexer = get_lexer_by_name(lexer_map.get(config_ext)) 49 | print(highlight(config_str, lexer, self.formatter)) 50 | 51 | def colorize_entity(self, entity): 52 | """Colorize printed Python objects.""" 53 | 54 | lexer = PythonLexer() 55 | entity = pformat(entity, indent=1, compact=True, width=60) 56 | print(highlight(entity, lexer, self.formatter)) 57 | 58 | def colorize_title(self, text): 59 | """Colorize CLI title.""" 60 | 61 | CYAN = "\033[96m" 62 | BOLD = "\033[1m" 63 | ENDC = "\033[0m" 64 | print(CYAN + BOLD + text + ENDC) 65 | 66 | 67 | colorize = Colorize() 68 | 69 | 70 | class MissingVariableError(Exception): 71 | """Error is raised when an undefined variable is called. This 72 | encapsulates the built-in dict KeyError.""" 73 | 74 | 75 | class MissingConfigError(Exception): 76 | """Error is raised when the configuration file is not found. This 77 | encapsulates the built-in FileNotFoundError.""" 78 | 79 | 80 | class DotMap(dict): 81 | """Modified dictionary class that lets you access key:val via dot notation.""" 82 | 83 | def __init__(self, *args, **kwargs): 84 | # We trust the dict to init itself better than we can. 85 | super().__init__(*args, **kwargs) 86 | # Because of that, we do duplicate work, but it's worth it. 87 | for k, v in self.items(): 88 | self.__setitem__(k, v) 89 | 90 | def __getitem__(self, key): 91 | try: 92 | return super().__getitem__(key) 93 | except KeyError: 94 | raise MissingVariableError(f"No such variable '{key}' exists") from None 95 | 96 | def __setitem__(self, key, val): 97 | super().__setitem__(key, self._convert(val)) 98 | 99 | def __delitem__(self, key): 100 | if hasattr(self, key): 101 | super().__delitem__(key) 102 | 103 | __getattr__ = __getitem__ 104 | __setattr__ = __setitem__ 105 | __delattr__ = __delitem__ 106 | 107 | @classmethod 108 | def _convert(cls, o): 109 | """ 110 | Recursively convert `dict` objects inside `dict`, `list`, `set`, and 111 | `tuple` objects to `DotMap` objects. 112 | """ 113 | if isinstance(o, dict): 114 | o = cls(o) 115 | elif isinstance(o, list): 116 | o = list(cls._convert(v) for v in o) 117 | elif isinstance(o, set): 118 | o = set(cls._convert(v) for v in o) 119 | elif isinstance(o, tuple): 120 | o = tuple(cls._convert(v) for v in o) 121 | return o 122 | 123 | 124 | class Konfik: 125 | """Primary class that holds all the public APIs.""" 126 | 127 | def __init__( 128 | self, 129 | config_path, 130 | dotmap_cls=DotMap, 131 | ): 132 | self._config_path = config_path 133 | self._config_ext = str(self._config_path).split(".")[-1] 134 | self._config_raw = self._load_config() 135 | self.config = dotmap_cls(self._config_raw) 136 | 137 | def show_config(self): 138 | """Printing evaluated config file as a Python dict.""" 139 | 140 | colorize.colorize_entity(self._config_raw) 141 | 142 | def show_config_literal(self): 143 | """Print literal config file contents.""" 144 | 145 | with open(self._config_path) as f: 146 | config_str = f.read() 147 | colorize.colorize_config(config_str, self._config_ext) 148 | 149 | def show_config_var(self, query): 150 | """Print the config variables.""" 151 | 152 | if isinstance(query, str): 153 | query_lst = query.split(".") 154 | value = self.get_by_path(self._config_raw, query_lst) 155 | colorize.colorize_entity(value) 156 | 157 | def _load_config(self): 158 | """Load config.toml file.""" 159 | 160 | # Making sure that pathlib.Path object are converted to string 161 | if self._config_path: 162 | config_path = str(self._config_path) 163 | 164 | if self._config_ext == "env": 165 | return self._load_env(config_path) 166 | 167 | elif self._config_ext == "json": 168 | return self._load_json(config_path) 169 | 170 | elif self._config_ext == "toml": 171 | return self._load_toml(config_path) 172 | 173 | elif self._config_ext == "yaml" or self._config_ext == "yml": 174 | return self._load_yaml(config_path) 175 | 176 | else: 177 | raise NotImplementedError( 178 | f"Config type '{self._config_ext}' is not supported." 179 | ) 180 | 181 | @staticmethod 182 | def _load_env(config_path): 183 | """Load .env file.""" 184 | 185 | try: 186 | # Instead of using `load_dotenv()``, this is done to avoid recursively searching for dotenv file. 187 | # There is no element of surprise. If the file is not found in the explicit path, this will raise an error! 188 | dotenv_file = find_dotenv( 189 | filename=config_path, raise_error_if_not_found=True, usecwd=True 190 | ) 191 | 192 | if dotenv_file: 193 | config = dotenv_values(dotenv_file) 194 | return config 195 | 196 | except OSError: 197 | raise MissingConfigError("DOTENV file not found.") from None 198 | 199 | @staticmethod 200 | def _load_json(config_path): 201 | try: 202 | with open(config_path) as f: 203 | config = json.load(f) 204 | return config 205 | except FileNotFoundError: 206 | raise MissingConfigError("JSON file not found.") 207 | 208 | @staticmethod 209 | def _load_toml(config_path): 210 | """Load .toml file.""" 211 | 212 | # FileNotFound & TomlDecodeError will be raised. 213 | try: 214 | config = toml.load(config_path) 215 | return config 216 | 217 | except FileNotFoundError: 218 | raise MissingConfigError("TOML file not found.") from None 219 | 220 | @staticmethod 221 | def _load_yaml(config_path): 222 | try: 223 | with open(config_path) as f: 224 | config = yaml.safe_load(f) 225 | return config 226 | except FileNotFoundError: 227 | raise MissingConfigError("YAML file not found.") 228 | 229 | @staticmethod 230 | def get_by_path(dct, key_list): 231 | """Access a nested object in root by item sequence.""" 232 | 233 | try: 234 | return reduce(operator.getitem, key_list, dct) 235 | except KeyError as e: 236 | raise MissingVariableError( 237 | f"No such variable '{e.args[0]}' exists." 238 | ) from None 239 | 240 | 241 | class KonfikCLI: 242 | """Access and show config variables using the CLI.""" 243 | 244 | def build_parser(self): 245 | parser = argparse.ArgumentParser( 246 | description=colorize.colorize_title( 247 | "\nKonfik -- The strangely familiar config parser ⚙️\n" 248 | ) 249 | ) 250 | 251 | # Add arguments. 252 | parser.add_argument("--path", help="add config file path") 253 | parser.add_argument( 254 | "--show", 255 | action="store_true", 256 | help="print config as a dict", 257 | ) 258 | parser.add_argument( 259 | "--show-literal", 260 | action="store_true", 261 | help="print config file content literally", 262 | ) 263 | parser.add_argument("--var", help="print config variable") 264 | parser.add_argument( 265 | "--version", 266 | action="store_true", 267 | help="print konfik-cli version number", 268 | ) 269 | 270 | return parser 271 | 272 | def raise_arg_error(self, parser, args): 273 | # Deal with argument dependencies. 274 | for k, v in vars(args).items(): 275 | if k == "version": 276 | continue 277 | 278 | if k == "path": 279 | continue 280 | 281 | if v and not args.path: 282 | parser.error(f"The --{k} argument requires the --path argument.") 283 | 284 | def trigger_handler(self, args, konfik_cls=Konfik, version=__version__): 285 | if args.version: 286 | colorize.colorize_entity(version) 287 | 288 | if args.path: 289 | konfik = konfik_cls(args.path) 290 | 291 | if args.show: 292 | konfik.show_config() 293 | elif args.show_literal: 294 | konfik.show_config_literal() 295 | elif args.var: 296 | konfik.show_config_var(args.var) 297 | 298 | 299 | def cli_entrypoint(argv=None): 300 | """CLI entrypoint callable.""" 301 | 302 | konfik_cli = KonfikCLI() 303 | parser = konfik_cli.build_parser() 304 | args = parser.parse_args(argv) 305 | 306 | konfik_cli.raise_arg_error(parser, args) 307 | konfik_cli.trigger_handler(args) 308 | 309 | 310 | # if __name__ == "__main__": 311 | # cli_entrypoint() 312 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help 2 | help: 3 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 4 | 5 | 6 | .PHONY: venvcheck ## Check if venv is active 7 | venvcheck: 8 | ifeq ("$(VIRTUAL_ENV)","") 9 | @echo "Venv is not activated!" 10 | @echo "Activate venv first." 11 | @echo 12 | exit 1 13 | endif 14 | 15 | install: venvcheck ## Install the dependencies 16 | @poetry install 17 | 18 | test: venvcheck ## Run the tests 19 | @tox 20 | 21 | lint: venvcheck ## Run Black and Isort linters 22 | @black . 23 | @isort . 24 | 25 | upgrade: venvcheck ## Upgrade the dependencies 26 | poetry update 27 | 28 | downgrade: venvcheck ## Downgrade the dependencies 29 | git checkout pyproject.toml && git checkout poetry.lock 30 | 31 | publish: venvcheck ## Build and publish to PYPI 32 | @poetry build 33 | @poetry publish 34 | 35 | coverage: venvcheck ## Upload code coverage 36 | 37 | pytest -v -s --cov-report=xml --cov=konfik tests/ 38 | 39 | export: venvcheck ## Export pyproject.toml deps to requirements.txt 40 | poetry export -f requirements.txt -o requirements.txt --without-hashes 41 | poetry export -f requirements.txt -o requirements-dev.txt --without-hashes --dev 42 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "main" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "atomicwrites" 11 | version = "1.4.0" 12 | description = "Atomic file writes." 13 | category = "main" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | 17 | [[package]] 18 | name = "attrs" 19 | version = "20.3.0" 20 | description = "Classes Without Boilerplate" 21 | category = "main" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | 25 | [package.extras] 26 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 27 | docs = ["furo", "sphinx", "zope.interface"] 28 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 29 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 30 | 31 | [[package]] 32 | name = "black" 33 | version = "20.8b1" 34 | description = "The uncompromising code formatter." 35 | category = "main" 36 | optional = false 37 | python-versions = ">=3.6" 38 | 39 | [package.dependencies] 40 | appdirs = "*" 41 | click = ">=7.1.2" 42 | dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} 43 | mypy-extensions = ">=0.4.3" 44 | pathspec = ">=0.6,<1" 45 | regex = ">=2020.1.8" 46 | toml = ">=0.10.1" 47 | typed-ast = ">=1.4.0" 48 | typing-extensions = ">=3.7.4" 49 | 50 | [package.extras] 51 | colorama = ["colorama (>=0.4.3)"] 52 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 53 | 54 | [[package]] 55 | name = "click" 56 | version = "7.1.2" 57 | description = "Composable command line interface toolkit" 58 | category = "main" 59 | optional = false 60 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 61 | 62 | [[package]] 63 | name = "colorama" 64 | version = "0.4.4" 65 | description = "Cross-platform colored terminal text." 66 | category = "main" 67 | optional = false 68 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 69 | 70 | [[package]] 71 | name = "coverage" 72 | version = "5.4" 73 | description = "Code coverage measurement for Python" 74 | category = "main" 75 | optional = false 76 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 77 | 78 | [package.extras] 79 | toml = ["toml"] 80 | 81 | [[package]] 82 | name = "dataclasses" 83 | version = "0.8" 84 | description = "A backport of the dataclasses module for Python 3.6" 85 | category = "main" 86 | optional = false 87 | python-versions = ">=3.6, <3.7" 88 | 89 | [[package]] 90 | name = "distlib" 91 | version = "0.3.1" 92 | description = "Distribution utilities" 93 | category = "main" 94 | optional = false 95 | python-versions = "*" 96 | 97 | [[package]] 98 | name = "filelock" 99 | version = "3.0.12" 100 | description = "A platform independent file lock." 101 | category = "main" 102 | optional = false 103 | python-versions = "*" 104 | 105 | [[package]] 106 | name = "importlib-metadata" 107 | version = "3.4.0" 108 | description = "Read metadata from Python packages" 109 | category = "main" 110 | optional = false 111 | python-versions = ">=3.6" 112 | 113 | [package.dependencies] 114 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 115 | zipp = ">=0.5" 116 | 117 | [package.extras] 118 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 119 | testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 120 | 121 | [[package]] 122 | name = "importlib-resources" 123 | version = "5.1.0" 124 | description = "Read resources from Python packages" 125 | category = "main" 126 | optional = false 127 | python-versions = ">=3.6" 128 | 129 | [package.dependencies] 130 | zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} 131 | 132 | [package.extras] 133 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 134 | testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] 135 | 136 | [[package]] 137 | name = "iniconfig" 138 | version = "1.1.1" 139 | description = "iniconfig: brain-dead simple config-ini parsing" 140 | category = "main" 141 | optional = false 142 | python-versions = "*" 143 | 144 | [[package]] 145 | name = "isort" 146 | version = "5.7.0" 147 | description = "A Python utility / library to sort Python imports." 148 | category = "main" 149 | optional = false 150 | python-versions = ">=3.6,<4.0" 151 | 152 | [package.extras] 153 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 154 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 155 | colors = ["colorama (>=0.4.3,<0.5.0)"] 156 | 157 | [[package]] 158 | name = "mypy-extensions" 159 | version = "0.4.3" 160 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 161 | category = "main" 162 | optional = false 163 | python-versions = "*" 164 | 165 | [[package]] 166 | name = "packaging" 167 | version = "20.9" 168 | description = "Core utilities for Python packages" 169 | category = "main" 170 | optional = false 171 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 172 | 173 | [package.dependencies] 174 | pyparsing = ">=2.0.2" 175 | 176 | [[package]] 177 | name = "pathspec" 178 | version = "0.8.1" 179 | description = "Utility library for gitignore style pattern matching of file paths." 180 | category = "main" 181 | optional = false 182 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 183 | 184 | [[package]] 185 | name = "pluggy" 186 | version = "0.13.1" 187 | description = "plugin and hook calling mechanisms for python" 188 | category = "main" 189 | optional = false 190 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 191 | 192 | [package.dependencies] 193 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 194 | 195 | [package.extras] 196 | dev = ["pre-commit", "tox"] 197 | 198 | [[package]] 199 | name = "py" 200 | version = "1.10.0" 201 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 202 | category = "main" 203 | optional = false 204 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 205 | 206 | [[package]] 207 | name = "pygments" 208 | version = "2.8.1" 209 | description = "Pygments is a syntax highlighting package written in Python." 210 | category = "main" 211 | optional = false 212 | python-versions = ">=3.5" 213 | 214 | [[package]] 215 | name = "pyparsing" 216 | version = "2.4.7" 217 | description = "Python parsing module" 218 | category = "main" 219 | optional = false 220 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 221 | 222 | [[package]] 223 | name = "pytest" 224 | version = "6.2.2" 225 | description = "pytest: simple powerful testing with Python" 226 | category = "main" 227 | optional = false 228 | python-versions = ">=3.6" 229 | 230 | [package.dependencies] 231 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 232 | attrs = ">=19.2.0" 233 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 234 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 235 | iniconfig = "*" 236 | packaging = "*" 237 | pluggy = ">=0.12,<1.0.0a1" 238 | py = ">=1.8.2" 239 | toml = "*" 240 | 241 | [package.extras] 242 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 243 | 244 | [[package]] 245 | name = "pytest-cov" 246 | version = "2.11.1" 247 | description = "Pytest plugin for measuring coverage." 248 | category = "main" 249 | optional = false 250 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 251 | 252 | [package.dependencies] 253 | coverage = ">=5.2.1" 254 | pytest = ">=4.6" 255 | 256 | [package.extras] 257 | testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] 258 | 259 | [[package]] 260 | name = "python-dotenv" 261 | version = "0.17.1" 262 | description = "Read key-value pairs from a .env file and set them as environment variables" 263 | category = "main" 264 | optional = false 265 | python-versions = "*" 266 | 267 | [package.extras] 268 | cli = ["click (>=5.0)"] 269 | 270 | [[package]] 271 | name = "pyyaml" 272 | version = "5.4.1" 273 | description = "YAML parser and emitter for Python" 274 | category = "main" 275 | optional = false 276 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 277 | 278 | [[package]] 279 | name = "regex" 280 | version = "2020.11.13" 281 | description = "Alternative regular expression module, to replace re." 282 | category = "main" 283 | optional = false 284 | python-versions = "*" 285 | 286 | [[package]] 287 | name = "six" 288 | version = "1.15.0" 289 | description = "Python 2 and 3 compatibility utilities" 290 | category = "main" 291 | optional = false 292 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 293 | 294 | [[package]] 295 | name = "toml" 296 | version = "0.10.2" 297 | description = "Python Library for Tom's Obvious, Minimal Language" 298 | category = "main" 299 | optional = false 300 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 301 | 302 | [[package]] 303 | name = "tox" 304 | version = "3.21.4" 305 | description = "tox is a generic virtualenv management and test command line tool" 306 | category = "main" 307 | optional = false 308 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 309 | 310 | [package.dependencies] 311 | colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} 312 | filelock = ">=3.0.0" 313 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 314 | packaging = ">=14" 315 | pluggy = ">=0.12.0" 316 | py = ">=1.4.17" 317 | six = ">=1.14.0" 318 | toml = ">=0.9.4" 319 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 320 | 321 | [package.extras] 322 | docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] 323 | testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] 324 | 325 | [[package]] 326 | name = "typed-ast" 327 | version = "1.4.2" 328 | description = "a fork of Python 2 and 3 ast modules with type comment support" 329 | category = "main" 330 | optional = false 331 | python-versions = "*" 332 | 333 | [[package]] 334 | name = "typing-extensions" 335 | version = "3.7.4.3" 336 | description = "Backported and Experimental Type Hints for Python 3.5+" 337 | category = "main" 338 | optional = false 339 | python-versions = "*" 340 | 341 | [[package]] 342 | name = "virtualenv" 343 | version = "20.4.2" 344 | description = "Virtual Python Environment builder" 345 | category = "main" 346 | optional = false 347 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 348 | 349 | [package.dependencies] 350 | appdirs = ">=1.4.3,<2" 351 | distlib = ">=0.3.1,<1" 352 | filelock = ">=3.0.0,<4" 353 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 354 | importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} 355 | six = ">=1.9.0,<2" 356 | 357 | [package.extras] 358 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] 359 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] 360 | 361 | [[package]] 362 | name = "zipp" 363 | version = "3.4.0" 364 | description = "Backport of pathlib-compatible object wrapper for zip files" 365 | category = "main" 366 | optional = false 367 | python-versions = ">=3.6" 368 | 369 | [package.extras] 370 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 371 | testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 372 | 373 | [metadata] 374 | lock-version = "1.1" 375 | python-versions = "^3.6" 376 | content-hash = "6f29a2e36ffb2317a5cc477b61a210738d7ae42b039bbe1677c0e32eca78eb58" 377 | 378 | [metadata.files] 379 | appdirs = [ 380 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 381 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 382 | ] 383 | atomicwrites = [ 384 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 385 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 386 | ] 387 | attrs = [ 388 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 389 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 390 | ] 391 | black = [ 392 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 393 | ] 394 | click = [ 395 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 396 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 397 | ] 398 | colorama = [ 399 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 400 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 401 | ] 402 | coverage = [ 403 | {file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"}, 404 | {file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"}, 405 | {file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"}, 406 | {file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"}, 407 | {file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"}, 408 | {file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"}, 409 | {file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"}, 410 | {file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"}, 411 | {file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"}, 412 | {file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"}, 413 | {file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"}, 414 | {file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"}, 415 | {file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"}, 416 | {file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"}, 417 | {file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"}, 418 | {file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"}, 419 | {file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"}, 420 | {file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"}, 421 | {file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"}, 422 | {file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"}, 423 | {file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"}, 424 | {file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"}, 425 | {file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"}, 426 | {file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"}, 427 | {file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"}, 428 | {file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"}, 429 | {file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"}, 430 | {file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"}, 431 | {file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"}, 432 | {file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"}, 433 | {file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"}, 434 | {file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"}, 435 | {file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"}, 436 | {file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"}, 437 | {file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"}, 438 | {file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"}, 439 | {file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"}, 440 | {file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"}, 441 | {file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"}, 442 | {file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"}, 443 | {file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"}, 444 | {file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"}, 445 | {file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"}, 446 | {file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"}, 447 | {file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"}, 448 | {file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"}, 449 | {file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"}, 450 | {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"}, 451 | {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"}, 452 | ] 453 | dataclasses = [ 454 | {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, 455 | {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, 456 | ] 457 | distlib = [ 458 | {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, 459 | {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, 460 | ] 461 | filelock = [ 462 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 463 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 464 | ] 465 | importlib-metadata = [ 466 | {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"}, 467 | {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"}, 468 | ] 469 | importlib-resources = [ 470 | {file = "importlib_resources-5.1.0-py3-none-any.whl", hash = "sha256:885b8eae589179f661c909d699a546cf10d83692553e34dca1bf5eb06f7f6217"}, 471 | {file = "importlib_resources-5.1.0.tar.gz", hash = "sha256:bfdad047bce441405a49cf8eb48ddce5e56c696e185f59147a8b79e75e9e6380"}, 472 | ] 473 | iniconfig = [ 474 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 475 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 476 | ] 477 | isort = [ 478 | {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, 479 | {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, 480 | ] 481 | mypy-extensions = [ 482 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 483 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 484 | ] 485 | packaging = [ 486 | {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, 487 | {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, 488 | ] 489 | pathspec = [ 490 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 491 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 492 | ] 493 | pluggy = [ 494 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 495 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 496 | ] 497 | py = [ 498 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 499 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 500 | ] 501 | pygments = [ 502 | {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, 503 | {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, 504 | ] 505 | pyparsing = [ 506 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 507 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 508 | ] 509 | pytest = [ 510 | {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, 511 | {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, 512 | ] 513 | pytest-cov = [ 514 | {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, 515 | {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, 516 | ] 517 | python-dotenv = [ 518 | {file = "python-dotenv-0.17.1.tar.gz", hash = "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f"}, 519 | {file = "python_dotenv-0.17.1-py2.py3-none-any.whl", hash = "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544"}, 520 | ] 521 | pyyaml = [ 522 | {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, 523 | {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, 524 | {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, 525 | {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, 526 | {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, 527 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, 528 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, 529 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, 530 | {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, 531 | {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, 532 | {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, 533 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, 534 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, 535 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, 536 | {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, 537 | {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, 538 | {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, 539 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, 540 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, 541 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, 542 | {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, 543 | {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, 544 | {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, 545 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, 546 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, 547 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, 548 | {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, 549 | {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, 550 | {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, 551 | ] 552 | regex = [ 553 | {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, 554 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, 555 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, 556 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, 557 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, 558 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, 559 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, 560 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, 561 | {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, 562 | {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, 563 | {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, 564 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, 565 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, 566 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, 567 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, 568 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, 569 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, 570 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, 571 | {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, 572 | {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, 573 | {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, 574 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, 575 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, 576 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, 577 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, 578 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, 579 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, 580 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, 581 | {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, 582 | {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, 583 | {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, 584 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, 585 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, 586 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, 587 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, 588 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, 589 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, 590 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, 591 | {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, 592 | {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, 593 | {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, 594 | ] 595 | six = [ 596 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 597 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 598 | ] 599 | toml = [ 600 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 601 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 602 | ] 603 | tox = [ 604 | {file = "tox-3.21.4-py2.py3-none-any.whl", hash = "sha256:65d0e90ceb816638a50d64f4b47b11da767b284c0addda2294cb3cd69bd72425"}, 605 | {file = "tox-3.21.4.tar.gz", hash = "sha256:cf7fef81a3a2434df4d7af2a6d1bf606d2970220addfbe7dea2615bd4bb2c252"}, 606 | ] 607 | typed-ast = [ 608 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, 609 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, 610 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, 611 | {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, 612 | {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, 613 | {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, 614 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, 615 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, 616 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, 617 | {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, 618 | {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, 619 | {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, 620 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, 621 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, 622 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, 623 | {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, 624 | {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, 625 | {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, 626 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, 627 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, 628 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, 629 | {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, 630 | {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, 631 | {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, 632 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, 633 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, 634 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, 635 | {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, 636 | {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, 637 | {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, 638 | ] 639 | typing-extensions = [ 640 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 641 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 642 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 643 | ] 644 | virtualenv = [ 645 | {file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"}, 646 | {file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"}, 647 | ] 648 | zipp = [ 649 | {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, 650 | {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, 651 | ] 652 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Package manager metadata 2 | [tool.poetry] 3 | name = "konfik" 4 | version = "2.0.5" 5 | description = "The Strangely Familiar Config Parser" 6 | authors = ["rednafi "] 7 | license = "MIT" 8 | readme = "README.md" 9 | homepage = "https://github.com/rednafi/konfik" 10 | repository = "https://github.com/rednafi/konfik" 11 | keywords = ["config", "configuration", "toml", "dotenv", "yaml"] 12 | classifiers = [ 13 | "Environment :: Console", 14 | "Operating System :: OS Independent", 15 | "Topic :: Software Development", 16 | "Topic :: Software Development :: Libraries :: Python Modules", 17 | "Topic :: Software Development :: Quality Assurance", 18 | ] 19 | include = ["LICENSE"] 20 | 21 | [tool.poetry.dependencies] 22 | python = "^3.6" 23 | toml = "^0.10.2" 24 | python-dotenv = ">=0.15,<0.18" 25 | PyYAML = "^5.4.1" 26 | Pygments = "^2.8.0" 27 | pytest = "^6.2.2" 28 | tox = "^3.21.4" 29 | black = "^20.8b1" 30 | pytest-cov = "^2.11.1" 31 | isort = "^5.7.0" 32 | 33 | [tool.poetry.dev-dependencies] 34 | pytest = "^6.2.2" 35 | tox = "^3.21.4" 36 | black = "^20.8b1" 37 | pytest-cov = "^2.11.1" 38 | isort = "^5.7.0" 39 | 40 | # CLI entrypoints 41 | [tool.poetry.scripts] 42 | konfik = "konfik:cli_entrypoint" 43 | 44 | # Third-party configs 45 | [tool.black] 46 | line-length = 88 47 | 48 | [tool.isort] 49 | profile = "black" 50 | atomic = true 51 | 52 | [tool.tox] 53 | legacy_tox_ini = """ 54 | # content of: tox.ini , put in same dir as setup.py 55 | [tox] 56 | envlist = py36,py37,py38,py39 57 | isolated_build = True 58 | 59 | [testenv] 60 | # install pytest in the virtualenv where commands will be executed 61 | deps = pytest 62 | install_commands = 63 | curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - 64 | poetry install --no-dev 65 | commands = 66 | # NOTE: you can run any command line tool here - not just tests 67 | pytest -v -s 68 | """ 69 | 70 | # PEP-518: build-requires 71 | [build-system] 72 | requires = ["poetry-core>=1.0.0"] 73 | build-backend = "poetry.core.masonry.api" 74 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 2 | atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") 3 | attrs==20.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 4 | black==20.8b1; python_version >= "3.6" 5 | click==7.1.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 6 | colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and platform_system == "Windows" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.5.0" and platform_system == "Windows" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") 7 | coverage==5.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" 8 | dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" 9 | distlib==0.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" 10 | filelock==3.0.12; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" 11 | importlib-metadata==3.4.0; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.6") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version >= "3.6" and python_version < "3.8") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") 12 | importlib-resources==5.1.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_full_version >= "3.5.0" and python_version < "3.7" and python_version >= "3.6" 13 | iniconfig==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 14 | isort==5.7.0; python_version >= "3.6" and python_version < "4.0" 15 | mypy-extensions==0.4.3; python_version >= "3.6" 16 | packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 17 | pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 18 | pluggy==0.13.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 19 | py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" 20 | pygments==2.7.4; python_version >= "3.5" 21 | pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" 22 | pytest-cov==2.11.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") 23 | pytest==6.2.2; python_version >= "3.6" 24 | python-dotenv==0.15.0 25 | pyyaml==5.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") 26 | regex==2020.11.13; python_version >= "3.6" 27 | six==1.15.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" 28 | toml==0.10.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") 29 | tox==3.21.4; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") 30 | typed-ast==1.4.2; python_version >= "3.6" 31 | typing-extensions==3.7.4.3; python_version < "3.8" and python_version >= "3.6" 32 | virtualenv==20.4.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" 33 | zipp==3.4.0; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_full_version >= "3.5.0" and python_version < "3.7" and python_version >= "3.6") 34 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pygments==2.7.4; python_version >= "3.5" 2 | python-dotenv==0.15.0 3 | pyyaml==5.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") 4 | toml==0.10.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") 5 | -------------------------------------------------------------------------------- /scripts/update.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import subprocess 4 | import sys 5 | 6 | import toml 7 | 8 | 9 | class UpdateDeps: 10 | """Automatically updating the dependencies, and revert if the tests fail.""" 11 | 12 | def __init__(self, dep_file="pyproject.toml", lock_file="poetry.lock"): 13 | self.dep_file = dep_file 14 | self.lock_file = lock_file 15 | 16 | @staticmethod 17 | def is_env_active(): 18 | """Check if the virtual environment is active or not.""" 19 | 20 | if sys.prefix == sys.base_prefix: 21 | print("Virtual environment is not active, exiting...\n") 22 | sys.exit(1) 23 | 24 | print("Virtual environment is active, proceeding...\n") 25 | 26 | @staticmethod 27 | def revert_changes(filename): 28 | print(f"Reverting file {filename} to HEAD...\n") 29 | subprocess.run(["git", "checkout", "HEAD", "--", filename]) 30 | 31 | def update_deps(self): 32 | with open(self.dep_file, "r") as f: 33 | toml_dct = toml.loads(f.read()) 34 | 35 | app_deps = toml_dct["tool"]["poetry"]["dependencies"] 36 | dev_deps = toml_dct["tool"]["poetry"]["dev-dependencies"] 37 | 38 | for k in {**app_deps, **dev_deps}: 39 | if k.lower() == "python": 40 | continue 41 | 42 | subprocess.run(["poetry", "add", f"{k}@latest"]) 43 | 44 | def run_tests(self): 45 | print("Running the tests...\n") 46 | 47 | result = subprocess.run(["pytest", "-v"], capture_output=True) 48 | 49 | if "FAILED" in str(result.stdout): 50 | print( 51 | f"Tests failed. Reverting {self.dep_file} and {self.lock_file} file..." 52 | ) 53 | self.revert_changes(self.dep_file) 54 | self.revert_changes(self.lock_file) 55 | 56 | print("Tests ran successfully. Your dependencies are now up to date.") 57 | 58 | def execute(self): 59 | self.is_env_active() 60 | self.update_deps() 61 | self.run_tests() 62 | 63 | 64 | if __name__ == "__main__": 65 | update = UpdateDeps() 66 | update.execute() 67 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rednafi/konfik/1c8792e549f4263b02e8cd039293957f94a274e6/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def config_dict(): 6 | """Sample config dict.""" 7 | 8 | d = { 9 | "title": "TOML Example", 10 | "owner": { 11 | "name": "Tom Preston-Werner", 12 | "dob": "1979-05-27", 13 | }, 14 | "database": { 15 | "server": "192.168.1.1", 16 | "ports": [8001, 8001, 8002], 17 | "connection_max": 5000, 18 | "enabled": True, 19 | }, 20 | "servers": { 21 | "alpha": {"ip": "10.0.0.1", "dc": "eqdc10"}, 22 | "beta": {"ip": "10.0.0.2", "dc": "eqdc10"}, 23 | }, 24 | "clients": { 25 | "data": [["gamma", "delta"], [1, 2]], 26 | }, 27 | } 28 | return d 29 | 30 | 31 | @pytest.fixture 32 | def toml_str(): 33 | toml_str = """ 34 | # This is a TOML document. 35 | 36 | title = "TOML Example" 37 | 38 | [owner] 39 | name = "Tom Preston-Werner" 40 | dob = 1979-05-27T07:32:00-08:00 41 | 42 | [database] 43 | server = "192.168.1.1" 44 | ports = [ 8001, 8001, 8002 ] 45 | connection_max = 5000 46 | enabled = true 47 | 48 | [servers] 49 | [servers.alpha] 50 | ip = "10.0.0.1" 51 | dc = "eqdc10" 52 | 53 | [servers.beta] 54 | ip = "10.0.0.2" 55 | dc = "eqdc10" 56 | 57 | [clients] 58 | data = [ ["gamma", "delta"], [1, 2] ] 59 | """ 60 | 61 | return toml_str 62 | 63 | 64 | @pytest.fixture 65 | def dotenv_str(): 66 | dotenv_str = """ 67 | # This is an example .env document 68 | 69 | TITLE = DOTENV_EXAMPLE 70 | NAME = TOM 71 | DOB = 1994-03-24T07:32:00-08:00 72 | SERVER = 192.168.1.1 73 | PORT = 8001 74 | CONNECTION_MAX = 5000 75 | ENABLED = True 76 | IP = 10.0.0.1 77 | DC = eqdc10 78 | 79 | """ 80 | return dotenv_str 81 | 82 | 83 | @pytest.fixture 84 | def json_str(): 85 | json_str = """ 86 | { 87 | "title": "JSON Example", 88 | "owner": { 89 | "name": "Tom Preston-Werner", 90 | "dob": "1979-05-27" 91 | }, 92 | "database": { 93 | "server": "192.168.1.1", 94 | "ports": [ 95 | 8001, 96 | 8001, 97 | 8002 98 | ], 99 | "connection_max": 5000, 100 | "enabled": true 101 | }, 102 | "servers": { 103 | "alpha": { 104 | "ip": "10.0.0.1", 105 | "dc": "eqdc10" 106 | }, 107 | "beta": { 108 | "ip": "10.0.0.2", 109 | "dc": "eqdc10" 110 | } 111 | }, 112 | "clients": { 113 | "data": [ 114 | [ 115 | "gamma", 116 | "delta" 117 | ], 118 | [ 119 | 1, 120 | 2 121 | ] 122 | ] 123 | } 124 | } 125 | """ 126 | return json_str 127 | 128 | 129 | @pytest.fixture 130 | def yaml_str(): 131 | 132 | yaml_str = """ 133 | title: YAML Example 134 | owner: 135 | name: Tom Preston-Werner 136 | dob: 1979-05-27T15:32:00.000Z 137 | database: 138 | server: 192.168.1.1 139 | ports: 140 | - 8001 141 | - 8001 142 | - 8002 143 | connection_max: 5000 144 | enabled: true 145 | servers: 146 | alpha: 147 | ip: 10.0.0.1 148 | dc: eqdc10 149 | beta: 150 | ip: 10.0.0.2 151 | dc: eqdc10 152 | clients: 153 | data: 154 | - - gamma 155 | - delta 156 | - - 1 157 | - 2 158 | """ 159 | return yaml_str 160 | -------------------------------------------------------------------------------- /tests/test_konfik.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from konfik import ( 4 | Colorize, 5 | DotMap, 6 | Konfik, 7 | MissingConfigError, 8 | MissingVariableError, 9 | __version__, 10 | cli_entrypoint, 11 | ) 12 | 13 | 14 | def test_colorize(config_dict, capsys): 15 | colorize = Colorize() 16 | 17 | colorize.colorize_entity(config_dict) 18 | out, err = capsys.readouterr() 19 | assert err == "" 20 | assert "{\x1b[33m'\x1b[39;49;00m\x1b[33mclients\x1b[39;49;00m\x1b[33m" in out 21 | 22 | colorize.colorize_title("Hello from the other side!") 23 | out, err = capsys.readouterr() 24 | assert err == "" 25 | assert "Hello from the" in out 26 | 27 | 28 | def make_config_path(tmp_path, config_str, config_ext): 29 | # Making a temporary directory to hold the config file. 30 | # https://docs.pytest.org/en/stable/tmpdir.html#the-tmp-path-fixture 31 | tmp_dir = tmp_path / "sub" 32 | tmp_dir.mkdir() 33 | 34 | # So the actual directory would be `tmp_path/sub/test_config.extension`. 35 | test_config_path = tmp_dir / f"test_config.{config_ext}" 36 | test_config_path.write_text(config_str) 37 | return test_config_path 38 | 39 | 40 | def test_dotmap(config_dict): 41 | """Test the DotMap class.""" 42 | 43 | # Test dot notation features. 44 | d = DotMap(config_dict) 45 | 46 | # Test iteration. 47 | for (k1, v1), (k2, v2) in zip(d.items(), config_dict.items()): 48 | assert k1 == k2 49 | assert v1 == v2 50 | 51 | # Test pop. 52 | d.pop("title") 53 | assert "title" not in d.keys() 54 | d.title = "TOML Example" 55 | 56 | # Test len. 57 | len(d) == len(config_dict) 58 | 59 | # Check if d is an instance of `DotMap` object. 60 | assert isinstance(d, DotMap) is True 61 | 62 | # Check if the nested dictionary instance is also a `DotMap` object. 63 | assert isinstance(d["database"], DotMap) is True 64 | 65 | # Check if the dot notation key access works. 66 | assert d.title == "TOML Example" 67 | assert d.owner.name == "Tom Preston-Werner" 68 | assert d.owner.dob == "1979-05-27" 69 | 70 | assert isinstance(d.database, DotMap) is True 71 | assert d.database == { 72 | "server": "192.168.1.1", 73 | "ports": [8001, 8001, 8002], 74 | "connection_max": 5000, 75 | "enabled": True, 76 | } 77 | assert d.database.server == "192.168.1.1" 78 | assert d.database.ports == [8001, 8001, 8002] 79 | assert d.database.enabled is True 80 | 81 | assert isinstance(d.servers, DotMap) is True 82 | assert d.servers == { 83 | "alpha": {"ip": "10.0.0.1", "dc": "eqdc10"}, 84 | "beta": {"ip": "10.0.0.2", "dc": "eqdc10"}, 85 | } 86 | 87 | assert isinstance(d.servers.alpha, DotMap) is True 88 | assert d.servers.alpha == {"ip": "10.0.0.1", "dc": "eqdc10"} 89 | 90 | assert d.servers.alpha.ip == "10.0.0.1" 91 | assert d.servers.alpha.dc == "eqdc10" 92 | 93 | # Check if dot notation assignment works. 94 | d.title = "Test Environ" 95 | assert d.title == "Test Environ" 96 | 97 | d.owner = "Redowan Delowar" 98 | assert d.owner == "Redowan Delowar" 99 | 100 | d.database = {"servers": "localhost", "ports": [5000, 5001, 5002]} 101 | 102 | assert isinstance(d.database, DotMap) is True 103 | 104 | d.servers.beta.ip = "localhost" 105 | assert d.servers.beta.ip == "localhost" 106 | 107 | # Check if dot notation deletion works. 108 | del d.title 109 | with pytest.raises(MissingVariableError): 110 | d.title 111 | 112 | # Normal [] notation features. 113 | d = config_dict 114 | d = DotMap(d) 115 | 116 | # Check if the angle notation key access works. 117 | assert d["title"] == "TOML Example" 118 | assert d["owner"]["name"] == "Tom Preston-Werner" 119 | assert d["owner"]["dob"] == "1979-05-27" 120 | 121 | assert isinstance(d["database"], DotMap) is True 122 | assert d["database"] == { 123 | "server": "192.168.1.1", 124 | "ports": [8001, 8001, 8002], 125 | "connection_max": 5000, 126 | "enabled": True, 127 | } 128 | assert d["database"]["server"] == "192.168.1.1" 129 | assert d["database"]["ports"] == [8001, 8001, 8002] 130 | assert d["database"]["enabled"] is True 131 | 132 | assert isinstance(d["servers"], DotMap) is True 133 | assert d["servers"] == { 134 | "alpha": {"ip": "10.0.0.1", "dc": "eqdc10"}, 135 | "beta": {"ip": "10.0.0.2", "dc": "eqdc10"}, 136 | } 137 | 138 | assert isinstance(d["servers"]["alpha"], DotMap) is True 139 | assert d["servers"]["alpha"] == {"ip": "10.0.0.1", "dc": "eqdc10"} 140 | 141 | assert d["servers"]["alpha"]["ip"] == "10.0.0.1" 142 | assert d["servers"]["alpha"]["dc"] == "eqdc10" 143 | 144 | # Check if angle notation assignment works. 145 | d["title"] = "Test Environ" 146 | assert d["title"] == "Test Environ" 147 | 148 | d["owner"] = "Redowan Delowar" 149 | assert d["owner"] == "Redowan Delowar" 150 | 151 | d["database"] = {"servers": "localhost", "ports": [5000, 5001, 5002]} 152 | 153 | assert isinstance(d["database"], DotMap) is True 154 | 155 | d["servers"]["beta"]["ip"] = "localhost" 156 | assert d["servers"]["beta"]["ip"] == "localhost" 157 | 158 | # Check if angle notation deletion works. 159 | del d["title"] 160 | with pytest.raises(MissingVariableError): 161 | d["title"] 162 | 163 | # Check if `popitem` works. 164 | d.popitem() 165 | assert len(d) < len(config_dict) 166 | 167 | # Check if `clear` works. 168 | assert d.clear() is None 169 | 170 | # Test _convert. 171 | m = { 172 | "i": { 173 | "j": [ 174 | {"1": 1}, 175 | {"2": 2}, 176 | ], 177 | "k": ( 178 | {1: 1}, 179 | {2: 2}, 180 | ), 181 | } 182 | } 183 | 184 | m = DotMap._convert(m) 185 | assert isinstance(m.i, DotMap) is True 186 | assert isinstance(m.i.j[0], DotMap) is True 187 | assert isinstance(m.i.k[1], DotMap) is True 188 | 189 | 190 | def test_konfik(tmp_path, toml_str): 191 | """Unit test different parts of the Konfik class.""" 192 | 193 | test_toml_real_path = make_config_path(tmp_path, toml_str, "toml") 194 | test_toml_fake_path = "some/fake/path/config.toml" 195 | 196 | # Test if konfik raises `TypeError` when an argument is missing. 197 | with pytest.raises(TypeError): 198 | konfik = Konfik() 199 | 200 | # Test if konfik raises `MissingConfigError` when the config is missing. 201 | with pytest.raises(MissingConfigError): 202 | konfik = Konfik(config_path=test_toml_fake_path) 203 | 204 | konfik = Konfik(config_path=test_toml_real_path) 205 | 206 | # Test if a nested key can be resolved by `get_by_path` (used in the CLI). 207 | assert konfik.get_by_path({"hello": {"world": 2}}, ["hello", "world"]) == 2 208 | 209 | # Test if konfik can find the file path. 210 | assert konfik._config_path == test_toml_real_path 211 | 212 | # Check raw config type. 213 | assert isinstance(konfik._config_raw, dict) is True 214 | 215 | # Check transformed config type. 216 | assert isinstance(konfik.config, DotMap) 217 | 218 | # Test if konfik can parse file extension from path. 219 | assert konfik._config_ext == "toml" 220 | 221 | 222 | def test_konfik_toml(tmp_path, toml_str, capsys): 223 | """Test the Konfik class for toml.""" 224 | 225 | test_toml_path = make_config_path(tmp_path, toml_str, "toml") 226 | 227 | # Load toml from the test toml path. 228 | konfik = Konfik(config_path=test_toml_path) 229 | 230 | # Make sure show_config works. 231 | konfik.show_config() 232 | 233 | # Make sure show_config_literal works. 234 | konfik.show_config_literal() 235 | 236 | # Make sure show_var works. 237 | konfik.show_config_var("title") 238 | out, err = capsys.readouterr() 239 | assert err == "" 240 | assert "TOML Example" in out 241 | 242 | # Test variable access with dot notation. 243 | config = konfik.config 244 | 245 | assert config.title == "TOML Example" 246 | assert config.owner.name == "Tom Preston-Werner" 247 | assert config.database == { 248 | "server": "192.168.1.1", 249 | "ports": [8001, 8001, 8002], 250 | "connection_max": 5000, 251 | "enabled": True, 252 | } 253 | assert config.database.server == "192.168.1.1" 254 | assert config.database.ports == [8001, 8001, 8002] 255 | assert config.database.connection_max == 5000 256 | assert config.database.enabled is True 257 | 258 | assert config.servers.alpha == {"ip": "10.0.0.1", "dc": "eqdc10"} 259 | assert config.servers.beta.dc == "eqdc10" 260 | 261 | assert config.clients.data == [["gamma", "delta"], [1, 2]] 262 | 263 | with pytest.raises(MissingVariableError): 264 | config.fakekey 265 | 266 | 267 | def test_konfik_env(tmp_path, dotenv_str): 268 | """Test the Konfik class for dotenv file.""" 269 | 270 | test_env_path = make_config_path(tmp_path, dotenv_str, "env") 271 | 272 | # Load toml from the test toml path. 273 | konfik = Konfik(config_path=test_env_path) 274 | 275 | # Make sure the serializer works. 276 | konfik.show_config() 277 | 278 | # Test variable access with dot notation. 279 | config = konfik.config 280 | 281 | assert config.TITLE == "DOTENV_EXAMPLE" 282 | assert config.NAME == "TOM" 283 | assert config.DOB == "1994-03-24T07:32:00-08:00" 284 | assert config.SERVER == "192.168.1.1" 285 | assert config.PORT == "8001" 286 | assert config.CONNECTION_MAX == "5000" 287 | assert config.ENABLED == "True" 288 | assert config.IP == "10.0.0.1" 289 | assert config.DC == "eqdc10" 290 | 291 | with pytest.raises(MissingVariableError): 292 | config.fakekey 293 | 294 | 295 | def test_konfik_json(tmp_path, json_str, capsys): 296 | """Test the Konfik class for json.""" 297 | 298 | test_json_path = make_config_path(tmp_path, json_str, "json") 299 | 300 | # Load toml from the test toml path. 301 | konfik = Konfik(config_path=test_json_path) 302 | 303 | # Make sure show_config works. 304 | konfik.show_config() 305 | 306 | # Make sure show_config_literal works. 307 | konfik.show_config_literal() 308 | 309 | # Make sure show_var works. 310 | konfik.show_config_var("title") 311 | out, err = capsys.readouterr() 312 | assert err == "" 313 | assert "JSON Example" in out 314 | 315 | # Test variable access with dot notation. 316 | config = konfik.config 317 | 318 | assert config.title == "JSON Example" 319 | assert config.owner.name == "Tom Preston-Werner" 320 | assert config.database == { 321 | "server": "192.168.1.1", 322 | "ports": [8001, 8001, 8002], 323 | "connection_max": 5000, 324 | "enabled": True, 325 | } 326 | assert config.database.server == "192.168.1.1" 327 | assert config.database.ports == [8001, 8001, 8002] 328 | assert config.database.connection_max == 5000 329 | assert config.database.enabled is True 330 | 331 | assert config.servers.alpha == {"ip": "10.0.0.1", "dc": "eqdc10"} 332 | assert config.servers.beta.dc == "eqdc10" 333 | 334 | assert config.clients.data == [["gamma", "delta"], [1, 2]] 335 | 336 | with pytest.raises(MissingVariableError): 337 | config.fakekey 338 | 339 | 340 | def test_konfik_yaml(tmp_path, yaml_str, capsys): 341 | """Test the Konfik class for yaml.""" 342 | 343 | test_yaml_path = make_config_path(tmp_path, yaml_str, "yaml") 344 | 345 | konfik = Konfik(config_path=test_yaml_path) 346 | 347 | # Make sure show_config works. 348 | konfik.show_config() 349 | 350 | # Make sure show_config_literal works. 351 | konfik.show_config_literal() 352 | 353 | # Make sure show_var works. 354 | konfik.show_config_var("title") 355 | out, err = capsys.readouterr() 356 | assert err == "" 357 | assert "YAML Example" in out 358 | 359 | # Test variable access with dot notation. 360 | config = konfik.config 361 | assert config.title == "YAML Example" 362 | assert config.owner.name == "Tom Preston-Werner" 363 | assert config.database == { 364 | "server": "192.168.1.1", 365 | "ports": [8001, 8001, 8002], 366 | "connection_max": 5000, 367 | "enabled": True, 368 | } 369 | assert config.database.server == "192.168.1.1" 370 | assert config.database.ports == [8001, 8001, 8002] 371 | assert config.database.connection_max == 5000 372 | assert config.database.enabled is True 373 | 374 | assert config.servers.alpha == {"ip": "10.0.0.1", "dc": "eqdc10"} 375 | assert config.servers.beta.dc == "eqdc10" 376 | 377 | assert config.clients.data == [["gamma", "delta"], [1, 2]] 378 | 379 | with pytest.raises(MissingVariableError): 380 | config.fakekey 381 | 382 | 383 | def test_konfik_cli_version(capsys): 384 | """Test the CLI version.""" 385 | 386 | cli_entrypoint(argv=["--version"]) 387 | capture = capsys.readouterr() 388 | assert __version__ in capture.out 389 | assert capture.err == "" 390 | 391 | 392 | def test_konfik_cli_path(capsys): 393 | """Test the CLI help message.""" 394 | 395 | cli_entrypoint(argv=["--path=examples/config.toml"]) 396 | capture = capsys.readouterr() 397 | assert capture.err == "" 398 | assert "Konfik -- The strangely familiar config parser ⚙️" in capture.out 399 | 400 | 401 | def test_konfik_cli_show(capsys): 402 | """Test the CLI help message.""" 403 | 404 | cli_entrypoint(argv=["--path=examples/config.toml", "--show"]) 405 | capture = capsys.readouterr() 406 | assert capture.err == "" 407 | assert "Konfik -- The strangely familiar config parser ⚙️" in capture.out 408 | assert "TOML Example" in capture.out 409 | 410 | 411 | def test_konfik_cli_show_literal(capsys): 412 | """Test the CLI help message.""" 413 | 414 | cli_entrypoint(argv=["--path=examples/config.toml", "--show-literal"]) 415 | capture = capsys.readouterr() 416 | assert capture.err == "" 417 | assert "Konfik -- The strangely familiar config parser ⚙️" in capture.out 418 | assert "TOML Example" in capture.out 419 | 420 | 421 | def test_konfik_cli_show_var(capsys): 422 | """Test the CLI help message.""" 423 | 424 | cli_entrypoint(argv=["--path=examples/config.toml", "--var=owner.dob"]) 425 | capture = capsys.readouterr() 426 | assert capture.err == "" 427 | assert "Konfik -- The strangely familiar config parser ⚙️" in capture.out 428 | assert "tzinfo=