├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── .tool-versions ├── .verchew.ini ├── .vscode └── settings.json ├── LICENSE.txt ├── Makefile ├── README.md ├── cookiecutter.json ├── poetry.lock ├── pyproject.toml ├── scent.py └── {{cookiecutter.project_name}} ├── .appveyor.yml ├── .coveragerc ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .pydocstyle.ini ├── .pylint.ini ├── .scrutinizer.yml ├── .tool-versions ├── .verchew.ini ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── bin ├── checksum ├── open ├── update ├── verchew └── verchew-wrapper ├── docs ├── about │ ├── changelog.md │ ├── contributing.md │ └── license.md └── advanced.md ├── mkdocs.yml ├── notebooks └── profile_default │ ├── .gitignore │ ├── ipython_config.py │ └── startup │ ├── README │ └── ipython_startup.py ├── pyproject.toml ├── scent.py ├── tests ├── __init__.py ├── conftest.py └── test_cli.py └── {{cookiecutter.package_name}} ├── __init__.py ├── __main__.py ├── cli.py ├── gui.py ├── tests ├── __init__.py ├── conftest.py └── test_utils.py └── utils.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jacebrowning 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ['3.11'] 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | 21 | - uses: Gr1N/setup-poetry@v8 22 | 23 | - name: Check dependencies 24 | run: make doctor 25 | 26 | - uses: actions/cache@v4 27 | with: 28 | path: .venv 29 | key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} 30 | 31 | - name: Install dependencies 32 | run: make install 33 | 34 | - name: Build and test 35 | run: make all 36 | 37 | - name: Publish the demo 38 | if: github.ref_name == 'main' && github.event_name != 'pull_request' 39 | env: 40 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 41 | run: | 42 | 43 | # Clone the existing live demo 44 | git clone https://github.com/jacebrowning/template-python-demo temp 45 | mv temp/.git TemplateDemo/.git 46 | cd TemplateDemo 47 | 48 | # Configure Git with GHA information, for more info see: 49 | # https://github.com/actions/checkout?tab=readme-ov-file#push-a-commit-using-the-built-in-token 50 | git config user.name "github-actions[bot]" 51 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 52 | 53 | # Rebuild the repository from the generated files and push to GitHub 54 | git add --all 55 | git commit -m "Deploy GHA build ${{ github.run_id }} to GitHub" 56 | git push -f https://${GH_TOKEN}@github.com/jacebrowning/template-python-demo main 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Virtual environment 2 | /.venv/ 3 | 4 | # Temporary Python files 5 | *.pyc 6 | __pycache__/ 7 | 8 | # Generated project 9 | /TemplateDemo/ 10 | 11 | .codegpt -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | {{cookiecutter.project_name}}/.tool-versions -------------------------------------------------------------------------------- /.verchew.ini: -------------------------------------------------------------------------------- 1 | [Make] 2 | 3 | cli = make 4 | version = GNU Make 5 | 6 | [Python] 7 | 8 | cli = python 9 | version = 3 10 | 11 | [Poetry] 12 | 13 | cli = poetry 14 | version = 2 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "choco", 4 | "cookiecutter", 5 | "coveragerc", 6 | "endraw", 7 | "pync", 8 | "USERPROFILE", 9 | "verchew", 10 | "virtualenvs" 11 | ], 12 | "python.formatting.provider": "black" 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCE_FILES = Makefile cookiecutter.json {{cookiecutter.project_name}}/* {{cookiecutter.project_name}}/*/* 2 | GENERATED_PROJECT := TemplateDemo 3 | 4 | ENV := .venv 5 | 6 | # MAIN ######################################################################## 7 | 8 | .PHONY: all 9 | all: build 10 | make all -C $(GENERATED_PROJECT) 11 | 12 | .PHONY: dev 13 | dev: install 14 | poetry run sniffer 15 | 16 | # DEPENDENCIES ################################################################ 17 | 18 | .PHONY: bootstrap 19 | bootstrap: 20 | asdf plugin add python || asdf plugin update python 21 | asdf plugin add poetry || asdf plugin update poetry 22 | asdf install 23 | 24 | .PHONY: doctor 25 | doctor: 26 | {{cookiecutter.project_name}}/bin/verchew 27 | 28 | .PHONY: install 29 | install: $(ENV) 30 | $(ENV): poetry.lock 31 | @ poetry config virtualenvs.in-project true 32 | ifdef CI 33 | poetry install --no-root --only=main 34 | else 35 | poetry install --no-root 36 | endif 37 | @ touch $@ 38 | 39 | ifndef CI 40 | poetry.lock: pyproject.toml 41 | poetry lock 42 | @ touch $@ 43 | endif 44 | 45 | # BUILD ####################################################################### 46 | 47 | .PHONY: build 48 | build: install $(GENERATED_PROJECT) 49 | $(GENERATED_PROJECT): $(SOURCE_FILES) 50 | cat cookiecutter.json 51 | poetry run cookiecutter . --no-input --overwrite-if-exists 52 | ifndef CI 53 | mkdir -p $(GENERATED_PROJECT)/.git 54 | echo '[remote "origin"]\nurl = https://github.com/jacebrowning/template-python-demo' > $(GENERATED_PROJECT)/.git/config 55 | endif 56 | cd $(GENERATED_PROJECT) && poetry lock 57 | @ touch $(GENERATED_PROJECT) 58 | 59 | # CLEANUP ##################################################################### 60 | 61 | .PHONY: clean 62 | clean: 63 | rm -rf $(GENERATED_PROJECT) 64 | rm -rf $(ENV) 65 | 66 | .DEFAULT_GOAL := install 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jace's Python Template 2 | 3 | This is a [cookiecutter](https://github.com/audreyr/cookiecutter) template for a typical Python library following modern packaging conventions. It utilizes popular libraries alongside Make and Graphviz to fully automate all development and deployment tasks. Check out the live demo: [jacebrowning/template-python-demo](https://github.com/jacebrowning/template-python-demo) 4 | 5 | [![Build Status](https://github.com/jacebrowning/template-python/actions/workflows/main.yml/badge.svg)](https://github.com/jacebrowning/template-python/actions/workflows/main.yml) 6 | 7 | ## Features 8 | 9 | * Preconfigured setup for CI, coverage, and analysis services 10 | * `pyproject.toml` for managing dependencies and package metadata 11 | * `Makefile` for automating common [development tasks](https://github.com/jacebrowning/template-python/blob/main/%7B%7Bcookiecutter.project_name%7D%7D/CONTRIBUTING.md): 12 | - Installing dependencies with `poetry` 13 | - Automatic formatting with `isort` and `black` 14 | - Static analysis with `pylint` 15 | - Type checking with `mypy` 16 | - Docstring styling with `pydocstyle` 17 | - Running tests with `pytest` 18 | - Building documentation with `mkdocs` 19 | - Publishing to PyPI using `poetry` 20 | * Tooling to launch an IPython session with automatic reloading enabled 21 | 22 | If you are instead looking for a [Python application](https://caremad.io/posts/2013/07/setup-vs-requirement/) template, check out one of the sibling projects: 23 | 24 | * [jacebrowning/template-django](https://github.com/jacebrowning/template-django) 25 | * [jacebrowning/template-flask](https://github.com/jacebrowning/template-flask) 26 | 27 | ## Examples 28 | 29 | Here are a few sample projects based on this template: 30 | 31 | * [jacebrowning/minilog](https://github.com/jacebrowning/minilog) 32 | * [theovoss/Chess](https://github.com/theovoss/Chess) 33 | * [sprout42/StarStruct](https://github.com/sprout42/StarStruct) 34 | * [MichiganLabs/flask-gcm](https://github.com/MichiganLabs/flask-gcm) 35 | * [flask-restful/flask-restful](https://github.com/flask-restful/flask-restful) 36 | 37 | ## Usage 38 | 39 | Install `cookiecutter` and generate a project: 40 | 41 | ``` 42 | $ pip install cookiecutter 43 | $ cookiecutter gh:jacebrowning/template-python -f 44 | ``` 45 | 46 | Cookiecutter will ask you for some basic info (your name, project name, python package name, etc.) and generate a base Python project for you. 47 | Once created, run the code formatter to updates files based on your chosen names: 48 | 49 | ``` 50 | $ cd 51 | $ make format 52 | ``` 53 | 54 | Finally, commit all files generated by this template. 55 | 56 | ## Updates 57 | 58 | Run the update tool, which is generated inside each project: 59 | 60 | ``` 61 | $ bin/update 62 | ``` 63 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "Your Name", 3 | "email": "you@yourdomain.com", 4 | "github_username": "jacebrowning", 5 | "github_repo": "template-python-demo", 6 | "default_branch": "main", 7 | "project_name": "TemplateDemo", 8 | "package_name": "demo", 9 | "project_short_description": "Sample project generated from Jace's Python Template.", 10 | "python_major_version": 3, 11 | "python_minor_version": 11, 12 | "license": ["MIT", "Apache-2.0", "AGPL-3.0-only", "Unlicense"] 13 | } 14 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "arrow" 5 | version = "1.2.3" 6 | description = "Better dates & times for Python" 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, 11 | {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, 12 | ] 13 | 14 | [package.dependencies] 15 | python-dateutil = ">=2.7.0" 16 | 17 | [[package]] 18 | name = "binaryornot" 19 | version = "0.4.4" 20 | description = "Ultra-lightweight pure Python package to check if a file is binary or text." 21 | optional = false 22 | python-versions = "*" 23 | files = [ 24 | {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, 25 | {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, 26 | ] 27 | 28 | [package.dependencies] 29 | chardet = ">=3.0.2" 30 | 31 | [[package]] 32 | name = "black" 33 | version = "23.1.0" 34 | description = "The uncompromising code formatter." 35 | optional = false 36 | python-versions = ">=3.7" 37 | files = [ 38 | {file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"}, 39 | {file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"}, 40 | {file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"}, 41 | {file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"}, 42 | {file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"}, 43 | {file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"}, 44 | {file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"}, 45 | {file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"}, 46 | {file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"}, 47 | {file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"}, 48 | {file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"}, 49 | {file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"}, 50 | {file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"}, 51 | {file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"}, 52 | {file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"}, 53 | {file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"}, 54 | {file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"}, 55 | {file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"}, 56 | {file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"}, 57 | {file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"}, 58 | {file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"}, 59 | {file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"}, 60 | {file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"}, 61 | {file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"}, 62 | {file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"}, 63 | ] 64 | 65 | [package.dependencies] 66 | click = ">=8.0.0" 67 | mypy-extensions = ">=0.4.3" 68 | packaging = ">=22.0" 69 | pathspec = ">=0.9.0" 70 | platformdirs = ">=2" 71 | 72 | [package.extras] 73 | colorama = ["colorama (>=0.4.3)"] 74 | d = ["aiohttp (>=3.7.4)"] 75 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 76 | uvloop = ["uvloop (>=0.15.2)"] 77 | 78 | [[package]] 79 | name = "certifi" 80 | version = "2022.12.7" 81 | description = "Python package for providing Mozilla's CA Bundle." 82 | optional = false 83 | python-versions = ">=3.6" 84 | files = [ 85 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 86 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 87 | ] 88 | 89 | [[package]] 90 | name = "chardet" 91 | version = "5.1.0" 92 | description = "Universal encoding detector for Python 3" 93 | optional = false 94 | python-versions = ">=3.7" 95 | files = [ 96 | {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, 97 | {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, 98 | ] 99 | 100 | [[package]] 101 | name = "charset-normalizer" 102 | version = "3.0.1" 103 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 104 | optional = false 105 | python-versions = "*" 106 | files = [ 107 | {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, 108 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, 109 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, 110 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, 111 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, 112 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, 113 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, 114 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, 115 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, 116 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, 117 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, 118 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, 119 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, 120 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, 121 | {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, 122 | {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, 123 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, 124 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, 125 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, 126 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, 127 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, 128 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, 129 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, 130 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, 131 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, 132 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, 133 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, 134 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, 135 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, 136 | {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, 137 | {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, 138 | {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, 139 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, 140 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, 141 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, 142 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, 143 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, 144 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, 145 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, 146 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, 147 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, 148 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, 149 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, 150 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, 151 | {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, 152 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, 153 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, 154 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, 155 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, 156 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, 157 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, 158 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, 159 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, 160 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, 161 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, 162 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, 163 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, 164 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, 165 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, 166 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, 167 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, 168 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, 169 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, 170 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, 171 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, 172 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, 173 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, 174 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, 175 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, 176 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, 177 | {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, 178 | {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, 179 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, 180 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, 181 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, 182 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, 183 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, 184 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, 185 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, 186 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, 187 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, 188 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, 189 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, 190 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, 191 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, 192 | {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, 193 | {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, 194 | {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, 195 | ] 196 | 197 | [[package]] 198 | name = "click" 199 | version = "8.1.3" 200 | description = "Composable command line interface toolkit" 201 | optional = false 202 | python-versions = ">=3.7" 203 | files = [ 204 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 205 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 206 | ] 207 | 208 | [package.dependencies] 209 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 210 | 211 | [[package]] 212 | name = "colorama" 213 | version = "0.4.6" 214 | description = "Cross-platform colored terminal text." 215 | optional = false 216 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 217 | files = [ 218 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 219 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 220 | ] 221 | 222 | [[package]] 223 | name = "cookiecutter" 224 | version = "2.6.0" 225 | description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." 226 | optional = false 227 | python-versions = ">=3.7" 228 | files = [ 229 | {file = "cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d"}, 230 | {file = "cookiecutter-2.6.0.tar.gz", hash = "sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c"}, 231 | ] 232 | 233 | [package.dependencies] 234 | arrow = "*" 235 | binaryornot = ">=0.4.4" 236 | click = ">=7.0,<9.0.0" 237 | Jinja2 = ">=2.7,<4.0.0" 238 | python-slugify = ">=4.0.0" 239 | pyyaml = ">=5.3.1" 240 | requests = ">=2.23.0" 241 | rich = "*" 242 | 243 | [[package]] 244 | name = "idna" 245 | version = "3.4" 246 | description = "Internationalized Domain Names in Applications (IDNA)" 247 | optional = false 248 | python-versions = ">=3.5" 249 | files = [ 250 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 251 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 252 | ] 253 | 254 | [[package]] 255 | name = "jinja2" 256 | version = "3.1.2" 257 | description = "A very fast and expressive template engine." 258 | optional = false 259 | python-versions = ">=3.7" 260 | files = [ 261 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 262 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 263 | ] 264 | 265 | [package.dependencies] 266 | MarkupSafe = ">=2.0" 267 | 268 | [package.extras] 269 | i18n = ["Babel (>=2.7)"] 270 | 271 | [[package]] 272 | name = "macfsevents" 273 | version = "0.8.1" 274 | description = "Thread-based interface to file system observation primitives." 275 | optional = false 276 | python-versions = "*" 277 | files = [ 278 | {file = "MacFSEvents-0.8.1.tar.gz", hash = "sha256:1324b66b356051de662ba87d84f73ada062acd42b047ed1246e60a449f833e10"}, 279 | ] 280 | 281 | [[package]] 282 | name = "markdown-it-py" 283 | version = "3.0.0" 284 | description = "Python port of markdown-it. Markdown parsing, done right!" 285 | optional = false 286 | python-versions = ">=3.8" 287 | files = [ 288 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 289 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 290 | ] 291 | 292 | [package.dependencies] 293 | mdurl = ">=0.1,<1.0" 294 | 295 | [package.extras] 296 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 297 | code-style = ["pre-commit (>=3.0,<4.0)"] 298 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 299 | linkify = ["linkify-it-py (>=1,<3)"] 300 | plugins = ["mdit-py-plugins"] 301 | profiling = ["gprof2dot"] 302 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 303 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 304 | 305 | [[package]] 306 | name = "markupsafe" 307 | version = "2.1.2" 308 | description = "Safely add untrusted strings to HTML/XML markup." 309 | optional = false 310 | python-versions = ">=3.7" 311 | files = [ 312 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, 313 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, 314 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, 315 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, 316 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, 317 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, 318 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, 319 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, 320 | {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, 321 | {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, 322 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, 323 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, 324 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, 325 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, 326 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, 327 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, 328 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, 329 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, 330 | {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, 331 | {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, 332 | {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, 333 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, 334 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, 335 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, 336 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, 337 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, 338 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, 339 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, 340 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, 341 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, 342 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, 343 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, 344 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, 345 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, 346 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, 347 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, 348 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, 349 | {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, 350 | {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, 351 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, 352 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, 353 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, 354 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, 355 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, 356 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, 357 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, 358 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, 359 | {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, 360 | {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, 361 | {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, 362 | ] 363 | 364 | [[package]] 365 | name = "mdurl" 366 | version = "0.1.2" 367 | description = "Markdown URL utilities" 368 | optional = false 369 | python-versions = ">=3.7" 370 | files = [ 371 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 372 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 373 | ] 374 | 375 | [[package]] 376 | name = "mypy-extensions" 377 | version = "1.0.0" 378 | description = "Type system extensions for programs checked with the mypy type checker." 379 | optional = false 380 | python-versions = ">=3.5" 381 | files = [ 382 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 383 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 384 | ] 385 | 386 | [[package]] 387 | name = "nose" 388 | version = "1.3.7" 389 | description = "nose extends unittest to make testing easier" 390 | optional = false 391 | python-versions = "*" 392 | files = [ 393 | {file = "nose-1.3.7-py2-none-any.whl", hash = "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a"}, 394 | {file = "nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac"}, 395 | {file = "nose-1.3.7.tar.gz", hash = "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"}, 396 | ] 397 | 398 | [[package]] 399 | name = "packaging" 400 | version = "23.0" 401 | description = "Core utilities for Python packages" 402 | optional = false 403 | python-versions = ">=3.7" 404 | files = [ 405 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 406 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 407 | ] 408 | 409 | [[package]] 410 | name = "pathspec" 411 | version = "0.11.0" 412 | description = "Utility library for gitignore style pattern matching of file paths." 413 | optional = false 414 | python-versions = ">=3.7" 415 | files = [ 416 | {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, 417 | {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, 418 | ] 419 | 420 | [[package]] 421 | name = "platformdirs" 422 | version = "3.1.0" 423 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 424 | optional = false 425 | python-versions = ">=3.7" 426 | files = [ 427 | {file = "platformdirs-3.1.0-py3-none-any.whl", hash = "sha256:13b08a53ed71021350c9e300d4ea8668438fb0046ab3937ac9a29913a1a1350a"}, 428 | {file = "platformdirs-3.1.0.tar.gz", hash = "sha256:accc3665857288317f32c7bebb5a8e482ba717b474f3fc1d18ca7f9214be0cef"}, 429 | ] 430 | 431 | [package.extras] 432 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] 433 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 434 | 435 | [[package]] 436 | name = "pygments" 437 | version = "2.18.0" 438 | description = "Pygments is a syntax highlighting package written in Python." 439 | optional = false 440 | python-versions = ">=3.8" 441 | files = [ 442 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, 443 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, 444 | ] 445 | 446 | [package.extras] 447 | windows-terminal = ["colorama (>=0.4.6)"] 448 | 449 | [[package]] 450 | name = "python-dateutil" 451 | version = "2.8.2" 452 | description = "Extensions to the standard Python datetime module" 453 | optional = false 454 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 455 | files = [ 456 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 457 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 458 | ] 459 | 460 | [package.dependencies] 461 | six = ">=1.5" 462 | 463 | [[package]] 464 | name = "python-slugify" 465 | version = "8.0.1" 466 | description = "A Python slugify application that also handles Unicode" 467 | optional = false 468 | python-versions = ">=3.7" 469 | files = [ 470 | {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"}, 471 | {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"}, 472 | ] 473 | 474 | [package.dependencies] 475 | text-unidecode = ">=1.3" 476 | 477 | [package.extras] 478 | unidecode = ["Unidecode (>=1.1.1)"] 479 | 480 | [[package]] 481 | name = "python-termstyle" 482 | version = "0.1.10" 483 | description = "console colouring for python" 484 | optional = false 485 | python-versions = "*" 486 | files = [ 487 | {file = "python-termstyle-0.1.10.tar.gz", hash = "sha256:f42a6bb16fbfc5e2c66d553e7ad46524ea833872f75ee5d827c15115fafc94e2"}, 488 | {file = "python-termstyle-0.1.10.tgz", hash = "sha256:6faf42ba42f2826c38cf70dacb3ac51f248a418e48afc0e36593df11cf3ab1d2"}, 489 | ] 490 | 491 | [package.dependencies] 492 | setuptools = "*" 493 | 494 | [[package]] 495 | name = "pyyaml" 496 | version = "6.0.2" 497 | description = "YAML parser and emitter for Python" 498 | optional = false 499 | python-versions = ">=3.8" 500 | files = [ 501 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 502 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 503 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 504 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 505 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 506 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 507 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 508 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 509 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 510 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 511 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 512 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 513 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 514 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 515 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 516 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 517 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 518 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 519 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 520 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 521 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 522 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 523 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 524 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 525 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 526 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 527 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 528 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 529 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 530 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 531 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 532 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 533 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 534 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 535 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 536 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 537 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 538 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 539 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 540 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 541 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 542 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 543 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 544 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 545 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 546 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 547 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 548 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 549 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 550 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 551 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 552 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 553 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 554 | ] 555 | 556 | [[package]] 557 | name = "requests" 558 | version = "2.28.2" 559 | description = "Python HTTP for Humans." 560 | optional = false 561 | python-versions = ">=3.7, <4" 562 | files = [ 563 | {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, 564 | {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, 565 | ] 566 | 567 | [package.dependencies] 568 | certifi = ">=2017.4.17" 569 | charset-normalizer = ">=2,<4" 570 | idna = ">=2.5,<4" 571 | urllib3 = ">=1.21.1,<1.27" 572 | 573 | [package.extras] 574 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 575 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 576 | 577 | [[package]] 578 | name = "rich" 579 | version = "13.9.4" 580 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 581 | optional = false 582 | python-versions = ">=3.8.0" 583 | files = [ 584 | {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, 585 | {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, 586 | ] 587 | 588 | [package.dependencies] 589 | markdown-it-py = ">=2.2.0" 590 | pygments = ">=2.13.0,<3.0.0" 591 | 592 | [package.extras] 593 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 594 | 595 | [[package]] 596 | name = "setuptools" 597 | version = "67.4.0" 598 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 599 | optional = false 600 | python-versions = ">=3.7" 601 | files = [ 602 | {file = "setuptools-67.4.0-py3-none-any.whl", hash = "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"}, 603 | {file = "setuptools-67.4.0.tar.gz", hash = "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330"}, 604 | ] 605 | 606 | [package.extras] 607 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 608 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 609 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 610 | 611 | [[package]] 612 | name = "six" 613 | version = "1.16.0" 614 | description = "Python 2 and 3 compatibility utilities" 615 | optional = false 616 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 617 | files = [ 618 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 619 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 620 | ] 621 | 622 | [[package]] 623 | name = "sniffer" 624 | version = "0.4.1" 625 | description = "An automatic test runner. Supports nose out of the box." 626 | optional = false 627 | python-versions = "*" 628 | files = [ 629 | {file = "sniffer-0.4.1-py2.py3-none-any.whl", hash = "sha256:f120843fe152d0e380402fc11313b151e2044c47fdd36895de2efedc8624dbb8"}, 630 | {file = "sniffer-0.4.1.tar.gz", hash = "sha256:b37665053fb83d7790bf9e51d616c11970863d14b5ea5a51155a4e95759d1529"}, 631 | ] 632 | 633 | [package.dependencies] 634 | colorama = "*" 635 | nose = "*" 636 | python-termstyle = "*" 637 | 638 | [package.extras] 639 | growl = ["gntp (==0.7)"] 640 | libnotify = ["py-notify (==0.3.1)"] 641 | linux = ["pyinotify (==0.9.0)"] 642 | osx = ["MacFSEvents (==0.2.8)"] 643 | 644 | [[package]] 645 | name = "text-unidecode" 646 | version = "1.3" 647 | description = "The most basic Text::Unidecode port" 648 | optional = false 649 | python-versions = "*" 650 | files = [ 651 | {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, 652 | {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, 653 | ] 654 | 655 | [[package]] 656 | name = "urllib3" 657 | version = "1.26.14" 658 | description = "HTTP library with thread-safe connection pooling, file post, and more." 659 | optional = false 660 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 661 | files = [ 662 | {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, 663 | {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, 664 | ] 665 | 666 | [package.extras] 667 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 668 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 669 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 670 | 671 | [metadata] 672 | lock-version = "2.0" 673 | python-versions = "^3.11" 674 | content-hash = "0cdb37d2eb07cfec7671468422881a1f3b8c66bfe290061c3e766495c2482b43" 675 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | 3 | name = "template-python" 4 | version = "1.0" 5 | description = "A template for new Python libraries." 6 | authors = ["Jace Browning "] 7 | 8 | [tool.poetry.dependencies] 9 | 10 | python = "^3.11" 11 | 12 | cookiecutter = "^2.6.0" 13 | 14 | black = "^23.1" 15 | sniffer = "*" 16 | MacFSEvents = { version = "*", platform = "darwin" } 17 | -------------------------------------------------------------------------------- /scent.py: -------------------------------------------------------------------------------- 1 | """Configuration file for sniffer.""" 2 | 3 | import time 4 | import subprocess 5 | 6 | from sniffer.api import select_runnable, file_validator, runnable 7 | 8 | try: 9 | from pync import Notifier 10 | except ImportError: 11 | notify = None 12 | else: 13 | notify = Notifier.notify 14 | 15 | 16 | watch_paths = ["."] 17 | 18 | 19 | @select_runnable("python") 20 | @file_validator 21 | def py_files(filename): 22 | return "TemplateDemo" not in filename 23 | 24 | 25 | @runnable 26 | def python(*_): 27 | group = int(time.time()) # unique per run 28 | 29 | for count, (command, title) in enumerate( 30 | ( 31 | (("make", "build"), "Generate Sample"), 32 | (("make", "all"), "Test Sample"), 33 | ), 34 | start=1, 35 | ): 36 | print("") 37 | print("$ %s" % " ".join(command)) 38 | failure = subprocess.call(command) 39 | 40 | if failure: 41 | if notify and title: 42 | mark = "❌" * count 43 | notify(mark + " [FAIL] " + mark, title=title, group=group) 44 | return False 45 | else: 46 | if notify and title: 47 | mark = "✅" * count 48 | notify(mark + " [PASS] " + mark, title=title, group=group) 49 | 50 | return True 51 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | environment: 4 | global: 5 | RANDOM_SEED: 0 6 | matrix: 7 | - PYTHON_MAJOR: {{cookiecutter.python_major_version}} 8 | PYTHON_MINOR: {{cookiecutter.python_minor_version}} 9 | 10 | cache: 11 | - .venv -> poetry.lock 12 | 13 | install: 14 | # Add Python to the PATH 15 | - set PATH=C:\Python%PYTHON_MAJOR%%PYTHON_MINOR%;%PATH% 16 | - set PATH=C:\Python%PYTHON_MAJOR%%PYTHON_MINOR%\Scripts;%PATH% 17 | # Install system dependencies 18 | - choco install make 19 | - curl -sSL https://install.python-poetry.org | python - 20 | - set PATH=%USERPROFILE%\AppData\Roaming\Python\Scripts;%PATH% 21 | - make doctor 22 | # Install project dependencies 23 | - make install 24 | 25 | build: off 26 | 27 | test_script: 28 | - make check 29 | - make test 30 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | 3 | branch = true 4 | 5 | data_file = .cache/coverage 6 | 7 | omit = 8 | .venv/* 9 | */tests/* 10 | */__main__.py 11 | 12 | [report] 13 | 14 | exclude_lines = 15 | pragma: no cover 16 | raise NotImplementedError 17 | except DistributionNotFound 18 | TYPE_CHECKING 19 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | CHANGELOG.md merge=union 3 | poetry.lock merge=binary 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ['{{cookiecutter.python_major_version}}.{{cookiecutter.python_minor_version}}'] 12 | 13 | {%- raw %} 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - uses: Gr1N/setup-poetry@v8 24 | 25 | - name: Check dependencies 26 | run: make doctor 27 | 28 | - uses: actions/cache@v4 29 | with: 30 | path: .venv 31 | key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} 32 | 33 | - name: Install dependencies 34 | run: make install 35 | 36 | - name: Check code 37 | run: make check 38 | 39 | - name: Test code 40 | run: make test 41 | 42 | - name: Upload coverage 43 | uses: codecov/codecov-action@v4 44 | if: steps.fork-check.outputs.is-fork == 'false' 45 | with: 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | fail_ci_if_error: true 48 | 49 | {%- endraw %} 50 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary Python files 2 | *.pyc 3 | *.egg-info/ 4 | __pycache__/ 5 | .ipynb_checkpoints/ 6 | setup.py 7 | pip-wheel-metadata/ 8 | 9 | # Temporary OS files 10 | Icon* 11 | 12 | # Temporary virtual environment files 13 | /.cache/ 14 | /.venv/ 15 | tmp/ 16 | 17 | # Temporary server files 18 | .env 19 | *.pid 20 | 21 | # Generated documentation 22 | /docs/gen/ 23 | /docs/apidocs/ 24 | /site/ 25 | /*.html 26 | /docs/*.png 27 | 28 | # Google Drive 29 | *.gdoc 30 | *.gsheet 31 | *.gslides 32 | *.gdraw 33 | 34 | # Testing and coverage results 35 | /.coverage 36 | /.coverage.* 37 | /htmlcov/ 38 | /prof/ 39 | coverage.xml 40 | 41 | # Build and release directories 42 | /build/ 43 | /dist/ 44 | *.spec 45 | 46 | # Sublime Text 47 | *.sublime-workspace 48 | 49 | # Eclipse 50 | .settings 51 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.pydocstyle.ini: -------------------------------------------------------------------------------- 1 | [pydocstyle] 2 | 3 | # D211: No blank lines allowed before class docstring 4 | add_select = D211 5 | 6 | # D100: Missing docstring in public module 7 | # D101: Missing docstring in public class 8 | # D102: Missing docstring in public method 9 | # D103: Missing docstring in public function 10 | # D104: Missing docstring in public package 11 | # D105: Missing docstring in magic method 12 | # D107: Missing docstring in __init__ 13 | # D202: No blank lines allowed after function docstring 14 | add_ignore = D100,D101,D102,D103,D104,D105,D107,D202 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.pylint.ini: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. 21 | jobs=0 22 | 23 | # List of plugins (as comma separated values of python modules names) to load, 24 | # usually to register additional checkers. 25 | load-plugins=pylint_pytest 26 | 27 | # Pickle collected data for later comparisons. 28 | persistent=yes 29 | 30 | # Specify a configuration file. 31 | #rcfile= 32 | 33 | # Allow loading of arbitrary C extensions. Extensions are imported into the 34 | # active Python interpreter and may run arbitrary code. 35 | unsafe-load-any-extension=no 36 | 37 | 38 | [MESSAGES CONTROL] 39 | 40 | # Only show warnings with the listed confidence levels. Leave empty to show 41 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 42 | confidence= 43 | 44 | # Disable the message, report, category or checker with the given id(s). You 45 | # can either give multiple identifiers separated by comma (,) or put this 46 | # option multiple times (only on the command line, not in the configuration 47 | # file where it should appear only once).You can also use "--disable=all" to 48 | # disable everything first and then reenable specific checks. For example, if 49 | # you want to run only the similarities checker, you can use "--disable=all 50 | # --enable=similarities". If you want to run only the classes checker, but have 51 | # no Warning level messages displayed, use"--disable=all --enable=classes 52 | # --disable=W" 53 | disable= 54 | fixme, 55 | global-statement, 56 | invalid-name, 57 | missing-docstring, 58 | redefined-outer-name, 59 | too-few-public-methods, 60 | too-many-locals, 61 | too-many-arguments, 62 | unnecessary-pass, 63 | broad-except, 64 | duplicate-code, 65 | too-many-branches, 66 | too-many-return-statements, 67 | too-many-public-methods, 68 | too-many-ancestors, 69 | too-many-instance-attributes, 70 | too-many-statements, 71 | attribute-defined-outside-init, 72 | unsupported-assignment-operation, 73 | unsupported-delete-operation, 74 | too-many-nested-blocks, 75 | protected-access, 76 | 77 | # Enable the message, report, category or checker with the given id(s). You can 78 | # either give multiple identifier separated by comma (,) or put this option 79 | # multiple time (only on the command line, not in the configuration file where 80 | # it should appear only once). See also the "--disable" option for examples. 81 | enable= 82 | 83 | 84 | [REPORTS] 85 | 86 | # Python expression which should return a note less than 10 (10 is the highest 87 | # note). You have access to the variables errors warning, statement which 88 | # respectively contain the number of errors / warnings messages and the total 89 | # number of statements analyzed. This is used by the global evaluation report 90 | # (RP0004). 91 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 92 | 93 | # Template used to display messages. This is a python new-style format string 94 | # used to format the message information. See doc for all details 95 | #msg-template= 96 | 97 | # Set the output format. Available formats are text, parseable, colorized, json 98 | # and msvs (visual studio).You can also give a reporter class, eg 99 | # mypackage.mymodule.MyReporterClass. 100 | output-format=text 101 | 102 | # Tells whether to display a full report or only the messages 103 | reports=no 104 | 105 | # Activate the evaluation score. 106 | score=no 107 | 108 | 109 | [REFACTORING] 110 | 111 | # Maximum number of nested blocks for function / method body 112 | max-nested-blocks=5 113 | 114 | 115 | [BASIC] 116 | 117 | # Regular expression matching correct argument names 118 | argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 119 | 120 | # Regular expression matching correct attribute names 121 | attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 122 | 123 | # Bad variable names which should always be refused, separated by a comma 124 | bad-names=foo,bar,baz,toto,tutu,tata 125 | 126 | # Regular expression matching correct class attribute names 127 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 128 | 129 | # Regular expression matching correct class names 130 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 131 | 132 | # Regular expression matching correct constant names 133 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 134 | 135 | # Minimum line length for functions/classes that require docstrings, shorter 136 | # ones are exempt. 137 | docstring-min-length=-1 138 | 139 | # Regular expression matching correct function names 140 | function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 141 | 142 | # Good variable names which should always be accepted, separated by a comma 143 | good-names=i,j,k,ex,Run,_ 144 | 145 | # Include a hint for the correct naming format with invalid-name 146 | include-naming-hint=no 147 | 148 | # Regular expression matching correct inline iteration names 149 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 150 | 151 | # Regular expression matching correct method names 152 | method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 153 | 154 | # Regular expression matching correct module names 155 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 156 | 157 | # Colon-delimited sets of names that determine each other's naming style when 158 | # the name regexes allow several styles. 159 | name-group= 160 | 161 | # Regular expression which should only match function or class names that do 162 | # not require a docstring. 163 | no-docstring-rgx=^_ 164 | 165 | # List of decorators that produce properties, such as abc.abstractproperty. Add 166 | # to this list to register other decorators that produce valid properties. 167 | property-classes=abc.abstractproperty 168 | 169 | # Regular expression matching correct variable names 170 | variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 171 | 172 | 173 | [FORMAT] 174 | 175 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 176 | expected-line-ending-format= 177 | 178 | # Regexp for a line that is allowed to be longer than the limit. 179 | ignore-long-lines=^.*((https?:)|(pragma:)|(TODO:)).*$ 180 | 181 | # Number of spaces of indent required inside a hanging or continued line. 182 | indent-after-paren=4 183 | 184 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 185 | # tab). 186 | indent-string=' ' 187 | 188 | # Maximum number of characters on a single line. 189 | max-line-length=88 190 | 191 | # Maximum number of lines in a module 192 | max-module-lines=1000 193 | 194 | # Allow the body of a class to be on the same line as the declaration if body 195 | # contains single statement. 196 | single-line-class-stmt=no 197 | 198 | # Allow the body of an if to be on the same line as the test if there is no 199 | # else. 200 | single-line-if-stmt=no 201 | 202 | 203 | [LOGGING] 204 | 205 | # Logging modules to check that the string format arguments are in logging 206 | # function parameter format 207 | logging-modules=logging 208 | 209 | 210 | [MISCELLANEOUS] 211 | 212 | # List of note tags to take in consideration, separated by a comma. 213 | notes=FIXME,XXX,TODO 214 | 215 | 216 | [SIMILARITIES] 217 | 218 | # Ignore comments when computing similarities. 219 | ignore-comments=yes 220 | 221 | # Ignore docstrings when computing similarities. 222 | ignore-docstrings=yes 223 | 224 | # Ignore imports when computing similarities. 225 | ignore-imports=no 226 | 227 | # Minimum lines number of a similarity. 228 | min-similarity-lines=4 229 | 230 | 231 | [SPELLING] 232 | 233 | # Spelling dictionary name. Available dictionaries: none. To make it working 234 | # install python-enchant package. 235 | spelling-dict= 236 | 237 | # List of comma separated words that should not be checked. 238 | spelling-ignore-words= 239 | 240 | # A path to a file that contains private dictionary; one word per line. 241 | spelling-private-dict-file= 242 | 243 | # Tells whether to store unknown words to indicated private dictionary in 244 | # --spelling-private-dict-file option instead of raising a message. 245 | spelling-store-unknown-words=no 246 | 247 | 248 | [TYPECHECK] 249 | 250 | # List of decorators that produce context managers, such as 251 | # contextlib.contextmanager. Add to this list to register other decorators that 252 | # produce valid context managers. 253 | contextmanager-decorators=contextlib.contextmanager 254 | 255 | # List of members which are set dynamically and missed by pylint inference 256 | # system, and so shouldn't trigger E1101 when accessed. Python regular 257 | # expressions are accepted. 258 | generated-members= 259 | 260 | # Tells whether missing members accessed in mixin class should be ignored. A 261 | # mixin class is detected if its name ends with "mixin" (case insensitive). 262 | ignore-mixin-members=yes 263 | 264 | # This flag controls whether pylint should warn about no-member and similar 265 | # checks whenever an opaque object is returned when inferring. The inference 266 | # can return multiple potential results while evaluating a Python object, but 267 | # some branches might not be evaluated, which results in partial inference. In 268 | # that case, it might be useful to still emit no-member and other checks for 269 | # the rest of the inferred objects. 270 | ignore-on-opaque-inference=yes 271 | 272 | # List of class names for which member attributes should not be checked (useful 273 | # for classes with dynamically set attributes). This supports the use of 274 | # qualified names. 275 | ignored-classes=optparse.Values,thread._local,_thread._local 276 | 277 | # List of module names for which member attributes should not be checked 278 | # (useful for modules/projects where namespaces are manipulated during runtime 279 | # and thus existing member attributes cannot be deduced by static analysis. It 280 | # supports qualified module names, as well as Unix pattern matching. 281 | ignored-modules= 282 | 283 | # Show a hint with possible names when a member name was not found. The aspect 284 | # of finding the hint is based on edit distance. 285 | missing-member-hint=yes 286 | 287 | # The minimum edit distance a name should have in order to be considered a 288 | # similar match for a missing member name. 289 | missing-member-hint-distance=1 290 | 291 | # The total number of similar names that should be taken in consideration when 292 | # showing a hint for a missing member. 293 | missing-member-max-choices=1 294 | 295 | 296 | [VARIABLES] 297 | 298 | # List of additional names supposed to be defined in builtins. Remember that 299 | # you should avoid to define new builtins when possible. 300 | additional-builtins= 301 | 302 | # Tells whether unused global variables should be treated as a violation. 303 | allow-global-unused-variables=yes 304 | 305 | # List of strings which can identify a callback function by name. A callback 306 | # name must start or end with one of those strings. 307 | callbacks=cb_,_cb 308 | 309 | # A regular expression matching the name of dummy variables (i.e. expectedly 310 | # not used). 311 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 312 | 313 | # Argument names that match this expression will be ignored. Default to name 314 | # with leading underscore 315 | ignored-argument-names=_.*|^ignored_|^unused_ 316 | 317 | # Tells whether we should check for unused import in __init__ files. 318 | init-import=no 319 | 320 | # List of qualified module names which can have objects that can redefine 321 | # builtins. 322 | redefining-builtins-modules=six.moves,future.builtins 323 | 324 | 325 | [CLASSES] 326 | 327 | # List of method names used to declare (i.e. assign) instance attributes. 328 | defining-attr-methods=__init__,__new__,setUp 329 | 330 | # List of member names, which should be excluded from the protected access 331 | # warning. 332 | exclude-protected=_asdict,_fields,_replace,_source,_make 333 | 334 | # List of valid names for the first argument in a class method. 335 | valid-classmethod-first-arg=cls 336 | 337 | # List of valid names for the first argument in a metaclass class method. 338 | valid-metaclass-classmethod-first-arg=mcs 339 | 340 | 341 | [DESIGN] 342 | 343 | # Maximum number of arguments for function / method 344 | max-args=5 345 | 346 | # Maximum number of attributes for a class (see R0902). 347 | max-attributes=7 348 | 349 | # Maximum number of boolean expressions in a if statement 350 | max-bool-expr=5 351 | 352 | # Maximum number of branch for function / method body 353 | max-branches=12 354 | 355 | # Maximum number of locals for function / method body 356 | max-locals=15 357 | 358 | # Maximum number of parents for a class (see R0901). 359 | max-parents=7 360 | 361 | # Maximum number of public methods for a class (see R0904). 362 | max-public-methods=20 363 | 364 | # Maximum number of return / yield for function / method body 365 | max-returns=6 366 | 367 | # Maximum number of statements in function / method body 368 | max-statements=50 369 | 370 | # Minimum number of public methods for a class (see R0903). 371 | min-public-methods=2 372 | 373 | 374 | [IMPORTS] 375 | 376 | # Allow wildcard imports from modules that define __all__. 377 | allow-wildcard-with-all=no 378 | 379 | # Analyse import fallback blocks. This can be used to support both Python 2 and 380 | # 3 compatible code, which means that the block might have code that exists 381 | # only in one or another interpreter, leading to false positives when analysed. 382 | analyse-fallback-blocks=no 383 | 384 | # Deprecated modules which should not be used, separated by a comma 385 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 386 | 387 | # Create a graph of external dependencies in the given file (report RP0402 must 388 | # not be disabled) 389 | ext-import-graph= 390 | 391 | # Create a graph of every (i.e. internal and external) dependencies in the 392 | # given file (report RP0402 must not be disabled) 393 | import-graph= 394 | 395 | # Create a graph of internal dependencies in the given file (report RP0402 must 396 | # not be disabled) 397 | int-import-graph= 398 | 399 | # Force import order to recognize a module as part of the standard 400 | # compatibility libraries. 401 | known-standard-library= 402 | 403 | # Force import order to recognize a module as part of a third party library. 404 | known-third-party=enchant 405 | 406 | 407 | [EXCEPTIONS] 408 | 409 | # Exceptions that will emit a warning when being caught. Defaults to 410 | # "Exception" 411 | overgeneral-exceptions=Exception 412 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | tests: 3 | override: 4 | - pylint-run --rcfile=.pylint.ini 5 | - py-scrutinizer-run 6 | checks: 7 | python: 8 | code_rating: true 9 | duplicate_code: true 10 | filter: 11 | excluded_paths: 12 | - "*/tests/*" 13 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.tool-versions: -------------------------------------------------------------------------------- 1 | python 3.11.11 2 | poetry 2.0.1 3 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.verchew.ini: -------------------------------------------------------------------------------- 1 | [Make] 2 | 3 | cli = make 4 | version = GNU Make 5 | 6 | [Python] 7 | 8 | cli = python 9 | version = 3 10 | 11 | [Poetry] 12 | 13 | cli = poetry 14 | version = 2 15 | 16 | [Graphviz] 17 | 18 | cli = dot 19 | cli_version_arg = -V 20 | version = 12 21 | optional = true 22 | message = This is only needed to generate UML diagrams for documentation. 23 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | ".cache/": true, 4 | ".venv/": true, 5 | "*.egg-info": true, 6 | "pip-wheel-metadata/": true, 7 | "**/__pycache__": true, 8 | "**/*.pyc": true, 9 | "**/.ipynb_checkpoints": true, 10 | "**/tmp/": true, 11 | "dist/": true, 12 | "htmlcov/": true, 13 | "notebooks/*.yml": true, 14 | "notebooks/files/": true, 15 | "notebooks/inventory/": true, 16 | "prof/": true, 17 | "site/": true, 18 | "geckodriver.log": true, 19 | "targets.log": true, 20 | "bin/verchew": true 21 | }, 22 | "editor.formatOnSave": true, 23 | "pylint.args": ["--rcfile=.pylint.ini"], 24 | "cSpell.words": [ 25 | "asdf", 26 | "builtins", 27 | "codecov", 28 | "codehilite", 29 | "choco", 30 | "cygstart", 31 | "cygwin", 32 | "dataclasses", 33 | "Graphviz", 34 | "ipython", 35 | "mkdocs", 36 | "noclasses", 37 | "pipx", 38 | "pyenv", 39 | "ruamel", 40 | "showfspath", 41 | "USERPROFILE", 42 | "venv", 43 | "verchew" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 0.0.0 (YYYY-MM-DD) 4 | 5 | - TBD 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Guide 2 | 3 | ## Setup 4 | 5 | ### Requirements 6 | 7 | * Make: 8 | - macOS: `$ xcode-select --install` 9 | - Linux: [https://www.gnu.org](https://www.gnu.org/software/make) 10 | - Windows: `$ choco install make` [https://chocolatey.org](https://chocolatey.org/install) 11 | * Python: `$ asdf install` (https://asdf-vm.com)[https://asdf-vm.com/guide/getting-started.html] 12 | * Poetry: [https://python-poetry.org](https://python-poetry.org/docs/#installation) 13 | * Graphviz: 14 | * macOS: `$ brew install graphviz` 15 | * Linux: [https://graphviz.org/download](https://graphviz.org/download/) 16 | * Windows: [https://graphviz.org/download](https://graphviz.org/download/) 17 | 18 | To confirm these system dependencies are configured correctly: 19 | 20 | ```text 21 | $ make bootstrap 22 | $ make doctor 23 | ``` 24 | 25 | ### Installation 26 | 27 | Install project dependencies into a virtual environment: 28 | 29 | ```text 30 | $ make install 31 | ``` 32 | 33 | ## Development Tasks 34 | 35 | ### Manual 36 | 37 | Run the tests: 38 | 39 | ```text 40 | $ make test 41 | ``` 42 | 43 | Run static analysis: 44 | 45 | ```text 46 | $ make check 47 | ``` 48 | 49 | Build the documentation: 50 | 51 | ```text 52 | $ make docs 53 | ``` 54 | 55 | ### Automatic 56 | 57 | Keep all of the above tasks running on change: 58 | 59 | ```text 60 | $ make dev 61 | ``` 62 | 63 | > In order to have OS X notifications, `brew install terminal-notifier`. 64 | 65 | ### Continuous Integration 66 | 67 | The CI server will report overall build status: 68 | 69 | ```text 70 | $ make all 71 | ``` 72 | 73 | ## Demo Tasks 74 | 75 | Run the program: 76 | 77 | ```text 78 | $ make run 79 | ``` 80 | 81 | Launch an IPython session: 82 | 83 | ```text 84 | $ make shell 85 | ``` 86 | 87 | ## Release Tasks 88 | 89 | Release to PyPI: 90 | 91 | ```text 92 | $ make upload 93 | ``` 94 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/LICENSE.md: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.license == "MIT" %} 2 | MIT License 3 | 4 | Copyright © 2022, {{cookiecutter.full_name}} 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | {% elif cookiecutter.license == "Apache-2.0" %} 24 | Apache License 25 | Version 2.0, January 2004 26 | http://www.apache.org/licenses/ 27 | 28 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 29 | 30 | 1. Definitions. 31 | 32 | "License" shall mean the terms and conditions for use, reproduction, 33 | and distribution as defined by Sections 1 through 9 of this document. 34 | 35 | "Licensor" shall mean the copyright owner or entity authorized by 36 | the copyright owner that is granting the License. 37 | 38 | "Legal Entity" shall mean the union of the acting entity and all 39 | other entities that control, are controlled by, or are under common 40 | control with that entity. For the purposes of this definition, 41 | "control" means (i) the power, direct or indirect, to cause the 42 | direction or management of such entity, whether by contract or 43 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 44 | outstanding shares, or (iii) beneficial ownership of such entity. 45 | 46 | "You" (or "Your") shall mean an individual or Legal Entity 47 | exercising permissions granted by this License. 48 | 49 | "Source" form shall mean the preferred form for making modifications, 50 | including but not limited to software source code, documentation 51 | source, and configuration files. 52 | 53 | "Object" form shall mean any form resulting from mechanical 54 | transformation or translation of a Source form, including but 55 | not limited to compiled object code, generated documentation, 56 | and conversions to other media types. 57 | 58 | "Work" shall mean the work of authorship, whether in Source or 59 | Object form, made available under the License, as indicated by a 60 | copyright notice that is included in or attached to the work 61 | (an example is provided in the Appendix below). 62 | 63 | "Derivative Works" shall mean any work, whether in Source or Object 64 | form, that is based on (or derived from) the Work and for which the 65 | editorial revisions, annotations, elaborations, or other modifications 66 | represent, as a whole, an original work of authorship. For the purposes 67 | of this License, Derivative Works shall not include works that remain 68 | separable from, or merely link (or bind by name) to the interfaces of, 69 | the Work and Derivative Works thereof. 70 | 71 | "Contribution" shall mean any work of authorship, including 72 | the original version of the Work and any modifications or additions 73 | to that Work or Derivative Works thereof, that is intentionally 74 | submitted to Licensor for inclusion in the Work by the copyright owner 75 | or by an individual or Legal Entity authorized to submit on behalf of 76 | the copyright owner. For the purposes of this definition, "submitted" 77 | means any form of electronic, verbal, or written communication sent 78 | to the Licensor or its representatives, including but not limited to 79 | communication on electronic mailing lists, source code control systems, 80 | and issue tracking systems that are managed by, or on behalf of, the 81 | Licensor for the purpose of discussing and improving the Work, but 82 | excluding communication that is conspicuously marked or otherwise 83 | designated in writing by the copyright owner as "Not a Contribution." 84 | 85 | "Contributor" shall mean Licensor and any individual or Legal Entity 86 | on behalf of whom a Contribution has been received by Licensor and 87 | subsequently incorporated within the Work. 88 | 89 | 2. Grant of Copyright License. Subject to the terms and conditions of 90 | this License, each Contributor hereby grants to You a perpetual, 91 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 92 | copyright license to reproduce, prepare Derivative Works of, 93 | publicly display, publicly perform, sublicense, and distribute the 94 | Work and such Derivative Works in Source or Object form. 95 | 96 | 3. Grant of Patent License. Subject to the terms and conditions of 97 | this License, each Contributor hereby grants to You a perpetual, 98 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 99 | (except as stated in this section) patent license to make, have made, 100 | use, offer to sell, sell, import, and otherwise transfer the Work, 101 | where such license applies only to those patent claims licensable 102 | by such Contributor that are necessarily infringed by their 103 | Contribution(s) alone or by combination of their Contribution(s) 104 | with the Work to which such Contribution(s) was submitted. If You 105 | institute patent litigation against any entity (including a 106 | cross-claim or counterclaim in a lawsuit) alleging that the Work 107 | or a Contribution incorporated within the Work constitutes direct 108 | or contributory patent infringement, then any patent licenses 109 | granted to You under this License for that Work shall terminate 110 | as of the date such litigation is filed. 111 | 112 | 4. Redistribution. You may reproduce and distribute copies of the 113 | Work or Derivative Works thereof in any medium, with or without 114 | modifications, and in Source or Object form, provided that You 115 | meet the following conditions: 116 | 117 | (a) You must give any other recipients of the Work or 118 | Derivative Works a copy of this License; and 119 | 120 | (b) You must cause any modified files to carry prominent notices 121 | stating that You changed the files; and 122 | 123 | (c) You must retain, in the Source form of any Derivative Works 124 | that You distribute, all copyright, patent, trademark, and 125 | attribution notices from the Source form of the Work, 126 | excluding those notices that do not pertain to any part of 127 | the Derivative Works; and 128 | 129 | (d) If the Work includes a "NOTICE" text file as part of its 130 | distribution, then any Derivative Works that You distribute must 131 | include a readable copy of the attribution notices contained 132 | within such NOTICE file, excluding those notices that do not 133 | pertain to any part of the Derivative Works, in at least one 134 | of the following places: within a NOTICE text file distributed 135 | as part of the Derivative Works; within the Source form or 136 | documentation, if provided along with the Derivative Works; or, 137 | within a display generated by the Derivative Works, if and 138 | wherever such third-party notices normally appear. The contents 139 | of the NOTICE file are for informational purposes only and 140 | do not modify the License. You may add Your own attribution 141 | notices within Derivative Works that You distribute, alongside 142 | or as an addendum to the NOTICE text from the Work, provided 143 | that such additional attribution notices cannot be construed 144 | as modifying the License. 145 | 146 | You may add Your own copyright statement to Your modifications and 147 | may provide additional or different license terms and conditions 148 | for use, reproduction, or distribution of Your modifications, or 149 | for any such Derivative Works as a whole, provided Your use, 150 | reproduction, and distribution of the Work otherwise complies with 151 | the conditions stated in this License. 152 | 153 | 5. Submission of Contributions. Unless You explicitly state otherwise, 154 | any Contribution intentionally submitted for inclusion in the Work 155 | by You to the Licensor shall be under the terms and conditions of 156 | this License, without any additional terms or conditions. 157 | Notwithstanding the above, nothing herein shall supersede or modify 158 | the terms of any separate license agreement you may have executed 159 | with Licensor regarding such Contributions. 160 | 161 | 6. Trademarks. This License does not grant permission to use the trade 162 | names, trademarks, service marks, or product names of the Licensor, 163 | except as required for reasonable and customary use in describing the 164 | origin of the Work and reproducing the content of the NOTICE file. 165 | 166 | 7. Disclaimer of Warranty. Unless required by applicable law or 167 | agreed to in writing, Licensor provides the Work (and each 168 | Contributor provides its Contributions) on an "AS IS" BASIS, 169 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 170 | implied, including, without limitation, any warranties or conditions 171 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 172 | PARTICULAR PURPOSE. You are solely responsible for determining the 173 | appropriateness of using or redistributing the Work and assume any 174 | risks associated with Your exercise of permissions under this License. 175 | 176 | 8. Limitation of Liability. In no event and under no legal theory, 177 | whether in tort (including negligence), contract, or otherwise, 178 | unless required by applicable law (such as deliberate and grossly 179 | negligent acts) or agreed to in writing, shall any Contributor be 180 | liable to You for damages, including any direct, indirect, special, 181 | incidental, or consequential damages of any character arising as a 182 | result of this License or out of the use or inability to use the 183 | Work (including but not limited to damages for loss of goodwill, 184 | work stoppage, computer failure or malfunction, or any and all 185 | other commercial damages or losses), even if such Contributor 186 | has been advised of the possibility of such damages. 187 | 188 | 9. Accepting Warranty or Additional Liability. While redistributing 189 | the Work or Derivative Works thereof, You may choose to offer, 190 | and charge a fee for, acceptance of support, warranty, indemnity, 191 | or other liability obligations and/or rights consistent with this 192 | License. However, in accepting such obligations, You may act only 193 | on Your own behalf and on Your sole responsibility, not on behalf 194 | of any other Contributor, and only if You agree to indemnify, 195 | defend, and hold each Contributor harmless for any liability 196 | incurred by, or claims asserted against, such Contributor by reason 197 | of your accepting any such warranty or additional liability. 198 | 199 | END OF TERMS AND CONDITIONS 200 | 201 | APPENDIX: How to apply the Apache License to your work. 202 | 203 | To apply the Apache License to your work, attach the following 204 | boilerplate notice, with the fields enclosed by brackets "[]" 205 | replaced with your own identifying information. (Don't include 206 | the brackets!) The text should be enclosed in the appropriate 207 | comment syntax for the file format. We also recommend that a 208 | file or class name and description of purpose be included on the 209 | same "printed page" as the copyright notice for easier 210 | identification within third-party archives. 211 | 212 | Copyright [yyyy] [name of copyright owner] 213 | 214 | Licensed under the Apache License, Version 2.0 (the "License"); 215 | you may not use this file except in compliance with the License. 216 | You may obtain a copy of the License at 217 | 218 | http://www.apache.org/licenses/LICENSE-2.0 219 | 220 | Unless required by applicable law or agreed to in writing, software 221 | distributed under the License is distributed on an "AS IS" BASIS, 222 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 223 | See the License for the specific language governing permissions and 224 | limitations under the License. 225 | {% elif cookiecutter.license == "AGPL-3.0-only" %} 226 | {{cookiecutter.project_name}} 227 | Copyright © 2022, {{cookiecutter.full_name}} 228 | 229 | This program is free software: you can redistribute it and/or modify 230 | it under the terms of the GNU General Public License as published by 231 | the Free Software Foundation, either version 3 of the License. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU Affero General Public License 239 | along with this program. If not, see . 240 | 241 | ------------------------------------------------------------------------ 242 | 243 | GNU AFFERO GENERAL PUBLIC LICENSE 244 | Version 3, 19 November 2007 245 | 246 | Copyright © 2007 Free Software Foundation, Inc. 247 | Everyone is permitted to copy and distribute verbatim copies 248 | of this license document, but changing it is not allowed. 249 | 250 | Preamble 251 | 252 | The GNU Affero General Public License is a free, copyleft license for 253 | software and other kinds of works, specifically designed to ensure 254 | cooperation with the community in the case of network server software. 255 | 256 | The licenses for most software and other practical works are designed 257 | to take away your freedom to share and change the works. By contrast, 258 | our General Public Licenses are intended to guarantee your freedom to 259 | share and change all versions of a program--to make sure it remains free 260 | software for all its users. 261 | 262 | When we speak of free software, we are referring to freedom, not 263 | price. Our General Public Licenses are designed to make sure that you 264 | have the freedom to distribute copies of free software (and charge for 265 | them if you wish), that you receive source code or can get it if you 266 | want it, that you can change the software or use pieces of it in new 267 | free programs, and that you know you can do these things. 268 | 269 | Developers that use our General Public Licenses protect your rights 270 | with two steps: (1) assert copyright on the software, and (2) offer 271 | you this License which gives you legal permission to copy, distribute 272 | and/or modify the software. 273 | 274 | A secondary benefit of defending all users' freedom is that 275 | improvements made in alternate versions of the program, if they 276 | receive widespread use, become available for other developers to 277 | incorporate. Many developers of free software are heartened and 278 | encouraged by the resulting cooperation. However, in the case of 279 | software used on network servers, this result may fail to come about. 280 | The GNU General Public License permits making a modified version and 281 | letting the public access it on a server without ever releasing its 282 | source code to the public. 283 | 284 | The GNU Affero General Public License is designed specifically to 285 | ensure that, in such cases, the modified source code becomes available 286 | to the community. It requires the operator of a network server to 287 | provide the source code of the modified version running there to the 288 | users of that server. Therefore, public use of a modified version, on 289 | a publicly accessible server, gives the public access to the source 290 | code of the modified version. 291 | 292 | An older license, called the Affero General Public License and 293 | published by Affero, was designed to accomplish similar goals. This is 294 | a different license, not a version of the Affero GPL, but Affero has 295 | released a new version of the Affero GPL which permits relicensing under 296 | this license. 297 | 298 | The precise terms and conditions for copying, distribution and 299 | modification follow. 300 | 301 | TERMS AND CONDITIONS 302 | 303 | 0. Definitions. 304 | 305 | "This License" refers to version 3 of the GNU Affero General Public License. 306 | 307 | "Copyright" also means copyright-like laws that apply to other kinds of 308 | works, such as semiconductor masks. 309 | 310 | "The Program" refers to any copyrightable work licensed under this 311 | License. Each licensee is addressed as "you". "Licensees" and 312 | "recipients" may be individuals or organizations. 313 | 314 | To "modify" a work means to copy from or adapt all or part of the work 315 | in a fashion requiring copyright permission, other than the making of an 316 | exact copy. The resulting work is called a "modified version" of the 317 | earlier work or a work "based on" the earlier work. 318 | 319 | A "covered work" means either the unmodified Program or a work based 320 | on the Program. 321 | 322 | To "propagate" a work means to do anything with it that, without 323 | permission, would make you directly or secondarily liable for 324 | infringement under applicable copyright law, except executing it on a 325 | computer or modifying a private copy. Propagation includes copying, 326 | distribution (with or without modification), making available to the 327 | public, and in some countries other activities as well. 328 | 329 | To "convey" a work means any kind of propagation that enables other 330 | parties to make or receive copies. Mere interaction with a user through 331 | a computer network, with no transfer of a copy, is not conveying. 332 | 333 | An interactive user interface displays "Appropriate Legal Notices" 334 | to the extent that it includes a convenient and prominently visible 335 | feature that (1) displays an appropriate copyright notice, and (2) 336 | tells the user that there is no warranty for the work (except to the 337 | extent that warranties are provided), that licensees may convey the 338 | work under this License, and how to view a copy of this License. If 339 | the interface presents a list of user commands or options, such as a 340 | menu, a prominent item in the list meets this criterion. 341 | 342 | 1. Source Code. 343 | 344 | The "source code" for a work means the preferred form of the work 345 | for making modifications to it. "Object code" means any non-source 346 | form of a work. 347 | 348 | A "Standard Interface" means an interface that either is an official 349 | standard defined by a recognized standards body, or, in the case of 350 | interfaces specified for a particular programming language, one that 351 | is widely used among developers working in that language. 352 | 353 | The "System Libraries" of an executable work include anything, other 354 | than the work as a whole, that (a) is included in the normal form of 355 | packaging a Major Component, but which is not part of that Major 356 | Component, and (b) serves only to enable use of the work with that 357 | Major Component, or to implement a Standard Interface for which an 358 | implementation is available to the public in source code form. A 359 | "Major Component", in this context, means a major essential component 360 | (kernel, window system, and so on) of the specific operating system 361 | (if any) on which the executable work runs, or a compiler used to 362 | produce the work, or an object code interpreter used to run it. 363 | 364 | The "Corresponding Source" for a work in object code form means all 365 | the source code needed to generate, install, and (for an executable 366 | work) run the object code and to modify the work, including scripts to 367 | control those activities. However, it does not include the work's 368 | System Libraries, or general-purpose tools or generally available free 369 | programs which are used unmodified in performing those activities but 370 | which are not part of the work. For example, Corresponding Source 371 | includes interface definition files associated with source files for 372 | the work, and the source code for shared libraries and dynamically 373 | linked subprograms that the work is specifically designed to require, 374 | such as by intimate data communication or control flow between those 375 | subprograms and other parts of the work. 376 | 377 | The Corresponding Source need not include anything that users 378 | can regenerate automatically from other parts of the Corresponding 379 | Source. 380 | 381 | The Corresponding Source for a work in source code form is that 382 | same work. 383 | 384 | 2. Basic Permissions. 385 | 386 | All rights granted under this License are granted for the term of 387 | copyright on the Program, and are irrevocable provided the stated 388 | conditions are met. This License explicitly affirms your unlimited 389 | permission to run the unmodified Program. The output from running a 390 | covered work is covered by this License only if the output, given its 391 | content, constitutes a covered work. This License acknowledges your 392 | rights of fair use or other equivalent, as provided by copyright law. 393 | 394 | You may make, run and propagate covered works that you do not 395 | convey, without conditions so long as your license otherwise remains 396 | in force. You may convey covered works to others for the sole purpose 397 | of having them make modifications exclusively for you, or provide you 398 | with facilities for running those works, provided that you comply with 399 | the terms of this License in conveying all material for which you do 400 | not control copyright. Those thus making or running the covered works 401 | for you must do so exclusively on your behalf, under your direction 402 | and control, on terms that prohibit them from making any copies of 403 | your copyrighted material outside their relationship with you. 404 | 405 | Conveying under any other circumstances is permitted solely under 406 | the conditions stated below. Sublicensing is not allowed; section 10 407 | makes it unnecessary. 408 | 409 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 410 | 411 | No covered work shall be deemed part of an effective technological 412 | measure under any applicable law fulfilling obligations under article 413 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 414 | similar laws prohibiting or restricting circumvention of such 415 | measures. 416 | 417 | When you convey a covered work, you waive any legal power to forbid 418 | circumvention of technological measures to the extent such circumvention 419 | is effected by exercising rights under this License with respect to 420 | the covered work, and you disclaim any intention to limit operation or 421 | modification of the work as a means of enforcing, against the work's 422 | users, your or third parties' legal rights to forbid circumvention of 423 | technological measures. 424 | 425 | 4. Conveying Verbatim Copies. 426 | 427 | You may convey verbatim copies of the Program's source code as you 428 | receive it, in any medium, provided that you conspicuously and 429 | appropriately publish on each copy an appropriate copyright notice; 430 | keep intact all notices stating that this License and any 431 | non-permissive terms added in accord with section 7 apply to the code; 432 | keep intact all notices of the absence of any warranty; and give all 433 | recipients a copy of this License along with the Program. 434 | 435 | You may charge any price or no price for each copy that you convey, 436 | and you may offer support or warranty protection for a fee. 437 | 438 | 5. Conveying Modified Source Versions. 439 | 440 | You may convey a work based on the Program, or the modifications to 441 | produce it from the Program, in the form of source code under the 442 | terms of section 4, provided that you also meet all of these conditions: 443 | 444 | a) The work must carry prominent notices stating that you modified 445 | it, and giving a relevant date. 446 | 447 | b) The work must carry prominent notices stating that it is 448 | released under this License and any conditions added under section 449 | 7. This requirement modifies the requirement in section 4 to 450 | "keep intact all notices". 451 | 452 | c) You must license the entire work, as a whole, under this 453 | License to anyone who comes into possession of a copy. This 454 | License will therefore apply, along with any applicable section 7 455 | additional terms, to the whole of the work, and all its parts, 456 | regardless of how they are packaged. This License gives no 457 | permission to license the work in any other way, but it does not 458 | invalidate such permission if you have separately received it. 459 | 460 | d) If the work has interactive user interfaces, each must display 461 | Appropriate Legal Notices; however, if the Program has interactive 462 | interfaces that do not display Appropriate Legal Notices, your 463 | work need not make them do so. 464 | 465 | A compilation of a covered work with other separate and independent 466 | works, which are not by their nature extensions of the covered work, 467 | and which are not combined with it such as to form a larger program, 468 | in or on a volume of a storage or distribution medium, is called an 469 | "aggregate" if the compilation and its resulting copyright are not 470 | used to limit the access or legal rights of the compilation's users 471 | beyond what the individual works permit. Inclusion of a covered work 472 | in an aggregate does not cause this License to apply to the other 473 | parts of the aggregate. 474 | 475 | 6. Conveying Non-Source Forms. 476 | 477 | You may convey a covered work in object code form under the terms 478 | of sections 4 and 5, provided that you also convey the 479 | machine-readable Corresponding Source under the terms of this License, 480 | in one of these ways: 481 | 482 | a) Convey the object code in, or embodied in, a physical product 483 | (including a physical distribution medium), accompanied by the 484 | Corresponding Source fixed on a durable physical medium 485 | customarily used for software interchange. 486 | 487 | b) Convey the object code in, or embodied in, a physical product 488 | (including a physical distribution medium), accompanied by a 489 | written offer, valid for at least three years and valid for as 490 | long as you offer spare parts or customer support for that product 491 | model, to give anyone who possesses the object code either (1) a 492 | copy of the Corresponding Source for all the software in the 493 | product that is covered by this License, on a durable physical 494 | medium customarily used for software interchange, for a price no 495 | more than your reasonable cost of physically performing this 496 | conveying of source, or (2) access to copy the 497 | Corresponding Source from a network server at no charge. 498 | 499 | c) Convey individual copies of the object code with a copy of the 500 | written offer to provide the Corresponding Source. This 501 | alternative is allowed only occasionally and noncommercially, and 502 | only if you received the object code with such an offer, in accord 503 | with subsection 6b. 504 | 505 | d) Convey the object code by offering access from a designated 506 | place (gratis or for a charge), and offer equivalent access to the 507 | Corresponding Source in the same way through the same place at no 508 | further charge. You need not require recipients to copy the 509 | Corresponding Source along with the object code. If the place to 510 | copy the object code is a network server, the Corresponding Source 511 | may be on a different server (operated by you or a third party) 512 | that supports equivalent copying facilities, provided you maintain 513 | clear directions next to the object code saying where to find the 514 | Corresponding Source. Regardless of what server hosts the 515 | Corresponding Source, you remain obligated to ensure that it is 516 | available for as long as needed to satisfy these requirements. 517 | 518 | e) Convey the object code using peer-to-peer transmission, provided 519 | you inform other peers where the object code and Corresponding 520 | Source of the work are being offered to the general public at no 521 | charge under subsection 6d. 522 | 523 | A separable portion of the object code, whose source code is excluded 524 | from the Corresponding Source as a System Library, need not be 525 | included in conveying the object code work. 526 | 527 | A "User Product" is either (1) a "consumer product", which means any 528 | tangible personal property which is normally used for personal, family, 529 | or household purposes, or (2) anything designed or sold for incorporation 530 | into a dwelling. In determining whether a product is a consumer product, 531 | doubtful cases shall be resolved in favor of coverage. For a particular 532 | product received by a particular user, "normally used" refers to a 533 | typical or common use of that class of product, regardless of the status 534 | of the particular user or of the way in which the particular user 535 | actually uses, or expects or is expected to use, the product. A product 536 | is a consumer product regardless of whether the product has substantial 537 | commercial, industrial or non-consumer uses, unless such uses represent 538 | the only significant mode of use of the product. 539 | 540 | "Installation Information" for a User Product means any methods, 541 | procedures, authorization keys, or other information required to install 542 | and execute modified versions of a covered work in that User Product from 543 | a modified version of its Corresponding Source. The information must 544 | suffice to ensure that the continued functioning of the modified object 545 | code is in no case prevented or interfered with solely because 546 | modification has been made. 547 | 548 | If you convey an object code work under this section in, or with, or 549 | specifically for use in, a User Product, and the conveying occurs as 550 | part of a transaction in which the right of possession and use of the 551 | User Product is transferred to the recipient in perpetuity or for a 552 | fixed term (regardless of how the transaction is characterized), the 553 | Corresponding Source conveyed under this section must be accompanied 554 | by the Installation Information. But this requirement does not apply 555 | if neither you nor any third party retains the ability to install 556 | modified object code on the User Product (for example, the work has 557 | been installed in ROM). 558 | 559 | The requirement to provide Installation Information does not include a 560 | requirement to continue to provide support service, warranty, or updates 561 | for a work that has been modified or installed by the recipient, or for 562 | the User Product in which it has been modified or installed. Access to a 563 | network may be denied when the modification itself materially and 564 | adversely affects the operation of the network or violates the rules and 565 | protocols for communication across the network. 566 | 567 | Corresponding Source conveyed, and Installation Information provided, 568 | in accord with this section must be in a format that is publicly 569 | documented (and with an implementation available to the public in 570 | source code form), and must require no special password or key for 571 | unpacking, reading or copying. 572 | 573 | 7. Additional Terms. 574 | 575 | "Additional permissions" are terms that supplement the terms of this 576 | License by making exceptions from one or more of its conditions. 577 | Additional permissions that are applicable to the entire Program shall 578 | be treated as though they were included in this License, to the extent 579 | that they are valid under applicable law. If additional permissions 580 | apply only to part of the Program, that part may be used separately 581 | under those permissions, but the entire Program remains governed by 582 | this License without regard to the additional permissions. 583 | 584 | When you convey a copy of a covered work, you may at your option 585 | remove any additional permissions from that copy, or from any part of 586 | it. (Additional permissions may be written to require their own 587 | removal in certain cases when you modify the work.) You may place 588 | additional permissions on material, added by you to a covered work, 589 | for which you have or can give appropriate copyright permission. 590 | 591 | Notwithstanding any other provision of this License, for material you 592 | add to a covered work, you may (if authorized by the copyright holders of 593 | that material) supplement the terms of this License with terms: 594 | 595 | a) Disclaiming warranty or limiting liability differently from the 596 | terms of sections 15 and 16 of this License; or 597 | 598 | b) Requiring preservation of specified reasonable legal notices or 599 | author attributions in that material or in the Appropriate Legal 600 | Notices displayed by works containing it; or 601 | 602 | c) Prohibiting misrepresentation of the origin of that material, or 603 | requiring that modified versions of such material be marked in 604 | reasonable ways as different from the original version; or 605 | 606 | d) Limiting the use for publicity purposes of names of licensors or 607 | authors of the material; or 608 | 609 | e) Declining to grant rights under trademark law for use of some 610 | trade names, trademarks, or service marks; or 611 | 612 | f) Requiring indemnification of licensors and authors of that 613 | material by anyone who conveys the material (or modified versions of 614 | it) with contractual assumptions of liability to the recipient, for 615 | any liability that these contractual assumptions directly impose on 616 | those licensors and authors. 617 | 618 | All other non-permissive additional terms are considered "further 619 | restrictions" within the meaning of section 10. If the Program as you 620 | received it, or any part of it, contains a notice stating that it is 621 | governed by this License along with a term that is a further 622 | restriction, you may remove that term. If a license document contains 623 | a further restriction but permits relicensing or conveying under this 624 | License, you may add to a covered work material governed by the terms 625 | of that license document, provided that the further restriction does 626 | not survive such relicensing or conveying. 627 | 628 | If you add terms to a covered work in accord with this section, you 629 | must place, in the relevant source files, a statement of the 630 | additional terms that apply to those files, or a notice indicating 631 | where to find the applicable terms. 632 | 633 | Additional terms, permissive or non-permissive, may be stated in the 634 | form of a separately written license, or stated as exceptions; 635 | the above requirements apply either way. 636 | 637 | 8. Termination. 638 | 639 | You may not propagate or modify a covered work except as expressly 640 | provided under this License. Any attempt otherwise to propagate or 641 | modify it is void, and will automatically terminate your rights under 642 | this License (including any patent licenses granted under the third 643 | paragraph of section 11). 644 | 645 | However, if you cease all violation of this License, then your 646 | license from a particular copyright holder is reinstated (a) 647 | provisionally, unless and until the copyright holder explicitly and 648 | finally terminates your license, and (b) permanently, if the copyright 649 | holder fails to notify you of the violation by some reasonable means 650 | prior to 60 days after the cessation. 651 | 652 | Moreover, your license from a particular copyright holder is 653 | reinstated permanently if the copyright holder notifies you of the 654 | violation by some reasonable means, this is the first time you have 655 | received notice of violation of this License (for any work) from that 656 | copyright holder, and you cure the violation prior to 30 days after 657 | your receipt of the notice. 658 | 659 | Termination of your rights under this section does not terminate the 660 | licenses of parties who have received copies or rights from you under 661 | this License. If your rights have been terminated and not permanently 662 | reinstated, you do not qualify to receive new licenses for the same 663 | material under section 10. 664 | 665 | 9. Acceptance Not Required for Having Copies. 666 | 667 | You are not required to accept this License in order to receive or 668 | run a copy of the Program. Ancillary propagation of a covered work 669 | occurring solely as a consequence of using peer-to-peer transmission 670 | to receive a copy likewise does not require acceptance. However, 671 | nothing other than this License grants you permission to propagate or 672 | modify any covered work. These actions infringe copyright if you do 673 | not accept this License. Therefore, by modifying or propagating a 674 | covered work, you indicate your acceptance of this License to do so. 675 | 676 | 10. Automatic Licensing of Downstream Recipients. 677 | 678 | Each time you convey a covered work, the recipient automatically 679 | receives a license from the original licensors, to run, modify and 680 | propagate that work, subject to this License. You are not responsible 681 | for enforcing compliance by third parties with this License. 682 | 683 | An "entity transaction" is a transaction transferring control of an 684 | organization, or substantially all assets of one, or subdividing an 685 | organization, or merging organizations. If propagation of a covered 686 | work results from an entity transaction, each party to that 687 | transaction who receives a copy of the work also receives whatever 688 | licenses to the work the party's predecessor in interest had or could 689 | give under the previous paragraph, plus a right to possession of the 690 | Corresponding Source of the work from the predecessor in interest, if 691 | the predecessor has it or can get it with reasonable efforts. 692 | 693 | You may not impose any further restrictions on the exercise of the 694 | rights granted or affirmed under this License. For example, you may 695 | not impose a license fee, royalty, or other charge for exercise of 696 | rights granted under this License, and you may not initiate litigation 697 | (including a cross-claim or counterclaim in a lawsuit) alleging that 698 | any patent claim is infringed by making, using, selling, offering for 699 | sale, or importing the Program or any portion of it. 700 | 701 | 11. Patents. 702 | 703 | A "contributor" is a copyright holder who authorizes use under this 704 | License of the Program or a work on which the Program is based. The 705 | work thus licensed is called the contributor's "contributor version". 706 | 707 | A contributor's "essential patent claims" are all patent claims 708 | owned or controlled by the contributor, whether already acquired or 709 | hereafter acquired, that would be infringed by some manner, permitted 710 | by this License, of making, using, or selling its contributor version, 711 | but do not include claims that would be infringed only as a 712 | consequence of further modification of the contributor version. For 713 | purposes of this definition, "control" includes the right to grant 714 | patent sublicenses in a manner consistent with the requirements of 715 | this License. 716 | 717 | Each contributor grants you a non-exclusive, worldwide, royalty-free 718 | patent license under the contributor's essential patent claims, to 719 | make, use, sell, offer for sale, import and otherwise run, modify and 720 | propagate the contents of its contributor version. 721 | 722 | In the following three paragraphs, a "patent license" is any express 723 | agreement or commitment, however denominated, not to enforce a patent 724 | (such as an express permission to practice a patent or covenant not to 725 | sue for patent infringement). To "grant" such a patent license to a 726 | party means to make such an agreement or commitment not to enforce a 727 | patent against the party. 728 | 729 | If you convey a covered work, knowingly relying on a patent license, 730 | and the Corresponding Source of the work is not available for anyone 731 | to copy, free of charge and under the terms of this License, through a 732 | publicly available network server or other readily accessible means, 733 | then you must either (1) cause the Corresponding Source to be so 734 | available, or (2) arrange to deprive yourself of the benefit of the 735 | patent license for this particular work, or (3) arrange, in a manner 736 | consistent with the requirements of this License, to extend the patent 737 | license to downstream recipients. "Knowingly relying" means you have 738 | actual knowledge that, but for the patent license, your conveying the 739 | covered work in a country, or your recipient's use of the covered work 740 | in a country, would infringe one or more identifiable patents in that 741 | country that you have reason to believe are valid. 742 | 743 | If, pursuant to or in connection with a single transaction or 744 | arrangement, you convey, or propagate by procuring conveyance of, a 745 | covered work, and grant a patent license to some of the parties 746 | receiving the covered work authorizing them to use, propagate, modify 747 | or convey a specific copy of the covered work, then the patent license 748 | you grant is automatically extended to all recipients of the covered 749 | work and works based on it. 750 | 751 | A patent license is "discriminatory" if it does not include within 752 | the scope of its coverage, prohibits the exercise of, or is 753 | conditioned on the non-exercise of one or more of the rights that are 754 | specifically granted under this License. You may not convey a covered 755 | work if you are a party to an arrangement with a third party that is 756 | in the business of distributing software, under which you make payment 757 | to the third party based on the extent of your activity of conveying 758 | the work, and under which the third party grants, to any of the 759 | parties who would receive the covered work from you, a discriminatory 760 | patent license (a) in connection with copies of the covered work 761 | conveyed by you (or copies made from those copies), or (b) primarily 762 | for and in connection with specific products or compilations that 763 | contain the covered work, unless you entered into that arrangement, 764 | or that patent license was granted, prior to 28 March 2007. 765 | 766 | Nothing in this License shall be construed as excluding or limiting 767 | any implied license or other defenses to infringement that may 768 | otherwise be available to you under applicable patent law. 769 | 770 | 12. No Surrender of Others' Freedom. 771 | 772 | If conditions are imposed on you (whether by court order, agreement or 773 | otherwise) that contradict the conditions of this License, they do not 774 | excuse you from the conditions of this License. If you cannot convey a 775 | covered work so as to satisfy simultaneously your obligations under this 776 | License and any other pertinent obligations, then as a consequence you may 777 | not convey it at all. For example, if you agree to terms that obligate you 778 | to collect a royalty for further conveying from those to whom you convey 779 | the Program, the only way you could satisfy both those terms and this 780 | License would be to refrain entirely from conveying the Program. 781 | 782 | 13. Remote Network Interaction; Use with the GNU General Public License. 783 | 784 | Notwithstanding any other provision of this License, if you modify the 785 | Program, your modified version must prominently offer all users 786 | interacting with it remotely through a computer network (if your version 787 | supports such interaction) an opportunity to receive the Corresponding 788 | Source of your version by providing access to the Corresponding Source 789 | from a network server at no charge, through some standard or customary 790 | means of facilitating copying of software. This Corresponding Source 791 | shall include the Corresponding Source for any work covered by version 3 792 | of the GNU General Public License that is incorporated pursuant to the 793 | following paragraph. 794 | 795 | Notwithstanding any other provision of this License, you have 796 | permission to link or combine any covered work with a work licensed 797 | under version 3 of the GNU General Public License into a single 798 | combined work, and to convey the resulting work. The terms of this 799 | License will continue to apply to the part which is the covered work, 800 | but the work with which it is combined will remain governed by version 801 | 3 of the GNU General Public License. 802 | 803 | 14. Revised Versions of this License. 804 | 805 | The Free Software Foundation may publish revised and/or new versions of 806 | the GNU Affero General Public License from time to time. Such new versions 807 | will be similar in spirit to the present version, but may differ in detail to 808 | address new problems or concerns. 809 | 810 | Each version is given a distinguishing version number. If the 811 | Program specifies that a certain numbered version of the GNU Affero General 812 | Public License "or any later version" applies to it, you have the 813 | option of following the terms and conditions either of that numbered 814 | version or of any later version published by the Free Software 815 | Foundation. If the Program does not specify a version number of the 816 | GNU Affero General Public License, you may choose any version ever published 817 | by the Free Software Foundation. 818 | 819 | If the Program specifies that a proxy can decide which future 820 | versions of the GNU Affero General Public License can be used, that proxy's 821 | public statement of acceptance of a version permanently authorizes you 822 | to choose that version for the Program. 823 | 824 | Later license versions may give you additional or different 825 | permissions. However, no additional obligations are imposed on any 826 | author or copyright holder as a result of your choosing to follow a 827 | later version. 828 | 829 | 15. Disclaimer of Warranty. 830 | 831 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 832 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 833 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 834 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 835 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 836 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 837 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 838 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 839 | 840 | 16. Limitation of Liability. 841 | 842 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 843 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 844 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 845 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 846 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 847 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 848 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 849 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 850 | SUCH DAMAGES. 851 | 852 | 17. Interpretation of Sections 15 and 16. 853 | 854 | If the disclaimer of warranty and limitation of liability provided 855 | above cannot be given local legal effect according to their terms, 856 | reviewing courts shall apply local law that most closely approximates 857 | an absolute waiver of all civil liability in connection with the 858 | Program, unless a warranty or assumption of liability accompanies a 859 | copy of the Program in return for a fee. 860 | 861 | END OF TERMS AND CONDITIONS 862 | 863 | How to Apply These Terms to Your New Programs 864 | 865 | If you develop a new program, and you want it to be of the greatest 866 | possible use to the public, the best way to achieve this is to make it 867 | free software which everyone can redistribute and change under these terms. 868 | 869 | To do so, attach the following notices to the program. It is safest 870 | to attach them to the start of each source file to most effectively 871 | state the exclusion of warranty; and each file should have at least 872 | the "copyright" line and a pointer to where the full notice is found. 873 | 874 | 875 | Copyright (C) 876 | 877 | This program is free software: you can redistribute it and/or modify 878 | it under the terms of the GNU Affero General Public License as published 879 | by the Free Software Foundation, either version 3 of the License. 880 | 881 | This program is distributed in the hope that it will be useful, 882 | but WITHOUT ANY WARRANTY; without even the implied warranty of 883 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 884 | GNU Affero General Public License for more details. 885 | 886 | You should have received a copy of the GNU Affero General Public License 887 | along with this program. If not, see . 888 | 889 | Also add information on how to contact you by electronic and paper mail. 890 | 891 | If your software can interact with users remotely through a computer 892 | network, you should also make sure that it provides a way for users to 893 | get its source. For example, if your program is a web application, its 894 | interface could display a "Source" link that leads users to an archive 895 | of the code. There are many ways you could offer source, and different 896 | solutions will be better for different programs; see section 13 for the 897 | specific requirements. 898 | 899 | You should also get your employer (if you work as a programmer) or school, 900 | if any, to sign a "copyright disclaimer" for the program, if necessary. 901 | For more information on this, and how to apply and follow the GNU AGPL, see 902 | . 903 | {% elif cookiecutter.license == "Unlicense" %} 904 | This is free and unencumbered software released into the public domain. 905 | 906 | Anyone is free to copy, modify, publish, use, compile, sell, or 907 | distribute this software, either in source code form or as a compiled 908 | binary, for any purpose, commercial or non-commercial, and by any 909 | means. 910 | 911 | In jurisdictions that recognize copyright laws, the author or authors 912 | of this software dedicate any and all copyright interest in the 913 | software to the public domain. We make this dedication for the benefit 914 | of the public at large and to the detriment of our heirs and 915 | successors. We intend this dedication to be an overt act of 916 | relinquishment in perpetuity of all present and future rights to this 917 | software under copyright law. 918 | 919 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 920 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 921 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 922 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 923 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 924 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 925 | OTHER DEALINGS IN THE SOFTWARE. 926 | 927 | For more information, please refer to 928 | {% endif %} -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT := {{cookiecutter.project_name}} 2 | PACKAGE := {{cookiecutter.package_name}} 3 | MODULES := $(wildcard $(PACKAGE)/*.py) 4 | 5 | # MAIN TASKS ################################################################## 6 | 7 | .PHONY: all 8 | all: doctor format check test mkdocs ## Run all tasks that determine CI status 9 | 10 | .PHONY: dev 11 | dev: install ## Continuously run CI tasks when files change 12 | poetry run sniffer 13 | 14 | # SYSTEM DEPENDENCIES ######################################################### 15 | 16 | .PHONY: bootstrap 17 | bootstrap: ## Attempt to install system dependencies 18 | asdf plugin add python || asdf plugin update python 19 | asdf plugin add poetry || asdf plugin update poetry 20 | asdf install 21 | 22 | .PHONY: doctor 23 | doctor: ## Confirm system dependencies are available 24 | bin/verchew --exit-code 25 | 26 | # PROJECT DEPENDENCIES ######################################################## 27 | 28 | VIRTUAL_ENV ?= .venv 29 | DEPENDENCIES := $(VIRTUAL_ENV)/.poetry-$(shell bin/checksum pyproject.toml poetry.lock) 30 | 31 | .PHONY: install 32 | install: $(DEPENDENCIES) .cache ## Install project dependencies 33 | 34 | $(DEPENDENCIES): poetry.lock 35 | @ rm -rf $(VIRTUAL_ENV)/.poetry-* 36 | @ rm -rf ~/Library/Preferences/pypoetry 37 | @ poetry config virtualenvs.in-project true 38 | poetry install --no-root 39 | @ touch $@ 40 | 41 | ifndef CI 42 | poetry.lock: pyproject.toml 43 | poetry lock 44 | @ touch $@ 45 | endif 46 | 47 | .cache: 48 | @ mkdir -p .cache 49 | 50 | .PHONY: clean 51 | clean: ## Delete all generated and temporary files 52 | find $(PACKAGE) tests -name '__pycache__' -delete 53 | rm -rf *.egg-info 54 | rm -rf .cache .pytest .coverage htmlcov 55 | rm -rf docs/*.png site 56 | rm -rf *.spec dist build 57 | rm -rf $(VIRTUAL_ENV) 58 | 59 | # TEST ######################################################################## 60 | 61 | RANDOM_SEED ?= $(shell date +%s) 62 | FAILURES := .cache/pytest/v/cache/lastfailed 63 | 64 | PYTEST_OPTIONS := --random --random-seed=$(RANDOM_SEED) 65 | ifndef DISABLE_COVERAGE 66 | PYTEST_OPTIONS += --cov=$(PACKAGE) 67 | endif 68 | ifdef CI 69 | PYTEST_OPTIONS += --cov-report=xml 70 | endif 71 | PYTEST_RERUN_OPTIONS := --last-failed --exitfirst 72 | 73 | .PHONY: test 74 | test: test-all ## Run unit and integration tests 75 | 76 | .PHONY: test-unit 77 | test-unit: install 78 | @ ( mv $(FAILURES) $(FAILURES).bak || true ) > /dev/null 2>&1 79 | poetry run pytest $(PACKAGE) $(PYTEST_OPTIONS) 80 | @ ( mv $(FAILURES).bak $(FAILURES) || true ) > /dev/null 2>&1 81 | ifndef DISABLE_COVERAGE 82 | poetry run coveragespace update unit 83 | endif 84 | 85 | .PHONY: test-int 86 | test-int: install 87 | @ if test -e $(FAILURES); then poetry run pytest tests $(PYTEST_RERUN_OPTIONS); fi 88 | @ rm -rf $(FAILURES) 89 | poetry run pytest tests $(PYTEST_OPTIONS) 90 | ifndef DISABLE_COVERAGE 91 | poetry run coveragespace update integration 92 | endif 93 | 94 | .PHONY: test-all 95 | test-all: install 96 | @ if test -e $(FAILURES); then poetry run pytest $(PACKAGE) tests $(PYTEST_RERUN_OPTIONS); fi 97 | @ rm -rf $(FAILURES) 98 | poetry run pytest $(PACKAGE) tests $(PYTEST_OPTIONS) 99 | ifndef DISABLE_COVERAGE 100 | poetry run coveragespace update overall 101 | endif 102 | 103 | .PHONY: read-coverage 104 | read-coverage: 105 | bin/open htmlcov/index.html 106 | 107 | # CHECK ####################################################################### 108 | 109 | .PHONY: format 110 | format: install 111 | poetry run isort $(PACKAGE) tests notebooks 112 | poetry run black $(PACKAGE) tests notebooks 113 | @ echo 114 | 115 | .PHONY: check 116 | check: install format ## Run formatters, linters, and static analysis 117 | ifdef CI 118 | git diff --exit-code 119 | endif 120 | poetry run mypy $(PACKAGE) tests 121 | poetry run pylint $(PACKAGE) tests --rcfile=.pylint.ini 122 | poetry run pydocstyle $(PACKAGE) tests 123 | 124 | # DOCUMENTATION ############################################################### 125 | 126 | MKDOCS_INDEX := site/index.html 127 | 128 | .PHONY: docs 129 | docs: mkdocs uml ## Generate documentation and UML 130 | ifndef CI 131 | @ eval "sleep 3; bin/open http://127.0.0.1:8000" & 132 | poetry run mkdocs serve 133 | endif 134 | 135 | .PHONY: mkdocs 136 | mkdocs: install $(MKDOCS_INDEX) 137 | $(MKDOCS_INDEX): docs/requirements.txt mkdocs.yml docs/*.md 138 | @ mkdir -p docs/about 139 | @ cd docs && ln -sf ../README.md index.md 140 | @ cd docs/about && ln -sf ../../CHANGELOG.md changelog.md 141 | @ cd docs/about && ln -sf ../../CONTRIBUTING.md contributing.md 142 | @ cd docs/about && ln -sf ../../LICENSE.md license.md 143 | poetry run mkdocs build --clean --strict 144 | 145 | docs/requirements.txt: poetry.lock 146 | @ poetry export --all-groups --without-hashes | grep mkdocs > $@ 147 | @ poetry export --all-groups --without-hashes | grep pygments >> $@ 148 | @ poetry export --all-groups --without-hashes | grep jinja2 >> $@ 149 | 150 | .PHONY: uml 151 | uml: install docs/*.png 152 | docs/*.png: $(MODULES) 153 | poetry run pyreverse $(PACKAGE) -p $(PACKAGE) -a 1 -f ALL -o png --ignore tests 154 | - mv -f classes_$(PACKAGE).png docs/classes.png 155 | - mv -f packages_$(PACKAGE).png docs/packages.png 156 | 157 | # DEMO ######################################################################## 158 | 159 | .PHONY: run 160 | run: install ## Start the program 161 | poetry run python $(PACKAGE)/__main__.py 162 | 163 | .PHONY: shell 164 | shell: install ## Launch an IPython session 165 | poetry run ipython --ipython-dir=notebooks 166 | 167 | # BUILD ####################################################################### 168 | 169 | DIST_FILES := dist/*.tar.gz dist/*.whl 170 | EXE_FILES := dist/$(PACKAGE).* 171 | 172 | .PHONY: dist 173 | dist: install $(DIST_FILES) 174 | $(DIST_FILES): $(MODULES) pyproject.toml 175 | rm -f $(DIST_FILES) 176 | poetry build 177 | 178 | .PHONY: exe 179 | exe: install $(EXE_FILES) 180 | $(EXE_FILES): $(MODULES) $(PACKAGE).spec 181 | poetry run pyinstaller $(PACKAGE).spec --noconfirm --clean 182 | 183 | $(PACKAGE).spec: 184 | poetry run pyi-makespec $(PACKAGE)/__main__.py --onefile --windowed --name=$(PACKAGE) 185 | 186 | # RELEASE ##################################################################### 187 | 188 | .PHONY: upload 189 | upload: dist ## Upload the current version to PyPI 190 | git diff --name-only --exit-code 191 | poetry publish 192 | bin/open https://pypi.org/project/$(PROJECT) 193 | 194 | # HELP ######################################################################## 195 | 196 | .PHONY: help 197 | help: install 198 | @ grep -E '^[^[:space:]]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 199 | 200 | .DEFAULT_GOAL := help 201 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | {{cookiecutter.project_short_description}} 4 | 5 | This project was generated with [cookiecutter](https://github.com/audreyr/cookiecutter) using [jacebrowning/template-python](https://github.com/jacebrowning/template-python). 6 | 7 | [![Linux Build](https://img.shields.io/github/actions/workflow/status/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/main.yml?branch=main&label=linux)](https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/actions) 8 | [![Windows Build](https://img.shields.io/appveyor/ci/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/main.svg?label=windows)](https://ci.appveyor.com/project/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}) 9 | [![Code Coverage](https://img.shields.io/codecov/c/github/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}) 10 | ](https://codecov.io/gh/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}) 11 | [![Code Quality](https://img.shields.io/scrutinizer/g/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}.svg?label=quality)](https://scrutinizer-ci.com/g/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/?branch=main) 12 | [![PyPI License](https://img.shields.io/pypi/l/{{cookiecutter.project_name}}.svg)](https://pypi.org/project/{{cookiecutter.project_name}}) 13 | [![PyPI Version](https://img.shields.io/pypi/v/{{cookiecutter.project_name}}.svg?label=version)](https://pypi.org/project/{{cookiecutter.project_name}}) 14 | [![PyPI Downloads](https://img.shields.io/pypi/dm/{{cookiecutter.project_name}}.svg?color=orange)](https://pypistats.org/packages/{{cookiecutter.project_name}}) 15 | 16 | ## Setup 17 | 18 | ### Requirements 19 | 20 | * Python {{cookiecutter.python_major_version}}.{{cookiecutter.python_minor_version}}+ 21 | 22 | ### Installation 23 | 24 | Install it directly into an activated virtual environment: 25 | 26 | ```text 27 | $ pip install {{cookiecutter.project_name}} 28 | ``` 29 | 30 | or add it to your [Poetry](https://poetry.eustace.io/) project: 31 | 32 | ```text 33 | $ poetry add {{cookiecutter.project_name}} 34 | ``` 35 | 36 | ## Usage 37 | 38 | After installation, the package can be imported: 39 | 40 | ```text 41 | $ python 42 | >>> import {{cookiecutter.package_name}} 43 | >>> {{cookiecutter.package_name}}.__version__ 44 | ``` 45 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/bin/checksum: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import hashlib 5 | import sys 6 | 7 | 8 | def run(paths): 9 | sha = hashlib.sha1() 10 | 11 | for path in paths: 12 | try: 13 | with open(path, 'rb') as f: 14 | for chunk in iter(lambda: f.read(4096), b''): 15 | sha.update(chunk) 16 | except IOError: 17 | sha.update(path.encode()) 18 | 19 | print(sha.hexdigest()) 20 | 21 | 22 | if __name__ == '__main__': 23 | run(sys.argv[1:]) 24 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/bin/open: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | 7 | 8 | COMMANDS = { 9 | 'linux': "open", 10 | 'win32': "cmd /c start", 11 | 'cygwin': "cygstart", 12 | 'darwin': "open", 13 | } 14 | 15 | 16 | def run(path): 17 | command = COMMANDS.get(sys.platform, "open") 18 | os.system(command + ' ' + path) 19 | 20 | 21 | if __name__ == '__main__': 22 | run(sys.argv[-1]) 23 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from contextlib import suppress 6 | import importlib 7 | import tempfile 8 | import shutil 9 | import subprocess 10 | import sys 11 | 12 | 13 | CWD = os.getcwd() 14 | TMP = tempfile.gettempdir() 15 | CONFIG = { 16 | "full_name": "{{ cookiecutter.full_name }}", 17 | "email": "{{ cookiecutter.email }}", 18 | "github_username": "{{ cookiecutter.github_username }}", 19 | "github_repo": "{{ cookiecutter.github_repo }}", 20 | "default_branch": "{{ cookiecutter.default_branch }}", 21 | "project_name": "{{ cookiecutter.project_name }}", 22 | "package_name": "{{ cookiecutter.package_name }}", 23 | "project_short_description": "{{ cookiecutter.project_short_description }}", 24 | "python_major_version": {{ cookiecutter.python_major_version }}, 25 | "python_minor_version": {{ cookiecutter.python_minor_version }}, 26 | } 27 | 28 | 29 | def install(package="cookiecutter"): 30 | try: 31 | importlib.import_module(package) 32 | except ImportError: 33 | print("Installing cookiecutter") 34 | subprocess.check_call([sys.executable, "-m", "pip", "install", package]) 35 | 36 | 37 | def run(): 38 | print("Generating project") 39 | 40 | from cookiecutter.main import cookiecutter 41 | 42 | os.chdir(TMP) 43 | cookiecutter( 44 | "https://github.com/jacebrowning/template-python.git", 45 | no_input=True, 46 | overwrite_if_exists=True, 47 | extra_context=CONFIG, 48 | ) 49 | 50 | 51 | def copy(): 52 | for filename in [ 53 | os.path.join("bin", "update"), 54 | os.path.join("bin", "checksum"), 55 | os.path.join("bin", "open"), 56 | os.path.join("bin", "verchew"), 57 | ".appveyor.yml", 58 | ".coveragerc", 59 | ".gitattributes", 60 | ".gitignore", 61 | ".pydocstyle.ini", 62 | ".pylint.ini", 63 | ".scrutinizer.yml", 64 | ".tool-versions", 65 | ".verchew.ini", 66 | "CONTRIBUTING.md", 67 | "Makefile", 68 | "scent.py", 69 | ]: 70 | src = os.path.join(TMP, CONFIG["project_name"], filename) 71 | dst = os.path.join(CWD, filename) 72 | print("Updating " + filename) 73 | with suppress(FileNotFoundError): 74 | shutil.copy(src, dst) 75 | 76 | 77 | if __name__ == "__main__": 78 | install() 79 | run() 80 | copy() 81 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/bin/verchew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # The MIT License (MIT) 5 | # Copyright © 2016, Jace Browning 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | # Source: https://github.com/jacebrowning/verchew 26 | # Documentation: https://verchew.readthedocs.io 27 | # Package: https://pypi.org/project/verchew 28 | 29 | 30 | from __future__ import unicode_literals 31 | 32 | import argparse 33 | import logging 34 | import os 35 | import re 36 | import sys 37 | from collections import OrderedDict 38 | from subprocess import PIPE, STDOUT, Popen 39 | 40 | PY2 = sys.version_info[0] == 2 41 | 42 | if PY2: 43 | import ConfigParser as configparser 44 | from urllib import urlretrieve 45 | else: 46 | import configparser 47 | from urllib.request import urlretrieve 48 | 49 | __version__ = "3.4.2" 50 | 51 | SCRIPT_URL = ( 52 | "https://raw.githubusercontent.com/jacebrowning/verchew/main/verchew/script.py" 53 | ) 54 | WRAPPER_URL = ( 55 | "https://raw.githubusercontent.com/jacebrowning/verchew/main/verchew/wrapper.sh" 56 | ) 57 | 58 | CONFIG_FILENAMES = ["verchew.ini", ".verchew.ini", ".verchewrc", ".verchew"] 59 | 60 | SAMPLE_CONFIG = """ 61 | [Python] 62 | 63 | cli = python 64 | version = Python 3.5 || Python 3.6 65 | 66 | [Legacy Python] 67 | 68 | cli = python2 69 | version = Python 2.7 70 | 71 | [virtualenv] 72 | 73 | cli = virtualenv 74 | version = 15 75 | message = Only required with Python 2. 76 | 77 | [Make] 78 | 79 | cli = make 80 | version = GNU Make 81 | optional = true 82 | 83 | """.strip() 84 | 85 | STYLE = { 86 | "~": "✔", 87 | "?": "▴", 88 | "x": "✘", 89 | "#": "䷉", 90 | } 91 | 92 | COLOR = { 93 | "~": "\033[92m", # green 94 | "?": "\033[93m", # yellow 95 | "x": "\033[91m", # red 96 | "#": "\033[96m", # cyan 97 | None: "\033[0m", # reset 98 | } 99 | 100 | QUIET = False 101 | 102 | log = logging.getLogger(__name__) 103 | 104 | 105 | def main(): 106 | global QUIET 107 | 108 | args = parse_args() 109 | configure_logging(args.verbose) 110 | if args.quiet: 111 | QUIET = True 112 | 113 | log.debug("PWD: %s", os.getenv("PWD")) 114 | log.debug("PATH: %s", os.getenv("PATH")) 115 | 116 | if args.vendor: 117 | vendor_script(SCRIPT_URL, args.vendor) 118 | vendor_script(WRAPPER_URL, args.vendor + "-wrapper") 119 | sys.exit(0) 120 | 121 | path = find_config(args.root, generate=args.init) 122 | config = parse_config(path) 123 | 124 | if not check_dependencies(config) and args.exit_code: 125 | sys.exit(1) 126 | 127 | 128 | def parse_args(): 129 | parser = argparse.ArgumentParser(description="System dependency version checker.") 130 | 131 | version = "%(prog)s v" + __version__ 132 | parser.add_argument("--version", action="version", version=version) 133 | parser.add_argument( 134 | "-r", "--root", metavar="PATH", help="specify a custom project root directory" 135 | ) 136 | parser.add_argument( 137 | "--exit-code", 138 | action="store_true", 139 | help="return a non-zero exit code on failure", 140 | ) 141 | 142 | group_logging = parser.add_mutually_exclusive_group() 143 | group_logging.add_argument( 144 | "-v", "--verbose", action="count", default=0, help="enable verbose logging" 145 | ) 146 | group_logging.add_argument( 147 | "-q", "--quiet", action="store_true", help="suppress all output on success" 148 | ) 149 | 150 | group_commands = parser.add_argument_group("commands") 151 | group_commands.add_argument( 152 | "--init", action="store_true", help="generate a sample configuration file" 153 | ) 154 | 155 | group_commands.add_argument( 156 | "--vendor", metavar="PATH", help="download the program for offline use" 157 | ) 158 | 159 | args = parser.parse_args() 160 | 161 | return args 162 | 163 | 164 | def configure_logging(count=0): 165 | if count == 0: 166 | level = logging.WARNING 167 | elif count == 1: 168 | level = logging.INFO 169 | else: 170 | level = logging.DEBUG 171 | 172 | logging.basicConfig(level=level, format="%(levelname)s: %(message)s") 173 | 174 | 175 | def vendor_script(url, path): 176 | root = os.path.abspath(os.path.join(path, os.pardir)) 177 | if not os.path.isdir(root): 178 | log.info("Creating directory %s", root) 179 | os.makedirs(root) 180 | 181 | log.info("Downloading %s to %s", url, path) 182 | urlretrieve(url, path) 183 | 184 | log.debug("Making %s executable", path) 185 | mode = os.stat(path).st_mode 186 | os.chmod(path, mode | 0o111) 187 | 188 | 189 | def find_config(root=None, filenames=None, generate=False): 190 | root = root or os.getcwd() 191 | filenames = filenames or CONFIG_FILENAMES 192 | 193 | path = None 194 | log.info("Looking for config file in: %s", root) 195 | log.debug("Filename options: %s", ", ".join(filenames)) 196 | for filename in os.listdir(root): 197 | if filename in filenames: 198 | path = os.path.join(root, filename) 199 | log.info("Found config file: %s", path) 200 | return path 201 | 202 | if generate: 203 | path = generate_config(root, filenames) 204 | return path 205 | 206 | msg = "No config file found in: {0}".format(root) 207 | raise RuntimeError(msg) 208 | 209 | 210 | def generate_config(root=None, filenames=None): 211 | root = root or os.getcwd() 212 | filenames = filenames or CONFIG_FILENAMES 213 | 214 | path = os.path.join(root, filenames[0]) 215 | 216 | log.info("Generating sample config: %s", path) 217 | with open(path, "w") as config: 218 | config.write(SAMPLE_CONFIG + "\n") 219 | 220 | return path 221 | 222 | 223 | def parse_config(path): 224 | data = OrderedDict() # type: ignore 225 | 226 | log.info("Parsing config file: %s", path) 227 | config = configparser.ConfigParser() 228 | config.read(path) 229 | 230 | for section in config.sections(): 231 | data[section] = OrderedDict() 232 | for name, value in config.items(section): 233 | data[section][name] = value 234 | 235 | for name in data: 236 | version = data[name].get("version") or "" 237 | data[name]["version"] = version 238 | data[name]["patterns"] = [v.strip() for v in version.split("||")] 239 | 240 | data[name]["optional"] = data[name].get( 241 | "optional", "false" 242 | ).strip().lower() in ("true", "yes", "y", True) 243 | 244 | return data 245 | 246 | 247 | def check_dependencies(config): 248 | success = [] 249 | 250 | for name, settings in config.items(): 251 | show("Checking for {0}...".format(name), head=True) 252 | output = get_version(settings["cli"], settings.get("cli_version_arg")) 253 | 254 | for pattern in settings["patterns"]: 255 | if match_version(pattern, output): 256 | show(_("~") + " MATCHED: {0}".format(pattern or "")) 257 | success.append(_("~")) 258 | break 259 | else: 260 | if settings.get("optional"): 261 | show(_("?") + " EXPECTED (OPTIONAL): {0}".format(settings["version"])) 262 | success.append(_("?")) 263 | else: 264 | if QUIET: 265 | if "not found" in output: 266 | actual = "Not found" 267 | else: 268 | actual = output.split("\n", maxsplit=1)[0].strip(".") 269 | expected = settings["version"] or "" 270 | print("{0}: {1}, EXPECTED: {2}".format(name, actual, expected)) 271 | show( 272 | _("x") 273 | + " EXPECTED: {0}".format(settings["version"] or "") 274 | ) 275 | success.append(_("x")) 276 | if settings.get("message"): 277 | show(_("#") + " MESSAGE: {0}".format(settings["message"])) 278 | 279 | show("Results: " + " ".join(success), head=True) 280 | 281 | return _("x") not in success 282 | 283 | 284 | def get_version(program, argument=None): 285 | if argument is None: 286 | args = [program, "--version"] 287 | elif argument: 288 | args = [program] + argument.split() 289 | else: 290 | args = [program] 291 | 292 | show("$ {0}".format(" ".join(args))) 293 | output = call(args) 294 | lines = output.splitlines() 295 | 296 | if lines: 297 | for line in lines: 298 | if any(char.isdigit() for char in line): 299 | show(line) 300 | break 301 | else: 302 | show(lines[0]) 303 | else: 304 | show("") 305 | 306 | return output 307 | 308 | 309 | def match_version(pattern, output): 310 | lines = output.splitlines() 311 | if not lines: 312 | return False 313 | if "not found" in lines[0]: 314 | return False 315 | if re.match(r"No .+ executable found", " ".join(lines)): 316 | return False 317 | 318 | regex = pattern.replace(".", r"\.") + r"(\b|/)" 319 | 320 | for line in lines: 321 | log.debug("Matching %s: %s", regex, line) 322 | match = re.match(regex, line) 323 | if match is None: 324 | log.debug("Matching %s: %s", regex, line) 325 | match = re.match(r".*[^\d.]" + regex, line) 326 | if match: 327 | return True 328 | 329 | return False 330 | 331 | 332 | def call(args): 333 | try: 334 | process = Popen(args, stdout=PIPE, stderr=STDOUT) 335 | except OSError: 336 | log.debug("Command not found: %s", args[0]) 337 | output = "sh: command not found: {0}".format(args[0]) 338 | else: 339 | raw = process.communicate()[0] 340 | output = raw.decode("utf-8").strip() 341 | log.debug("Command output: %r", output) 342 | 343 | return output 344 | 345 | 346 | def show(text, start="", end="\n", head=False): 347 | """Python 2 and 3 compatible version of print.""" 348 | if QUIET: 349 | return 350 | 351 | if head: 352 | start = "\n" 353 | end = "\n\n" 354 | 355 | if log.getEffectiveLevel() < logging.WARNING: 356 | log.info(text) 357 | else: 358 | formatted = start + text + end 359 | if PY2: 360 | formatted = formatted.encode("utf-8") 361 | sys.stdout.write(formatted) 362 | sys.stdout.flush() 363 | 364 | 365 | def _(word, is_tty=None, supports_utf8=None, supports_ansi=None): 366 | """Format and colorize a word based on available encoding.""" 367 | formatted = word 368 | 369 | if is_tty is None: 370 | is_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty() 371 | if supports_utf8 is None: 372 | supports_utf8 = str(sys.stdout.encoding).lower() == "utf-8" 373 | if supports_ansi is None: 374 | supports_ansi = sys.platform != "win32" or "ANSICON" in os.environ 375 | 376 | style_support = supports_utf8 377 | color_support = is_tty and supports_ansi 378 | 379 | if style_support: 380 | formatted = STYLE.get(word, word) 381 | 382 | if color_support and COLOR.get(word): 383 | formatted = COLOR[word] + formatted + COLOR[None] 384 | 385 | return formatted 386 | 387 | 388 | if __name__ == "__main__": # pragma: no cover 389 | main() 390 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/bin/verchew-wrapper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # -*- coding: utf-8 -*- 3 | # 4 | # See `verchew` for licensing and documentation links. 5 | 6 | set -e 7 | set -o pipefail 8 | 9 | BIN=$(dirname $(realpath $0)) 10 | 11 | if [ -e "${BIN}/verchew" ]; then 12 | python3 "${BIN}/verchew" "$@" 13 | elif [ -e "${BIN}/script.py" ]; then 14 | python3 "${BIN}/script.py" "$@" 15 | else 16 | echo "ERROR: 'verchew' script is missing, run 'verchew --vendor' again" 17 | exit 1 18 | fi 19 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/docs/about/changelog.md: -------------------------------------------------------------------------------- 1 | ../../CHANGELOG.md -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/docs/about/contributing.md: -------------------------------------------------------------------------------- 1 | ../../CONTRIBUTING.md -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/docs/about/license.md: -------------------------------------------------------------------------------- 1 | ../../LICENSE.md -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/docs/advanced.md: -------------------------------------------------------------------------------- 1 | # Advanced 2 | 3 | ## Feature A 4 | 5 | ## Feature B 6 | 7 | ## Feature C 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: {{cookiecutter.project_name}} 2 | site_description: {{cookiecutter.project_short_description}} 3 | site_author: {{cookiecutter.full_name}} 4 | 5 | repo_url: https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}} 6 | edit_uri: https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/edit/{{cookiecutter.default_branch}}/docs 7 | 8 | theme: readthedocs 9 | 10 | markdown_extensions: 11 | - codehilite 12 | 13 | nav: 14 | - Home: index.md 15 | - Advanced: advanced.md 16 | - About: 17 | - Release Notes: about/changelog.md 18 | - Contributor Guide: about/contributing.md 19 | - License: about/license.md 20 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/notebooks/profile_default/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/notebooks/profile_default/ipython_config.py: -------------------------------------------------------------------------------- 1 | # Configuration file for ipython. 2 | 3 | # ------------------------------------------------------------------------------ 4 | # InteractiveShellApp(Configurable) configuration 5 | # ------------------------------------------------------------------------------ 6 | 7 | ## A Mixin for applications that start InteractiveShell instances. 8 | # 9 | # Provides configurables for loading extensions and executing files as part of 10 | # configuring a Shell environment. 11 | # 12 | # The following methods should be called by the :meth:`initialize` method of the 13 | # subclass: 14 | # 15 | # - :meth:`init_path` 16 | # - :meth:`init_shell` (to be implemented by the subclass) 17 | # - :meth:`init_gui_pylab` 18 | # - :meth:`init_extensions` 19 | # - :meth:`init_code` 20 | 21 | ## Execute the given command string. 22 | # c.InteractiveShellApp.code_to_run = '' 23 | 24 | ## Run the file referenced by the PYTHONSTARTUP environment variable at IPython 25 | # startup. 26 | # c.InteractiveShellApp.exec_PYTHONSTARTUP = True 27 | 28 | ## List of files to run at IPython startup. 29 | # c.InteractiveShellApp.exec_files = [] 30 | 31 | ## lines of code to run at IPython startup. 32 | c.InteractiveShellApp.exec_lines = ['%autoreload 2'] 33 | 34 | ## A list of dotted module names of IPython extensions to load. 35 | c.InteractiveShellApp.extensions = ['autoreload'] 36 | 37 | ## dotted module name of an IPython extension to load. 38 | # c.InteractiveShellApp.extra_extension = '' 39 | 40 | ## A file to be run 41 | # c.InteractiveShellApp.file_to_run = '' 42 | 43 | ## Enable GUI event loop integration with any of ('asyncio', 'glut', 'gtk', 44 | # 'gtk2', 'gtk3', 'osx', 'pyglet', 'qt', 'qt4', 'qt5', 'tk', 'wx', 'gtk2', 45 | # 'qt4'). 46 | # c.InteractiveShellApp.gui = None 47 | 48 | ## Should variables loaded at startup (by startup files, exec_lines, etc.) be 49 | # hidden from tools like %who? 50 | # c.InteractiveShellApp.hide_initial_ns = True 51 | 52 | ## Configure matplotlib for interactive use with the default matplotlib backend. 53 | # c.InteractiveShellApp.matplotlib = None 54 | 55 | ## Run the module as a script. 56 | # c.InteractiveShellApp.module_to_run = '' 57 | 58 | ## Pre-load matplotlib and numpy for interactive use, selecting a particular 59 | # matplotlib backend and loop integration. 60 | # c.InteractiveShellApp.pylab = None 61 | 62 | ## If true, IPython will populate the user namespace with numpy, pylab, etc. and 63 | # an ``import *`` is done from numpy and pylab, when using pylab mode. 64 | # 65 | # When False, pylab mode should not import any names into the user namespace. 66 | # c.InteractiveShellApp.pylab_import_all = True 67 | 68 | ## Reraise exceptions encountered loading IPython extensions? 69 | # c.InteractiveShellApp.reraise_ipython_extension_failures = False 70 | 71 | # ------------------------------------------------------------------------------ 72 | # Application(SingletonConfigurable) configuration 73 | # ------------------------------------------------------------------------------ 74 | 75 | ## This is an application. 76 | 77 | ## The date format used by logging formatters for %(asctime)s 78 | # c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S' 79 | 80 | ## The Logging format template 81 | # c.Application.log_format = '[%(name)s]%(highlevel)s %(message)s' 82 | 83 | ## Set the log level by value or name. 84 | # c.Application.log_level = 30 85 | 86 | # ------------------------------------------------------------------------------ 87 | # BaseIPythonApplication(Application) configuration 88 | # ------------------------------------------------------------------------------ 89 | 90 | ## IPython: an enhanced interactive Python shell. 91 | 92 | ## Whether to create profile dir if it doesn't exist 93 | # c.BaseIPythonApplication.auto_create = False 94 | 95 | ## Whether to install the default config files into the profile dir. If a new 96 | # profile is being created, and IPython contains config files for that profile, 97 | # then they will be staged into the new directory. Otherwise, default config 98 | # files will be automatically generated. 99 | # c.BaseIPythonApplication.copy_config_files = False 100 | 101 | ## Path to an extra config file to load. 102 | # 103 | # If specified, load this config file in addition to any other IPython config. 104 | # c.BaseIPythonApplication.extra_config_file = '' 105 | 106 | ## The name of the IPython directory. This directory is used for logging 107 | # configuration (through profiles), history storage, etc. The default is usually 108 | # $HOME/.ipython. This option can also be specified through the environment 109 | # variable IPYTHONDIR. 110 | # c.BaseIPythonApplication.ipython_dir = '' 111 | 112 | ## Whether to overwrite existing config files when copying 113 | # c.BaseIPythonApplication.overwrite = False 114 | 115 | ## The IPython profile to use. 116 | # c.BaseIPythonApplication.profile = 'default' 117 | 118 | ## Create a massive crash report when IPython encounters what may be an internal 119 | # error. The default is to append a short message to the usual traceback 120 | # c.BaseIPythonApplication.verbose_crash = False 121 | 122 | # ------------------------------------------------------------------------------ 123 | # TerminalIPythonApp(BaseIPythonApplication,InteractiveShellApp) configuration 124 | # ------------------------------------------------------------------------------ 125 | 126 | ## Whether to display a banner upon starting IPython. 127 | # c.TerminalIPythonApp.display_banner = True 128 | 129 | ## If a command or file is given via the command-line, e.g. 'ipython foo.py', 130 | # start an interactive shell after executing the file or command. 131 | # c.TerminalIPythonApp.force_interact = False 132 | 133 | ## Class to use to instantiate the TerminalInteractiveShell object. Useful for 134 | # custom Frontends 135 | # c.TerminalIPythonApp.interactive_shell_class = 'IPython.terminal.interactiveshell.TerminalInteractiveShell' 136 | 137 | ## Start IPython quickly by skipping the loading of config files. 138 | # c.TerminalIPythonApp.quick = False 139 | 140 | # ------------------------------------------------------------------------------ 141 | # InteractiveShell(SingletonConfigurable) configuration 142 | # ------------------------------------------------------------------------------ 143 | 144 | ## An enhanced, interactive shell for Python. 145 | 146 | ## 'all', 'last', 'last_expr' or 'none', 'last_expr_or_assign' specifying which 147 | # nodes should be run interactively (displaying output from expressions). 148 | # c.InteractiveShell.ast_node_interactivity = 'last_expr' 149 | 150 | ## A list of ast.NodeTransformer subclass instances, which will be applied to 151 | # user input before code is run. 152 | # c.InteractiveShell.ast_transformers = [] 153 | 154 | ## Automatically run await statement in the top level repl. 155 | # c.InteractiveShell.autoawait = True 156 | 157 | ## Make IPython automatically call any callable object even if you didn't type 158 | # explicit parentheses. For example, 'str 43' becomes 'str(43)' automatically. 159 | # The value can be '0' to disable the feature, '1' for 'smart' autocall, where 160 | # it is not applied if there are no more arguments on the line, and '2' for 161 | # 'full' autocall, where all callable objects are automatically called (even if 162 | # no arguments are present). 163 | # c.InteractiveShell.autocall = 0 164 | 165 | ## Autoindent IPython code entered interactively. 166 | # c.InteractiveShell.autoindent = True 167 | 168 | ## Enable magic commands to be called without the leading %. 169 | # c.InteractiveShell.automagic = True 170 | 171 | ## The part of the banner to be printed before the profile 172 | # c.InteractiveShell.banner1 = "Python 3.8.1 (default, Jan 9 2020, 14:37:22) \nType 'copyright', 'credits' or 'license' for more information\nIPython 7.12.0 -- An enhanced Interactive Python. Type '?' for help.\n" 173 | 174 | ## The part of the banner to be printed after the profile 175 | # c.InteractiveShell.banner2 = '' 176 | 177 | ## Set the size of the output cache. The default is 1000, you can change it 178 | # permanently in your config file. Setting it to 0 completely disables the 179 | # caching system, and the minimum value accepted is 3 (if you provide a value 180 | # less than 3, it is reset to 0 and a warning is issued). This limit is defined 181 | # because otherwise you'll spend more time re-flushing a too small cache than 182 | # working 183 | # c.InteractiveShell.cache_size = 1000 184 | 185 | ## Use colors for displaying information about objects. Because this information 186 | # is passed through a pager (like 'less'), and some pagers get confused with 187 | # color codes, this capability can be turned off. 188 | # c.InteractiveShell.color_info = True 189 | 190 | ## Set the color scheme (NoColor, Neutral, Linux, or LightBG). 191 | # c.InteractiveShell.colors = 'Neutral' 192 | 193 | ## 194 | # c.InteractiveShell.debug = False 195 | 196 | ## Don't call post-execute functions that have failed in the past. 197 | # c.InteractiveShell.disable_failing_post_execute = False 198 | 199 | ## If True, anything that would be passed to the pager will be displayed as 200 | # regular output instead. 201 | # c.InteractiveShell.display_page = False 202 | 203 | ## (Provisional API) enables html representation in mime bundles sent to pagers. 204 | # c.InteractiveShell.enable_html_pager = False 205 | 206 | ## Total length of command history 207 | # c.InteractiveShell.history_length = 10000 208 | 209 | ## The number of saved history entries to be loaded into the history buffer at 210 | # startup. 211 | # c.InteractiveShell.history_load_length = 1000 212 | 213 | ## 214 | # c.InteractiveShell.ipython_dir = '' 215 | 216 | ## Start logging to the given file in append mode. Use `logfile` to specify a log 217 | # file to **overwrite** logs to. 218 | # c.InteractiveShell.logappend = '' 219 | 220 | ## The name of the logfile to use. 221 | # c.InteractiveShell.logfile = '' 222 | 223 | ## Start logging to the default log file in overwrite mode. Use `logappend` to 224 | # specify a log file to **append** logs to. 225 | # c.InteractiveShell.logstart = False 226 | 227 | ## Select the loop runner that will be used to execute top-level asynchronous 228 | # code 229 | # c.InteractiveShell.loop_runner = 'IPython.core.interactiveshell._asyncio_runner' 230 | 231 | ## 232 | # c.InteractiveShell.object_info_string_level = 0 233 | 234 | ## Automatically call the pdb debugger after every exception. 235 | # c.InteractiveShell.pdb = False 236 | 237 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 238 | # TerminalInteractiveShell.prompts object directly. 239 | # c.InteractiveShell.prompt_in1 = 'In [\\#]: ' 240 | 241 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 242 | # TerminalInteractiveShell.prompts object directly. 243 | # c.InteractiveShell.prompt_in2 = ' .\\D.: ' 244 | 245 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 246 | # TerminalInteractiveShell.prompts object directly. 247 | # c.InteractiveShell.prompt_out = 'Out[\\#]: ' 248 | 249 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 250 | # TerminalInteractiveShell.prompts object directly. 251 | # c.InteractiveShell.prompts_pad_left = True 252 | 253 | ## 254 | # c.InteractiveShell.quiet = False 255 | 256 | ## 257 | # c.InteractiveShell.separate_in = '\n' 258 | 259 | ## 260 | # c.InteractiveShell.separate_out = '' 261 | 262 | ## 263 | # c.InteractiveShell.separate_out2 = '' 264 | 265 | ## Show rewritten input, e.g. for autocall. 266 | # c.InteractiveShell.show_rewritten_input = True 267 | 268 | ## Enables rich html representation of docstrings. (This requires the docrepr 269 | # module). 270 | # c.InteractiveShell.sphinxify_docstring = False 271 | 272 | ## 273 | # c.InteractiveShell.wildcards_case_sensitive = True 274 | 275 | ## Switch modes for the IPython exception handlers. 276 | # c.InteractiveShell.xmode = 'Context' 277 | 278 | # ------------------------------------------------------------------------------ 279 | # TerminalInteractiveShell(InteractiveShell) configuration 280 | # ------------------------------------------------------------------------------ 281 | 282 | ## Autoformatter to reformat Terminal code. Can be `'black'` or `None` 283 | # c.TerminalInteractiveShell.autoformatter = None 284 | 285 | ## Set to confirm when you try to exit IPython with an EOF (Control-D in Unix, 286 | # Control-Z/Enter in Windows). By typing 'exit' or 'quit', you can force a 287 | # direct exit without any confirmation. 288 | # c.TerminalInteractiveShell.confirm_exit = True 289 | 290 | ## Options for displaying tab completions, 'column', 'multicolumn', and 291 | # 'readlinelike'. These options are for `prompt_toolkit`, see `prompt_toolkit` 292 | # documentation for more information. 293 | # c.TerminalInteractiveShell.display_completions = 'multicolumn' 294 | 295 | ## Shortcut style to use at the prompt. 'vi' or 'emacs'. 296 | # c.TerminalInteractiveShell.editing_mode = 'emacs' 297 | 298 | ## Set the editor used by IPython (default to $EDITOR/vi/notepad). 299 | # c.TerminalInteractiveShell.editor = 'vim' 300 | 301 | ## Allows to enable/disable the prompt toolkit history search 302 | # c.TerminalInteractiveShell.enable_history_search = True 303 | 304 | ## Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. This is 305 | # in addition to the F2 binding, which is always enabled. 306 | # c.TerminalInteractiveShell.extra_open_editor_shortcuts = False 307 | 308 | ## Provide an alternative handler to be called when the user presses Return. This 309 | # is an advanced option intended for debugging, which may be changed or removed 310 | # in later releases. 311 | # c.TerminalInteractiveShell.handle_return = None 312 | 313 | ## Highlight matching brackets. 314 | # c.TerminalInteractiveShell.highlight_matching_brackets = True 315 | 316 | ## The name or class of a Pygments style to use for syntax highlighting. To see 317 | # available styles, run `pygmentize -L styles`. 318 | # c.TerminalInteractiveShell.highlighting_style = traitlets.Undefined 319 | 320 | ## Override highlighting format for specific tokens 321 | # c.TerminalInteractiveShell.highlighting_style_overrides = {} 322 | 323 | ## 324 | # c.TerminalInteractiveShell.mime_renderers = {} 325 | 326 | ## Enable mouse support in the prompt (Note: prevents selecting text with the 327 | # mouse) 328 | # c.TerminalInteractiveShell.mouse_support = False 329 | 330 | ## Display the current vi mode (when using vi editing mode). 331 | # c.TerminalInteractiveShell.prompt_includes_vi_mode = True 332 | 333 | ## Class used to generate Prompt token for prompt_toolkit 334 | # c.TerminalInteractiveShell.prompts_class = 'IPython.terminal.prompts.Prompts' 335 | 336 | ## Use `raw_input` for the REPL, without completion and prompt colors. 337 | # 338 | # Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. 339 | # Known usage are: IPython own testing machinery, and emacs inferior-shell 340 | # integration through elpy. 341 | # 342 | # This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` environment 343 | # variable is set, or the current terminal is not a tty. 344 | # c.TerminalInteractiveShell.simple_prompt = False 345 | 346 | ## Number of line at the bottom of the screen to reserve for the completion menu 347 | # c.TerminalInteractiveShell.space_for_menu = 6 348 | 349 | ## Automatically set the terminal title 350 | # c.TerminalInteractiveShell.term_title = True 351 | 352 | ## Customize the terminal title format. This is a python format string. 353 | # Available substitutions are: {cwd}. 354 | # c.TerminalInteractiveShell.term_title_format = 'IPython: {cwd}' 355 | 356 | ## Use 24bit colors instead of 256 colors in prompt highlighting. If your 357 | # terminal supports true color, the following command should print 'TRUECOLOR' 358 | # in orange: printf "\x1b[38;2;255;100;0mTRUECOLOR\x1b[0m\n" 359 | # c.TerminalInteractiveShell.true_color = False 360 | 361 | # ------------------------------------------------------------------------------ 362 | # HistoryAccessor(HistoryAccessorBase) configuration 363 | # ------------------------------------------------------------------------------ 364 | 365 | ## Access the history database without adding to it. 366 | # 367 | # This is intended for use by standalone history tools. IPython shells use 368 | # HistoryManager, below, which is a subclass of this. 369 | 370 | ## Options for configuring the SQLite connection 371 | # 372 | # These options are passed as keyword args to sqlite3.connect when establishing 373 | # database connections. 374 | # c.HistoryAccessor.connection_options = {} 375 | 376 | ## enable the SQLite history 377 | # 378 | # set enabled=False to disable the SQLite history, in which case there will be 379 | # no stored history, no SQLite connection, and no background saving thread. 380 | # This may be necessary in some threaded environments where IPython is embedded. 381 | # c.HistoryAccessor.enabled = True 382 | 383 | ## Path to file to use for SQLite history database. 384 | # 385 | # By default, IPython will put the history database in the IPython profile 386 | # directory. If you would rather share one history among profiles, you can set 387 | # this value in each, so that they are consistent. 388 | # 389 | # Due to an issue with fcntl, SQLite is known to misbehave on some NFS mounts. 390 | # If you see IPython hanging, try setting this to something on a local disk, 391 | # e.g:: 392 | # 393 | # ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite 394 | # 395 | # you can also use the specific value `:memory:` (including the colon at both 396 | # end but not the back ticks), to avoid creating an history file. 397 | # c.HistoryAccessor.hist_file = '' 398 | 399 | # ------------------------------------------------------------------------------ 400 | # HistoryManager(HistoryAccessor) configuration 401 | # ------------------------------------------------------------------------------ 402 | 403 | ## A class to organize all history-related functionality in one place. 404 | 405 | ## Write to database every x commands (higher values save disk access & power). 406 | # Values of 1 or less effectively disable caching. 407 | # c.HistoryManager.db_cache_size = 0 408 | 409 | ## Should the history database include output? (default: no) 410 | # c.HistoryManager.db_log_output = False 411 | 412 | # ------------------------------------------------------------------------------ 413 | # ProfileDir(LoggingConfigurable) configuration 414 | # ------------------------------------------------------------------------------ 415 | 416 | ## An object to manage the profile directory and its resources. 417 | # 418 | # The profile directory is used by all IPython applications, to manage 419 | # configuration, logging and security. 420 | # 421 | # This object knows how to find, create and manage these directories. This 422 | # should be used by any code that wants to handle profiles. 423 | 424 | ## Set the profile location directly. This overrides the logic used by the 425 | # `profile` option. 426 | # c.ProfileDir.location = '' 427 | 428 | # ------------------------------------------------------------------------------ 429 | # BaseFormatter(Configurable) configuration 430 | # ------------------------------------------------------------------------------ 431 | 432 | ## A base formatter class that is configurable. 433 | # 434 | # This formatter should usually be used as the base class of all formatters. It 435 | # is a traited :class:`Configurable` class and includes an extensible API for 436 | # users to determine how their objects are formatted. The following logic is 437 | # used to find a function to format an given object. 438 | # 439 | # 1. The object is introspected to see if it has a method with the name 440 | # :attr:`print_method`. If is does, that object is passed to that method 441 | # for formatting. 442 | # 2. If no print method is found, three internal dictionaries are consulted 443 | # to find print method: :attr:`singleton_printers`, :attr:`type_printers` 444 | # and :attr:`deferred_printers`. 445 | # 446 | # Users should use these dictionaries to register functions that will be used to 447 | # compute the format data for their objects (if those objects don't have the 448 | # special print methods). The easiest way of using these dictionaries is through 449 | # the :meth:`for_type` and :meth:`for_type_by_name` methods. 450 | # 451 | # If no function/callable is found to compute the format data, ``None`` is 452 | # returned and this format type is not used. 453 | 454 | ## 455 | # c.BaseFormatter.deferred_printers = {} 456 | 457 | ## 458 | # c.BaseFormatter.enabled = True 459 | 460 | ## 461 | # c.BaseFormatter.singleton_printers = {} 462 | 463 | ## 464 | # c.BaseFormatter.type_printers = {} 465 | 466 | # ------------------------------------------------------------------------------ 467 | # PlainTextFormatter(BaseFormatter) configuration 468 | # ------------------------------------------------------------------------------ 469 | 470 | ## The default pretty-printer. 471 | # 472 | # This uses :mod:`IPython.lib.pretty` to compute the format data of the object. 473 | # If the object cannot be pretty printed, :func:`repr` is used. See the 474 | # documentation of :mod:`IPython.lib.pretty` for details on how to write pretty 475 | # printers. Here is a simple example:: 476 | # 477 | # def dtype_pprinter(obj, p, cycle): 478 | # if cycle: 479 | # return p.text('dtype(...)') 480 | # if hasattr(obj, 'fields'): 481 | # if obj.fields is None: 482 | # p.text(repr(obj)) 483 | # else: 484 | # p.begin_group(7, 'dtype([') 485 | # for i, field in enumerate(obj.descr): 486 | # if i > 0: 487 | # p.text(',') 488 | # p.breakable() 489 | # p.pretty(field) 490 | # p.end_group(7, '])') 491 | 492 | ## 493 | # c.PlainTextFormatter.float_precision = '' 494 | 495 | ## Truncate large collections (lists, dicts, tuples, sets) to this size. 496 | # 497 | # Set to 0 to disable truncation. 498 | # c.PlainTextFormatter.max_seq_length = 1000 499 | 500 | ## 501 | # c.PlainTextFormatter.max_width = 79 502 | 503 | ## 504 | # c.PlainTextFormatter.newline = '\n' 505 | 506 | ## 507 | # c.PlainTextFormatter.pprint = True 508 | 509 | ## 510 | # c.PlainTextFormatter.verbose = False 511 | 512 | # ------------------------------------------------------------------------------ 513 | # Completer(Configurable) configuration 514 | # ------------------------------------------------------------------------------ 515 | 516 | ## Enable unicode completions, e.g. \alpha . Includes completion of latex 517 | # commands, unicode names, and expanding unicode characters back to latex 518 | # commands. 519 | # c.Completer.backslash_combining_completions = True 520 | 521 | ## Enable debug for the Completer. Mostly print extra information for 522 | # experimental jedi integration. 523 | # c.Completer.debug = False 524 | 525 | ## Activate greedy completion PENDING DEPRECTION. this is now mostly taken care 526 | # of with Jedi. 527 | # 528 | # This will enable completion on elements of lists, results of function calls, 529 | # etc., but can be unsafe because the code is actually evaluated on TAB. 530 | # c.Completer.greedy = False 531 | 532 | ## Experimental: restrict time (in milliseconds) during which Jedi can compute 533 | # types. Set to 0 to stop computing types. Non-zero value lower than 100ms may 534 | # hurt performance by preventing jedi to build its cache. 535 | # c.Completer.jedi_compute_type_timeout = 400 536 | 537 | ## Experimental: Use Jedi to generate autocompletions. Default to True if jedi is 538 | # installed. 539 | # c.Completer.use_jedi = True 540 | 541 | # ------------------------------------------------------------------------------ 542 | # IPCompleter(Completer) configuration 543 | # ------------------------------------------------------------------------------ 544 | 545 | ## Extension of the completer class with IPython-specific features 546 | 547 | ## DEPRECATED as of version 5.0. 548 | # 549 | # Instruct the completer to use __all__ for the completion 550 | # 551 | # Specifically, when completing on ``object.``. 552 | # 553 | # When True: only those names in obj.__all__ will be included. 554 | # 555 | # When False [default]: the __all__ attribute is ignored 556 | # c.IPCompleter.limit_to__all__ = False 557 | 558 | ## Whether to merge completion results into a single list 559 | # 560 | # If False, only the completion results from the first non-empty completer will 561 | # be returned. 562 | # c.IPCompleter.merge_completions = True 563 | 564 | ## Instruct the completer to omit private method names 565 | # 566 | # Specifically, when completing on ``object.``. 567 | # 568 | # When 2 [default]: all names that start with '_' will be excluded. 569 | # 570 | # When 1: all 'magic' names (``__foo__``) will be excluded. 571 | # 572 | # When 0: nothing will be excluded. 573 | # c.IPCompleter.omit__names = 2 574 | 575 | # ------------------------------------------------------------------------------ 576 | # ScriptMagics(Magics) configuration 577 | # ------------------------------------------------------------------------------ 578 | 579 | ## Magics for talking to scripts 580 | # 581 | # This defines a base `%%script` cell magic for running a cell with a program in 582 | # a subprocess, and registers a few top-level magics that call %%script with 583 | # common interpreters. 584 | 585 | ## Extra script cell magics to define 586 | # 587 | # This generates simple wrappers of `%%script foo` as `%%foo`. 588 | # 589 | # If you want to add script magics that aren't on your path, specify them in 590 | # script_paths 591 | # c.ScriptMagics.script_magics = [] 592 | 593 | ## Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby' 594 | # 595 | # Only necessary for items in script_magics where the default path will not find 596 | # the right interpreter. 597 | # c.ScriptMagics.script_paths = {} 598 | 599 | # ------------------------------------------------------------------------------ 600 | # LoggingMagics(Magics) configuration 601 | # ------------------------------------------------------------------------------ 602 | 603 | ## Magics related to all logging machinery. 604 | 605 | ## Suppress output of log state when logging is enabled 606 | # c.LoggingMagics.quiet = False 607 | 608 | # ------------------------------------------------------------------------------ 609 | # StoreMagics(Magics) configuration 610 | # ------------------------------------------------------------------------------ 611 | 612 | ## Lightweight persistence for python variables. 613 | # 614 | # Provides the %store magic. 615 | 616 | ## If True, any %store-d variables will be automatically restored when IPython 617 | # starts. 618 | # c.StoreMagics.autorestore = False 619 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/notebooks/profile_default/startup/README: -------------------------------------------------------------------------------- 1 | This is the IPython startup directory 2 | 3 | .py and .ipy files in this directory will be run *prior* to any code or files specified 4 | via the exec_lines or exec_files configurables whenever you load this profile. 5 | 6 | Files will be run in lexicographical order, so you can control the execution order of files 7 | with a prefix, e.g.:: 8 | 9 | 00-first.py 10 | 50-middle.py 11 | 99-last.ipy 12 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/notebooks/profile_default/startup/ipython_startup.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacebrowning/template-python/73138a2c024f8fd09e39fe2e65f2fa7259d5f771/{{cookiecutter.project_name}}/notebooks/profile_default/startup/ipython_startup.py -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | 3 | name = "{{cookiecutter.project_name}}" 4 | version = "0.0" 5 | description = "{{cookiecutter.project_short_description}}" 6 | 7 | packages = [{ include = "{{cookiecutter.package_name}}" }] 8 | 9 | license = "MIT" 10 | authors = ["{{cookiecutter.full_name}} <{{cookiecutter.email}}>"] 11 | 12 | readme = "README.md" 13 | homepage = "https://pypi.org/project/{{cookiecutter.project_name}}" 14 | documentation = "https://{{cookiecutter.project_name}}.readthedocs.io" 15 | repository = "https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}" 16 | 17 | keywords = [ 18 | ] 19 | classifiers = [ 20 | # TODO: update this list to match your application: https://pypi.org/pypi?%3Aaction=list_classifiers 21 | "Development Status :: 1 - Planning", 22 | "Natural Language :: English", 23 | "Operating System :: OS Independent", 24 | "Programming Language :: Python", 25 | "Programming Language :: Python :: {{cookiecutter.python_major_version}}", 26 | "Programming Language :: Python :: {{cookiecutter.python_major_version}}.{{cookiecutter.python_minor_version}}", 27 | ] 28 | 29 | [tool.poetry.dependencies] 30 | 31 | python = "^{{cookiecutter.python_major_version}}.{{cookiecutter.python_minor_version}}" 32 | 33 | # TODO: Remove these and add your library's requirements 34 | click = "*" 35 | minilog = "*" 36 | 37 | [tool.poetry.group.dev.dependencies] 38 | 39 | # Formatters 40 | black = "^22.1" 41 | tomli = "*" # missing 'black' dependency 42 | isort = "^5.10" 43 | 44 | # Linters 45 | mypy = "^1.0" 46 | pydocstyle = "^6.1" 47 | pylint = "~2.15" 48 | pylint-pytest = "*" 49 | wrapt = "*" # missing 'pylint' dependency 50 | 51 | # Testing 52 | pytest = "^8.1" 53 | pytest-describe = "^2.0" 54 | pytest-expecter = "^3.0" 55 | pytest-random = "*" 56 | pytest-cov = "^4.1" 57 | freezegun = "*" 58 | 59 | # Reports 60 | coveragespace = "^6.1" 61 | 62 | # Documentation 63 | mkdocs = "~1.3" 64 | pygments = "^2.11.1" 65 | 66 | # Tooling 67 | pyinstaller = "*" 68 | sniffer = "*" 69 | MacFSEvents = { version = "*", platform = "darwin" } 70 | pync = { version = "*", platform = "darwin" } 71 | ipython = "^7.12.0" 72 | 73 | [tool.poetry.requires-plugins] 74 | 75 | poetry-plugin-export = ">=1.8" 76 | 77 | [tool.poetry.scripts] 78 | 79 | {{cookiecutter.project_name}} = "{{cookiecutter.package_name}}.cli:main" 80 | 81 | [tool.black] 82 | 83 | quiet = true 84 | 85 | [tool.isort] 86 | 87 | profile = "black" 88 | 89 | [tool.mypy] 90 | 91 | ignore_missing_imports = true 92 | no_implicit_optional = true 93 | check_untyped_defs = true 94 | 95 | cache_dir = ".cache/mypy/" 96 | 97 | [tool.pytest.ini_options] 98 | 99 | addopts = """ 100 | --strict-markers 101 | 102 | -r sxX 103 | --show-capture=log 104 | 105 | --cov-report=html 106 | --cov-report=term-missing:skip-covered 107 | --no-cov-on-fail 108 | """ 109 | 110 | cache_dir = ".cache/pytest/" 111 | 112 | markers = [] 113 | 114 | [build-system] 115 | 116 | requires = ["poetry-core>=1.0.0"] 117 | build-backend = "poetry.core.masonry.api" 118 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/scent.py: -------------------------------------------------------------------------------- 1 | """Configuration file for sniffer.""" 2 | 3 | import time 4 | import subprocess 5 | 6 | from sniffer.api import select_runnable, file_validator, runnable 7 | try: 8 | from pync import Notifier 9 | except ImportError: 10 | notify = None 11 | else: 12 | notify = Notifier.notify 13 | 14 | 15 | watch_paths = ["{{cookiecutter.package_name}}", "tests"] 16 | 17 | 18 | class Options: 19 | group = int(time.time()) # unique per run 20 | show_coverage = False 21 | rerun_args = None 22 | 23 | targets = [ 24 | (("make", "test-unit", "DISABLE_COVERAGE=true"), "Unit Tests", True), 25 | (("make", "test-all"), "Integration Tests", False), 26 | (("make", "check"), "Static Analysis", True), 27 | (("make", "docs", "CI=true"), None, True), 28 | ] 29 | 30 | 31 | @select_runnable("run_targets") 32 | @file_validator 33 | def python_files(filename): 34 | return filename.endswith(".py") and ".py." not in filename 35 | 36 | 37 | @select_runnable("run_targets") 38 | @file_validator 39 | def html_files(filename): 40 | return filename.split(".")[-1] in ["html", "css", "js"] 41 | 42 | 43 | @runnable 44 | def run_targets(*args): 45 | """Run targets for Python.""" 46 | Options.show_coverage = "coverage" in args 47 | 48 | count = 0 49 | for count, (command, title, retry) in enumerate(Options.targets, start=1): 50 | 51 | success = call(command, title, retry) 52 | if not success: 53 | message = "✅ " * (count - 1) + "❌" 54 | show_notification(message, title) 55 | 56 | return False 57 | 58 | message = "✅ " * count 59 | title = "All Targets" 60 | show_notification(message, title) 61 | show_coverage() 62 | 63 | return True 64 | 65 | 66 | def call(command, title, retry): 67 | """Run a command-line program and display the result.""" 68 | if Options.rerun_args: 69 | command, title, retry = Options.rerun_args 70 | Options.rerun_args = None 71 | success = call(command, title, retry) 72 | if not success: 73 | return False 74 | 75 | print("") 76 | print("$ %s" % " ".join(command)) 77 | failure = subprocess.call(command) 78 | 79 | if failure and retry: 80 | Options.rerun_args = command, title, retry 81 | 82 | return not failure 83 | 84 | 85 | def show_notification(message, title): 86 | """Show a user notification.""" 87 | if notify and title: 88 | notify(message, title=title, group=Options.group) 89 | 90 | 91 | def show_coverage(): 92 | """Launch the coverage report.""" 93 | if Options.show_coverage: 94 | subprocess.call(["make", "read-coverage"]) 95 | 96 | Options.show_coverage = False 97 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Integration tests for the package.""" 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Integration tests configuration file.""" 2 | 3 | # pylint: disable=unused-import 4 | 5 | from {{cookiecutter.package_name}}.tests.conftest import pytest_configure 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | """Sample integration test module using pytest-describe and expecter.""" 2 | # pylint: disable=unused-variable,expression-not-assigned 3 | 4 | import pytest 5 | from expecter import expect 6 | 7 | from click.testing import CliRunner 8 | 9 | from {{cookiecutter.package_name}}.cli import main 10 | 11 | 12 | @pytest.fixture 13 | def runner(): 14 | return CliRunner() 15 | 16 | 17 | def describe_cli(): 18 | 19 | def describe_conversion(): 20 | 21 | def when_integer(runner): 22 | result = runner.invoke(main, ['42']) 23 | 24 | expect(result.exit_code) == 0 25 | expect(result.output) == "12.80165\n" 26 | 27 | def when_invalid(runner): 28 | result = runner.invoke(main, ['foobar']) 29 | 30 | expect(result.exit_code) == 0 31 | expect(result.output) == "" 32 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib.metadata import PackageNotFoundError, version 2 | 3 | try: 4 | __version__ = version('{{cookiecutter.project_name}}') 5 | except PackageNotFoundError: 6 | __version__ = '(local)' 7 | 8 | del PackageNotFoundError 9 | del version 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Package entry point.""" 4 | 5 | 6 | from {{cookiecutter.package_name}}.cli import main 7 | 8 | 9 | if __name__ == '__main__': # pragma: no cover 10 | main() # pylint: disable=no-value-for-parameter 11 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/cli.py: -------------------------------------------------------------------------------- 1 | """A sample CLI.""" 2 | 3 | import click 4 | import log 5 | 6 | from . import utils 7 | 8 | 9 | @click.command() 10 | @click.argument('feet') 11 | def main(feet: str): 12 | log.init() 13 | 14 | meters = utils.feet_to_meters(feet) 15 | 16 | if meters is not None: 17 | click.echo(meters) 18 | 19 | 20 | if __name__ == '__main__': # pragma: no cover 21 | main() # pylint: disable=no-value-for-parameter 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/gui.py: -------------------------------------------------------------------------------- 1 | """A sample GUI.""" 2 | 3 | # pylint: disable=wildcard-import,unused-wildcard-import 4 | 5 | from tkinter import * 6 | from tkinter.ttk import * # type: ignore 7 | 8 | import log 9 | 10 | from . import utils 11 | 12 | 13 | class Application: 14 | """A sample class.""" 15 | 16 | def __init__(self): 17 | log.info("Starting the application...") 18 | 19 | # Configure the root window 20 | self.root = Tk() 21 | self.root.title("Feet to Meters") 22 | self.root.minsize(400, 150) 23 | 24 | # Initialize elements 25 | self.feet = StringVar() 26 | self.meters = StringVar() 27 | frame = self.init(self.root) 28 | frame.pack(fill=BOTH, expand=1) 29 | 30 | # Start the event loop 31 | self.root.mainloop() 32 | 33 | def init(self, root): 34 | padded = {'padding': 5} 35 | sticky = {'sticky': NSEW} 36 | 37 | # Configure grid 38 | frame = Frame(root, **padded) # type: ignore 39 | frame.rowconfigure(0, weight=1) 40 | frame.rowconfigure(1, weight=1) 41 | frame.columnconfigure(0, weight=1) 42 | 43 | def frame_inputs(root): 44 | frame = Frame(root, **padded) # type: ignore 45 | 46 | # Configure grid 47 | frame.rowconfigure(0, weight=1) 48 | frame.rowconfigure(1, weight=1) 49 | frame.columnconfigure(0, weight=1) 50 | frame.columnconfigure(1, weight=1) 51 | frame.columnconfigure(2, weight=1) 52 | 53 | # Place widgets 54 | entry = Entry(frame, width=7, textvariable=self.feet) 55 | entry.grid(column=1, row=0) 56 | entry.focus() 57 | label = Label(frame, text="feet") 58 | label.grid(column=2, row=0) 59 | label = Label(frame, text="is equivalent to") 60 | label.grid(column=0, row=1) 61 | label = Label(frame, text="meters") 62 | label.grid(column=2, row=1) 63 | label = Label(frame, textvariable=self.meters) 64 | label.grid(column=1, row=1) 65 | 66 | return frame 67 | 68 | def frame_commands(root): 69 | frame = Frame(root, **padded) # type: ignore 70 | 71 | # Configure grid 72 | frame.rowconfigure(0, weight=1) 73 | frame.columnconfigure(0, weight=1) 74 | 75 | # Place widgets 76 | button = Button(frame, text="Calculate", command=self.calculate) 77 | button.grid(column=0, row=0) 78 | self.root.bind('', self.calculate) 79 | 80 | return frame 81 | 82 | def separator(root): 83 | return Separator(root) 84 | 85 | # Place widgets 86 | frame_inputs(frame).grid(row=0, **sticky) 87 | separator(frame).grid(row=1, padx=10, pady=5, **sticky) 88 | frame_commands(frame).grid(row=2, **sticky) 89 | 90 | return frame 91 | 92 | def calculate(self, event=None): 93 | log.debug("Event: %s", event) 94 | meters = utils.feet_to_meters(self.feet.get()) 95 | if meters is not None: 96 | self.meters.set(meters) 97 | 98 | 99 | def main(): 100 | log.init() 101 | Application() 102 | 103 | 104 | if __name__ == '__main__': # pragma: no cover 105 | main() 106 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the package.""" 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Unit tests configuration file.""" 2 | 3 | import log 4 | 5 | 6 | def pytest_configure(config): 7 | """Disable verbose output when running tests.""" 8 | log.init(debug=True) 9 | 10 | terminal = config.pluginmanager.getplugin('terminal') 11 | terminal.TerminalReporter.showfspath = False 12 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """Sample unit test module using pytest-describe and expecter.""" 2 | # pylint: disable=unused-variable,expression-not-assigned,singleton-comparison 3 | 4 | from {{cookiecutter.package_name}} import utils 5 | 6 | 7 | def describe_feet_to_meters(): 8 | 9 | def when_integer(expect): 10 | expect(utils.feet_to_meters(42)) == 12.80165 11 | 12 | def when_string(expect): 13 | expect(utils.feet_to_meters("hello")) == None 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_name}}/{{cookiecutter.package_name}}/utils.py: -------------------------------------------------------------------------------- 1 | """A sample module.""" 2 | 3 | import log 4 | 5 | 6 | def feet_to_meters(feet): 7 | """Convert feet to meters.""" 8 | try: 9 | value = float(feet) 10 | except ValueError: 11 | log.error("Unable to convert to float: %s", feet) 12 | return None 13 | else: 14 | return (0.3048 * value * 10000.0 + 0.5) / 10000.0 15 | --------------------------------------------------------------------------------