├── MANIFEST.in ├── Pipfile ├── python_starter_pack ├── funcs.py └── __init__.py ├── tests └── test_module.py ├── makefile ├── .travis.yml ├── LICENSE ├── setup.py ├── .gitignore ├── Readme.md └── Pipfile.lock /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Pipfile 2 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pylint = "*" 10 | codecov = "*" 11 | 12 | [packages] 13 | toml = "*" 14 | 15 | [requires] 16 | python_version = "3.6" 17 | -------------------------------------------------------------------------------- /python_starter_pack/funcs.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | 3 | """This module contains sample functions for illustrative purposes. 4 | """ 5 | 6 | def some_func(): 7 | """Some random function that can be imported from unit tests. 8 | """ 9 | return True 10 | 11 | 12 | def say_hello(): 13 | """Entry-point function 14 | """ 15 | print("This is the Python Starter Pack.\n" 16 | "Check details in Github: https://github.com/apiad/python-starter-pack.") 17 | -------------------------------------------------------------------------------- /tests/test_module.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | 3 | """Boilerplate code for unit testing, meant to be tested with `pytest`. 4 | Here are some easy tests to begin with. 5 | """ 6 | 7 | from python_starter_pack import some_func 8 | from python_starter_pack import say_hello 9 | 10 | 11 | def test_some_func(): 12 | assert some_func() 13 | 14 | 15 | def test_say_hello(): 16 | assert say_hello() is None 17 | 18 | 19 | def test_the_meaning(): 20 | assert 6 * 9 == int("42", base=13) 21 | 22 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean install test lint cov 2 | 3 | # TODO: Update your project folder 4 | PROJECT=python_starter_pack 5 | 6 | build: 7 | pipenv run python setup.py sdist bdist_wheel 8 | 9 | clean: 10 | git clean -fxd 11 | 12 | install: 13 | pip install pipenv 14 | pipenv install --dev --skip-lock 15 | 16 | test: 17 | make lint && pipenv run pytest --doctest-modules --cov=python_starter_pack --cov-report=xml -v 18 | 19 | lint: 20 | pipenv run pylint $(PROJECT) 21 | 22 | cov: 23 | pipenv run codecov 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | cache: pip 4 | 5 | python: 6 | - 3.6 7 | 8 | install: make install 9 | script: make test 10 | after_success: make cov 11 | 12 | deploy: 13 | - provider: pypi 14 | user: $PYPI_USER 15 | password: $PYPI_PASSWORD 16 | distributions: "sdist bdist_wheel" 17 | on: 18 | tags: true 19 | 20 | - provider: pypi 21 | user: $TEST_PYPI_USER 22 | password: $TEST_PYPI_PASSWORD 23 | distributions: "sdist bdist_wheel" 24 | server: https://test.pypi.org/legacy/ 25 | on: 26 | branch: develop 27 | -------------------------------------------------------------------------------- /python_starter_pack/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | 3 | """This file is necesary if you want to import your module as a library, as in: 4 | 5 | >>> import python_starter_pack 6 | >>> python_starter_pack.some_func() 7 | True 8 | 9 | This documentation contains doctests that should be automatically run 10 | when you execute `pipenv run pytest`. 11 | """ 12 | 13 | # This is how you "export" inner-module functionality to the top-level module 14 | # Also, this is to test that local imports work inside your package 15 | from .funcs import some_func 16 | from .funcs import say_hello 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alejandro Piad 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 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | 3 | import toml 4 | from setuptools import setup 5 | 6 | 7 | # TODO: Update version whenever changes 8 | VERSION = '0.2.0' 9 | 10 | 11 | def get_install_requirements(): 12 | """Automatically pull requirements from Pipfile. 13 | Adapted from: 14 | """ 15 | try: 16 | # read my pipfile 17 | with open ('Pipfile', 'r') as fh: 18 | pipfile = fh.read() 19 | # parse the toml 20 | pipfile_toml = toml.loads(pipfile) 21 | except FileNotFoundError: 22 | return [] 23 | # if the package's key isn't there then just return an empty 24 | # list 25 | try: 26 | required_packages = pipfile_toml['packages'].items() 27 | except KeyError: 28 | return [] 29 | # If a version/range is specified in the Pipfile honor it 30 | # otherwise just list the package 31 | return ["{0}{1}".format(pkg,ver) if ver != "*" 32 | else pkg for pkg,ver in required_packages] 33 | 34 | 35 | setup( 36 | # TODO: Change your library name and additional information down here 37 | name='python-starter-pack', 38 | packages=['python_starter_pack'], 39 | url='https://github.com/apiad/python-starter-pack', 40 | download_url='https://github.com/apiad/python-starter-pack/tarball/{}'.format(VERSION), 41 | license='MIT', 42 | author='Alejandro Piad', 43 | author_email='apiad@apiad.net', 44 | description='A starter pack for Python modules.', 45 | 46 | # This should automatically take your long description from Readme.md 47 | long_description=open('Readme.md').read(), 48 | long_description_content_type='text/markdown', 49 | 50 | # This should automatically pull your requirements from `Pipfile` 51 | install_requires=get_install_requirements(), 52 | version=VERSION, 53 | 54 | # TODO (Optional): Set your entry-points (CLI apps to register) here 55 | entry_points={ 56 | 'console_scripts': ['python-starter-pack=python_starter_pack:say_hello'], 57 | }, 58 | 59 | # TODO: Choose your classifiers carefully 60 | classifiers=[ 61 | 'Development Status :: 4 - Beta', 62 | 'License :: OSI Approved :: MIT License', 63 | 'Programming Language :: Python :: 3.6', 64 | 'Topic :: Software Development :: Libraries :: Python Modules', 65 | ] 66 | ) 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | 3 | # Created by https://www.gitignore.io/api/linux,visualstudiocode,python 4 | # Edit at https://www.gitignore.io/?templates=linux,visualstudiocode,python 5 | 6 | ### Linux ### 7 | *~ 8 | 9 | # temporary files which can be created if a process still has a handle open of a deleted file 10 | .fuse_hidden* 11 | 12 | # KDE directory preferences 13 | .directory 14 | 15 | # Linux trash folder which might appear on any partition or disk 16 | .Trash-* 17 | 18 | # .nfs files are created when an open file is removed but is still being accessed 19 | .nfs* 20 | 21 | ### Python ### 22 | # Byte-compiled / optimized / DLL files 23 | __pycache__/ 24 | *.py[cod] 25 | *$py.class 26 | 27 | # C extensions 28 | *.so 29 | 30 | # Distribution / packaging 31 | .Python 32 | build/ 33 | develop-eggs/ 34 | dist/ 35 | downloads/ 36 | eggs/ 37 | .eggs/ 38 | lib/ 39 | lib64/ 40 | parts/ 41 | sdist/ 42 | var/ 43 | wheels/ 44 | pip-wheel-metadata/ 45 | share/python-wheels/ 46 | *.egg-info/ 47 | .installed.cfg 48 | *.egg 49 | MANIFEST 50 | 51 | # PyInstaller 52 | # Usually these files are written by a python script from a template 53 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 54 | *.manifest 55 | *.spec 56 | 57 | # Installer logs 58 | pip-log.txt 59 | pip-delete-this-directory.txt 60 | 61 | # Unit test / coverage reports 62 | htmlcov/ 63 | .tox/ 64 | .nox/ 65 | .coverage 66 | .coverage.* 67 | .cache 68 | nosetests.xml 69 | coverage.xml 70 | *.cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | 74 | # Translations 75 | *.mo 76 | *.pot 77 | 78 | # Django stuff: 79 | *.log 80 | local_settings.py 81 | db.sqlite3 82 | 83 | # Flask stuff: 84 | instance/ 85 | .webassets-cache 86 | 87 | # Scrapy stuff: 88 | .scrapy 89 | 90 | # Sphinx documentation 91 | docs/_build/ 92 | 93 | # PyBuilder 94 | target/ 95 | 96 | # Jupyter Notebook 97 | .ipynb_checkpoints 98 | 99 | # IPython 100 | profile_default/ 101 | ipython_config.py 102 | 103 | # pyenv 104 | .python-version 105 | 106 | # pipenv 107 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 108 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 109 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 110 | # install all needed dependencies. 111 | #Pipfile.lock 112 | 113 | # celery beat schedule file 114 | celerybeat-schedule 115 | 116 | # SageMath parsed files 117 | *.sage.py 118 | 119 | # Environments 120 | .env 121 | .venv 122 | env/ 123 | venv/ 124 | ENV/ 125 | env.bak/ 126 | venv.bak/ 127 | 128 | # Spyder project settings 129 | .spyderproject 130 | .spyproject 131 | 132 | # Rope project settings 133 | .ropeproject 134 | 135 | # mkdocs documentation 136 | /site 137 | 138 | # mypy 139 | .mypy_cache/ 140 | .dmypy.json 141 | dmypy.json 142 | 143 | # Pyre type checker 144 | .pyre/ 145 | 146 | ### VisualStudioCode ### 147 | .vscode/* 148 | # !.vscode/settings.json 149 | # !.vscode/tasks.json 150 | # !.vscode/launch.json 151 | # !.vscode/extensions.json 152 | 153 | ### VisualStudioCode Patch ### 154 | # Ignore all local history of files 155 | .history 156 | 157 | # End of https://www.gitignore.io/api/linux,visualstudiocode,python 158 | 159 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) 160 | 161 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Python Starter Pack 2 | 3 | PyPI - License PyPI - Python Version PyPI Travis (.org) Codecov 4 | 5 | > Quickly setup a Python 3 library complete with continuous integration, code coverage and automatic deployment to PyPi in 5... err... 35 minutes. 6 | 7 | ## What's this about? 8 | 9 | Have you ever wanted to make a Python library available on PyPi, but struggled with all the fuss about `setup.py`, continuous integration, unit testing, and such? Been there ;). 10 | 11 | After reading a bunch of tutorials and trying a few different ways on my own, this is the most condensed and streamlined checklist I've come up with. Just by forking this project and following the next few steps you'll be up on your own with a brand new Python library project, together with unit testing, continuous integration, code coverage, and automatic deployment to PyPi. Tag along. 12 | 13 | ## A Python starter pack 14 | 15 | Starting a new Python library project? Follow these steps: 16 | 17 | ### Step 1: Setting up the environment 18 | 19 | First, it should go without saying, get a [Github account](https://github.com/signup) if you haven't. 20 | 21 | Next, [fork this project](https://github.com/apiad/python-starter-pack), and then clone your own version, or directly clone the project: 22 | 23 | ```bash 24 | $ git clone git@github.com:apiad/python-starter-pack 25 | ``` 26 | 27 | Now you can head over to your project's folder and see what's inside: 28 | 29 | ```bash 30 | $ cd 31 | $ ls 32 | 33 | total 48K 34 | -rw-r--r-- 1 user user 1,1K may 19 18:44 LICENSE 35 | -rw-r--r-- 1 user user 321 may 22 15:29 makefile 36 | -rw-r--r-- 1 user user 16 may 19 19:34 MANIFEST.in 37 | -rw-r--r-- 1 user user 206 may 19 19:36 Pipfile 38 | -rw-r--r-- 1 user user 13K may 19 19:36 Pipfile.lock 39 | drwxr-xr-x 2 user user 4,0K may 22 15:31 python_starter_pack 40 | -rw-r--r-- 1 user user 2,2K may 22 15:40 Readme.md 41 | -rw-r--r-- 1 user user 2,1K may 22 15:31 setup.py 42 | drwxr-xr-x 2 user user 4,0K may 22 15:31 tests 43 | ``` 44 | 45 | We'll go in depth on the contents of each file later on. The most important things now is to notice that we have a `Pipfile`, hence, we will be using `pipenv` for dependency management. 46 | 47 | So, if you haven't already, [install pipenv](https://github.com/pypa/pipenv). The easiest way, in Linux, is simply to run: 48 | 49 | ```bash 50 | $ make install 51 | 52 | pip install pipenv 53 | ... 54 | pipenv install --dev --skip-lock 55 | Installing dependencies from Pipfile… 56 | 🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 5/5 — 00:00:01 57 | ... 58 | ``` 59 | 60 | This will use our `makefile` definition for `install` which basically installs `pipenv` and updates the dependencies. 61 | 62 | ### Step 2: Adding your code 63 | 64 | Now that you have the dependencies and development environment in place, you can start adding your code. 65 | We have sample code in the `python_starter_pack` folder. Check the folder and files' content if you need a little guidance, or simply replace with your own code. 66 | 67 | For starters, the `python_starter_pack` folder is what we call a _Python module_, because it contains a `__init__.py` file which allows it to be imported from Python code. 68 | 69 | There are also some basic functions in there just to illustrate the basic functionality for importing code and, as we'll see next, for testing. 70 | 71 | ### Step 3: Running tests 72 | 73 | The `tests` folder will contain all your unit tests. We'll be using the awesome `pytest` module, and also `pylint` for ensuring our code is Pythonic and beautiful. 74 | 75 | If you have been changing code, you will need to make some changes to `makefile` to ensure everything is consistent. Open it and update the value of the `PROJECT` variable to point to your project's folder. In Linux you can just hack your way with the following (where `` is your project's folder): 76 | 77 | ```bash 78 | $ sed -i -E "/^PROJECT/s/(.*)/PROJECT=/" makefile 79 | ``` 80 | 81 | In any case, now you can test your code with: 82 | 83 | ```bash 84 | $ make test 85 | ``` 86 | 87 | This will run `pylint` and then `pytest`, testing doc-strings and unit tests in the `tests` folder. Check the file `tests/test_module.py` for head-start on unit testing in Python. 88 | This will also create and print `codecov` coverage reports, telling you how much of your code is tested. 89 | Make sure to re-test every time you change something. 90 | 91 | ### Step 4: Publishing on Github 92 | 93 | If you forked your project then your git remote is set. Otherwise, you will need [to create a new project](https://github.com/new) on Github and set up your remote. In any case, when ready, you can just push your code: 94 | 95 | ```bash 96 | $ git push origin master 97 | ``` 98 | 99 | ### Step 5: Setup continuous integration 100 | 101 | Now that your project is on Github, the next step is to setup continuous integration with [Travis-CI](https://travis-ci.org). If you don't still have an account on Travis-CI, register there and [activate your repository](https://travis-ci.org/account/repositories). 102 | 103 | Travis-CI will ask you to link with your Github account, and install the `travis` app in your Github profile. Once that is done, every push will automatically trigger Travis-CI to run the tests online. 104 | 105 | Plus, Travis-CI will automatically push coverage reports to [Codecov](https://codecov.io). Make sure to register there as well, and you will see coverage statistics automatically (there is no need to "activate" a repository there, it happens automatically when Travis pushes coverage stats). 106 | 107 | This all just works because of the file `.travis.yml` which you are free to open and modify according to your preferences (e.g., change the preferred Python version). 108 | 109 | Once Travis-CI and Codecov are setup, make sure to modify the top of this `Readme.md` file and update these links: 110 | 111 | ```html 112 | Travis (.org) 113 | Codecov 114 | ``` 115 | 116 | Change the `apiad/python-starter-pack` part to match your Github user/repository and you will immediately get these nice badges on your Readme file. 117 | 118 | ### Step 6: Automatic deploy on PyPi 119 | 120 | The next step is to setup automatic deployment on the Python Package Index. We will start with deploying to the test channel before moving on deploying to the real channel. 121 | 122 | First, to keep things tidy up, let me explain the how the workflow will be. We will create a `develop` branch: 123 | 124 | ```bash 125 | $ git branch -C develop 126 | $ git checkout develop 127 | ``` 128 | 129 | Now, on this branch, we will test that deployment to PyPi works. Head over to [test.pypi.org](https://test.pypi.org) and register there. Remember your **username** and **password**. 130 | 131 | Now it's time to setup up your package configuration. Open the file `setup.py` and modify the necessary lines. They all say `TODO` on top. You should define there your project's name and modules, copyright info, entry-points (if any) and other metadata (known as classifiers). 132 | 133 | Once that is ready, **make sure to change** the `VERSION` variable on top. This `VERSION` variable is what PyPi will use to determine the current version, and if you push twice with the same version you'll get an error because you cannot override something published to PyPi. 134 | 135 | Now head over to [Travis-CI](https://travis-ci.org) and navigate to your project's settings. There you will need to set two **environment variables**: `TEST_PYPI_USER` and `TEST_PYPI_PASSWORD` with the values of your username and password for [test.pypi.org](https://test.pypi.org). 136 | 137 | Once that is done, you can now push to Github from the `develop` branch and your project will be automatically published on [test.pypi.org](https://test.pypi.org). You can check it there. 138 | 139 | By now you should have a workflow cycle that looks something like this: 140 | - Work on the `develop` branch (or a `feature-*` and them merge to `develop`). 141 | - Commit as much as you like. 142 | - Run `make test` often to make sure everything works. 143 | - When you are confident the next feature is working, go over to `setup.py` and bump the `VERSION` variable to your new version. 144 | - Push the `develop` branch to Github. 145 | - Check [Travis-CI](https://travis-ci.org) and [test.pypi.org](https://test.pypi.org) to make sure everything is Ok. 146 | 147 | ### Step 7: Deploy on PyPi for real 148 | 149 | Now you are going to setup deployment on the **real** PyPi index. Head over to [pypi.org](https://test.pypi.org) and register there. 150 | 151 | Now go over to Travis-CI settings for your project and set the **environment variables** `PYPI_USERNAME` and `PYPI_PASSWORD`. Once this is ready, Travis will be able to push to PyPi when you commit and push to `master`. 152 | 153 | However, for safety reasons, we **do not** deploy on PyPI on every commit to `master`, but **only on tags**. Hence, the workflow is the following: 154 | 155 | - Develop on the `develop` branch and commit, bump version, push, rinse, repeat. 156 | - Once you are confident everything is Ok on `develop`, navigate to [Github](https://github.com), and in your project's page, create a **pull request** from `develop` to `master`. 157 | - When the pull request has been created, you will notice that automatically Travis and Codecov start working and basically block your commit until all tests pass. 158 | - Once everything is green, you will be able to **merge** to `master`. 159 | - Finally, **create a release** on Github, with a proper version number (please, the same as in `setup.py`) and then, and only then, will Travis deploy to PyPi. 160 | 161 | When everything is working, make sure to change the remaining `` tags in this `Readme.md` to match your repository's name. 162 | 163 | **Enjoy!** 164 | 165 | ## Collaboration 166 | 167 | License is MIT, so you know the drill. 168 | 169 | > MIT License 170 | > 171 | > Copyright (c) 2019 Alejandro Piad 172 | > 173 | > Permission is hereby granted, free of charge, to any person obtaining a copy 174 | > of this software and associated documentation files (the "Software"), to deal 175 | > in the Software without restriction, including without limitation the rights 176 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 177 | > copies of the Software, and to permit persons to whom the Software is 178 | > furnished to do so, subject to the following conditions: 179 | > 180 | > The above copyright notice and this permission notice shall be included in all 181 | > copies or substantial portions of the Software. 182 | > 183 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 184 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 185 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 186 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 187 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 188 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 189 | > SOFTWARE. 190 | 191 | 192 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "8fa95cd1ee1f053684f50577cb716c1714fafe018bee48dbbe459103e267e0ae" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "toml": { 20 | "hashes": [ 21 | "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", 22 | "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" 23 | ], 24 | "index": "pypi", 25 | "version": "==0.10.0" 26 | } 27 | }, 28 | "develop": { 29 | "astroid": { 30 | "hashes": [ 31 | "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", 32 | "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" 33 | ], 34 | "version": "==2.2.5" 35 | }, 36 | "atomicwrites": { 37 | "hashes": [ 38 | "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", 39 | "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" 40 | ], 41 | "version": "==1.3.0" 42 | }, 43 | "attrs": { 44 | "hashes": [ 45 | "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", 46 | "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" 47 | ], 48 | "version": "==19.1.0" 49 | }, 50 | "certifi": { 51 | "hashes": [ 52 | "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", 53 | "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" 54 | ], 55 | "version": "==2019.3.9" 56 | }, 57 | "chardet": { 58 | "hashes": [ 59 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 60 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 61 | ], 62 | "version": "==3.0.4" 63 | }, 64 | "codecov": { 65 | "hashes": [ 66 | "sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788", 67 | "sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4" 68 | ], 69 | "index": "pypi", 70 | "version": "==2.0.15" 71 | }, 72 | "coverage": { 73 | "hashes": [ 74 | "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", 75 | "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", 76 | "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", 77 | "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", 78 | "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", 79 | "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", 80 | "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", 81 | "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", 82 | "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", 83 | "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", 84 | "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", 85 | "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", 86 | "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", 87 | "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", 88 | "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", 89 | "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", 90 | "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", 91 | "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", 92 | "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", 93 | "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", 94 | "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", 95 | "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", 96 | "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", 97 | "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", 98 | "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", 99 | "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", 100 | "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", 101 | "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", 102 | "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", 103 | "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", 104 | "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" 105 | ], 106 | "version": "==4.5.3" 107 | }, 108 | "idna": { 109 | "hashes": [ 110 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 111 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 112 | ], 113 | "version": "==2.8" 114 | }, 115 | "isort": { 116 | "hashes": [ 117 | "sha256:c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a", 118 | "sha256:f57abacd059dc3bd666258d1efb0377510a89777fda3e3274e3c01f7c03ae22d" 119 | ], 120 | "version": "==4.3.20" 121 | }, 122 | "lazy-object-proxy": { 123 | "hashes": [ 124 | "sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661", 125 | "sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f", 126 | "sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13", 127 | "sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821", 128 | "sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71", 129 | "sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e", 130 | "sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea", 131 | "sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229", 132 | "sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4", 133 | "sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e", 134 | "sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20", 135 | "sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16", 136 | "sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b", 137 | "sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7", 138 | "sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c", 139 | "sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a", 140 | "sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e", 141 | "sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1" 142 | ], 143 | "version": "==1.4.1" 144 | }, 145 | "mccabe": { 146 | "hashes": [ 147 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 148 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 149 | ], 150 | "version": "==0.6.1" 151 | }, 152 | "more-itertools": { 153 | "hashes": [ 154 | "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", 155 | "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" 156 | ], 157 | "markers": "python_version > '2.7'", 158 | "version": "==7.0.0" 159 | }, 160 | "pluggy": { 161 | "hashes": [ 162 | "sha256:25a1bc1d148c9a640211872b4ff859878d422bccb59c9965e04eed468a0aa180", 163 | "sha256:964cedd2b27c492fbf0b7f58b3284a09cf7f99b0f715941fb24a439b3af1bd1a" 164 | ], 165 | "version": "==0.11.0" 166 | }, 167 | "py": { 168 | "hashes": [ 169 | "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", 170 | "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" 171 | ], 172 | "version": "==1.8.0" 173 | }, 174 | "pylint": { 175 | "hashes": [ 176 | "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", 177 | "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1" 178 | ], 179 | "index": "pypi", 180 | "version": "==2.3.1" 181 | }, 182 | "pytest": { 183 | "hashes": [ 184 | "sha256:1a8aa4fa958f8f451ac5441f3ac130d9fc86ea38780dd2715e6d5c5882700b24", 185 | "sha256:b8bf138592384bd4e87338cb0f256bf5f615398a649d4bd83915f0e4047a5ca6" 186 | ], 187 | "index": "pypi", 188 | "version": "==4.5.0" 189 | }, 190 | "pytest-cov": { 191 | "hashes": [ 192 | "sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", 193 | "sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a" 194 | ], 195 | "index": "pypi", 196 | "version": "==2.7.1" 197 | }, 198 | "requests": { 199 | "hashes": [ 200 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 201 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 202 | ], 203 | "version": "==2.22.0" 204 | }, 205 | "six": { 206 | "hashes": [ 207 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 208 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 209 | ], 210 | "version": "==1.12.0" 211 | }, 212 | "typed-ast": { 213 | "hashes": [ 214 | "sha256:132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b", 215 | "sha256:18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d", 216 | "sha256:2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a", 217 | "sha256:3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462", 218 | "sha256:4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee", 219 | "sha256:4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a", 220 | "sha256:5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4", 221 | "sha256:6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649", 222 | "sha256:7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a", 223 | "sha256:8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f", 224 | "sha256:8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7", 225 | "sha256:912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760", 226 | "sha256:b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18", 227 | "sha256:c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616", 228 | "sha256:c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd", 229 | "sha256:ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21", 230 | "sha256:eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93", 231 | "sha256:f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb", 232 | "sha256:f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7" 233 | ], 234 | "markers": "implementation_name == 'cpython'", 235 | "version": "==1.3.5" 236 | }, 237 | "urllib3": { 238 | "hashes": [ 239 | "sha256:a53063d8b9210a7bdec15e7b272776b9d42b2fd6816401a0d43006ad2f9902db", 240 | "sha256:d363e3607d8de0c220d31950a8f38b18d5ba7c0830facd71a1c6b1036b7ce06c" 241 | ], 242 | "version": "==1.25.2" 243 | }, 244 | "wcwidth": { 245 | "hashes": [ 246 | "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", 247 | "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" 248 | ], 249 | "version": "==0.1.7" 250 | }, 251 | "wrapt": { 252 | "hashes": [ 253 | "sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533" 254 | ], 255 | "version": "==1.11.1" 256 | } 257 | } 258 | } 259 | --------------------------------------------------------------------------------