├── gitlab_lint ├── __init__.py └── gll.py ├── requirements.txt ├── .gitignore ├── .pre-commit-hooks.yaml ├── test-ci.yml ├── pyproject.toml ├── setup.py ├── LICENSE.txt ├── .gitlab-ci.yml ├── tests └── gll_test.py ├── examples └── .gitlab-ci.yml ├── README.md └── poetry.lock /gitlab_lint/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__/* 3 | gitlab_lint.egg-info/ 4 | gitlab_lint/__pycache__/* 5 | venv 6 | .eggs 7 | tests/__pycache__/* 8 | .venv 9 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: gitlab-ci-check 2 | name: Check Gitlab CI file 3 | description: This hook checks your .gitlab-ci.yml file 4 | entry: gll 5 | language: python 6 | types: [yaml] 7 | -------------------------------------------------------------------------------- /test-ci.yml: -------------------------------------------------------------------------------- 1 | include: "/nix_env.yml" 2 | 3 | stages: 4 | - test 5 | 6 | test_nix: 7 | extends: .nix_env 8 | stage: test 9 | variables: 10 | NIX_CMD: "nix-shell --run" 11 | DAX_CFG: ".dax" 12 | script: 13 | # Enable DAX simulation mode 14 | - echo "[dax.sim]" > $DAX_CFG 15 | - echo "enable = true" >> $DAX_CFG 16 | # Run test commands 17 | - $NIX_CMD "python3 --version; python3 -m unittest -v" 18 | - $NIX_CMD "flake8 --version; flake8 --select=ATQ" 19 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "gitlab-lint" 3 | version = "0.3.0" 4 | description = "This is a CLI application to quickly lint .gitlab-ci.yml files using the gitlab api" 5 | authors = ["Elijah Roberts "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | click = "^8.1.3" 11 | requests = "^2.28.1" 12 | 13 | [tool.poetry.dev-dependencies] 14 | pytest = "^7.1.2" 15 | 16 | [build-system] 17 | requires = ["poetry-core>=1.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name='gitlab-lint', 8 | version='0.4.0', 9 | py_modules=['gitlab-lint'], 10 | author="Elijah Roberts", 11 | author_email="elijah@elijahjamesroberts.com", 12 | description="This is a CLI application to quickly lint .gitlab-ci.yml files using the gitlab api", 13 | license="MIT", 14 | long_description=long_description, 15 | long_description_content_type="text/markdown", 16 | download_url="https://github.com/elijah-roberts/gitlab_lint/archive/0.3.0.tar.gz", 17 | keywords=['GITLAB', 'LINT', 'GIT'], 18 | packages=find_packages(), 19 | classifiers=[ 20 | "Programming Language :: Python :: 3", 21 | "License :: OSI Approved :: MIT License", 22 | "Operating System :: OS Independent", 23 | ], 24 | python_requires='>=3.6', 25 | install_requires=[ 26 | 'Click', 27 | 'Requests' 28 | ], 29 | entry_points=''' 30 | [console_scripts] 31 | gll=gitlab_lint.gll:gll 32 | ''', 33 | ) 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Elijah Roberts 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. -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | # Official language image. Look for the different tagged releases at: 3 | # https://hub.docker.com/r/library/python/tags/ 4 | image: python:latest 5 | 6 | # Change pip's cache directory to be inside the project directory since we can 7 | # only cache local items. 8 | variables: 9 | PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" 10 | 11 | # Pip's cache doesn't store the python packages 12 | # https://pip.pypa.io/en/stable/reference/pip_install/#caching 13 | # 14 | # If you want to also cache the installed packages, you have to install 15 | # them in a virtualenv and cache it as well. 16 | cache: 17 | paths: 18 | - .cache/pip 19 | - venv/ 20 | 21 | before_script: 22 | - python -V # Print out python version for debugging 23 | - pip install virtualenv 24 | - virtualenv venv 25 | - source venv/bin/activate 26 | 27 | test: 28 | script: 29 | - python setup.py test 30 | 31 | run: 32 | before_script: 33 | - python3 -m pip install --user --upgrade twine 34 | script: 35 | - python3 setup.py sdist bdist_wheel 36 | - python3 -m twine upload dist/* 37 | only: [tags] 38 | artifacts: 39 | paths: 40 | - dist/* 41 | 42 | -------------------------------------------------------------------------------- /tests/gll_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from gitlab_lint.gll import * 3 | 4 | 5 | def test_successful_validation(): 6 | # results from default linting api 7 | data = {'status': 'valid', 'errors': [], 'warnings': []} 8 | with pytest.raises(SystemExit) as e: 9 | generate_exit_info(data) 10 | assert e.type == SystemExit 11 | assert e.value.code == 0 12 | 13 | # results from project-specific api 14 | data = {'valid': True, 'errors': [], 'warnings': []} 15 | with pytest.raises(SystemExit) as e: 16 | generate_exit_info(data) 17 | assert e.type == SystemExit 18 | assert e.value.code == 0 19 | 20 | 21 | def test_error_validation(): 22 | # results from default linting api 23 | data = {'status': 'invalid', 24 | 'errors': ['(): did not find expected key while parsing a block mapping at line 1 column 1'], 25 | 'warnings': []} 26 | with pytest.raises(SystemExit) as e: 27 | generate_exit_info(data) 28 | assert e.type == SystemExit 29 | assert e.value.code == 1 30 | 31 | # results from project-specific api 32 | data = {'valid': False, 33 | 'errors': ['(): did not find expected key while parsing a block mapping at line 1 column 1'], 34 | 'warnings': []} 35 | with pytest.raises(SystemExit) as e: 36 | generate_exit_info(data) 37 | assert e.type == SystemExit 38 | assert e.value.code == 1 39 | -------------------------------------------------------------------------------- /examples/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | # Official language image. Look for the different tagged releases at: 3 | # https://hub.docker.com/r/library/python/tags/ 4 | image: python:latest 5 | 6 | # Change pip's cache directory to be inside the project directory since we can 7 | # only cache local items. 8 | variables: 9 | PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" 10 | 11 | # Pip's cache doesn't store the python packages 12 | # https://pip.pypa.io/en/stable/reference/pip_install/#caching 13 | # 14 | # If you want to also cache the installed packages, you have to install 15 | # them in a virtualenv and cache it as well. 16 | cache: 17 | paths: 18 | - .cache/pip 19 | - venv/ 20 | 21 | before_script: 22 | - python -V # Print out python version for debugging 23 | - pip install virtualenv 24 | - virtualenv venv 25 | - source venv/bin/activate 26 | 27 | test: 28 | script: 29 | - python setup.py test 30 | - pip install tox flake8 # you can also use tox 31 | - tox -e py36,flake8 32 | 33 | run: 34 | script: 35 | - python setup.py bdist_wheel 36 | # an alternative approach is to install and run: 37 | - pip install dist/* 38 | # run the command here 39 | artifacts: 40 | paths: 41 | - dist/*.whl 42 | 43 | pages: 44 | script: 45 | - pip install sphinx sphinx-rtd-theme 46 | - cd doc ; make html 47 | - mv build/html/ ../public/ 48 | artifacts: 49 | paths: 50 | - public 51 | only: 52 | - master 53 | -------------------------------------------------------------------------------- /gitlab_lint/gll.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # script to validate .gitlab-ci.yml 3 | 4 | import sys 5 | 6 | import click 7 | import requests 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | 10 | 11 | @click.command() 12 | @click.option("--domain", "-d", envvar='GITLAB_LINT_DOMAIN', default="gitlab.com", 13 | help="Gitlab Domain. You can set envvar GITLAB_LINT_DOMAIN") 14 | @click.option("--project", "-r", envvar='GITLAB_LINT_PROJECT', 15 | help="Gitlab Project ID. You can set envvar GITLAB_LINT_PROJECT") 16 | @click.option("--token", "-t", envvar='GITLAB_LINT_TOKEN', 17 | help="Gitlab Personal Token. You can set envvar GITLAB_LINT_TOKEN") 18 | @click.option("--path", "-p", envvar='GITLAB_LINT_PATH', default=".gitlab-ci.yml", 19 | help="Path to .gitlab-ci.yml, defaults to local directory", 20 | type=click.Path(exists=True, readable=True, file_okay=True)) 21 | @click.option("--verify", "-v", envvar='GITLAB_LINT_VERIFY', default=False, 22 | is_flag=True, help="Enables HTTPS verification, which is disabled by default to support privately hosted instances") 23 | def gll(domain, project, token, path, verify): 24 | data = get_validation_data(path, domain, project, token, verify) 25 | generate_exit_info(data) 26 | 27 | 28 | def get_validation_data(path, domain, project, token, verify): 29 | """ 30 | Creates a post to gitlab ci/lint api endpoint 31 | Reference: https://docs.gitlab.com/ee/api/lint.html 32 | :param path: str path to .gitlab-ci.yml file 33 | :param domain: str gitlab endpoint defaults to gitlab.com, this can be overriden for privately hosted instances 34 | :param project: str gitlab project id. If specified, used to validate .gitlab-ci.yml file with a namespace 35 | :param token: str gitlab token. If your .gitlab-ci.yml file has includes you may need it to authenticate other repos 36 | :param verify: bool flag to enable/disable https checking. False by default to support privately hosted instances 37 | :return: data json response data from api request 38 | """ 39 | 40 | if not verify: 41 | # mask error message for not verifying https if verify is False 42 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 43 | 44 | params = {'private_token': token} if token else None 45 | project_id = f"projects/{project}/" if project else "" 46 | 47 | 48 | with open(path) as f: 49 | r = requests.post(f"https://{domain}/api/v4/{project_id}ci/lint", json={'content': f.read()}, params=params, verify=verify) 50 | if r.status_code != 200: 51 | raise click.ClickException( 52 | f"API endpoint returned invalid response: \n {r.text} \n confirm your `domain`, `project`, and `token` have been set correctly") 53 | data = r.json() 54 | return data 55 | 56 | 57 | def generate_exit_info(data): 58 | """ 59 | Parses response data and generates exit message and code 60 | :param data: json gitlab API ci/lint response data 61 | """ 62 | valid = None 63 | 64 | # for calling the lint api 65 | if 'status' in data: 66 | valid = data['status'] == 'valid' 67 | 68 | # for calling the lint api in the project context 69 | if 'valid' in data: 70 | valid = data['valid'] 71 | 72 | if not valid: 73 | print("GitLab CI configuration is invalid") 74 | for e in data['errors']: 75 | print(e, file=sys.stderr) 76 | sys.exit(1) 77 | else: 78 | print("GitLab CI configuration is valid") 79 | sys.exit(0) 80 | 81 | 82 | if __name__ == '__main__': 83 | gll() 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitlab_lint 2 | 3 | [![Downloads](https://pepy.tech/badge/gitlab-lint)](https://pepy.tech/project/gitlab-lint) 4 | 5 | This is a CLI application to quickly lint .gitlab-ci.yml files using the gitlab api. This can easily be added as a pre-commit step to locally catch any issues with your configuration prior to pushing your changes. 6 | 7 | ## Installation 8 | ```python3 -m pip install -U gitlab_lint``` 9 | 10 | ## Configuration 11 | You can set the following environmental variables: 12 | 13 | `GITLAB_LINT_DOMAIN` - Which allows you to override the default gitlab.com domain, and point at a local instance 14 | 15 | `GITLAB_LINT_TOKEN` - If your .gitlab-ci.yml contains any includes, you may need to set a private token to pull data from those other repos 16 | 17 | `GITLAB_LINT_PROJECT` - If your .gitlab-ci.yml contains any includes which source from your local repo, you may set your project ID. If your repo is private, you must set your token as well. 18 | 19 | `GITLAB_LINT_PATH` - If you're linting anything other than the default `.gitlab-ci.yml` file. 20 | 21 | `GITLAB_LINT_VERIFY` - If you prefer TLS checking 22 | 23 | I would recommend adding these to your ~/.profile or ~/.bash_profile 24 | 25 | ## Parameters 26 | 27 | | Flag | Description | Type | Default | Required | 28 | |------|-------------|:----:|:-----:|:-----:| 29 | | --domain | Gitlab Domain. You can set envvar `GITLAB_LINT_DOMAIN` | string | `gitlab.com` | no | 30 | | --project | Gitlab Project ID. You can set envvar `GITLAB_LINT_PROJECT` | string | `None` | no | 31 | | --token | Gitlab Personal Token. You can set envvar `GITLAB_LINT_TOKEN` | string | `None`| no | 32 | | --path | Path to .gitlab-ci.yml, defaults to local directory. You can set envvar `GITLAB_LINT_PATH` | string | `.gitlab-ci.yml` | no | 33 | | --verify | Enables HTTPS verification, which is disabled by default to support privately hosted instances. You can set envvar `GITLAB_LINT_VERIFY` | Flag | `False` | no | 34 | 35 | ## Example Usage 36 | If your .gitlab-ci.yml is in the current directory it is as easy as: 37 | ``` 38 | $ gll 39 | GitLab CI configuration is valid 40 | 41 | ``` 42 | 43 | Failures will appear like so: 44 | ``` 45 | $ gll 46 | GitLab CI configuration is invalid 47 | (): could not find expected ':' while scanning a simple key at line 26 column 1 48 | ``` 49 | 50 | If you need to you can specify the path: 51 | ``` 52 | $ gll --path path/to/.gitlab-ci.yml 53 | GitLab CI configuration is valid 54 | 55 | ``` 56 | 57 | If you choose not to set the envvars for domain and token you can pass them in as flags: 58 | ``` 59 | $ gll --path path/to/.gitlab-ci.yml --domain gitlab.mycompany.com --project 1234 --token 60 | GitLab CI configuration is valid 61 | 62 | ``` 63 | 64 | 65 | Https verification is disabled by default to support privately hosted instances, if you would like to enable pass the `--verify | -v` flag 66 | 67 | ``` 68 | $ gll --verify 69 | GitLab CI configuration is valid 70 | 71 | ``` 72 | ## Development 73 | 74 | ### Bug Reports & Feature Requests 75 | 76 | Please use the submit a issue to report any bugs or file feature requests. 77 | 78 | ### Developing 79 | 80 | If you are interested in being a contributor and want to get involved in developing this CLI application feel free to reach out 81 | 82 | In general, PRs are welcome. We follow the typical trunk based development Git workflow. 83 | 84 | 1. **Branch** the repo 85 | 2. **Clone** the project to your own machine 86 | 3. **Commit** changes to your branch 87 | 4. **Push** your work back up to your branch 88 | 5. Submit a **Merge/Pull Request** so that we can review your changes 89 | 90 | **NOTE:** Be sure to merge the latest changes from "upstream" before making a pull request! 91 | 92 | ### Virtual environments 93 | 94 | This project supports Poetry for Python virtual environments. Poetry may be installed via `pip`, and environments can be accessed with `poetry shell` or `poetry run`. 95 | 96 | #### Tests 97 | 98 | Run tests in root directory with `pytest` 99 | 100 | ### pre-commit 101 | To use this with pre-commit.com, you can use something like 102 | ```yaml 103 | - repo: https://github.com/mick352/gitlab-lint 104 | rev: pre-commit-hook 105 | hooks: 106 | - id: gitlab-ci-check 107 | pass_filenames: false 108 | args: [-d, my.private.repo, -r, project_id, -t, private_token] 109 | ``` 110 | (or remove the `args` line for gitlab.com). 111 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.0" 4 | description = "Atomic file writes." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "21.4.0" 12 | description = "Classes Without Boilerplate" 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 16 | 17 | [package.extras] 18 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] 19 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 20 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 21 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 22 | 23 | [[package]] 24 | name = "certifi" 25 | version = "2022.6.15" 26 | description = "Python package for providing Mozilla's CA Bundle." 27 | category = "main" 28 | optional = false 29 | python-versions = ">=3.6" 30 | 31 | [[package]] 32 | name = "charset-normalizer" 33 | version = "2.1.0" 34 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 35 | category = "main" 36 | optional = false 37 | python-versions = ">=3.6.0" 38 | 39 | [package.extras] 40 | unicode_backport = ["unicodedata2"] 41 | 42 | [[package]] 43 | name = "click" 44 | version = "8.1.3" 45 | description = "Composable command line interface toolkit" 46 | category = "main" 47 | optional = false 48 | python-versions = ">=3.7" 49 | 50 | [package.dependencies] 51 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 52 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 53 | 54 | [[package]] 55 | name = "colorama" 56 | version = "0.4.5" 57 | description = "Cross-platform colored terminal text." 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 = "idna" 64 | version = "3.3" 65 | description = "Internationalized Domain Names in Applications (IDNA)" 66 | category = "main" 67 | optional = false 68 | python-versions = ">=3.5" 69 | 70 | [[package]] 71 | name = "importlib-metadata" 72 | version = "4.12.0" 73 | description = "Read metadata from Python packages" 74 | category = "main" 75 | optional = false 76 | python-versions = ">=3.7" 77 | 78 | [package.dependencies] 79 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 80 | zipp = ">=0.5" 81 | 82 | [package.extras] 83 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 84 | perf = ["ipython"] 85 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] 86 | 87 | [[package]] 88 | name = "iniconfig" 89 | version = "1.1.1" 90 | description = "iniconfig: brain-dead simple config-ini parsing" 91 | category = "dev" 92 | optional = false 93 | python-versions = "*" 94 | 95 | [[package]] 96 | name = "packaging" 97 | version = "21.3" 98 | description = "Core utilities for Python packages" 99 | category = "dev" 100 | optional = false 101 | python-versions = ">=3.6" 102 | 103 | [package.dependencies] 104 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 105 | 106 | [[package]] 107 | name = "pluggy" 108 | version = "1.0.0" 109 | description = "plugin and hook calling mechanisms for python" 110 | category = "dev" 111 | optional = false 112 | python-versions = ">=3.6" 113 | 114 | [package.dependencies] 115 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 116 | 117 | [package.extras] 118 | dev = ["pre-commit", "tox"] 119 | testing = ["pytest", "pytest-benchmark"] 120 | 121 | [[package]] 122 | name = "py" 123 | version = "1.11.0" 124 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 125 | category = "dev" 126 | optional = false 127 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 128 | 129 | [[package]] 130 | name = "pyparsing" 131 | version = "3.0.9" 132 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 133 | category = "dev" 134 | optional = false 135 | python-versions = ">=3.6.8" 136 | 137 | [package.extras] 138 | diagrams = ["railroad-diagrams", "jinja2"] 139 | 140 | [[package]] 141 | name = "pytest" 142 | version = "7.1.2" 143 | description = "pytest: simple powerful testing with Python" 144 | category = "dev" 145 | optional = false 146 | python-versions = ">=3.7" 147 | 148 | [package.dependencies] 149 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 150 | attrs = ">=19.2.0" 151 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 152 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 153 | iniconfig = "*" 154 | packaging = "*" 155 | pluggy = ">=0.12,<2.0" 156 | py = ">=1.8.2" 157 | tomli = ">=1.0.0" 158 | 159 | [package.extras] 160 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 161 | 162 | [[package]] 163 | name = "requests" 164 | version = "2.28.1" 165 | description = "Python HTTP for Humans." 166 | category = "main" 167 | optional = false 168 | python-versions = ">=3.7, <4" 169 | 170 | [package.dependencies] 171 | certifi = ">=2017.4.17" 172 | charset-normalizer = ">=2,<3" 173 | idna = ">=2.5,<4" 174 | urllib3 = ">=1.21.1,<1.27" 175 | 176 | [package.extras] 177 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 178 | use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] 179 | 180 | [[package]] 181 | name = "tomli" 182 | version = "2.0.1" 183 | description = "A lil' TOML parser" 184 | category = "dev" 185 | optional = false 186 | python-versions = ">=3.7" 187 | 188 | [[package]] 189 | name = "typing-extensions" 190 | version = "4.2.0" 191 | description = "Backported and Experimental Type Hints for Python 3.7+" 192 | category = "main" 193 | optional = false 194 | python-versions = ">=3.7" 195 | 196 | [[package]] 197 | name = "urllib3" 198 | version = "1.26.9" 199 | description = "HTTP library with thread-safe connection pooling, file post, and more." 200 | category = "main" 201 | optional = false 202 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 203 | 204 | [package.extras] 205 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 206 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 207 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 208 | 209 | [[package]] 210 | name = "zipp" 211 | version = "3.8.0" 212 | description = "Backport of pathlib-compatible object wrapper for zip files" 213 | category = "main" 214 | optional = false 215 | python-versions = ">=3.7" 216 | 217 | [package.extras] 218 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 219 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 220 | 221 | [metadata] 222 | lock-version = "1.1" 223 | python-versions = "^3.7" 224 | content-hash = "ea2d6d82117619587a4b45e8a737d02f5b0ab2dc8f6d4d169547f3e99f05da94" 225 | 226 | [metadata.files] 227 | atomicwrites = [ 228 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 229 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 230 | ] 231 | attrs = [ 232 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 233 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 234 | ] 235 | certifi = [ 236 | {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, 237 | {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, 238 | ] 239 | charset-normalizer = [ 240 | {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, 241 | {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, 242 | ] 243 | click = [ 244 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 245 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 246 | ] 247 | colorama = [ 248 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 249 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 250 | ] 251 | idna = [ 252 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 253 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 254 | ] 255 | importlib-metadata = [ 256 | {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, 257 | {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, 258 | ] 259 | iniconfig = [ 260 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 261 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 262 | ] 263 | packaging = [ 264 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 265 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 266 | ] 267 | pluggy = [ 268 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 269 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 270 | ] 271 | py = [ 272 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 273 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 274 | ] 275 | pyparsing = [ 276 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 277 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 278 | ] 279 | pytest = [ 280 | {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, 281 | {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, 282 | ] 283 | requests = [ 284 | {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, 285 | {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, 286 | ] 287 | tomli = [ 288 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 289 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 290 | ] 291 | typing-extensions = [ 292 | {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, 293 | {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, 294 | ] 295 | urllib3 = [ 296 | {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, 297 | {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, 298 | ] 299 | zipp = [ 300 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 301 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 302 | ] 303 | --------------------------------------------------------------------------------