├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── CONTRIBUTING.md ├── HISTORY.md ├── LICENSE ├── README.md ├── cookiecutter.json ├── docs ├── console_script_setup.md ├── faq.md ├── history.md ├── index.md ├── pypi_release_checklist.md └── tutorial.md ├── hooks ├── aioproc.py ├── post_gen_project.py └── pre_gen_project.py ├── mkdocs.yml ├── poetry.lock ├── ppw └── cli.py ├── pyproject.toml ├── reload.sh ├── repo.sh ├── tests └── test_bake_project.py ├── tox.ini └── {{cookiecutter.project_slug}} ├── .coveragerc ├── .docstring.tpl ├── .editorconfig ├── .flake8 ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── .isort.cfg ├── .pre-commit-config.yaml ├── AUTHORS.md ├── CONTRIBUTING.md ├── HISTORY.md ├── LICENSE ├── README.md ├── docs ├── api.md ├── authors.md ├── contributing.md ├── history.md ├── index.md ├── installation.md └── usage.md ├── mkdocs.yml ├── mypy.ini ├── pyproject.toml ├── pyrightconfig.json ├── repo.sh ├── tests ├── __init__.py └── test_app.py ├── tox.ini └── {{cookiecutter.project_slug}} ├── __init__.py ├── app.py ├── cli.py └── py.typed /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | 23 | [*.{diff,patch}] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Date you used Cookiecutter PyPackage: 2 | * Cookiecutter version used, if any: 3 | * Python version, if any: 4 | * Operating System: 5 | 6 | ### Description 7 | 8 | Describe what you were trying to get done. Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | ``` 15 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: dev build CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events 8 | push: 9 | branches: 10 | - '*' 11 | pull_request: 12 | branches: 13 | - '*' 14 | 15 | # Allows you to run this workflow manually from the Actions tab 16 | workflow_dispatch: 17 | 18 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 19 | jobs: 20 | test: 21 | # The type of runner that the job will run on 22 | strategy: 23 | matrix: 24 | python-versions: ['3.8', '3.9', '3.10', '3.11'] 25 | # github action doesn't goes well with windows due to docker support 26 | # github action doesn't goes well with macos due to `no docker command` 27 | #os: [ubuntu-20.04, windows-latest, macos-latest] 28 | os: [ubuntu-20.04] 29 | runs-on: ${{ matrix.os }} 30 | # map step outputs to job outputs so they can be share among jobs 31 | outputs: 32 | package_version: ${{ steps.variables_step.outputs.package_version }} 33 | package_name: ${{ steps.variables_step.outputs.package_name }} 34 | repo_name: ${{ steps.variables_step.outputs.repo_name }} 35 | repo_owner: ${{ steps.variables_step.outputs.repo_owner }} 36 | 37 | # Steps represent a sequence of tasks that will be executed as part of the job 38 | steps: 39 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 40 | - uses: actions/checkout@v4 41 | - uses: actions/setup-python@v4 42 | with: 43 | python-version: ${{ matrix.python-versions }} 44 | 45 | - name: Install dependencies 46 | run: | 47 | python -m pip install --upgrade pip 48 | pip install tox tox-gh-actions poetry 49 | 50 | # declare package_version, repo_owner, repo_name, package_name so you may use it in web hooks. 51 | - name: Declare variables for convenient use 52 | id: variables_step 53 | run: | 54 | echo "repo_owner=${GITHUB_REPOSITORY%/*}" >> $GITHUB_OUTPUT 55 | echo "repo_name=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT 56 | echo "package_name=`poetry version | awk '{print $1}'`" >> $GITHUB_OUTPUT 57 | echo "package_version=`poetry version --short`" >> $GITHUB_OUTPUT 58 | shell: bash 59 | 60 | - name: test with tox 61 | run: tox 62 | 63 | publish_dev_build: 64 | # if test failed, we should not publish 65 | needs: test 66 | # you may need to change os below 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/checkout@v4 70 | - uses: actions/setup-python@v4 71 | with: 72 | python-version: '3.9' 73 | 74 | - name: Install dependencies 75 | run: | 76 | python -m pip install --upgrade pip 77 | pip install poetry tox tox-gh-actions 78 | 79 | - name: build documentation 80 | run: | 81 | poetry install -E dev 82 | poetry run mkdocs build 83 | git config --global user.name Docs deploy 84 | git config --global user.email docs@dummy.bot.com 85 | poetry run mike deploy -p -f --ignore "`poetry version --short`.dev" 86 | poetry run mike set-default -p "`poetry version --short`.dev" 87 | 88 | - name: Build wheels and source tarball 89 | run: | 90 | poetry version $(poetry version --short)-dev.$GITHUB_RUN_NUMBER 91 | poetry lock 92 | poetry build 93 | 94 | - name: publish to Test PyPI 95 | uses: pypa/gh-action-pypi-publish@release/v1 96 | with: 97 | user: __token__ 98 | password: ${{ secrets.TEST_PYPI_API_TOKEN}} 99 | repository_url: https://test.pypi.org/legacy/ 100 | skip_existing: true 101 | 102 | notification: 103 | needs: [test,publish_dev_build] 104 | if: always() 105 | runs-on: ubuntu-latest 106 | steps: 107 | - uses: martialonline/workflow-status@v2 108 | id: check 109 | 110 | - name: build success notification via email 111 | if: ${{ steps.check.outputs.status == 'success' }} 112 | uses: dawidd6/action-send-mail@v3 113 | with: 114 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 115 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 116 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 117 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 118 | from: build-bot 119 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 120 | subject: ${{ needs.test.outputs.package_name }}.${{ needs.test.outputs.package_version}}.${{ github.run_number }} build successfully 121 | convert_markdown: true 122 | html_body: | 123 | ## Build Success 124 | ${{ needs.test.outputs.package_name }}.${{ needs.test.outputs.package_version }}.dev.${{ github.run_number }} is built and published to test pypi 125 | 126 | ## Change Details 127 | ${{ github.event.head_commit.message }} 128 | 129 | For more information, please check change history at https://${{ needs.test.outputs.repo_owner }}.github.io/${{ needs.test.outputs.repo_name }}/${{ needs.test.outputs.package_version }}.dev/history 130 | 131 | ## Package Download 132 | The pacakge is available at: https://test.pypi.org/project/${{ needs.test.outputs.package_name }}/ 133 | 134 | - name: build failure notification via email 135 | if: ${{ steps.check.outputs.status == 'failure' }} 136 | uses: dawidd6/action-send-mail@v3 137 | with: 138 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 139 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 140 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 141 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 142 | from: build-bot 143 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 144 | subject: ${{ needs.test.outputs.package_name }}.${{ needs.test.outputs.package_version}}.dev.${{ github.run_number }} build failure 145 | convert_markdown: true 146 | html_body: | 147 | ## Change Details 148 | ${{ github.event.head_commit.message }} 149 | 150 | ## View Log 151 | https://github.com/${{ needs.test.outputs.repo_owner }}/${{ needs.test.outputs.repo_name }}/actions 152 | 153 | 154 | # - name: Dingtalk Robot Notify 155 | # if: always() 156 | # uses: leafney/dingtalk-action@v1.0.0 157 | # env: 158 | # DINGTALK_ACCESS_TOKEN: ${{ secrets.DINGTALK_ACCESS_TOKEN }} 159 | # DINGTALK_SECRET: ${{ secrets.DINGTALK_SECRET }} 160 | # with: 161 | # msgtype: markdown 162 | # title: CI Notification | Success 163 | # text: | 164 | # ### Build Success 165 | # ${{ needs.test.outputs.package_version_full }} is built and published to test pypi 166 | # ### Change History 167 | # Please check change history at https://${{ needs.test.outputs.repo_owner }}.github.io/${{ needs.test.outputs.repo_name }}/history 168 | # ### Package Download 169 | # Please download the pacakge at: https://test.pypi.org/project/${{ needs.test.outputs.repo_name }}/ 170 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Publish package on release branch if it's tagged with 'v*' 2 | 3 | name: build & release 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branch: [main, master] 10 | tags: 11 | - 'v*' 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | release: 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | matrix: 23 | python-versions: ['3.9'] 24 | 25 | # map step outputs to job outputs so they can be share among jobs 26 | outputs: 27 | package_version: ${{ steps.variables_step.outputs.package_version }} 28 | package_name: ${{ steps.variables_step.outputs.package_name }} 29 | repo_name: ${{ steps.variables_step.outputs.repo_name }} 30 | repo_owner: ${{ steps.variables_step.outputs.repo_owner }} 31 | 32 | # Steps represent a sequence of tasks that will be executed as part of the job 33 | steps: 34 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 35 | - uses: actions/checkout@v4 36 | 37 | - name: build change log 38 | id: build_changelog 39 | uses: mikepenz/release-changelog-builder-action@v3.2.0 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - uses: actions/setup-python@v4 44 | with: 45 | python-version: ${{ matrix.python-versions }} 46 | 47 | - name: Install dependencies 48 | run: | 49 | python -m pip install --upgrade pip 50 | pip install tox-gh-actions poetry 51 | 52 | # declare package_version, repo_owner, repo_name, package_name so you may use it in web hooks. 53 | - name: Declare variables for convenient use 54 | id: variables_step 55 | run: | 56 | echo "repo_owner=${GITHUB_REPOSITORY%/*}" >> $GITHUB_OUTPUT 57 | echo "repo_name=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT 58 | echo "package_name=`poetry version | awk '{print $1}'`" >> $GITHUB_OUTPUT 59 | echo "package_version=`poetry version --short`" >> $GITHUB_OUTPUT 60 | shell: bash 61 | 62 | - name: publish documentation 63 | run: | 64 | poetry install -E dev 65 | poetry run mkdocs build 66 | git config --global user.name Docs deploy 67 | git config --global user.email docs@dummy.bot.com 68 | poetry run mike deploy -p -f --ignore `poetry version --short` 69 | poetry run mike set-default -p `poetry version --short` 70 | 71 | - name: Build wheels and source tarball 72 | run: | 73 | poetry lock 74 | poetry build 75 | 76 | - name: Create Release 77 | id: create_release 78 | uses: actions/create-release@v1 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | with: 82 | tag_name: ${{ github.ref_name }} 83 | release_name: Release ${{ github.ref_name }} 84 | body: ${{ steps.build_changelog.outputs.changelog }} 85 | draft: false 86 | prerelease: false 87 | 88 | - name: publish to PYPI 89 | uses: pypa/gh-action-pypi-publish@release/v1 90 | with: 91 | user: __token__ 92 | password: ${{ secrets.PYPI_API_TOKEN }} 93 | skip_existing: true 94 | 95 | notification: 96 | needs: release 97 | if: always() 98 | runs-on: ubuntu-latest 99 | steps: 100 | - uses: martialonline/workflow-status@v2 101 | id: check 102 | 103 | - name: build success notification via email 104 | if: ${{ steps.check.outputs.status == 'success' }} 105 | uses: dawidd6/action-send-mail@v3 106 | with: 107 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 108 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 109 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 110 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 111 | from: build-bot 112 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 113 | subject: ${{ needs.release.outputs.package_name }}.${{ needs.release.outputs.package_version}} build successfully 114 | convert_markdown: true 115 | html_body: | 116 | ## Build Success 117 | ${{ needs.release.outputs.package_name }}.${{ needs.release.outputs.package_version }} is built and published to PYPI 118 | 119 | ## Change Details 120 | ${{ github.event.head_commit.message }} 121 | 122 | For more information, please check change history at https://${{ needs.release.outputs.repo_owner }}.github.io/${{ needs.release.outputs.repo_name }}/${{ needs.release.outputs.package_version }}/history 123 | 124 | ## Package Download 125 | The pacakge is available at: https://pypi.org/project/${{ needs.release.outputs.package_name }}/ 126 | 127 | - name: build failure notification via email 128 | if: ${{ steps.check.outputs.status == 'failure' }} 129 | uses: dawidd6/action-send-mail@v3 130 | with: 131 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 132 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 133 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 134 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 135 | from: build-bot 136 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 137 | subject: ${{ needs.release.outputs.package_name }}.${{ needs.release.outputs.package_version}} build failure 138 | convert_markdown: true 139 | html_body: | 140 | ## Change Details 141 | ${{ github.event.head_commit.message }} 142 | 143 | ## Status: ${{ steps.check.outputs.status }} 144 | 145 | 146 | ## View Log 147 | https://github.com/${{ needs.release.outputs.repo_owner }}/${{ needs.release.outputs.repo_name }}/actions 148 | 149 | 150 | # - name: Dingtalk Robot Notify 151 | # if: always() 152 | # uses: leafney/dingtalk-action@v1.0.0 153 | # env: 154 | # DINGTALK_ACCESS_TOKEN: ${{ secrets.DINGTALK_ACCESS_TOKEN }} 155 | # DINGTALK_SECRET: ${{ secrets.DINGTALK_SECRET }} 156 | # with: 157 | # msgtype: markdown 158 | # title: CI Notification | Success 159 | # text: | 160 | # ### Build Success 161 | # ${{ needs.release.outputs.package_version_full }} is built and published to test pypi 162 | # ### Change History 163 | # Please check change history at https://${{ needs.release.outputs.repo_owner }}.github.io/${{ needs.release.outputs.repo_name }}/history 164 | # ### Package Download 165 | # Please download the pacakge at: https://test.pypi.org/project/${{ needs.release.outputs.repo_name }}/ 166 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # OSX useful to ignore 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | .Python 35 | env/ 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | *.egg-info/ 48 | .installed.cfg 49 | *.egg 50 | 51 | # PyInstaller 52 | # Usually these files are written by a python script from a template 53 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 54 | *.manifest 55 | *.spec 56 | 57 | # Installer logs 58 | pip-log.txt 59 | pip-delete-this-directory.txt 60 | 61 | # Unit test / coverage reports 62 | htmlcov/ 63 | .tox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *,cover 70 | .hypothesis/ 71 | .pytest_cache/ 72 | 73 | # Translations 74 | *.mo 75 | *.pot 76 | 77 | # Django stuff: 78 | *.log 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # IntelliJ Idea family of suites 84 | .idea 85 | *.iml 86 | ## File-based project format: 87 | *.ipr 88 | *.iws 89 | ## mpeltonen/sbt-idea plugin 90 | .idea_modules/ 91 | 92 | # PyBuilder 93 | target/ 94 | 95 | # Cookiecutter 96 | output/ 97 | python_boilerplate/ 98 | cookiecutter-pypackage-env/ 99 | 100 | # IDE settings 101 | .vscode/ 102 | 103 | # mkdocs 104 | site/ 105 | .history/ 106 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! Every little bit 4 | helps, and credit will always be given. 5 | 6 | You can contribute in many ways: 7 | 8 | ## Types of Contributions 9 | 10 | ### Report Bugs 11 | 12 | Report bugs at https://github.com/zillionare/python-project-wizard/issues. 13 | 14 | If you are reporting a bug, please include: 15 | 16 | * Your operating system name and version. 17 | * Any details about your local setup that might be helpful in troubleshooting. 18 | * Detailed steps to reproduce the bug. 19 | 20 | ### Fix Bugs 21 | 22 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 23 | wanted" is open to whoever wants to implement it. 24 | 25 | ### Implement Features 26 | 27 | Look through the GitHub issues for features. Anything tagged with "enhancement" 28 | and "help wanted" is open to whoever wants to implement it. 29 | 30 | ### Write Documentation 31 | 32 | zillionare-backtest could always use more documentation, whether as part of the 33 | official zillionare-backtest docs, in docstrings, or even on the web in blog posts, 34 | articles, and such. 35 | 36 | ### Submit Feedback 37 | 38 | The best way to send feedback is to file an issue at https://github.com/zillionare/python-project-wizard/issues. 39 | 40 | If you are proposing a feature: 41 | 42 | * Explain in detail how it would work. 43 | * Keep the scope as narrow as possible, to make it easier to implement. 44 | * Remember that this is a volunteer-driven project, and that contributions 45 | are welcome :) 46 | 47 | ## Get Started! 48 | 49 | Ready to contribute? Here's how to set up `backtest` for local development. 50 | 51 | 1. Fork the `backtest` repo on GitHub. 52 | 2. Clone your fork locally 53 | 54 | ``` 55 | $ git clone git@github.com:your_name_here/backtest.git 56 | ``` 57 | 58 | 3. Ensure [poetry](https://python-poetry.org/docs/) is installed. 59 | 4. Install dependencies and start your virtualenv: 60 | 61 | ``` 62 | $ poetry install -E test -E doc -E dev 63 | ``` 64 | 65 | 5. Create a branch for local development: 66 | 67 | ``` 68 | $ git checkout -b name-of-your-bugfix-or-feature 69 | ``` 70 | 71 | Now you can make your changes locally. 72 | 73 | 6. When you're done making changes, check that your changes pass the 74 | tests, including testing other Python versions, with tox: 75 | 76 | ``` 77 | $ tox 78 | ``` 79 | 80 | 7. Commit your changes and push your branch to GitHub: 81 | 82 | ``` 83 | $ git add . 84 | $ git commit -m "Your detailed description of your changes." 85 | $ git push origin name-of-your-bugfix-or-feature 86 | ``` 87 | 88 | 8. Submit a pull request through the GitHub website. 89 | 90 | ## Pull Request Guidelines 91 | 92 | Before you submit a pull request, check that it meets these guidelines: 93 | 94 | 1. The pull request should include tests. 95 | 2. If the pull request adds functionality, the docs should be updated. Put 96 | your new functionality into a function with a docstring, and add the 97 | feature to the list in README.md. 98 | 3. The pull request should work for Python 3.6, 3.7, 3.8, 3.9 and for PyPy. Check 99 | https://github.com/zillionare/python-project-wizard/actions 100 | and make sure that the tests pass for all supported Python versions. 101 | 102 | ## Tips 103 | 104 | To run a subset of tests. 105 | 106 | ``` 107 | $ pytest tests.test_backtest 108 | ``` 109 | 110 | ## Deploying 111 | 112 | A reminder for the maintainers on how to deploy. 113 | Make sure all your changes are committed (including an entry in HISTORY.md). 114 | Then run: 115 | 116 | ``` 117 | $ poetry patch # possible: major / minor / patch 118 | $ git push 119 | $ git push --tags 120 | ``` 121 | 122 | Travis will then deploy to PyPI if tests pass. 123 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2 | ## ver1.3.5(2023-9-9) 3 | 1. fixed following issues due to github actions upgrade: 4 | 1. [#42](https://github.com/zillionare/python-project-wizard/issues/42) 5 | 2. checkout@v2, setup-python@v2 is deprecated 6 | 3. [#33](https://github.com/zillionare/python-project-wizard/issues/33) 7 | 4. [#34](https://github.com/zillionare/python-project-wizard/issues/34) 8 | 5. [#41](https://github.com/zillionare/python-project-wizard/issues/41) 9 | 2. add mypy as type checker 10 | 3. remove python 3.7, add python 3.11 and use python 3.11 as default 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Audrey Roy Greenfeld and individual contributors. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | * Neither the name of Audrey Roy Greenfeld nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Project Wizard 2 | 3 | A tool for creating skeleton python project, built with popular develop tools and 4 | conform to the best practice. 5 | 6 | [![Version](http://img.shields.io/pypi/v/ppw?color=brightgreen)](https://pypi.python.org/pypi/ppw) 7 | [![CI Status](https://github.com/zillionare/python-project-wizard/actions/workflows/release.yml/badge.svg)](https://github.com/zillionare/python-project-wizard) 8 | [![Dowloads](https://img.shields.io/pypi/dm/ppw)](https://pypi.org/project/ppw/) 9 | [![License](https://img.shields.io/pypi/l/ppw)](https://opensource.org/licenses/BSD-2-Clause) 10 | ![Python Versions](https://img.shields.io/pypi/pyversions/ppw) 11 | [![Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 12 | 13 | 14 | ## Features 15 | 16 | This tool will create Python project with the following features: 17 | 18 | * [Poetry]: Manage version, dependancy, build and release 19 | * [Mkdocs]: Writting your docs in markdown style 20 | * Testing with [Pytest] (unittest is still supported out of the box) 21 | * Code coverage report and endorsed by [Codecov] 22 | * [Tox]: Test your code against environment matrix, lint and artifact check. 23 | * Format with [Black] and [Isort] 24 | * Lint code with [Flake8] and [Flake8-docstrings] 25 | * [Pre-commit hooks]: Formatting/linting anytime when commit/run local tox/CI 26 | * [Mkdocstrings]: Auto API doc generation and docstring template (vscode and its extension [autodocStrings] is required) 27 | * Command line interface using [Python Fire] (optional) 28 | * Continuouse Integration/Deployment by [github actions], includes: 29 | - publish dev build/official release to TestPyPI/PyPI automatically when CI success 30 | - publish documents automatically when CI success 31 | - extract change log from github and integrate with release notes automatically 32 | * Host your documentation from [Git Pages] with zero-config 33 | * Support multiple versions of documentations (by [mike]) 34 | * Create repo and push initial commits by repo.sh script 35 | 36 | ## Quickstart 37 | 38 | Install ppw if you haven't install it yet: 39 | 40 | ``` 41 | pip install -U ppw 42 | ``` 43 | 44 | Generate a Python package project by simple run: 45 | 46 | ``` 47 | ppw 48 | ``` 49 | 50 | Then follow the **[Tutorial]** to finish configurations. 51 | 52 | # Credits 53 | 54 | This repo is forked from [audreyr/cookiecutter-pypackage], and borrowed some ideas from [briggySmalls] 55 | 56 | 57 | [poetry]: https://python-poetry.org/ 58 | [mkdocs]: https://www.mkdocs.org 59 | [pytest]: https://pytest.org 60 | [codecov]: https://codecov.io 61 | [tox]: https://tox.readthedocs.io 62 | [black]: https://github.com/psf/black 63 | [isort]: https://github.com/PyCQA/isort 64 | [flake8]: https://flake8.pycqa.org 65 | [flake8-docstrings]: https://pypi.org/project/flake8-docstrings/ 66 | [mkdocstrings]: https://mkdocstrings.github.io/ 67 | [Python Fire]: https://github.com/google/python-fire 68 | [github actions]: https://github.com/features/actions 69 | [Git Pages]: https://pages.github.com 70 | [Pre-commit hooks]: https://pre-commit.com/ 71 | [mike]: https://github.com/jimporter/mike 72 | [autoDocStrings]: https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring 73 | [Tutorial]: https://zillionare.github.io/python-project-wizard/tutorial/ 74 | [audreyr/cookiecutter-pypackage]: https://github.com/audreyr/cookiecutter-pypackage 75 | [briggySmalls]: https://github.com/briggySmalls/cookiecutter-pypackage 76 | 77 | # Links 78 | ## cfg4py 79 | [cfg4py](https://pypi.org/project/cfg4py/) is a great tool for managing configuration files, supporting configuration for different environments (dev, prodction and test), automatically converting yaml-based configuration to python class, so, you can access configuration items by attribute, thus, enable auto-completion (by IDE). It also supports live-reload, remoting central configuration, config template and more. 80 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "", 3 | "email": "", 4 | "github_username": "", 5 | "project_name": "Python Boilerplate", 6 | "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", 7 | "project_short_description": "Skeleton project created by Python Project Wizard (ppw)", 8 | "version": "0.1.0", 9 | "use_pytest": "y", 10 | "add_pyup_badge": "n", 11 | "command_line_interface": ["Fire", "No command-line interface"], 12 | "create_author_file": "y", 13 | "open_source_license": ["MIT", "BSD-3-Clause", "ISC", "Apache-2.0", "GPL-3.0-only", "Not open source"], 14 | "docstrings_style": ["google", "numpy", "rst"], 15 | "init_dev_env": "y", 16 | "_copy_without_render": ["docs/history.md", "docs/contributing.md", "docs/authors.md", "docs/index.md", ".github/workflows/*.yml", ".docstring.tpl"] 17 | } 18 | -------------------------------------------------------------------------------- /docs/console_script_setup.md: -------------------------------------------------------------------------------- 1 | # Console Script Setup 2 | 3 | Optionally, your package can include a console script using [Fire] 4 | 5 | # How It Works 6 | 7 | If the `command_line_interface` option is set to `fire` during setup, cookiecutter 8 | will add a file `cli.py` in the project_slug subdirectory. An entry point is added to 9 | pyproject.toml that points to the main function in cli.py. 10 | 11 | # Usage 12 | 13 | To use the console script in development: 14 | 15 | ``` bash 16 | poetry install 17 | ``` 18 | 19 | `projectdir` should be the top level project directory with the 20 | pyproject.toml file 21 | 22 | Then execute: 23 | ``` 24 | $your_package_name help 25 | ``` 26 | 27 | it will show your package name, project short description and exit. 28 | 29 | # More Details 30 | 31 | You can read more about Python Fire at [Fire] 32 | 33 | [Fire]: https://google.github.io/python-fire/guide/ 34 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | 2 | ???+ Question 3 | # Why not travis CI? 4 | Travis CI is a great service, however, github actions is super convenient, less configuration 5 | , better integration. Less configuration, less error prone. 6 | 7 | ???+ Question 8 | # Why not read the docs? 9 | Same reason as above. Git pages is convenient than read the docs, it requires no 10 | further configuration, except access token. As to read the docs, you need to 11 | write v2 config file, plus several settings on web pages. 12 | 13 | ???+ Question 14 | # Why mkdocs instead of sphinx? 15 | reStructured Text and Sphinx is way to tedious, though powerful. With extension, 16 | you'll find almost all features are available in mkdocs, in a neat and productive 17 | way. Poetry and Markdown, are the two key factors driven me develop this template. 18 | 19 | ???+ Question 20 | # How to trigger a release build? 21 | 22 | Once you've tagged either of (main, master) branch with `v`(for example, v1.0), then github actions will trigger a release build and finally publish documentation to https://{your_github_account}.github.io/{your_repo_slug} and push a wheels to pypi. 23 | 24 | You can also manually trigger this one: 25 | ``` 26 | git tag -a v1.0 -m "Release v1.0" 27 | git push --tags 28 | ``` 29 | then check on github to see if actions is executing. Once it's done successfully. 30 | 31 | ???+ Question 32 | # How to manually publish documentation? 33 | By default, every push to github will trigger a documentation dev build, with the name is ${poetry version --short}-dev. And every tag starts with 'v' on main/master branch will cause a release build, and documentation will be built too. 34 | 35 | However, by any chances, you can manually build and publish your documentation with: 36 | 37 | ``` 38 | poetry run mike deploy -p `poetry version --short` 39 | poetry run mike set-default -p `poetry version --short` 40 | ``` 41 | The above commands simply build documentation locally and push to github, then github will publish it. 42 | 43 | ???+ Question 44 | # What are the configuration items? 45 | 46 | Here is a list: 47 | 48 | ``` 49 | ## Templated Values 50 | 51 | The following appear in various parts of your generated project. 52 | 53 | full_name 54 | Your full name. 55 | 56 | email 57 | Your email address. 58 | 59 | github_username 60 | Your GitHub username. 61 | 62 | project_name 63 | The name of your new Python package project. This is used in 64 | documentation, so spaces and any characters are fine here. 65 | 66 | project_slug 67 | The namespace of your Python package. This should be Python 68 | import-friendly. Typically, it is the slugified version of 69 | project_name. 70 | 71 | project_short_description 72 | A 1-sentence description of what your Python package does. 73 | 74 | release_date 75 | The date of the first release. 76 | 77 | pypi_username 78 | Your Python Package Index account username. 79 | 80 | year 81 | The year of the initial package copyright in the license file. 82 | 83 | version 84 | The starting version number of the package. 85 | 86 | install_precommit_hooks 87 | If you choose yes, then cookiecutter will install pre-commit hooks for you. 88 | 89 | docstrings_style 90 | one of `google, numpy, rst`. It's required by flake8-docstrings. 91 | 92 | ## Options 93 | 94 | The following package configuration options set up different features 95 | for your project. 96 | 97 | command_line_interface 98 | Whether to create a console script using Python Fire. Console script 99 | entry point will match the project_slug. Options: \['fire', "No 100 | command-line interface"\] 101 | ``` 102 | 103 | except above settings, for CI/CD, you'll also need configure gitub repsitory secrets 104 | at page repo > settings > secrtes, and add the following secrets: 105 | 106 | - PERSONAL_TOKEN (required for publishing document to git pages) 107 | - TEST_PYPI_API_TOKEN (required for publishing dev release to testpypi) 108 | - PYPI_API_TOKEN (required for publish ) 109 | -------------------------------------------------------------------------------- /docs/history.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## v1.3.3 4 | *[#31](https://github.com/zillionare/python-project-wizard/issues/31) Use peotry to create test environment when running tox 5 | ## v1.3.2 6 | *[#28](https://github.com/zillionare/python-project-wizard/issues/28) Send notification mail whenever build success or failed 7 | *[#27](https://github.com/zillionare/python-project-wizard/issues/27) failed to send build notfication mail due to no from field 8 | ## v1.3.1 9 | *[#26](https://github.com/zillionare/python-project-wizard/issues/26) use mike to deploy multiple version of documentation directly, removed github action peaceiris/actions-gh-pages@v3 10 | ## v1.3 11 | * [#23](https://github.com/zillionare/python-project-wizard/issues/23) add email notification upon build success 12 | * [#24](https://github.com/zillionare/python-project-wizard/issues/24) config repo secrets by script 13 | * [#25](https://github.com/zillionare/python-project-wizard/issues/25) support create repo and upload source code 14 | ## v1.2.2 15 | * [#22](https://github.com/zillionare/python-project-wizard/issues/22) mike deploy failed 16 | ## v1.2.1 17 | * [#21](https://github.com/zillionare/python-project-wizard/issues/21) add environment variable: repo_name and repon_owner 18 | ## v1.2 19 | * [#7](https://github.com/zillionare/python-project-wizard/issues/7) documentatioin will now support multiple versions 20 | * [#8](https://github.com/zillionare/python-project-wizard/issues/8) add .docstring.tpl to project 21 | * [#10](https://github.com/zillionare/python-project-wizard/issues/10) fixed. 22 | * [#11](https://github.com/zillionare/python-project-wizard/issues/11) fixed. 23 | * [#12](https://github.com/zillionare/python-project-wizard/issues/12) replaced. 24 | * [#13](https://github.com/zillionare/python-project-wizard/issues/13) implemented. Only tested with vscode, please have autodocstrings extension installed. 25 | * [#14](https://github.com/zillionare/python-project-wizard/issues/14) implemented. 26 | * [#15](https://github.com/zillionare/python-project-wizard/issues/15) done. 27 | * [#16](https://github.com/zillionare/python-project-wizard/issues/16) fixed 28 | * [#18](https://github.com/zillionare/python-project-wizard/issues/18) fixed 29 | * [#19](https://github.com/zillionare/python-project-wizard/issues/19) implemented. 30 | * [#20](https://github.com/zillionare/python-project-wizard/issues/20) implemented. You can refer by using ${{ env.package_version_short }} and ${{ env.package_version_full }} now. 31 | ## v1.0 32 | ***first release with the following features:*** 33 | 34 | 1. [Poetry]: Manage version, dependancy, build and release 35 | 2. [Mkdocs]: Writting your docs in markdown style 36 | 3. Testing with [Pytest] (unittest is still supported out of the box) 37 | 4. Code coverage report and endorsed by [Codecov] 38 | * [Tox]: Test your code against environment matrix, lint and artifact check. 39 | * Format with [Black] and [Isort] 40 | * Lint code with [Flake8] and [Flake8-docstrings] 41 | * [Pre-commit hooks]: Formatting/linting anytime when commit/run local tox/CI 42 | * [Mkdocstrings]: Auto API doc generation 43 | * Command line interface using [Python Fire] (optional) 44 | * Continuouse Integration/Deployment by [github actions], includes: 45 | - publish dev build/official release to TestPyPI/PyPI automatically when CI success 46 | - publish documents automatically when CI success 47 | - extract change log from github and integrate with release notes automatically 48 | * Host your documentation from [Git Pages] with zero-config 49 | 50 | 51 | [poetry]: https://python-poetry.org/ 52 | [mkdocs]: https://www.mkdocs.org 53 | [pytest]: https://pytest.org 54 | [codecov]: https://codecov.io 55 | [tox]: https://tox.readthedocs.io 56 | [black]: https://github.com/psf/black 57 | [isort]: https://github.com/PyCQA/isort 58 | [flake8]: https://flake8.pycqa.org 59 | [flake8-docstrings]: https://pypi.org/project/flake8-docstrings/ 60 | [mkdocstrings]: https://mkdocstrings.github.io/ 61 | [Python Fire]: https://github.com/google/python-fire 62 | [github actions]: https://github.com/features/actions 63 | [Git Pages]: https://pages.github.com 64 | [Pre-commit hooks]: https://pre-commit.com/ 65 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | {% 2 | include-markdown "../README.md" 3 | %} 4 | -------------------------------------------------------------------------------- /docs/pypi_release_checklist.md: -------------------------------------------------------------------------------- 1 | 2 | ## For Every Release 3 | 4 | 0. Merge your changes from features/release branch to master/main. 5 | 6 | 1. Update HISTORY.md 7 | 8 | Be noticed that github workflow will generate a changelog for you automatically, but you'll have to make your own history.md. 9 | 10 | 2. Commit the changes: 11 | 12 | > ``` bash 13 | > git add HISTORY.md 14 | > git commit -m "Changelog for upcoming release 0.1.1." 15 | > ``` 16 | 17 | 3. Update version number (can also be patch or major) 18 | 19 | > ``` bash 20 | > poetry patch 21 | > ``` 22 | 23 | 4. Run the tests: 24 | 25 | > ``` bash 26 | > tox 27 | > ``` 28 | 29 | 5. Push the commit to main branch: 30 | 31 | > ``` bash 32 | > git push 33 | > ``` 34 | 35 | 6. Push the tags, creating the new release on both GitHub and PyPI: 36 | 37 | > ``` bash 38 | > git tag -a v`poetry version --short` -m "my great release" 39 | > git push --tags 40 | > ``` 41 | 42 | tag_name has to be started with 'v'(lower case), to leverage github release workflow. 43 | 44 | 7. Check the PyPI listing page to make sure that the README, release 45 | notes, and roadmap display properly. If tox test passed, this should be ok, since 46 | we have already run twine check during tox test. 47 | 48 | ???+ Info 49 | # About This Checklist 50 | 51 | This checklist is adapted from: 52 | 53 | - 54 | - 55 | 56 | It's assumed that you are using all features of [Python Project Wizard](https://zillionare.github.io/python-project-wizard). 57 | -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | 2 | ??? Note 3 | Did you find this article confusing? [Edit this file] and pull a request! 4 | 5 | To start with, you will need [GitHub], [Pypi] , [TestPyPi] and [Codecov] account. If you don't have one, please follow the links to apply one before getting started on this tutorial. 6 | 7 | If you are new to Git and GitHub, you should probably spend a few minutes on some of the tutorials at [GitHub Help] 8 | 9 | ## Step 1: Install Python Project Wizard (ppw) 10 | 11 | We'll need `ppw` to generate a skeleton project. Following the instructions to install `ppw` on to your machine. 12 | 13 | ``` bash 14 | pip install ppw 15 | ``` 16 | 17 | If virtual environment is used during your developement, install `ppw` as global. 18 | ## Step 2: Build a virtual environment for your development 19 | It's best practice that always developing your project in dedicated python virtual environment. So let's start from creating a virtual environment now: 20 | 21 | You may choose either annaconda or virtualenv, annaconda (actually miniconda) is preferred though. 22 | 23 | ``` 24 | conda create -n mypackage python=3.8 25 | conda activate mypackage 26 | conda install -c conda-forge tox-conda 27 | ``` 28 | 29 | Choose python version on your own decision. Be noticed that we're now at a virtual env called `mypackage` 30 | ## Step 3: Generate Your Package 31 | 32 | !!!Important 33 | Make sure run the following command under `mypackage` virtual env. 34 | 35 | Now it's time to generate your Python package. 36 | 37 | Run the following command and feed with answers: 38 | 39 | ```bash 40 | ppw 41 | ``` 42 | 43 | Finally a new folder will be created under current folder, the name is the answer you 44 | provided to `project_slug`. 45 | 46 | At last step, it'll ask you `ppw` should performe initialization for you. If the answer is 'yes', then `ppw` will: 47 | 48 | 1. install pre-commit hooks 49 | 2. install poetry 50 | 3. install necessary dependencies which required by test and documentation. These deps will include pytest, tox, mkdocs and etc. 51 | 4. lint your files has just been generated. 52 | 53 | The project layout should look like: 54 | 55 | ``` 56 | . 57 | ├── AUTHORS.md 58 | ├── CONTRIBUTING.md 59 | ├── .coveragerc 60 | ├── .docstring.tpl 61 | ├── dist 62 | ├── docs 63 | │ ├── api.md 64 | │ ├── authors.md 65 | │ ├── contributing.md 66 | │ ├── history.md 67 | │ ├── index.md 68 | │ ├── installation.md 69 | │ └── usage.md 70 | ├── .editorconfig 71 | ├── .flake8 72 | ├── .github 73 | │ ├── ISSUE_TEMPLATE.md 74 | │ └── workflows 75 | │ ├── dev.yml 76 | │ └── release.yml 77 | ├── .gitignore 78 | ├── HISTORY.md 79 | ├── .isort.cfg 80 | ├── LICENSE 81 | ├── mkdocs.yml 82 | ├── poetry.lock 83 | ├── ppw_0420_01 84 | │ ├── cli.py 85 | │ ├── __init__.py 86 | │ └── app.py 87 | ├── .pre-commit-config.yaml 88 | ├── pyproject.toml 89 | ├── pyrightconfig.json 90 | ├── README.md 91 | ├── site 92 | ├── tests 93 | │ ├── __init__.py 94 | │ └── test_ppw_0420_01.py 95 | └── tox.ini 96 | ``` 97 | 98 | Here the project_slug is ppw_0420_01. When you genereate yours, it could be other name. 99 | 100 | Also be noticed that there's pyproject.toml in this folder. This is the main 101 | configuration file of our project. 102 | 103 | You could choose your favorite python version here. 104 | ## Step 4: Install Dev Requirements 105 | 106 | !!!Important 107 | Skip this step if you've answered 'yes' to the question `init_dev_env`. They're performed automatically if the answer is 'yes'. 108 | 109 | You should still be in the folder named as `%proejct_slug`, which containing the 110 | `pyproject.toml` file. 111 | 112 | Install the new project's local development requirements inside a 113 | virtual environment: 114 | 115 | ``` bash 116 | pip install poetry 117 | poetry install -E doc -E dev -E test 118 | ``` 119 | 120 | We started with installing poetry, since the whole project is managed by poetry. Then we 121 | installed extra dependency need by developer, such as documentation building tools, lint, 122 | formatting and testing tools etc. 123 | 124 | ## Step 5: Smoke test 125 | Run the following command now: 126 | ``` 127 | tox 128 | ``` 129 | 130 | This will give you a test report and a lint report. You should see no errors except some lint warnings. 131 | 132 | ???+ Tips 133 | 134 | Extra dependencies are grouped into three groups, doc, dev and test for better 135 | granularity. When you ship the package, dependencies in group doc, dev and test 136 | might not be shipped. 137 | 138 | As the developer, you will need install all the dependencies. 139 | 140 | ??? Tips 141 | 142 | if you found erros like the following during tox runs: 143 | ``` 144 | ERROR: InterpreterNotFound: python3.9 145 | ``` 146 | don't be panic, this is just because python3.x is not found on your machine. If you 147 | decide to support that version of Python in your package, please install it on your 148 | machine. Otherwise, remove it from tox.ini and pyproject.toml (search python3.x then 149 | remove it). 150 | 151 | ## Step 6: Create GitHub Repo 152 | 153 | ???+Info 154 | Going through step 6 and 7 is tedious. So that's why we provide a bash script -- repo.sh, to help creating repo, setting secrets and publishing your code to the repo automatically. 155 | 156 | The script looks like the following: 157 | ``` 158 | # uncomment the following to create repo and push code to github 159 | # gh repo create {{cookiecutter.project_slug}} --public 160 | # git remote add origin git@github.com:{{cookiecutter.github_username}}/{{cookiecutter.project_slug}}.git 161 | # git add . 162 | # pre-commit run --all-files 163 | # git add . 164 | # git commit -m "Initial commit by ppw" 165 | # git branch -M main 166 | 167 | # Uncomment the following to config github secret used by github workflow. 168 | # gh secret set PERSONAL_TOKEN --body $GH_TOKEN 169 | # gh secret set PYPI_API_TOKEN --body $PYPI_API_TOKEN 170 | # gh secret set TEST_PYPI_API_TOKEN --body $TEST_PYPI_API_TOKEN 171 | 172 | # uncomment the following if you need to setup email notification 173 | # gh secret set BUILD_NOTIFY_MAIL_SERVER --body $BUILD_NOTIFY_MAIL_SERVER 174 | # gh secret set BUILD_NOTIFY_MAIL_PORT --body $BUILD_NOTIFY_MAIL_PORT 175 | # gh secret set BUILD_NOTIFY_MAIL_FROM --body $BUILD_NOTIFY_MAIL_FROM 176 | # gh secret set BUILD_NOTIFY_MAIL_PASSWORD --body $BUILD_NOTIFY_MAIL_PASSWORD 177 | # gh secret set BUILD_NOTIFY_MAIL_RCPT --body $BUILD_NOTIFY_MAIL_RCPT 178 | 179 | # git push -u origin main 180 | ``` 181 | before launch the script, you will need to apply github personal token and set environment variable GH_TOKEN beforehand. And you need install the tool [**gh**](https://cli.github.com/) too. 182 | 183 | Go to your GitHub account and create a new repo named `mypackage`, where 184 | `mypackage` matches the `[project_slug]` from your answers when running `ppw` 185 | 186 | Then goto repo > settings > secrets, click on 'New repository secret', add the following 187 | secrets: 188 | 189 | - TEST_PYPI_API_TOKEN, see [How to apply testpypi token] 190 | - PYPI_API_TOKEN, see [How to apply pypi token] 191 | - PERSONAL_TOKEN, see [How to apply personal token] 192 | 193 | ## Step 7: Upload code to github 194 | 195 | Back to your develop environment, find the folder named after the `[project_slug]`. 196 | Go to this folder, and then setup git to use your GitHub repo and upload the 197 | code: 198 | 199 | ``` bash 200 | cd mypackage 201 | 202 | git add . 203 | git commit -m "Initial skeleton." 204 | git branch -M main 205 | git remote add origin git@github.com:myusername/mypackage.git 206 | git push -u origin main 207 | ``` 208 | 209 | Where `myusername` and `mypackage` are adjusted for your username and 210 | package name. 211 | 212 | You'll need a ssh key to push the repo. You could [Generate] a key or 213 | [Add] an existing one. 214 | 215 | ???+ Warning 216 | 217 | if you answered 'yes' to the question if `init_dev_env` at last step, 218 | then you should find `pre-commit` was invoked when you run `git commit`, and some files 219 | may be modified by hooks. If so, please add these files and **commit again**. 220 | 221 | ## Step 8: Setup codecov integration 222 | 223 | ???+ Tips 224 | 225 | If you have already setup codecov integration and configured access for all your 226 | repositories, you can skip this step. 227 | 228 | In your browser, visit [install codecov app], you'll be landed at this page: 229 | 230 | ![](http://images.jieyu.ai/images/202104/20210419175222.png) 231 | 232 | Click on the green `install` button at top right, choose `all repositories` then click 233 | on `install` button, following directions until all sets. 234 | 235 | ## Step 9: Check the CI result 236 | 237 | After pushing your code to github, go to github web page, navigate to your repo, then 238 | click on actions link, you should find screen like this: 239 | 240 | ![](http://images.jieyu.ai/images/202104/20210419170304.png) 241 | 242 | There should be one workflow running. After it finished, go to [testpyi], check if a 243 | new artifact is published under the name {{ cookiecutter.project_slug }} 244 | 245 | ## Step 10. Check the documentation 246 | 247 | Documentation will be published and available at once: 248 | 249 | 1. the branch is either main or master 250 | 2. the commit is tagged, and the tag name is started with 'v' (lower case) 251 | 3. build/testing executed by github CI passed 252 | 253 | If you'd like to see what it looks like now, you could run the followng command: 254 | 255 | ``` 256 | mkdocs gh-deploy 257 | ``` 258 | 259 | then check your documentation at or you can serve it locally by: 260 | 261 | ``` 262 | mkdocs serve -a 0.0.0.0:8000 263 | ``` 264 | 265 | then open your browser, visit your dev machine on port 8000. 266 | 267 | ???+ Info 268 | Though we used mkdocs here, however, in order to support multiple versions of documentation, we actually use mike in github actions. 269 | 270 | ???+ Important 271 | ppw choose github pages to host your documentation. you need visit https://github.com/{github_account}/{your_repo}/settings/pages to enable it: 272 | 273 | ![](https://images.jieyu.ai/images/20220820220812134811.png) 274 | 275 | the above pages shows example on how to configure it. 276 | 277 | ## Step 11. Make an official release 278 | 279 | After done with your phased development, switch to either of (main, master) branch, following instructions at [release checklist](/pypi_release_checklist), trigger first official release and check result at [PYPI]. 280 | 281 | ## Step 12. Customization 282 | 283 | ppw assumed some settings for you, for example, it choose python version in pyproject.toml and tox.ini. Change accordingly to match you case. 284 | 285 | The following section will address how to customize github workflow: 286 | You may need to customize settings in workflow. Open .github/workflows/dev.yml: 287 | ``` 288 | jobs: 289 | test: 290 | # The type of runner that the job will run on 291 | strategy: 292 | matrix: 293 | python-versions: ['3.8',' 3.9', '3.10', '3.11'] 294 | # github action doesn't goes well with windows due to docker support 295 | # github action doesn't goes well with macos due to `no docker command` 296 | #os: [ubuntu-20.04, windows-latest, macos-latest] 297 | os: [ubuntu-20.04] 298 | runs-on: ${{ matrix.os }} 299 | ``` 300 | you may need to change python-version and os here. If you need to change python version, make sure never use 3.10 (rather than '3.10'). The former one will is actually equal to 3.1, according to yaml's parser. 301 | 302 | We need run build & publish job on one platform & python version only. So ppw have seperate test job from "build & publish" job, and you have to change `runs-on` and `python-version` accordingly too. 303 | 304 | ``` 305 | publish_dev_build: 306 | # if test failed, we should not publish 307 | needs: test 308 | # you may need to change os below 309 | runs-on: ubuntu-latest 310 | steps: 311 | - uses: actions/checkout@v4 312 | - uses: actions/setup-python@v4 313 | with: 314 | # you may need to change python version below 315 | python-version: '3.11' 316 | ``` 317 | 318 | ppw also provide example configuration about how to use service and webhooks (Dingtalk notification robot), but it's disabled by default. Uncomment these lines to enable it: 319 | ``` 320 | # uncomment the following to pickup services 321 | # services: 322 | # redis: 323 | # image: redis 324 | # options: >- 325 | # --health-cmd "redis-cli ping" 326 | # --health-interval 10s 327 | # --health-timeout 5s 328 | # --health-retries 5 329 | # ports: 330 | # - 6379:6379 331 | ``` 332 | 333 | ``` 334 | # - name: Dingtalk Robot Notify 335 | # uses: leafney/dingtalk-action@v1.0.0 336 | # env: 337 | # DINGTALK_ACCESS_TOKEN: ${{ secrets.DINGTALK_ACCESS_TOKEN }} 338 | # DINGTALK_SECRET: ${{ secrets.DINGTALK_SECRET }} 339 | # with: 340 | # msgtype: markdown 341 | # notify_when: 'success' 342 | # title: CI Notification | Success 343 | # text: | 344 | # ### Build success 345 | # ${{ env.package_version_full }} is built and published to test pypi 346 | # ### Change History 347 | # Please check change history at https://${{ env.repo_owner }}.github.io/${{ env.repo_name }}/history 348 | # ### package download 349 | # Please download the pacakge at: https://test.pypi.org/project/${{ env.repo_name }}/ 350 | ``` 351 | 352 | [Edit this file]: https://github.com/zillionare/cookiecutter-pypackage/blob/master/docs/tutorial.md 353 | [Codecov]: https://codecov.io/ 354 | [PYPI]: https://pypi.org 355 | [GitHub]: https://github.com/ 356 | [TestPyPI]: https://test.pypi.org/ 357 | [GitHub Help]: https://help.github.com/ 358 | [Generate]: https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/ 359 | [Add]: https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/ 360 | [How to apply testpypi token]: https://test.pypi.org/manage/account/ 361 | [How to apply pypi token]: https://pypi.org/manage/account/ 362 | [How to apply personal token]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token 363 | [install codecov app]: https://github.com/apps/codecov 364 | -------------------------------------------------------------------------------- /hooks/aioproc.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import functools 3 | import os 4 | import shlex 5 | 6 | from colorama import Fore, Style 7 | 8 | 9 | async def _echo(stream): 10 | while True: 11 | line = await stream.readline() 12 | line = line.decode("utf-8") 13 | if not line: 14 | break 15 | line = line.rstrip("\n") 16 | if line.upper().startswith("WARNING"): 17 | print(Fore.YELLOW + line + Style.RESET_ALL) 18 | elif line.upper().startswith("ERROR"): 19 | print(Fore.RED, line, Style.RESET_ALL) 20 | raise Exception(line) 21 | else: 22 | print(line) 23 | 24 | 25 | def async_run(func): 26 | @functools.wraps(func) 27 | def wrapper(*args, **kwargs): 28 | return asyncio.run(func(*args, **kwargs)) 29 | 30 | return wrapper 31 | 32 | 33 | async def aioprocess( 34 | *cmds, 35 | stdout_handler=_echo, 36 | stderr_handler=_echo, 37 | inherit_env=True, 38 | detached=False, 39 | cwd=None, 40 | ): 41 | """execute cmds in asyncio process, and echo it's stdout/stderr 42 | 43 | if detached is specified, the subprocess will be detached with parent after created. 44 | if inherit_env is specified, then subprocess with inherite envars from parent process. 45 | 46 | Args: 47 | *cmds: list of cmds to be executed 48 | stdout_handler: handler for stdout 49 | stderr_handler: handler for stderr 50 | inherit_env: inherit envars from parent process 51 | detached: detach subprocess with parent 52 | cwd: change working directory of subprocess 53 | Examples: 54 | >>> aioprocess("ls") 55 | >>> aioprocess("ping -c 10 www.baidu.com") 56 | >>> aioprocess("ping", "-c", "10", "www.baidu.com") 57 | >>> aioprocess("python -m http.server", detached=True) 58 | """ 59 | cur_dir = os.getcwd() 60 | 61 | try: 62 | if cwd: 63 | os.chdir(cwd) 64 | 65 | if len(cmds) == 1 and isinstance(cmds[0], str): 66 | cmds = shlex.split(cmds[0]) 67 | 68 | env = os.environ.copy() if inherit_env else None 69 | 70 | proc = await asyncio.create_subprocess_exec( 71 | *cmds, 72 | stdout=asyncio.subprocess.PIPE, 73 | stderr=asyncio.subprocess.PIPE, 74 | start_new_session=detached, 75 | env=env, 76 | ) 77 | 78 | if stdout_handler: 79 | asyncio.ensure_future(stdout_handler(proc.stdout)) 80 | if stderr_handler: 81 | asyncio.ensure_future(stderr_handler(proc.stderr)) 82 | 83 | return proc 84 | finally: 85 | os.chdir(cur_dir) 86 | 87 | 88 | # @async_run 89 | # async def main(): 90 | # try: 91 | # proc = await aioprocess("pip install tqdm") 92 | # await proc.wait() 93 | # except Exception as e: 94 | # print("ecxeption:", e) 95 | 96 | # main() 97 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from hooks.aioproc import aioprocess, async_run 5 | import colorama 6 | from colorama import Fore, Style 7 | 8 | PROJECT_DIRECTORY = os.path.realpath(os.path.curdir) 9 | 10 | 11 | def remove_file(filepath): 12 | try: 13 | os.remove(os.path.join(PROJECT_DIRECTORY, filepath)) 14 | except FileNotFoundError: 15 | pass 16 | 17 | 18 | @async_run 19 | async def execute(*args, cwd=None): 20 | cur_dir = os.getcwd() 21 | 22 | proc = await aioprocess(*args, cwd=cwd) 23 | await proc.wait() 24 | 25 | 26 | def init_git(): 27 | # workaround for issue #1 28 | if not os.path.exists(os.path.join(PROJECT_DIRECTORY, ".git")): 29 | execute( 30 | "git", 31 | "config", 32 | "--system", 33 | "init.defaultBranch", 34 | "main", 35 | cwd=PROJECT_DIRECTORY, 36 | ) 37 | execute("git", "init", cwd=PROJECT_DIRECTORY) 38 | execute( 39 | "git", 40 | "config", 41 | "user.name", 42 | "{{ cookiecutter.full_name }}", 43 | cwd=PROJECT_DIRECTORY, 44 | ) 45 | execute( 46 | "git", 47 | "config", 48 | "user.email", 49 | "{{ cookiecutter.email }}", 50 | cwd=PROJECT_DIRECTORY, 51 | ) 52 | 53 | 54 | def init_dev(): 55 | print(Style.NORMAL, Fore.BLUE, "installing pre-commit hooks...") 56 | print(Style.RESET_ALL, Style.DIM) 57 | try: 58 | execute(sys.executable, "-m", "pip", "install", "pre-commit") 59 | execute("pre-commit", "install", cwd="{{ cookiecutter.project_slug }}") 60 | print(Style.NORMAL, Fore.GREEN, "pre-commit hooks was successfully installed") 61 | print(Style.RESET_ALL) 62 | except Exception as e: 63 | print(e) 64 | print( 65 | Fore.YELLOW, 66 | "failed to install pre-commit hooks. You may need run `pre-commit install` later by your self", 67 | Style.RESET_ALL, 68 | ) 69 | 70 | print(Style.NORMAL, Fore.BLUE, "installing poetry...") 71 | print(Style.RESET_ALL, Style.DIM) 72 | 73 | try: 74 | execute(sys.executable, "-m", "pip", "install", "poetry") 75 | print(Style.NORMAL, Fore.GREEN, "poetry installed successfully", Style.RESET_ALL) 76 | except Exception as e: 77 | print(e) 78 | print( 79 | Fore.YELLOW, 80 | "failed to install poetry, you may need re-run the task by yourself.", 81 | Style.RESET_ALL, 82 | ) 83 | return 84 | 85 | try: 86 | print(Style.NORMAL, Fore.BLUE, "install all dev dependency packages...") 87 | print(Style.RESET_ALL, Style.DIM) 88 | execute("poetry", "install", "-E", "dev", "-E", "doc", "-E", "test", cwd=PROJECT_DIRECTORY) 89 | print( 90 | Style.NORMAL, 91 | Fore.GREEN, 92 | "all dev dependency packages installed successfully", 93 | Style.RESET_ALL, 94 | ) 95 | except Exception as e: 96 | print(e) 97 | print( 98 | Style.NORMAL, 99 | Fore.YELLOW, 100 | "failed to install dev dependency packages, you may need re-run the task by yourself: poetry install -E dev -E test -E doc", 101 | Style.RESET_ALL, 102 | ) 103 | 104 | 105 | 106 | if __name__ == "__main__": 107 | colorama.init() 108 | 109 | if "{{ cookiecutter.create_author_file }}" != "y": 110 | remove_file("AUTHORS.md") 111 | remove_file("docs/authors.md") 112 | 113 | if "no" in "{{ cookiecutter.command_line_interface|lower }}": 114 | cli_file = os.path.join("{{ cookiecutter.project_slug }}", "cli.py") 115 | remove_file(cli_file) 116 | 117 | if "Not open source" == "{{ cookiecutter.open_source_license }}": 118 | remove_file("LICENSE") 119 | 120 | try: 121 | init_git() 122 | except Exception as e: 123 | print(e) 124 | 125 | if "{{ cookiecutter.init_dev_env }}" == "y": 126 | try: 127 | init_dev() 128 | except Exception as e: 129 | print(e) 130 | -------------------------------------------------------------------------------- /hooks/pre_gen_project.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | 4 | MODULE_REGEX = r"^[_a-zA-Z][_a-zA-Z0-9]+$" 5 | 6 | module_name = "{{ cookiecutter.project_slug}}" 7 | 8 | if not re.match(MODULE_REGEX, module_name): 9 | print( 10 | "ERROR: The project slug (%s) is not a valid Python module name. Please do not use a - and use _ instead" 11 | % module_name 12 | ) 13 | 14 | # Exit to cancel project 15 | sys.exit(1) 16 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Python Project Wizard (ppw) 2 | repo_url: https://github.com/zillionare/python-project-wizard 3 | repo_name: Python Project Wizard 4 | nav: 5 | - Introduction: index.md 6 | - Tutorial: tutorial.md 7 | - Console script: console_script_setup.md 8 | - Release checklist: pypi_release_checklist.md 9 | - FAQ: faq.md 10 | - History: history.md 11 | 12 | theme: 13 | name: material 14 | language: en 15 | #logo: assets/logo.png 16 | palette: 17 | - media: "(prefers-color-scheme: light)" 18 | scheme: default 19 | toggle: 20 | icon: material/weather-night 21 | name: Switch to dark mode 22 | - media: "(prefers-color-scheme: dark)" 23 | scheme: slate 24 | toggle: 25 | icon: material/weather-sunny 26 | name: Switch to light mode 27 | features: 28 | - navigation.indexes 29 | - navigation.tabs 30 | - navigation.instant 31 | - navigation.tabs.sticky 32 | 33 | markdown_extensions: 34 | - pymdownx.highlight: 35 | linenums: true 36 | - pymdownx.emoji: 37 | emoji_index: !!python/name:materialx.emoji.twemoji 38 | emoji_generator: !!python/name:materialx.emoji.to_svg 39 | - pymdownx.superfences 40 | - pymdownx.details 41 | - admonition 42 | - toc: 43 | baselevel: 2 44 | permalink: true 45 | slugify: !!python/name:pymdownx.slugs.uslugify 46 | plugins: 47 | - include-markdown 48 | - search: 49 | lang: en 50 | extra: 51 | version: 52 | provider: mike 53 | social: 54 | - icon: fontawesome/brands/github 55 | link: https://zillionare.github.io/python-project-wizard 56 | name: Github 57 | - icon: material/email 58 | link: "mailto:code@jieyu.ai" 59 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "appdirs" 5 | version = "1.4.4" 6 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 7 | category = "main" 8 | optional = true 9 | python-versions = "*" 10 | files = [ 11 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 12 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 13 | ] 14 | 15 | [package.source] 16 | type = "legacy" 17 | url = "https://mirrors.aliyun.com/pypi/simple" 18 | reference = "ali" 19 | 20 | [[package]] 21 | name = "arrow" 22 | version = "0.13.2" 23 | description = "Better dates and times for Python" 24 | category = "main" 25 | optional = false 26 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 27 | files = [ 28 | {file = "arrow-0.13.2-py2.py3-none-any.whl", hash = "sha256:002f2315cf4c8404de737c42860441732d339bbc57fee584e2027520e055ecc1"}, 29 | {file = "arrow-0.13.2.tar.gz", hash = "sha256:82dd5e13b733787d4eb0fef42d1ee1a99136dc1d65178f70373b3678b3181bfc"}, 30 | ] 31 | 32 | [package.dependencies] 33 | python-dateutil = "*" 34 | 35 | [package.source] 36 | type = "legacy" 37 | url = "https://mirrors.aliyun.com/pypi/simple" 38 | reference = "ali" 39 | 40 | [[package]] 41 | name = "atomicwrites" 42 | version = "1.4.0" 43 | description = "Atomic file writes." 44 | category = "main" 45 | optional = true 46 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 47 | files = [ 48 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 49 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 50 | ] 51 | 52 | [package.source] 53 | type = "legacy" 54 | url = "https://mirrors.aliyun.com/pypi/simple" 55 | reference = "ali" 56 | 57 | [[package]] 58 | name = "attrs" 59 | version = "20.3.0" 60 | description = "Classes Without Boilerplate" 61 | category = "main" 62 | optional = true 63 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 64 | files = [ 65 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 66 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 67 | ] 68 | 69 | [package.extras] 70 | dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pympler", "pytest (>=4.3.0)", "six", "sphinx", "zope.interface"] 71 | docs = ["furo", "sphinx", "zope.interface"] 72 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 73 | tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 74 | 75 | [package.source] 76 | type = "legacy" 77 | url = "https://mirrors.aliyun.com/pypi/simple" 78 | reference = "ali" 79 | 80 | [[package]] 81 | name = "binaryornot" 82 | version = "0.4.4" 83 | description = "Ultra-lightweight pure Python package to check if a file is binary or text." 84 | category = "main" 85 | optional = false 86 | python-versions = "*" 87 | files = [ 88 | {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, 89 | {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, 90 | ] 91 | 92 | [package.dependencies] 93 | chardet = ">=3.0.2" 94 | 95 | [package.source] 96 | type = "legacy" 97 | url = "https://mirrors.aliyun.com/pypi/simple" 98 | reference = "ali" 99 | 100 | [[package]] 101 | name = "certifi" 102 | version = "2020.12.5" 103 | description = "Python package for providing Mozilla's CA Bundle." 104 | category = "main" 105 | optional = false 106 | python-versions = "*" 107 | files = [ 108 | {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, 109 | {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, 110 | ] 111 | 112 | [package.source] 113 | type = "legacy" 114 | url = "https://mirrors.aliyun.com/pypi/simple" 115 | reference = "ali" 116 | 117 | [[package]] 118 | name = "cfgv" 119 | version = "3.3.1" 120 | description = "Validate configuration and produce human readable error messages." 121 | category = "main" 122 | optional = true 123 | python-versions = ">=3.6.1" 124 | files = [ 125 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 126 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 127 | ] 128 | 129 | [package.source] 130 | type = "legacy" 131 | url = "https://mirrors.aliyun.com/pypi/simple" 132 | reference = "ali" 133 | 134 | [[package]] 135 | name = "chardet" 136 | version = "4.0.0" 137 | description = "Universal encoding detector for Python 2 and 3" 138 | category = "main" 139 | optional = false 140 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 141 | files = [ 142 | {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, 143 | {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, 144 | ] 145 | 146 | [package.source] 147 | type = "legacy" 148 | url = "https://mirrors.aliyun.com/pypi/simple" 149 | reference = "ali" 150 | 151 | [[package]] 152 | name = "click" 153 | version = "7.1.2" 154 | description = "Composable command line interface toolkit" 155 | category = "main" 156 | optional = false 157 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 158 | files = [ 159 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 160 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 161 | ] 162 | 163 | [package.source] 164 | type = "legacy" 165 | url = "https://mirrors.aliyun.com/pypi/simple" 166 | reference = "ali" 167 | 168 | [[package]] 169 | name = "colorama" 170 | version = "0.4.5" 171 | description = "Cross-platform colored terminal text." 172 | category = "main" 173 | optional = false 174 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 175 | files = [ 176 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 177 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 178 | ] 179 | 180 | [package.source] 181 | type = "legacy" 182 | url = "https://mirrors.aliyun.com/pypi/simple" 183 | reference = "ali" 184 | 185 | [[package]] 186 | name = "cookiecutter" 187 | version = "1.7.2" 188 | description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." 189 | category = "main" 190 | optional = false 191 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 192 | files = [ 193 | {file = "cookiecutter-1.7.2-py2.py3-none-any.whl", hash = "sha256:430eb882d028afb6102c084bab6cf41f6559a77ce9b18dc6802e3bc0cc5f4a30"}, 194 | {file = "cookiecutter-1.7.2.tar.gz", hash = "sha256:efb6b2d4780feda8908a873e38f0e61778c23f6a2ea58215723bcceb5b515dac"}, 195 | ] 196 | 197 | [package.dependencies] 198 | binaryornot = ">=0.4.4" 199 | click = ">=7.0" 200 | Jinja2 = "<3.0.0" 201 | jinja2-time = ">=0.2.0" 202 | MarkupSafe = "<2.0.0" 203 | poyo = ">=0.5.0" 204 | python-slugify = ">=4.0.0" 205 | requests = ">=2.23.0" 206 | six = ">=1.10" 207 | 208 | [package.source] 209 | type = "legacy" 210 | url = "https://mirrors.aliyun.com/pypi/simple" 211 | reference = "ali" 212 | 213 | [[package]] 214 | name = "coverage" 215 | version = "4.4.2" 216 | description = "Code coverage measurement for Python" 217 | category = "main" 218 | optional = true 219 | python-versions = "*" 220 | files = [ 221 | {file = "coverage-4.4.2-cp26-cp26m-macosx_10_10_x86_64.whl", hash = "sha256:d1ee76f560c3c3e8faada866a07a32485445e16ed2206ac8378bd90dadffb9f0"}, 222 | {file = "coverage-4.4.2-cp26-cp26m-manylinux1_i686.whl", hash = "sha256:007eeef7e23f9473622f7d94a3e029a45d55a92a1f083f0f3512f5ab9a669b05"}, 223 | {file = "coverage-4.4.2-cp26-cp26m-manylinux1_x86_64.whl", hash = "sha256:17307429935f96c986a1b1674f78079528833410750321d22b5fb35d1883828e"}, 224 | {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_i686.whl", hash = "sha256:845fddf89dca1e94abe168760a38271abfc2e31863fbb4ada7f9a99337d7c3dc"}, 225 | {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_x86_64.whl", hash = "sha256:3f4d0b3403d3e110d2588c275540649b1841725f5a11a7162620224155d00ba2"}, 226 | {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_intel.whl", hash = "sha256:4c4f368ffe1c2e7602359c2c50233269f3abe1c48ca6b288dcd0fb1d1c679733"}, 227 | {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:f8c55dd0f56d3d618dfacf129e010cbe5d5f94b6951c1b2f13ab1a2f79c284da"}, 228 | {file = "coverage-4.4.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cdd92dd9471e624cd1d8c1a2703d25f114b59b736b0f1f659a98414e535ffb3d"}, 229 | {file = "coverage-4.4.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2ad357d12971e77360034c1596011a03f50c0f9e1ecd12e081342b8d1aee2236"}, 230 | {file = "coverage-4.4.2-cp27-cp27m-win32.whl", hash = "sha256:700d7579995044dc724847560b78ac786f0ca292867447afda7727a6fbaa082e"}, 231 | {file = "coverage-4.4.2-cp27-cp27m-win_amd64.whl", hash = "sha256:66f393e10dd866be267deb3feca39babba08ae13763e0fc7a1063cbe1f8e49f6"}, 232 | {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9a0e1caed2a52f15c96507ab78a48f346c05681a49c5b003172f8073da6aa6b"}, 233 | {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:eea9135432428d3ca7ee9be86af27cb8e56243f73764a9b6c3e0bda1394916be"}, 234 | {file = "coverage-4.4.2-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:5ff16548492e8a12e65ff3d55857ccd818584ed587a6c2898a9ebbe09a880674"}, 235 | {file = "coverage-4.4.2-cp33-cp33m-manylinux1_i686.whl", hash = "sha256:d00e29b78ff610d300b2c37049a41234d48ea4f2d2581759ebcf67caaf731c31"}, 236 | {file = "coverage-4.4.2-cp33-cp33m-manylinux1_x86_64.whl", hash = "sha256:87d942863fe74b1c3be83a045996addf1639218c2cb89c5da18c06c0fe3917ea"}, 237 | {file = "coverage-4.4.2-cp34-cp34m-macosx_10_10_x86_64.whl", hash = "sha256:358d635b1fc22a425444d52f26287ae5aea9e96e254ff3c59c407426f44574f4"}, 238 | {file = "coverage-4.4.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:81912cfe276e0069dca99e1e4e6be7b06b5fc8342641c6b472cb2fed7de7ae18"}, 239 | {file = "coverage-4.4.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:079248312838c4c8f3494934ab7382a42d42d5f365f0cf7516f938dbb3f53f3f"}, 240 | {file = "coverage-4.4.2-cp34-cp34m-win32.whl", hash = "sha256:b0059630ca5c6b297690a6bf57bf2fdac1395c24b7935fd73ee64190276b743b"}, 241 | {file = "coverage-4.4.2-cp34-cp34m-win_amd64.whl", hash = "sha256:493082f104b5ca920e97a485913de254cbe351900deed72d4264571c73464cd0"}, 242 | {file = "coverage-4.4.2-cp35-cp35m-macosx_10_10_x86_64.whl", hash = "sha256:e3ba9b14607c23623cf38f90b23f5bed4a3be87cbfa96e2e9f4eabb975d1e98b"}, 243 | {file = "coverage-4.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:82cbd3317320aa63c65555aa4894bf33a13fb3a77f079059eb5935eea415938d"}, 244 | {file = "coverage-4.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9721f1b7275d3112dc7ccf63f0553c769f09b5c25a26ee45872c7f5c09edf6c1"}, 245 | {file = "coverage-4.4.2-cp35-cp35m-win32.whl", hash = "sha256:bd4800e32b4c8d99c3a2c943f1ac430cbf80658d884123d19639bcde90dad44a"}, 246 | {file = "coverage-4.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:f29841e865590af72c4b90d7b5b8e93fd560f5dea436c1d5ee8053788f9285de"}, 247 | {file = "coverage-4.4.2-cp36-cp36m-macosx_10_12_x86_64.whl", hash = "sha256:f3a5c6d054c531536a83521c00e5d4004f1e126e2e2556ce399bef4180fbe540"}, 248 | {file = "coverage-4.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:dd707a21332615108b736ef0b8513d3edaf12d2a7d5fc26cd04a169a8ae9b526"}, 249 | {file = "coverage-4.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2e1a5c6adebb93c3b175103c2f855eda957283c10cf937d791d81bef8872d6ca"}, 250 | {file = "coverage-4.4.2-cp36-cp36m-win32.whl", hash = "sha256:f87f522bde5540d8a4b11df80058281ac38c44b13ce29ced1e294963dd51a8f8"}, 251 | {file = "coverage-4.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a7cfaebd8f24c2b537fa6a271229b051cdac9c1734bb6f939ccfc7c055689baa"}, 252 | {file = "coverage-4.4.2.tar.gz", hash = "sha256:309d91bd7a35063ec7a0e4d75645488bfab3f0b66373e7722f23da7f5b0f34cc"}, 253 | ] 254 | 255 | [package.source] 256 | type = "legacy" 257 | url = "https://mirrors.aliyun.com/pypi/simple" 258 | reference = "ali" 259 | 260 | [[package]] 261 | name = "distlib" 262 | version = "0.3.1" 263 | description = "Distribution utilities" 264 | category = "main" 265 | optional = true 266 | python-versions = "*" 267 | files = [ 268 | {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, 269 | {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, 270 | ] 271 | 272 | [package.source] 273 | type = "legacy" 274 | url = "https://mirrors.aliyun.com/pypi/simple" 275 | reference = "ali" 276 | 277 | [[package]] 278 | name = "filelock" 279 | version = "3.0.12" 280 | description = "A platform independent file lock." 281 | category = "main" 282 | optional = true 283 | python-versions = "*" 284 | files = [ 285 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 286 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 287 | ] 288 | 289 | [package.source] 290 | type = "legacy" 291 | url = "https://mirrors.aliyun.com/pypi/simple" 292 | reference = "ali" 293 | 294 | [[package]] 295 | name = "fire" 296 | version = "0.4.0" 297 | description = "A library for automatically generating command line interfaces." 298 | category = "main" 299 | optional = true 300 | python-versions = "*" 301 | files = [ 302 | {file = "fire-0.4.0.tar.gz", hash = "sha256:c5e2b8763699d1142393a46d0e3e790c5eb2f0706082df8f647878842c216a62"}, 303 | ] 304 | 305 | [package.dependencies] 306 | six = "*" 307 | termcolor = "*" 308 | 309 | [package.source] 310 | type = "legacy" 311 | url = "https://mirrors.aliyun.com/pypi/simple" 312 | reference = "ali" 313 | 314 | [[package]] 315 | name = "future" 316 | version = "0.18.2" 317 | description = "Clean single-source support for Python 3 and 2" 318 | category = "main" 319 | optional = true 320 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 321 | files = [ 322 | {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, 323 | ] 324 | 325 | [package.source] 326 | type = "legacy" 327 | url = "https://mirrors.aliyun.com/pypi/simple" 328 | reference = "ali" 329 | 330 | [[package]] 331 | name = "identify" 332 | version = "2.4.12" 333 | description = "File identification library for Python" 334 | category = "main" 335 | optional = true 336 | python-versions = ">=3.7" 337 | files = [ 338 | {file = "identify-2.4.12-py2.py3-none-any.whl", hash = "sha256:5f06b14366bd1facb88b00540a1de05b69b310cbc2654db3c7e07fa3a4339323"}, 339 | {file = "identify-2.4.12.tar.gz", hash = "sha256:3f3244a559290e7d3deb9e9adc7b33594c1bc85a9dd82e0f1be519bf12a1ec17"}, 340 | ] 341 | 342 | [package.extras] 343 | license = ["ukkonen"] 344 | 345 | [package.source] 346 | type = "legacy" 347 | url = "https://mirrors.aliyun.com/pypi/simple" 348 | reference = "ali" 349 | 350 | [[package]] 351 | name = "idna" 352 | version = "2.10" 353 | description = "Internationalized Domain Names in Applications (IDNA)" 354 | category = "main" 355 | optional = false 356 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 357 | files = [ 358 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 359 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 360 | ] 361 | 362 | [package.source] 363 | type = "legacy" 364 | url = "https://mirrors.aliyun.com/pypi/simple" 365 | reference = "ali" 366 | 367 | [[package]] 368 | name = "iniconfig" 369 | version = "1.1.1" 370 | description = "iniconfig: brain-dead simple config-ini parsing" 371 | category = "main" 372 | optional = true 373 | python-versions = "*" 374 | files = [ 375 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 376 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 377 | ] 378 | 379 | [package.source] 380 | type = "legacy" 381 | url = "https://mirrors.aliyun.com/pypi/simple" 382 | reference = "ali" 383 | 384 | [[package]] 385 | name = "jinja2" 386 | version = "2.11.3" 387 | description = "A very fast and expressive template engine." 388 | category = "main" 389 | optional = false 390 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 391 | files = [ 392 | {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, 393 | {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, 394 | ] 395 | 396 | [package.dependencies] 397 | MarkupSafe = ">=0.23" 398 | 399 | [package.extras] 400 | i18n = ["Babel (>=0.8)"] 401 | 402 | [package.source] 403 | type = "legacy" 404 | url = "https://mirrors.aliyun.com/pypi/simple" 405 | reference = "ali" 406 | 407 | [[package]] 408 | name = "jinja2-time" 409 | version = "0.2.0" 410 | description = "Jinja2 Extension for Dates and Times" 411 | category = "main" 412 | optional = false 413 | python-versions = "*" 414 | files = [ 415 | {file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"}, 416 | {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"}, 417 | ] 418 | 419 | [package.dependencies] 420 | arrow = "*" 421 | jinja2 = "*" 422 | 423 | [package.source] 424 | type = "legacy" 425 | url = "https://mirrors.aliyun.com/pypi/simple" 426 | reference = "ali" 427 | 428 | [[package]] 429 | name = "joblib" 430 | version = "1.0.1" 431 | description = "Lightweight pipelining with Python functions" 432 | category = "main" 433 | optional = true 434 | python-versions = ">=3.6" 435 | files = [ 436 | {file = "joblib-1.0.1-py3-none-any.whl", hash = "sha256:feeb1ec69c4d45129954f1b7034954241eedfd6ba39b5e9e4b6883be3332d5e5"}, 437 | {file = "joblib-1.0.1.tar.gz", hash = "sha256:9c17567692206d2f3fb9ecf5e991084254fe631665c450b443761c4186a613f7"}, 438 | ] 439 | 440 | [package.source] 441 | type = "legacy" 442 | url = "https://mirrors.aliyun.com/pypi/simple" 443 | reference = "ali" 444 | 445 | [[package]] 446 | name = "livereload" 447 | version = "2.6.3" 448 | description = "Python LiveReload is an awesome tool for web developers" 449 | category = "main" 450 | optional = true 451 | python-versions = "*" 452 | files = [ 453 | {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, 454 | {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, 455 | ] 456 | 457 | [package.dependencies] 458 | six = "*" 459 | tornado = {version = "*", markers = "python_version > \"2.7\""} 460 | 461 | [package.source] 462 | type = "legacy" 463 | url = "https://mirrors.aliyun.com/pypi/simple" 464 | reference = "ali" 465 | 466 | [[package]] 467 | name = "lunr" 468 | version = "0.5.8" 469 | description = "A Python implementation of Lunr.js" 470 | category = "main" 471 | optional = true 472 | python-versions = "*" 473 | files = [ 474 | {file = "lunr-0.5.8-py2.py3-none-any.whl", hash = "sha256:aab3f489c4d4fab4c1294a257a30fec397db56f0a50273218ccc3efdbf01d6ca"}, 475 | {file = "lunr-0.5.8.tar.gz", hash = "sha256:c4fb063b98eff775dd638b3df380008ae85e6cb1d1a24d1cd81a10ef6391c26e"}, 476 | ] 477 | 478 | [package.dependencies] 479 | future = ">=0.16.0" 480 | nltk = {version = ">=3.2.5", optional = true, markers = "python_version > \"2.7\" and extra == \"languages\""} 481 | six = ">=1.11.0" 482 | 483 | [package.extras] 484 | languages = ["nltk (>=3.2.5)", "nltk (>=3.2.5,<3.5)"] 485 | 486 | [package.source] 487 | type = "legacy" 488 | url = "https://mirrors.aliyun.com/pypi/simple" 489 | reference = "ali" 490 | 491 | [[package]] 492 | name = "markdown" 493 | version = "3.3.4" 494 | description = "Python implementation of Markdown." 495 | category = "main" 496 | optional = true 497 | python-versions = ">=3.6" 498 | files = [ 499 | {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, 500 | {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, 501 | ] 502 | 503 | [package.extras] 504 | testing = ["coverage", "pyyaml"] 505 | 506 | [package.source] 507 | type = "legacy" 508 | url = "https://mirrors.aliyun.com/pypi/simple" 509 | reference = "ali" 510 | 511 | [[package]] 512 | name = "markupsafe" 513 | version = "1.1.1" 514 | description = "Safely add untrusted strings to HTML/XML markup." 515 | category = "main" 516 | optional = false 517 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 518 | files = [ 519 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 520 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 521 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 522 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 523 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 524 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 525 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 526 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 527 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 528 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 529 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 530 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 531 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 532 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 533 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 534 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 535 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 536 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 537 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, 538 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 539 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 540 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, 541 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, 542 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, 543 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 544 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 545 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 546 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, 547 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 548 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 549 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, 550 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, 551 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, 552 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 553 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 554 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 555 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 556 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 557 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, 558 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, 559 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, 560 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 561 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 562 | {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, 563 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, 564 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, 565 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, 566 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, 567 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, 568 | {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, 569 | {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, 570 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 571 | ] 572 | 573 | [package.source] 574 | type = "legacy" 575 | url = "https://mirrors.aliyun.com/pypi/simple" 576 | reference = "ali" 577 | 578 | [[package]] 579 | name = "mike" 580 | version = "1.1.2" 581 | description = "Manage multiple versions of your MkDocs-powered documentation" 582 | category = "main" 583 | optional = true 584 | python-versions = "*" 585 | files = [ 586 | {file = "mike-1.1.2-py3-none-any.whl", hash = "sha256:4c307c28769834d78df10f834f57f810f04ca27d248f80a75f49c6fa2d1527ca"}, 587 | {file = "mike-1.1.2.tar.gz", hash = "sha256:56c3f1794c2d0b5fdccfa9b9487beb013ca813de2e3ad0744724e9d34d40b77b"}, 588 | ] 589 | 590 | [package.dependencies] 591 | jinja2 = "*" 592 | mkdocs = ">=1.0" 593 | pyyaml = ">=5.1" 594 | verspec = "*" 595 | 596 | [package.extras] 597 | dev = ["coverage", "flake8 (>=3.0)", "shtab"] 598 | test = ["coverage", "flake8 (>=3.0)", "shtab"] 599 | 600 | [package.source] 601 | type = "legacy" 602 | url = "https://mirrors.aliyun.com/pypi/simple" 603 | reference = "ali" 604 | 605 | [[package]] 606 | name = "mkdocs" 607 | version = "1.1.2" 608 | description = "Project documentation with Markdown." 609 | category = "main" 610 | optional = true 611 | python-versions = ">=3.5" 612 | files = [ 613 | {file = "mkdocs-1.1.2-py3-none-any.whl", hash = "sha256:096f52ff52c02c7e90332d2e53da862fde5c062086e1b5356a6e392d5d60f5e9"}, 614 | {file = "mkdocs-1.1.2.tar.gz", hash = "sha256:f0b61e5402b99d7789efa032c7a74c90a20220a9c81749da06dbfbcbd52ffb39"}, 615 | ] 616 | 617 | [package.dependencies] 618 | click = ">=3.3" 619 | Jinja2 = ">=2.10.1" 620 | livereload = ">=2.5.1" 621 | lunr = {version = "0.5.8", extras = ["languages"]} 622 | Markdown = ">=3.2.1" 623 | PyYAML = ">=3.10" 624 | tornado = ">=5.0" 625 | 626 | [package.source] 627 | type = "legacy" 628 | url = "https://mirrors.aliyun.com/pypi/simple" 629 | reference = "ali" 630 | 631 | [[package]] 632 | name = "mkdocs-include-markdown-plugin" 633 | version = "2.8.0" 634 | description = "Mkdocs Markdown includer plugin." 635 | category = "main" 636 | optional = true 637 | python-versions = ">=3.6" 638 | files = [ 639 | {file = "mkdocs_include_markdown_plugin-2.8.0-py3-none-any.whl", hash = "sha256:29b7d40da2945414f4dcb4c39eac004da6a644433f10d9da0dd5e331e50a5dbf"}, 640 | {file = "mkdocs_include_markdown_plugin-2.8.0.tar.gz", hash = "sha256:a4171b1f8a5cb4e2e05f2989ca47f4825ed0723021af7a3a871f8abe7cb91ba0"}, 641 | ] 642 | 643 | [package.extras] 644 | dev = ["bump2version (==1.0.1)", "flake8 (==3.8.4)", "flake8-implicit-str-concat (==0.2.0)", "flake8-print (==4.0.0)", "isort (==5.6.4)", "pre-commit (==2.9.2)", "pytest (==6.1.2)", "pytest-cov (==2.10.1)", "pyupgrade (==2.9.0)", "yamllint (==1.25.0)"] 645 | test = ["pytest (==6.1.2)", "pytest-cov (==2.10.1)"] 646 | 647 | [package.source] 648 | type = "legacy" 649 | url = "https://mirrors.aliyun.com/pypi/simple" 650 | reference = "ali" 651 | 652 | [[package]] 653 | name = "mkdocs-material" 654 | version = "6.2.8" 655 | description = "A Material Design theme for MkDocs" 656 | category = "main" 657 | optional = true 658 | python-versions = "*" 659 | files = [ 660 | {file = "mkdocs-material-6.2.8.tar.gz", hash = "sha256:ce2f4a71e5db49540d71fd32f9afba7645765f7eca391e560d1d27f947eb344c"}, 661 | {file = "mkdocs_material-6.2.8-py2.py3-none-any.whl", hash = "sha256:c9b63d709d29778aa3dafc7178b6a8c655b00937be2594aab016d1423696c792"}, 662 | ] 663 | 664 | [package.dependencies] 665 | markdown = ">=3.2" 666 | mkdocs = ">=1.1" 667 | mkdocs-material-extensions = ">=1.0" 668 | Pygments = ">=2.4" 669 | pymdown-extensions = ">=7.0" 670 | 671 | [package.source] 672 | type = "legacy" 673 | url = "https://mirrors.aliyun.com/pypi/simple" 674 | reference = "ali" 675 | 676 | [[package]] 677 | name = "mkdocs-material-extensions" 678 | version = "1.0.1" 679 | description = "Extension pack for Python Markdown." 680 | category = "main" 681 | optional = true 682 | python-versions = ">=3.5" 683 | files = [ 684 | {file = "mkdocs-material-extensions-1.0.1.tar.gz", hash = "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f"}, 685 | {file = "mkdocs_material_extensions-1.0.1-py3-none-any.whl", hash = "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338"}, 686 | ] 687 | 688 | [package.dependencies] 689 | mkdocs-material = ">=5.0.0" 690 | 691 | [package.source] 692 | type = "legacy" 693 | url = "https://mirrors.aliyun.com/pypi/simple" 694 | reference = "ali" 695 | 696 | [[package]] 697 | name = "mypy" 698 | version = "1.5.1" 699 | description = "Optional static typing for Python" 700 | category = "main" 701 | optional = false 702 | python-versions = ">=3.8" 703 | files = [ 704 | {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, 705 | {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, 706 | {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, 707 | {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, 708 | {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, 709 | {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, 710 | {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, 711 | {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, 712 | {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, 713 | {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, 714 | {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, 715 | {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, 716 | {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, 717 | {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, 718 | {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, 719 | {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, 720 | {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, 721 | {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, 722 | {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, 723 | {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, 724 | {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, 725 | {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, 726 | {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, 727 | {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, 728 | {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, 729 | {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, 730 | {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, 731 | ] 732 | 733 | [package.dependencies] 734 | mypy-extensions = ">=1.0.0" 735 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 736 | typing-extensions = ">=4.1.0" 737 | 738 | [package.extras] 739 | dmypy = ["psutil (>=4.0)"] 740 | install-types = ["pip"] 741 | reports = ["lxml"] 742 | 743 | [package.source] 744 | type = "legacy" 745 | url = "https://mirrors.aliyun.com/pypi/simple" 746 | reference = "ali" 747 | 748 | [[package]] 749 | name = "mypy-extensions" 750 | version = "1.0.0" 751 | description = "Type system extensions for programs checked with the mypy type checker." 752 | category = "main" 753 | optional = false 754 | python-versions = ">=3.5" 755 | files = [ 756 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 757 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 758 | ] 759 | 760 | [package.source] 761 | type = "legacy" 762 | url = "https://mirrors.aliyun.com/pypi/simple" 763 | reference = "ali" 764 | 765 | [[package]] 766 | name = "nltk" 767 | version = "3.6.1" 768 | description = "Natural Language Toolkit" 769 | category = "main" 770 | optional = true 771 | python-versions = ">=3.5.*" 772 | files = [ 773 | {file = "nltk-3.6.1-py3-none-any.whl", hash = "sha256:1235660f52ab10fda34d5277096724747f767b2903e1c0c4e14bde013552c9ba"}, 774 | {file = "nltk-3.6.1.zip", hash = "sha256:cbc2ed576998fcf7cd181eeb3ca029e5f0025b264074b4beb57ce780673f8b86"}, 775 | ] 776 | 777 | [package.dependencies] 778 | click = "*" 779 | joblib = "*" 780 | regex = "*" 781 | tqdm = "*" 782 | 783 | [package.extras] 784 | all = ["gensim (<4.0.0)", "matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] 785 | corenlp = ["requests"] 786 | machine-learning = ["gensim (<4.0.0)", "numpy", "python-crfsuite", "scikit-learn", "scipy"] 787 | plot = ["matplotlib"] 788 | tgrep = ["pyparsing"] 789 | twitter = ["twython"] 790 | 791 | [package.source] 792 | type = "legacy" 793 | url = "https://mirrors.aliyun.com/pypi/simple" 794 | reference = "ali" 795 | 796 | [[package]] 797 | name = "nodeenv" 798 | version = "1.6.0" 799 | description = "Node.js virtual environment builder" 800 | category = "main" 801 | optional = true 802 | python-versions = "*" 803 | files = [ 804 | {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, 805 | {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, 806 | ] 807 | 808 | [package.source] 809 | type = "legacy" 810 | url = "https://mirrors.aliyun.com/pypi/simple" 811 | reference = "ali" 812 | 813 | [[package]] 814 | name = "packaging" 815 | version = "20.9" 816 | description = "Core utilities for Python packages" 817 | category = "main" 818 | optional = true 819 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 820 | files = [ 821 | {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, 822 | {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, 823 | ] 824 | 825 | [package.dependencies] 826 | pyparsing = ">=2.0.2" 827 | 828 | [package.source] 829 | type = "legacy" 830 | url = "https://mirrors.aliyun.com/pypi/simple" 831 | reference = "ali" 832 | 833 | [[package]] 834 | name = "pluggy" 835 | version = "0.13.1" 836 | description = "plugin and hook calling mechanisms for python" 837 | category = "main" 838 | optional = true 839 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 840 | files = [ 841 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 842 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 843 | ] 844 | 845 | [package.extras] 846 | dev = ["pre-commit", "tox"] 847 | 848 | [package.source] 849 | type = "legacy" 850 | url = "https://mirrors.aliyun.com/pypi/simple" 851 | reference = "ali" 852 | 853 | [[package]] 854 | name = "poyo" 855 | version = "0.5.0" 856 | description = "A lightweight YAML Parser for Python. 🐓" 857 | category = "main" 858 | optional = false 859 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 860 | files = [ 861 | {file = "poyo-0.5.0-py2.py3-none-any.whl", hash = "sha256:3e2ca8e33fdc3c411cd101ca395668395dd5dc7ac775b8e809e3def9f9fe041a"}, 862 | {file = "poyo-0.5.0.tar.gz", hash = "sha256:e26956aa780c45f011ca9886f044590e2d8fd8b61db7b1c1cf4e0869f48ed4dd"}, 863 | ] 864 | 865 | [package.source] 866 | type = "legacy" 867 | url = "https://mirrors.aliyun.com/pypi/simple" 868 | reference = "ali" 869 | 870 | [[package]] 871 | name = "pre-commit" 872 | version = "2.18.1" 873 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 874 | category = "main" 875 | optional = true 876 | python-versions = ">=3.7" 877 | files = [ 878 | {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, 879 | {file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"}, 880 | ] 881 | 882 | [package.dependencies] 883 | cfgv = ">=2.0.0" 884 | identify = ">=1.0.0" 885 | nodeenv = ">=0.11.1" 886 | pyyaml = ">=5.1" 887 | toml = "*" 888 | virtualenv = ">=20.0.8" 889 | 890 | [package.source] 891 | type = "legacy" 892 | url = "https://mirrors.aliyun.com/pypi/simple" 893 | reference = "ali" 894 | 895 | [[package]] 896 | name = "py" 897 | version = "1.10.0" 898 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 899 | category = "main" 900 | optional = true 901 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 902 | files = [ 903 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 904 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 905 | ] 906 | 907 | [package.source] 908 | type = "legacy" 909 | url = "https://mirrors.aliyun.com/pypi/simple" 910 | reference = "ali" 911 | 912 | [[package]] 913 | name = "pygments" 914 | version = "2.8.1" 915 | description = "Pygments is a syntax highlighting package written in Python." 916 | category = "main" 917 | optional = true 918 | python-versions = ">=3.5" 919 | files = [ 920 | {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, 921 | {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, 922 | ] 923 | 924 | [package.source] 925 | type = "legacy" 926 | url = "https://mirrors.aliyun.com/pypi/simple" 927 | reference = "ali" 928 | 929 | [[package]] 930 | name = "pymdown-extensions" 931 | version = "8.1.1" 932 | description = "Extension pack for Python Markdown." 933 | category = "main" 934 | optional = true 935 | python-versions = ">=3.6" 936 | files = [ 937 | {file = "pymdown-extensions-8.1.1.tar.gz", hash = "sha256:632371fa3bf1b21a0e3f4063010da59b41db049f261f4c0b0872069a9b6d1735"}, 938 | {file = "pymdown_extensions-8.1.1-py3-none-any.whl", hash = "sha256:478b2c04513fbb2db61688d5f6e9030a92fb9be14f1f383535c43f7be9dff95b"}, 939 | ] 940 | 941 | [package.dependencies] 942 | Markdown = ">=3.2" 943 | 944 | [package.source] 945 | type = "legacy" 946 | url = "https://mirrors.aliyun.com/pypi/simple" 947 | reference = "ali" 948 | 949 | [[package]] 950 | name = "pyparsing" 951 | version = "2.4.7" 952 | description = "Python parsing module" 953 | category = "main" 954 | optional = true 955 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 956 | files = [ 957 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 958 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 959 | ] 960 | 961 | [package.source] 962 | type = "legacy" 963 | url = "https://mirrors.aliyun.com/pypi/simple" 964 | reference = "ali" 965 | 966 | [[package]] 967 | name = "pytest" 968 | version = "6.2.5" 969 | description = "pytest: simple powerful testing with Python" 970 | category = "main" 971 | optional = true 972 | python-versions = ">=3.6" 973 | files = [ 974 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 975 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 976 | ] 977 | 978 | [package.dependencies] 979 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 980 | attrs = ">=19.2.0" 981 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 982 | iniconfig = "*" 983 | packaging = "*" 984 | pluggy = ">=0.12,<2.0" 985 | py = ">=1.8.2" 986 | toml = "*" 987 | 988 | [package.extras] 989 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 990 | 991 | [package.source] 992 | type = "legacy" 993 | url = "https://mirrors.aliyun.com/pypi/simple" 994 | reference = "ali" 995 | 996 | [[package]] 997 | name = "pytest-cookies" 998 | version = "0.6.1" 999 | description = "The pytest plugin for your Cookiecutter templates. 🍪" 1000 | category = "main" 1001 | optional = true 1002 | python-versions = ">=3.6" 1003 | files = [ 1004 | {file = "pytest-cookies-0.6.1.tar.gz", hash = "sha256:94c41ad914d420b57bc31d58ab2c0b103322c11d07d13b8d245c85fa9b069714"}, 1005 | {file = "pytest_cookies-0.6.1-py3-none-any.whl", hash = "sha256:2e2383c12a321753676c8944fb75b3c8ccac70fce58e27d6b201e1a3ff3dab27"}, 1006 | ] 1007 | 1008 | [package.dependencies] 1009 | cookiecutter = ">=1.4.0" 1010 | pytest = ">=3.3.0" 1011 | 1012 | [package.source] 1013 | type = "legacy" 1014 | url = "https://mirrors.aliyun.com/pypi/simple" 1015 | reference = "ali" 1016 | 1017 | [[package]] 1018 | name = "pytest-cov" 1019 | version = "2.10.1" 1020 | description = "Pytest plugin for measuring coverage." 1021 | category = "main" 1022 | optional = true 1023 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 1024 | files = [ 1025 | {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, 1026 | {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, 1027 | ] 1028 | 1029 | [package.dependencies] 1030 | coverage = ">=4.4" 1031 | pytest = ">=4.6" 1032 | 1033 | [package.extras] 1034 | testing = ["fields", "hunter", "process-tests (==2.0.2)", "pytest-xdist", "six", "virtualenv"] 1035 | 1036 | [package.source] 1037 | type = "legacy" 1038 | url = "https://mirrors.aliyun.com/pypi/simple" 1039 | reference = "ali" 1040 | 1041 | [[package]] 1042 | name = "python-dateutil" 1043 | version = "2.8.1" 1044 | description = "Extensions to the standard Python datetime module" 1045 | category = "main" 1046 | optional = false 1047 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1048 | files = [ 1049 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 1050 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 1051 | ] 1052 | 1053 | [package.dependencies] 1054 | six = ">=1.5" 1055 | 1056 | [package.source] 1057 | type = "legacy" 1058 | url = "https://mirrors.aliyun.com/pypi/simple" 1059 | reference = "ali" 1060 | 1061 | [[package]] 1062 | name = "python-slugify" 1063 | version = "4.0.1" 1064 | description = "A Python Slugify application that handles Unicode" 1065 | category = "main" 1066 | optional = false 1067 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 1068 | files = [ 1069 | {file = "python-slugify-4.0.1.tar.gz", hash = "sha256:69a517766e00c1268e5bbfc0d010a0a8508de0b18d30ad5a1ff357f8ae724270"}, 1070 | ] 1071 | 1072 | [package.dependencies] 1073 | text-unidecode = ">=1.3" 1074 | 1075 | [package.extras] 1076 | unidecode = ["Unidecode (>=1.1.1)"] 1077 | 1078 | [package.source] 1079 | type = "legacy" 1080 | url = "https://mirrors.aliyun.com/pypi/simple" 1081 | reference = "ali" 1082 | 1083 | [[package]] 1084 | name = "pyyaml" 1085 | version = "6.0.1" 1086 | description = "YAML parser and emitter for Python" 1087 | category = "main" 1088 | optional = true 1089 | python-versions = ">=3.6" 1090 | files = [ 1091 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, 1092 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, 1093 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, 1094 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, 1095 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, 1096 | {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, 1097 | {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, 1098 | {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, 1099 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, 1100 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, 1101 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, 1102 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, 1103 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, 1104 | {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, 1105 | {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, 1106 | {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, 1107 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, 1108 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, 1109 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, 1110 | {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, 1111 | {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, 1112 | {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, 1113 | {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, 1114 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, 1115 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, 1116 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, 1117 | {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, 1118 | {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, 1119 | {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, 1120 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, 1121 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, 1122 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, 1123 | {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, 1124 | {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, 1125 | {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, 1126 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, 1127 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, 1128 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, 1129 | {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, 1130 | {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, 1131 | {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, 1132 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, 1133 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, 1134 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, 1135 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, 1136 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, 1137 | {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, 1138 | {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, 1139 | {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, 1140 | {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, 1141 | ] 1142 | 1143 | [package.source] 1144 | type = "legacy" 1145 | url = "https://mirrors.aliyun.com/pypi/simple" 1146 | reference = "ali" 1147 | 1148 | [[package]] 1149 | name = "regex" 1150 | version = "2021.4.4" 1151 | description = "Alternative regular expression module, to replace re." 1152 | category = "main" 1153 | optional = true 1154 | python-versions = "*" 1155 | files = [ 1156 | {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, 1157 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, 1158 | {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, 1159 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, 1160 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, 1161 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, 1162 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, 1163 | {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, 1164 | {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, 1165 | {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, 1166 | {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, 1167 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, 1168 | {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, 1169 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, 1170 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, 1171 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, 1172 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, 1173 | {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, 1174 | {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, 1175 | {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, 1176 | {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, 1177 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, 1178 | {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, 1179 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, 1180 | {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, 1181 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, 1182 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, 1183 | {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, 1184 | {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, 1185 | {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, 1186 | {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, 1187 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, 1188 | {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, 1189 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, 1190 | {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, 1191 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, 1192 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, 1193 | {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, 1194 | {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, 1195 | {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, 1196 | {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, 1197 | ] 1198 | 1199 | [package.source] 1200 | type = "legacy" 1201 | url = "https://mirrors.aliyun.com/pypi/simple" 1202 | reference = "ali" 1203 | 1204 | [[package]] 1205 | name = "requests" 1206 | version = "2.25.1" 1207 | description = "Python HTTP for Humans." 1208 | category = "main" 1209 | optional = false 1210 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 1211 | files = [ 1212 | {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, 1213 | {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, 1214 | ] 1215 | 1216 | [package.dependencies] 1217 | certifi = ">=2017.4.17" 1218 | chardet = ">=3.0.2,<5" 1219 | idna = ">=2.5,<3" 1220 | urllib3 = ">=1.21.1,<1.27" 1221 | 1222 | [package.extras] 1223 | security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] 1224 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 1225 | 1226 | [package.source] 1227 | type = "legacy" 1228 | url = "https://mirrors.aliyun.com/pypi/simple" 1229 | reference = "ali" 1230 | 1231 | [[package]] 1232 | name = "six" 1233 | version = "1.15.0" 1234 | description = "Python 2 and 3 compatibility utilities" 1235 | category = "main" 1236 | optional = false 1237 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1238 | files = [ 1239 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 1240 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 1241 | ] 1242 | 1243 | [package.source] 1244 | type = "legacy" 1245 | url = "https://mirrors.aliyun.com/pypi/simple" 1246 | reference = "ali" 1247 | 1248 | [[package]] 1249 | name = "termcolor" 1250 | version = "1.1.0" 1251 | description = "ANSII Color formatting for output in terminal." 1252 | category = "main" 1253 | optional = true 1254 | python-versions = "*" 1255 | files = [ 1256 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 1257 | ] 1258 | 1259 | [package.source] 1260 | type = "legacy" 1261 | url = "https://mirrors.aliyun.com/pypi/simple" 1262 | reference = "ali" 1263 | 1264 | [[package]] 1265 | name = "text-unidecode" 1266 | version = "1.3" 1267 | description = "The most basic Text::Unidecode port" 1268 | category = "main" 1269 | optional = false 1270 | python-versions = "*" 1271 | files = [ 1272 | {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, 1273 | {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, 1274 | ] 1275 | 1276 | [package.source] 1277 | type = "legacy" 1278 | url = "https://mirrors.aliyun.com/pypi/simple" 1279 | reference = "ali" 1280 | 1281 | [[package]] 1282 | name = "toml" 1283 | version = "0.10.2" 1284 | description = "Python Library for Tom's Obvious, Minimal Language" 1285 | category = "main" 1286 | optional = true 1287 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 1288 | files = [ 1289 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1290 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1291 | ] 1292 | 1293 | [package.source] 1294 | type = "legacy" 1295 | url = "https://mirrors.aliyun.com/pypi/simple" 1296 | reference = "ali" 1297 | 1298 | [[package]] 1299 | name = "tomli" 1300 | version = "2.0.1" 1301 | description = "A lil' TOML parser" 1302 | category = "main" 1303 | optional = false 1304 | python-versions = ">=3.7" 1305 | files = [ 1306 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1307 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1308 | ] 1309 | 1310 | [package.source] 1311 | type = "legacy" 1312 | url = "https://mirrors.aliyun.com/pypi/simple" 1313 | reference = "ali" 1314 | 1315 | [[package]] 1316 | name = "tornado" 1317 | version = "6.1" 1318 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 1319 | category = "main" 1320 | optional = true 1321 | python-versions = ">= 3.5" 1322 | files = [ 1323 | {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, 1324 | {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, 1325 | {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"}, 1326 | {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"}, 1327 | {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"}, 1328 | {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"}, 1329 | {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"}, 1330 | {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"}, 1331 | {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"}, 1332 | {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"}, 1333 | {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"}, 1334 | {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"}, 1335 | {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"}, 1336 | {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"}, 1337 | {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"}, 1338 | {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"}, 1339 | {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"}, 1340 | {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"}, 1341 | {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"}, 1342 | {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"}, 1343 | {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"}, 1344 | {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"}, 1345 | {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"}, 1346 | {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"}, 1347 | {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"}, 1348 | {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"}, 1349 | {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"}, 1350 | {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"}, 1351 | {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"}, 1352 | {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"}, 1353 | {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"}, 1354 | {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"}, 1355 | {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"}, 1356 | {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"}, 1357 | {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"}, 1358 | {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"}, 1359 | {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"}, 1360 | {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"}, 1361 | {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"}, 1362 | {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, 1363 | {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, 1364 | ] 1365 | 1366 | [package.source] 1367 | type = "legacy" 1368 | url = "https://mirrors.aliyun.com/pypi/simple" 1369 | reference = "ali" 1370 | 1371 | [[package]] 1372 | name = "tox" 1373 | version = "3.23.0" 1374 | description = "tox is a generic virtualenv management and test command line tool" 1375 | category = "main" 1376 | optional = true 1377 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 1378 | files = [ 1379 | {file = "tox-3.23.0-py2.py3-none-any.whl", hash = "sha256:e007673f3595cede9b17a7c4962389e4305d4a3682a6c5a4159a1453b4f326aa"}, 1380 | {file = "tox-3.23.0.tar.gz", hash = "sha256:05a4dbd5e4d3d8269b72b55600f0b0303e2eb47ad5c6fe76d3576f4c58d93661"}, 1381 | ] 1382 | 1383 | [package.dependencies] 1384 | colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} 1385 | filelock = ">=3.0.0" 1386 | packaging = ">=14" 1387 | pluggy = ">=0.12.0" 1388 | py = ">=1.4.17" 1389 | six = ">=1.14.0" 1390 | toml = ">=0.9.4" 1391 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 1392 | 1393 | [package.extras] 1394 | docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] 1395 | testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)"] 1396 | 1397 | [package.source] 1398 | type = "legacy" 1399 | url = "https://mirrors.aliyun.com/pypi/simple" 1400 | reference = "ali" 1401 | 1402 | [[package]] 1403 | name = "tqdm" 1404 | version = "4.64.0" 1405 | description = "Fast, Extensible Progress Meter" 1406 | category = "main" 1407 | optional = true 1408 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 1409 | files = [ 1410 | {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"}, 1411 | {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"}, 1412 | ] 1413 | 1414 | [package.dependencies] 1415 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 1416 | 1417 | [package.extras] 1418 | dev = ["py-make (>=0.1.0)", "twine", "wheel"] 1419 | notebook = ["ipywidgets (>=6)"] 1420 | slack = ["slack-sdk"] 1421 | telegram = ["requests"] 1422 | 1423 | [package.source] 1424 | type = "legacy" 1425 | url = "https://mirrors.aliyun.com/pypi/simple" 1426 | reference = "ali" 1427 | 1428 | [[package]] 1429 | name = "typing-extensions" 1430 | version = "4.7.1" 1431 | description = "Backported and Experimental Type Hints for Python 3.7+" 1432 | category = "main" 1433 | optional = false 1434 | python-versions = ">=3.7" 1435 | files = [ 1436 | {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, 1437 | {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, 1438 | ] 1439 | 1440 | [package.source] 1441 | type = "legacy" 1442 | url = "https://mirrors.aliyun.com/pypi/simple" 1443 | reference = "ali" 1444 | 1445 | [[package]] 1446 | name = "urllib3" 1447 | version = "1.22" 1448 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1449 | category = "main" 1450 | optional = false 1451 | python-versions = "*" 1452 | files = [ 1453 | {file = "urllib3-1.22-py2.py3-none-any.whl", hash = "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b"}, 1454 | {file = "urllib3-1.22.tar.gz", hash = "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"}, 1455 | ] 1456 | 1457 | [package.extras] 1458 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] 1459 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 1460 | 1461 | [package.source] 1462 | type = "legacy" 1463 | url = "https://mirrors.aliyun.com/pypi/simple" 1464 | reference = "ali" 1465 | 1466 | [[package]] 1467 | name = "verspec" 1468 | version = "0.1.0" 1469 | description = "Flexible version handling" 1470 | category = "main" 1471 | optional = true 1472 | python-versions = "*" 1473 | files = [ 1474 | {file = "verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31"}, 1475 | {file = "verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e"}, 1476 | ] 1477 | 1478 | [package.extras] 1479 | test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] 1480 | 1481 | [package.source] 1482 | type = "legacy" 1483 | url = "https://mirrors.aliyun.com/pypi/simple" 1484 | reference = "ali" 1485 | 1486 | [[package]] 1487 | name = "virtualenv" 1488 | version = "20.4.3" 1489 | description = "Virtual Python Environment builder" 1490 | category = "main" 1491 | optional = true 1492 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 1493 | files = [ 1494 | {file = "virtualenv-20.4.3-py2.py3-none-any.whl", hash = "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c"}, 1495 | {file = "virtualenv-20.4.3.tar.gz", hash = "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107"}, 1496 | ] 1497 | 1498 | [package.dependencies] 1499 | appdirs = ">=1.4.3,<2" 1500 | distlib = ">=0.3.1,<1" 1501 | filelock = ">=3.0.0,<4" 1502 | six = ">=1.9.0,<2" 1503 | 1504 | [package.extras] 1505 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] 1506 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "xonsh (>=0.9.16)"] 1507 | 1508 | [package.source] 1509 | type = "legacy" 1510 | url = "https://mirrors.aliyun.com/pypi/simple" 1511 | reference = "ali" 1512 | 1513 | [extras] 1514 | dev = ["fire", "livereload", "mike", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocs-material", "mkdocs-material-extensions", "pytest", "pytest-cookies", "pytest-cov", "pyyaml", "tox"] 1515 | 1516 | [metadata] 1517 | lock-version = "2.0" 1518 | python-versions = ">=3.8,<4.0" 1519 | content-hash = "ea2a50659f0a559c52f7162f1cf46e6ae47fd9169481e711f03cec9c3ea11a65" 1520 | -------------------------------------------------------------------------------- /ppw/cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def main(): 5 | cwd = os.path.dirname(__file__) 6 | package_dir = os.path.abspath(os.path.join(cwd, "..")) 7 | os.system(f"cookiecutter {package_dir}") 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool] 2 | [tool.poetry] 3 | name = "ppw" 4 | version = "1.3.5" 5 | description = "A Wizard to create a skeleton python project with up-to-date technology" 6 | license = "BSD-3-Clause" 7 | authors = ["Aaron Yang "] 8 | readme = "README.md" 9 | repository = "https://github.com/zillionare/python-project-wizard" 10 | documentation = "https://zillionare.github.io/python-project-wizard/" 11 | keywords = ['cookiecutter', 'template', 'package'] 12 | packages = [ 13 | {include = "ppw"} 14 | ] 15 | 16 | include = [ 17 | '{{cookiecutter.project_slug}}/**/*', 18 | 'cookiecutter.json', 19 | 'hooks/*' 20 | ] 21 | 22 | [tool.poetry.dependencies] 23 | python = ">=3.8,<4.0" 24 | cookiecutter = "1.7.2" 25 | 26 | pytest = {version = "^6.2.5", optional = true} 27 | pyyaml = {version="^6.0",optional=true} 28 | mkdocs = {version="^1.1.2",optional=true} 29 | mkdocs-material = {version="^6.1.7",optional=true} 30 | mkdocs-material-extensions = {version="^1.0.1",optional=true} 31 | pytest-cov = {version="^2.10.1",optional=true} 32 | tox = {version = "^3.20.1", optional=true} 33 | mkdocs-include-markdown-plugin = {version = "^2.8.0", optional=true} 34 | fire = {version="^0.4.0", optional=true} 35 | pre-commit = {version="^2.18.1",optional=true} 36 | mike = { version="^1.1.2", optional=true} 37 | livereload = {version = "^2.6.3", optional = true} 38 | pytest-cookies = {version = "^0.6.1", optional = true} 39 | colorama = "^0.4.5" 40 | mypy = {version = "^1.5.1", optional = true} 41 | 42 | [tool.poetry.extras] 43 | dev = [ 44 | "pytest", 45 | "pytest-cookies", 46 | "pyyaml", 47 | "mkdocs", 48 | "mkdocs-material", 49 | "mkdocs-material-extensions", 50 | "pytest-cov", 51 | "tox", 52 | "mkdocs-include-markdown-plugin", 53 | "fire", 54 | "mike", 55 | "livereload", 56 | "mypy" 57 | ] 58 | 59 | [[tool.poetry.source]] 60 | name = "ali" 61 | url = "https://mirrors.aliyun.com/pypi/simple" 62 | default = false 63 | secondary = false 64 | 65 | [tool.black] 66 | line-length = 88 67 | include = '\.pyi?$' 68 | exclude = ''' 69 | /( 70 | \.eggs 71 | | \.git 72 | | \.hg 73 | | \.mypy_cache 74 | | \.tox 75 | | \.venv 76 | | _build 77 | | buck-out 78 | | build 79 | | dist 80 | )/ 81 | ''' 82 | 83 | [build-system] 84 | requires = ["poetry-core>=1.0.0"] 85 | build-backend = "poetry.core.masonry.api" 86 | 87 | [tool.poetry.scripts] 88 | ppw = 'ppw.cli:main' 89 | -------------------------------------------------------------------------------- /reload.sh: -------------------------------------------------------------------------------- 1 | echo "rebuild ppwtest virtual env" 2 | rm -rf dist/* 3 | poetry build 4 | conda env remove -y -n ppwtest 5 | conda create -n ppwtest --clone py311 6 | 7 | eval "$(conda shell.bash hook)" 8 | conda activate ppwtest 9 | # pip_="$(conda info --base)/envs/ppwtest/bin/pip" 10 | 11 | # $pip_ install dist/*.whl --force-reinstall 12 | 13 | echo "install ppw to ppwtest" 14 | pip install dist/*.whl --force-reinstall 15 | 16 | echo "go to /tmp folder and prepare folder" 17 | cd /tmp 18 | 19 | if [ $# == 1 ]; then 20 | rm -rf /tmp/$1 21 | fi 22 | 23 | rm -rf /tmp/ppw 24 | 25 | echo "running ppw" 26 | ppw 27 | 28 | # change to /tmp/ 29 | cd /tmp/$1 30 | 31 | -------------------------------------------------------------------------------- /repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Uncomment the following to config github secret used by github workflow. 4 | gh secret set PERSONAL_TOKEN --body $GH_TOKEN 5 | gh secret set PYPI_API_TOKEN --body $PYPI_API_TOKEN 6 | gh secret set TEST_PYPI_API_TOKEN --body $TEST_PYPI_API_TOKEN 7 | 8 | # uncomment the following if you need to setup email notification 9 | gh secret set BUILD_NOTIFY_MAIL_SERVER --body $BUILD_NOTIFY_MAIL_SERVER 10 | gh secret set BUILD_NOTIFY_MAIL_PORT --body $BUILD_NOTIFY_MAIL_PORT 11 | gh secret set BUILD_NOTIFY_MAIL_FROM --body $BUILD_NOTIFY_MAIL_FROM 12 | gh secret set BUILD_NOTIFY_MAIL_PASSWORD --body $BUILD_NOTIFY_MAIL_PASSWORD 13 | gh secret set BUILD_NOTIFY_MAIL_RCPT --body $BUILD_NOTIFY_MAIL_RCPT 14 | gh secret set DINGTALK_ACCESS_TOKEN --body $BUILD_DINGTALK_ACCESS_TOKEN 15 | gh secret set DINGTALK_SECRET --body $BUILD_DINGTALK_SECRET 16 | -------------------------------------------------------------------------------- /tests/test_bake_project.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import shlex 4 | import subprocess 5 | import sys 6 | from contextlib import contextmanager 7 | from typing import List 8 | from unittest import mock 9 | 10 | import pytest 11 | from cookiecutter.utils import rmtree 12 | 13 | # logging.basicConfig(level=logging.DEBUG) 14 | 15 | 16 | _DEPENDENCY_FILE = "pyproject.toml" 17 | _INSTALL_DEPS_COMMANDS = ["poetry install"] 18 | 19 | 20 | def build_commands(commands): 21 | cmds = _INSTALL_DEPS_COMMANDS.copy() 22 | cmds.extend(commands) 23 | return cmds 24 | 25 | 26 | @contextmanager 27 | def inside_dir(dirpath): 28 | """ 29 | Execute code from inside the given directory 30 | :param dirpath: String, path of the directory the command is being run. 31 | """ 32 | old_path = os.getcwd() 33 | try: 34 | os.chdir(dirpath) 35 | yield 36 | finally: 37 | os.chdir(old_path) 38 | 39 | 40 | @contextmanager 41 | def bake_in_temp_dir(cookies, *args, **kwargs): 42 | """ 43 | Delete the temporal directory that is created when executing the tests 44 | :param cookies: pytest_cookies.Cookies, 45 | cookie to be baked and its temporal files will be removed 46 | """ 47 | result = cookies.bake(*args, **kwargs) 48 | try: 49 | yield result 50 | finally: 51 | rmtree(str(result.project)) 52 | 53 | 54 | def run_inside_dir(commands, dirpath): 55 | """ 56 | Run a command from inside a given directory, returning the exit status 57 | :param commands: Commands that will be executed 58 | :param dirpath: String, path of the directory the command is being run. 59 | """ 60 | with inside_dir(dirpath): 61 | for command in commands: 62 | subprocess.check_call(shlex.split(command)) 63 | 64 | 65 | def check_output_inside_dir(command, dirpath): 66 | """Run a command from inside a given directory, returning the command output""" 67 | with inside_dir(dirpath): 68 | return subprocess.check_output(shlex.split(command)) 69 | 70 | 71 | def execute(command: List[str], dirpath: str, timeout=30, supress_warning=True): 72 | """Run command inside given directory and returns output 73 | 74 | if there's stderr, then it may raise exception according to supress_warning 75 | """ 76 | with inside_dir(dirpath): 77 | proc = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 78 | 79 | out, err = proc.communicate(timeout=timeout) 80 | out = out.decode("utf-8") 81 | err = err.decode("utf-8") 82 | 83 | if err and not supress_warning: 84 | raise RuntimeError(err) 85 | else: 86 | print(err) 87 | return out 88 | 89 | 90 | def test_year_compute_in_license_file(cookies): 91 | with bake_in_temp_dir(cookies) as result: 92 | license_file_path = result.project.join("LICENSE") 93 | now = datetime.datetime.now() 94 | assert str(now.year) in license_file_path.read() 95 | 96 | 97 | def project_info(result): 98 | """Get toplevel dir, project_slug, and project dir from baked cookies""" 99 | project_path = str(result.project) 100 | project_slug = os.path.split(project_path)[-1] 101 | project_dir = os.path.join(project_path, project_slug) 102 | return project_path, project_slug, project_dir 103 | 104 | 105 | def test_bake_with_defaults(cookies): 106 | with bake_in_temp_dir(cookies) as result: 107 | assert result.project.isdir() 108 | assert result.exit_code == 0 109 | assert result.exception is None 110 | 111 | found_toplevel_files = [f.basename for f in result.project.listdir()] 112 | assert _DEPENDENCY_FILE in found_toplevel_files 113 | assert "python_boilerplate" in found_toplevel_files 114 | assert "tox.ini" in found_toplevel_files 115 | assert "tests" in found_toplevel_files 116 | 117 | mkdocs_yml = os.path.join(result._project_dir, "mkdocs.yml") 118 | with open(mkdocs_yml, "r") as f: 119 | lines = f.readlines() 120 | assert " - authors: authors.md\n" in lines 121 | 122 | 123 | def test_bake_without_author_file(cookies): 124 | with bake_in_temp_dir(cookies, extra_context={"create_author_file": "n"}) as result: 125 | found_toplevel_files = [f.basename for f in result.project.listdir()] 126 | assert "AUTHORS.md" not in found_toplevel_files 127 | doc_files = [f.basename for f in result.project.join("docs").listdir()] 128 | assert "authors.md" not in doc_files 129 | 130 | # make sure '-authors: authors.md' not appeared in mkdocs.yml 131 | mkdocs_yml = os.path.join(result._project_dir, "mkdocs.yml") 132 | with open(mkdocs_yml, "r") as f: 133 | lines = f.readlines() 134 | assert " - authors: authors.md\n" not in lines 135 | 136 | 137 | @pytest.mark.parametrize( 138 | "license_info", 139 | [ 140 | ("MIT", "MIT "), 141 | ( 142 | "BSD-3-Clause", 143 | "Redistributions of source code must retain the " 144 | + "above copyright notice, this", 145 | ), 146 | ("ISC", "ISC License"), 147 | ("Apache-2.0", "Licensed under the Apache License, Version 2.0"), 148 | ("GPL-3.0-only", "GNU GENERAL PUBLIC LICENSE"), 149 | ], 150 | ) 151 | def test_bake_selecting_license(cookies, license_info): 152 | license, target_string = license_info 153 | with bake_in_temp_dir( 154 | cookies, extra_context={"open_source_license": license} 155 | ) as result: 156 | assert target_string in result.project.join("LICENSE").read() 157 | assert license in result.project.join(_DEPENDENCY_FILE).read() 158 | 159 | 160 | def test_bake_not_open_source(cookies): 161 | with bake_in_temp_dir( 162 | cookies, extra_context={"open_source_license": "Not open source"} 163 | ) as result: 164 | found_toplevel_files = [f.basename for f in result.project.listdir()] 165 | assert _DEPENDENCY_FILE in found_toplevel_files 166 | assert "LICENSE" not in found_toplevel_files 167 | assert "License" not in result.project.join("README.md").read() 168 | assert "license" not in result.project.join(_DEPENDENCY_FILE).read() 169 | 170 | 171 | def test_not_using_pytest(cookies): 172 | with bake_in_temp_dir(cookies, extra_context={"use_pytest": "n"}) as result: 173 | assert result.project.isdir() 174 | # Test pyproject doesn't install pytest 175 | dep_file_path = result.project.join(_DEPENDENCY_FILE) 176 | lines = dep_file_path.readlines() 177 | assert 'pytest = "*"\n' not in lines 178 | # Test contents of test file 179 | test_file_path = result.project.join("tests/test_app.py") 180 | lines = test_file_path.readlines() 181 | assert "import unittest" in "".join(lines) 182 | assert "import pytest" not in "".join(lines) 183 | 184 | 185 | def test_docstrings_style(cookies): 186 | with bake_in_temp_dir( 187 | cookies, extra_context={"docstrings_style": "google"} 188 | ) as result: 189 | assert result.project.isdir() 190 | # Test lint rule contains google style 191 | flake8_conf_file_apth = result.project.join(".flake8") 192 | lines = flake8_conf_file_apth.readlines() 193 | assert "docstring-convention = google" in "".join(lines) 194 | 195 | 196 | # def test_project_with_hyphen_in_module_name(cookies): 197 | # result = cookies.bake( 198 | # extra_context={'project_name': 'something-with-a-dash'} 199 | # ) 200 | # assert result.project is not None 201 | # project_path = str(result.project) 202 | # 203 | # # when: 204 | # travis_setup_cmd = ('python travis_pypi_setup.py' 205 | # ' --repo audreyr/cookiecutter-pypackage' 206 | # ' --password invalidpass') 207 | # run_inside_dir(travis_setup_cmd, project_path) 208 | # 209 | # # then: 210 | # result_travis_config = yaml.load( 211 | # open(os.path.join(project_path, ".travis.yml")) 212 | # ) 213 | # assert "secure" in result_travis_config["deploy"]["password"],\ 214 | # "missing password config in .travis.yml" 215 | 216 | 217 | @pytest.mark.parametrize( 218 | "args", 219 | [ 220 | ({"command_line_interface": "No command-line interface"}, False), 221 | ({"command_line_interface": "fire"}, True), 222 | ], 223 | ) 224 | def test_bake_with_no_console_script(cookies, args): 225 | context, is_present = args 226 | result = cookies.bake(extra_context=context) 227 | project_path, project_slug, project_dir = project_info(result) 228 | found_project_files = os.listdir(project_dir) 229 | assert ("cli.py" in found_project_files) == is_present 230 | 231 | pyproject_path = os.path.join(project_path, _DEPENDENCY_FILE) 232 | with open(pyproject_path, "r") as pyproject_file: 233 | assert ("[tool.poetry.scripts]" in pyproject_file.read()) == is_present 234 | 235 | 236 | def test_bake_with_console_script_cli(cookies): 237 | context = {"command_line_interface": "fire"} 238 | result = cookies.bake(extra_context=context) 239 | project_path, project_slug, project_dir = project_info(result) 240 | module_path = os.path.join(project_dir, "cli.py") 241 | 242 | out = execute([sys.executable, module_path], project_dir) 243 | assert ( 244 | f"is one of the following:{os.linesep}{os.linesep} help{os.linesep}" in out 245 | ) 246 | 247 | out = execute([sys.executable, module_path, "help"], project_dir) 248 | 249 | assert project_slug in out 250 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,py39,py310,py311,docs 3 | isolated_build = True 4 | 5 | [gh-actions] 6 | python = 7 | 3.8: py38, docs 8 | 3.9: py39 9 | 3.10: py310 10 | 3.11: py311 11 | 12 | [testenv:docs] 13 | basepython=python 14 | allowlist_externals = mkdocs 15 | commands= mkdocs build 16 | 17 | [testenv] 18 | extras = 19 | dev 20 | 21 | setenv = 22 | PYTHONPATH = {toxinidir} 23 | 24 | commands = pytest -s --cov-report=term-missing tests 25 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | # uncomment the following to omit files during running 3 | #omit = 4 | [report] 5 | exclude_lines = 6 | pragma: no cover 7 | def __repr__ 8 | if self.debug: 9 | if settings.DEBUG 10 | raise AssertionError 11 | raise NotImplementedError 12 | if 0: 13 | if __name__ == .__main__.: 14 | def main 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.docstring.tpl: -------------------------------------------------------------------------------- 1 | {{! Google Docstring Template }} 2 | {{summaryPlaceholder}} 3 | 4 | {{extendedSummaryPlaceholder}} 5 | 6 | {{#parametersExist}} 7 | Args: 8 | {{#args}} 9 | {{var}}: {{descriptionPlaceholder}} 10 | {{/args}} 11 | {{#kwargs}} 12 | {{var}}: {{descriptionPlaceholder}}. 13 | {{/kwargs}} 14 | {{/parametersExist}} 15 | 16 | {{#exceptionsExist}} 17 | Raises: 18 | {{#exceptions}} 19 | {{type}}: {{descriptionPlaceholder}} 20 | {{/exceptions}} 21 | {{/exceptionsExist}} 22 | 23 | {{#returnsExist}} 24 | Returns: 25 | {{#returns}} 26 | {{descriptionPlaceholder}} 27 | {{/returns}} 28 | {{/returnsExist}} 29 | 30 | {{#yieldsExist}} 31 | Yields: 32 | {{#yields}} 33 | {{descriptionPlaceholder}} 34 | {{/yields}} 35 | {{/yieldsExist}} 36 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # required by black, https://github.com/psf/black/blob/master/.flake8 3 | max-line-length = 88 4 | max-complexity = 18 5 | ignore = E203, E266, E501, W503, F403, F401 6 | select = B,C,E,F,W,T4,B9 7 | docstring-convention = {{ cookiecutter.docstrings_style }} 8 | per-file-ignores = 9 | __init__.py:F401 10 | exclude = 11 | .git, 12 | __pycache__, 13 | setup.py, 14 | build, 15 | dist, 16 | releases, 17 | .venv, 18 | .tox, 19 | .mypy_cache, 20 | .pytest_cache, 21 | .vscode, 22 | .github, 23 | tests 24 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * {{ cookiecutter.project_name }} version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: dev build CI 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push or pull request events 6 | push: 7 | branches: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - '*' 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # contains 3 jobs: test, publish_dev_build and notification 16 | jobs: 17 | test: 18 | # The type of runner that the job will run on 19 | strategy: 20 | matrix: 21 | python-versions: ['3.8', '3.9', '3.10', '3.11'] 22 | # github action doesn't goes well with windows due to docker support 23 | # github action doesn't goes well with macos due to `no docker command` 24 | #os: [ubuntu-latest, windows-latest, macos-latest] 25 | os: [ubuntu-latest] 26 | runs-on: ${{ matrix.os }} 27 | # map step outputs to job outputs so they can be share among jobs 28 | outputs: 29 | package_version: ${{ steps.variables_step.outputs.package_version }} 30 | package_name: ${{ steps.variables_step.outputs.package_name }} 31 | repo_name: ${{ steps.variables_step.outputs.repo_name }} 32 | repo_owner: ${{ steps.variables_step.outputs.repo_owner }} 33 | build_number: ${{ steps.variables_step.outputs.build_number }} 34 | 35 | # uncomment the following to pickup services 36 | # services: 37 | # redis: 38 | # image: redis 39 | # options: >- 40 | # --health-cmd "redis-cli ping" 41 | # --health-interval 10s 42 | # --health-timeout 5s 43 | # --health-retries 5 44 | # ports: 45 | # - 6379:6379 46 | 47 | # Steps represent a sequence of tasks that will be executed as part of the job 48 | steps: 49 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 50 | - uses: actions/checkout@v4 51 | - uses: actions/setup-python@v4 52 | with: 53 | python-version: ${{ matrix.python-versions }} 54 | 55 | - name: Install dependencies 56 | run: | 57 | python -m pip install --upgrade pip 58 | pip install tox tox-gh-actions poetry 59 | 60 | # declare package_version, repo_owner, repo_name, package_name so you may use it in web hooks. 61 | - name: Declare variables for convenient use 62 | id: variables_step 63 | run: | 64 | echo "repo_owner=${GITHUB_REPOSITORY%/*}" >> $GITHUB_OUTPUT 65 | echo "repo_name=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT 66 | echo "package_name=`poetry version | awk '{print $1}'`" >> $GITHUB_OUTPUT 67 | echo "package_version=`poetry version --short`" >> $GITHUB_OUTPUT 68 | shell: bash 69 | 70 | - name: test with tox 71 | run: tox 72 | 73 | - uses: codecov/codecov-action@v3 74 | with: 75 | fail_ci_if_error: true 76 | 77 | publish_dev_build: 78 | # if test failed, we should not publish 79 | needs: test 80 | # you may need to change os below 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: actions/checkout@v4 84 | - uses: actions/setup-python@v4 85 | with: 86 | python-version: '3.9' 87 | 88 | - name: Install dependencies 89 | run: | 90 | python -m pip install --upgrade pip 91 | pip install poetry tox tox-gh-actions 92 | 93 | - name: build documentation 94 | run: | 95 | poetry install -E doc 96 | mkdocs build 97 | git config --global user.name Docs deploy 98 | git config --global user.email docs@dummy.bot.com 99 | mike deploy -p -f --ignore "`poetry version --short`.dev" 100 | mike set-default -p "`poetry version --short`.dev" 101 | 102 | - name: Build wheels and source tarball 103 | run: | 104 | poetry version $(poetry version --short)-dev.$GITHUB_RUN_NUMBER 105 | poetry lock 106 | poetry build 107 | 108 | - name: publish to Test PyPI 109 | uses: pypa/gh-action-pypi-publish@release/v1 110 | with: 111 | user: __token__ 112 | password: ${{ secrets.TEST_PYPI_API_TOKEN}} 113 | repository_url: https://test.pypi.org/legacy/ 114 | skip_existing: true 115 | 116 | notification: 117 | needs: [test,publish_dev_build] 118 | if: always() 119 | runs-on: ubuntu-latest 120 | steps: 121 | - uses: martialonline/workflow-status@v2 122 | id: check 123 | 124 | - name: build success notification via email 125 | if: ${{ steps.check.outputs.status == 'success' }} 126 | uses: dawidd6/action-send-mail@v3 127 | with: 128 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 129 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 130 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 131 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 132 | from: build-bot 133 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 134 | subject: ${{ needs.test.outputs.package_name }}.${{ needs.test.outputs.package_version}}.dev.${{ github.run_number }} build successfully 135 | convert_markdown: true 136 | html_body: | 137 | ## Build Success 138 | ${{ needs.test.outputs.package_name }}.${{ needs.test.outputs.package_version }}.dev.${{ github.run_number }} is built and published to test pypi 139 | 140 | ## Change Details 141 | ${{ github.event.head_commit.message }} 142 | 143 | For more information, please check change history at https://${{ needs.test.outputs.repo_owner }}.github.io/${{ needs.test.outputs.repo_name }}/${{ needs.test.outputs.package_version }}.dev/history 144 | 145 | ## Package Download 146 | The pacakge is available at: https://test.pypi.org/project/${{ needs.test.outputs.package_name }}/ 147 | 148 | - name: build failure notification via email 149 | if: ${{ steps.check.outputs.status == 'failure' }} 150 | uses: dawidd6/action-send-mail@v3 151 | with: 152 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 153 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 154 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 155 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 156 | from: build-bot 157 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 158 | subject: ${{ needs.test.outputs.package_name }}.${{ needs.test.outputs.package_version}}.dev.${{ github.run_number }} build failure 159 | convert_markdown: true 160 | html_body: | 161 | ## Change Details 162 | ${{ github.event.head_commit.message }} 163 | 164 | ## View Log 165 | https://github.com/${{ needs.test.outputs.repo_owner }}/${{ needs.test.outputs.repo_name }}/actions 166 | 167 | 168 | # - name: Dingtalk Robot Notify 169 | # if: always() 170 | # uses: leafney/dingtalk-action@v1.0.0 171 | # env: 172 | # DINGTALK_ACCESS_TOKEN: ${{ secrets.DINGTALK_ACCESS_TOKEN }} 173 | # DINGTALK_SECRET: ${{ secrets.DINGTALK_SECRET }} 174 | # with: 175 | # msgtype: markdown 176 | # title: CI Notification | Success 177 | # text: | 178 | # ### Build Success 179 | # ${{ needs.test.outputs.package_version }}.dev.${{ github.run_number }}published to TEST pypi 180 | # ### Change History 181 | # Please check change history at https://${{ needs.test.outputs.repo_owner }}.github.io/${{ needs.test.outputs.repo_name }}/${{ needs.test.outputs.package_version }}.dev/history 182 | # ### Package Download 183 | # The pacakge is availabled at: https://test.pypi.org/project/${{ needs.test.outputs.repo_name }}/ 184 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Publish package on release branch if it's tagged with 'v*' 2 | 3 | name: build & release 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branch: [main, master] 10 | tags: 11 | - 'v*' 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | release: 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | matrix: 23 | python-versions: ['3.9'] 24 | 25 | # map step outputs to job outputs so they can be share among jobs 26 | outputs: 27 | package_version: ${{ steps.variables_step.outputs.package_version }} 28 | package_name: ${{ steps.variables_step.outputs.package_name }} 29 | repo_name: ${{ steps.variables_step.outputs.repo_name }} 30 | repo_owner: ${{ steps.variables_step.outputs.repo_owner }} 31 | 32 | # Steps represent a sequence of tasks that will be executed as part of the job 33 | steps: 34 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 35 | - uses: actions/checkout@v4 36 | 37 | - name: build change log 38 | id: build_changelog 39 | uses: mikepenz/release-changelog-builder-action@v3.2.0 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - uses: actions/setup-python@v4 44 | with: 45 | python-version: ${{ matrix.python-versions }} 46 | 47 | - name: Install dependencies 48 | run: | 49 | python -m pip install --upgrade pip 50 | pip install tox-gh-actions poetry 51 | 52 | # declare package_version, repo_owner, repo_name, package_name so you may use it in web hooks. 53 | - name: Declare variables for convenient use 54 | id: variables_step 55 | run: | 56 | echo "repo_owner=${GITHUB_REPOSITORY%/*}" >> $GITHUB_OUTPUT 57 | echo "repo_name=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT 58 | echo "package_name=`poetry version | awk '{print $1}'`" >> $GITHUB_OUTPUT 59 | echo "package_version=`poetry version --short`" >> $GITHUB_OUTPUT 60 | shell: bash 61 | 62 | - name: publish documentation 63 | run: | 64 | poetry install -E doc 65 | mkdocs build 66 | git config --global user.name Docs deploy 67 | git config --global user.email docs@dummy.bot.com 68 | mike deploy -p -f --ignore `poetry version --short` latest 69 | mike set-default -p `poetry version --short` 70 | 71 | - name: Build wheels and source tarball 72 | run: | 73 | poetry lock 74 | poetry build 75 | 76 | - name: Create Release 77 | id: create_release 78 | uses: actions/create-release@v1 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | with: 82 | tag_name: ${{ github.ref_name }} 83 | release_name: Release ${{ github.ref_name }} 84 | body: ${{ steps.build_changelog.outputs.changelog }} 85 | draft: false 86 | prerelease: false 87 | 88 | - name: publish to PYPI 89 | uses: pypa/gh-action-pypi-publish@release/v1 90 | with: 91 | user: __token__ 92 | password: ${{ secrets.PYPI_API_TOKEN }} 93 | skip_existing: true 94 | 95 | notification: 96 | needs: release 97 | if: always() 98 | runs-on: ubuntu-latest 99 | steps: 100 | - uses: martialonline/workflow-status@v2 101 | id: check 102 | 103 | - name: build success notification via email 104 | if: ${{ steps.check.outputs.status == 'success' }} 105 | uses: dawidd6/action-send-mail@v3 106 | with: 107 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 108 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 109 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 110 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 111 | from: build-bot 112 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 113 | subject: ${{ needs.release.outputs.package_name }}.${{ needs.release.outputs.package_version}} build successfully 114 | convert_markdown: true 115 | html_body: | 116 | ## Build Success 117 | ${{ needs.release.outputs.package_name }}.${{ needs.release.outputs.package_version }} has been published to PYPI 118 | 119 | ## Change Details 120 | ${{ github.event.head_commit.message }} 121 | 122 | For more information, please check change history at https://${{ needs.release.outputs.repo_owner }}.github.io/${{ needs.release.outputs.repo_name }}/${{ needs.release.outputs.package_version }}/history 123 | 124 | ## Package Download 125 | The pacakge is available at: https://pypi.org/project/${{ needs.release.outputs.package_name }}/ 126 | 127 | - name: build failure notification via email 128 | if: ${{ steps.check.outputs.status == 'failure' }} 129 | uses: dawidd6/action-send-mail@v3 130 | with: 131 | server_address: ${{ secrets.BUILD_NOTIFY_MAIL_SERVER }} 132 | server_port: ${{ secrets.BUILD_NOTIFY_MAIL_PORT }} 133 | username: ${{ secrets.BUILD_NOTIFY_MAIL_FROM }} 134 | password: ${{ secrets.BUILD_NOTIFY_MAIL_PASSWORD }} 135 | from: build-bot 136 | to: ${{ secrets.BUILD_NOTIFY_MAIL_RCPT }} 137 | subject: ${{ needs.release.outputs.package_name }}.${{ needs.release.outputs.package_version}} build failure 138 | convert_markdown: true 139 | html_body: | 140 | ## Change Details 141 | ${{ github.event.head_commit.message }} 142 | 143 | ## Status: ${{ steps.check.outputs.status }} 144 | 145 | 146 | ## View Log 147 | https://github.com/${{ needs.release.outputs.repo_owner }}/${{ needs.release.outputs.repo_name }}/actions 148 | 149 | 150 | # - name: Dingtalk Robot Notify 151 | # if: always() 152 | # uses: leafney/dingtalk-action@v1.0.0 153 | # env: 154 | # DINGTALK_ACCESS_TOKEN: ${{ secrets.DINGTALK_ACCESS_TOKEN }} 155 | # DINGTALK_SECRET: ${{ secrets.DINGTALK_SECRET }} 156 | # with: 157 | # msgtype: markdown 158 | # title: CI Notification | Success 159 | # text: | 160 | # ### ${{ needs.release.outputs.package_name }} Build Success 161 | # ${{ needs.release.outputs.package_version }} has been published to PYPI 162 | # ### Change History 163 | # Please check change history at https://${{ needs.release.outputs.repo_owner }}.github.io/${{ needs.release.outputs.repo_name }}/latest/history 164 | # ### Package Download 165 | # Please download the pacakge at: https://pypi.org/project/${{ needs.release.outputs.repo_name }}/ 166 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ 106 | 107 | # mkdocs build dir 108 | site/ 109 | 110 | # vscode extension - local history 111 | .history/ 112 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile=black 3 | # you can skip files as below 4 | #skip_glob = docs/conf.py 5 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/Lucas-C/pre-commit-hooks 3 | rev: v1.1.13 4 | hooks: 5 | - id: forbid-crlf 6 | - id: remove-crlf 7 | - id: forbid-tabs 8 | exclude_types: [csv] 9 | - id: remove-tabs 10 | exclude_types: [csv] 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: v4.1.0 13 | hooks: 14 | - id: trailing-whitespace 15 | - id: end-of-file-fixer 16 | - id: check-merge-conflict 17 | - id: check-yaml 18 | args: [--unsafe] 19 | - repo: https://github.com/pre-commit/mirrors-isort 20 | rev: v5.10.1 21 | hooks: 22 | - id: isort 23 | - repo: https://github.com/ambv/black 24 | rev: 22.3.0 25 | hooks: 26 | - id: black 27 | language_version: python3.8 28 | - repo: https://github.com/pycqa/flake8 29 | rev: 3.9.2 30 | hooks: 31 | - id: flake8 32 | additional_dependencies: [flake8-typing-imports==1.10.0] 33 | exclude: ^tests 34 | - repo: local 35 | hooks: 36 | - id: mypy 37 | name: mypy 38 | entry: mypy 39 | exclude: ^tests 40 | language: python 41 | types: [python] 42 | require_serial: true 43 | verbose: true 44 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | ## Development Lead 4 | 5 | * {{ cookiecutter.full_name }} <{{ cookiecutter.email }}> 6 | 7 | ## Contributors 8 | 9 | None yet. Why not be the first? 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! Every little bit 4 | helps, and credit will always be given. 5 | 6 | You can contribute in many ways: 7 | 8 | ## Types of Contributions 9 | 10 | ### Report Bugs 11 | 12 | Report bugs at https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/issues. 13 | 14 | If you are reporting a bug, please include: 15 | 16 | * Your operating system name and version. 17 | * Any details about your local setup that might be helpful in troubleshooting. 18 | * Detailed steps to reproduce the bug. 19 | 20 | ### Fix Bugs 21 | 22 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 23 | wanted" is open to whoever wants to implement it. 24 | 25 | ### Implement Features 26 | 27 | Look through the GitHub issues for features. Anything tagged with "enhancement" 28 | and "help wanted" is open to whoever wants to implement it. 29 | 30 | ### Write Documentation 31 | 32 | {{ cookiecutter.project_name }} could always use more documentation, whether as part of the 33 | official {{ cookiecutter.project_name }} docs, in docstrings, or even on the web in blog posts, 34 | articles, and such. 35 | 36 | ### Submit Feedback 37 | 38 | The best way to send feedback is to file an issue at https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/issues. 39 | 40 | If you are proposing a feature: 41 | 42 | * Explain in detail how it would work. 43 | * Keep the scope as narrow as possible, to make it easier to implement. 44 | * Remember that this is a volunteer-driven project, and that contributions 45 | are welcome :) 46 | 47 | ## Get Started! 48 | 49 | Ready to contribute? Here's how to set up `{{ cookiecutter.project_slug }}` for local development. 50 | 51 | 1. Fork the `{{ cookiecutter.project_slug }}` repo on GitHub. 52 | 2. Clone your fork locally 53 | 54 | ``` 55 | $ git clone git@github.com:your_name_here/{{ cookiecutter.project_slug }}.git 56 | ``` 57 | 58 | 3. Ensure [poetry](https://python-poetry.org/docs/) is installed. 59 | 4. Install dependencies and start your virtualenv: 60 | 61 | ``` 62 | $ poetry install -E test -E doc -E dev 63 | ``` 64 | 65 | 5. Create a branch for local development: 66 | 67 | ``` 68 | $ git checkout -b name-of-your-bugfix-or-feature 69 | ``` 70 | 71 | Now you can make your changes locally. 72 | 73 | 6. When you're done making changes, check that your changes pass the 74 | tests, including testing other Python versions, with tox: 75 | 76 | ``` 77 | $ tox 78 | ``` 79 | 80 | 7. Commit your changes and push your branch to GitHub: 81 | 82 | ``` 83 | $ git add . 84 | $ git commit -m "Your detailed description of your changes." 85 | $ git push origin name-of-your-bugfix-or-feature 86 | ``` 87 | 88 | 8. Submit a pull request through the GitHub website. 89 | 90 | ## Pull Request Guidelines 91 | 92 | Before you submit a pull request, check that it meets these guidelines: 93 | 94 | 1. The pull request should include tests. 95 | 2. If the pull request adds functionality, the docs should be updated. Put 96 | your new functionality into a function with a docstring, and add the 97 | feature to the list in README.md. 98 | 3. The pull request should work for Python 3.6, 3.7, 3.8, 3.9 and for PyPy. Check 99 | https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/actions 100 | and make sure that the tests pass for all supported Python versions. 101 | 102 | ## Tips 103 | 104 | {%- if cookiecutter.use_pytest == 'y' -%} 105 | ``` 106 | $ pytest tests.test_{{ cookiecutter.project_slug }} 107 | ``` 108 | {%- else -%} 109 | ``` 110 | $ python -m unittest tests.test_{{ cookiecutter.project_slug }} 111 | ``` 112 | {%- endif -%} 113 | 114 | To run a subset of tests. 115 | 116 | 117 | ## Deploying 118 | 119 | A reminder for the maintainers on how to deploy. 120 | Make sure all your changes are committed (including an entry in HISTORY.md). 121 | Then run: 122 | 123 | ``` 124 | $ poetry patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | ``` 128 | 129 | Github Actions will then deploy to PyPI if tests pass. 130 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## {{ cookiecutter.version }} ({% now 'local' %}) 4 | 5 | * First release on PyPI. 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/LICENSE: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.open_source_license == 'MIT' -%} 2 | MIT License 3 | 4 | Copyright (c) {% now 'local', '%Y' %}, {{ 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.open_source_license == 'BSD-3-Clause' %} 24 | 25 | BSD License 26 | 27 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} 28 | All rights reserved. 29 | 30 | Redistribution and use in source and binary forms, with or without modification, 31 | are permitted provided that the following conditions are met: 32 | 33 | * Redistributions of source code must retain the above copyright notice, this 34 | list of conditions and the following disclaimer. 35 | 36 | * Redistributions in binary form must reproduce the above copyright notice, this 37 | list of conditions and the following disclaimer in the documentation and/or 38 | other materials provided with the distribution. 39 | 40 | * Neither the name of the copyright holder nor the names of its 41 | contributors may be used to endorse or promote products derived from this 42 | software without specific prior written permission. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 45 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 46 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 47 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 48 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 49 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 51 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 52 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 53 | OF THE POSSIBILITY OF SUCH DAMAGE. 54 | {% elif cookiecutter.open_source_license == 'ISC' -%} 55 | ISC License 56 | 57 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} 58 | 59 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 60 | 61 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 62 | {% elif cookiecutter.open_source_license == 'Apache-2.0' -%} 63 | Apache Software License 2.0 64 | 65 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} 66 | 67 | Licensed under the Apache License, Version 2.0 (the "License"); 68 | you may not use this file except in compliance with the License. 69 | You may obtain a copy of the License at 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software 74 | distributed under the License is distributed on an "AS IS" BASIS, 75 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | See the License for the specific language governing permissions and 77 | limitations under the License. 78 | {% elif cookiecutter.open_source_license == 'GPL-3.0-only' -%} 79 | GNU GENERAL PUBLIC LICENSE 80 | Version 3, 29 June 2007 81 | 82 | {{ cookiecutter.project_short_description }} 83 | Copyright (C) {% now 'local', '%Y' %} {{ cookiecutter.full_name }} 84 | 85 | This program is free software: you can redistribute it and/or modify 86 | it under the terms of the GNU General Public License as published by 87 | the Free Software Foundation, either version 3 of the License, or 88 | (at your option) any later version. 89 | 90 | This program is distributed in the hope that it will be useful, 91 | but WITHOUT ANY WARRANTY; without even the implied warranty of 92 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 93 | GNU General Public License for more details. 94 | 95 | You should have received a copy of the GNU General Public License 96 | along with this program. If not, see . 97 | 98 | Also add information on how to contact you by electronic and paper mail. 99 | 100 | You should also get your employer (if you work as a programmer) or school, 101 | if any, to sign a "copyright disclaimer" for the program, if necessary. 102 | For more information on this, and how to apply and follow the GNU GPL, see 103 | . 104 | 105 | The GNU General Public License does not permit incorporating your program 106 | into proprietary programs. If your program is a subroutine library, you 107 | may consider it more useful to permit linking proprietary applications with 108 | the library. If this is what you want to do, use the GNU Lesser General 109 | Public License instead of this License. But first, please read 110 | . 111 | {% endif %} 112 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/README.md: -------------------------------------------------------------------------------- 1 | {% set is_open_source = cookiecutter.open_source_license != 'Not open source' -%} 2 | # {{ cookiecutter.project_name }} 3 | 4 | {% if is_open_source %} 5 |

6 | 7 | Release Status 9 | 10 | 11 | 12 | CI Status 13 | 14 | 15 | 16 | Documentation Status 17 | 18 | {% if cookiecutter.add_pyup_badge == 'y' %} 19 | 20 | Updates 21 | 22 | {% endif %} 23 |

24 | {% else %} 25 | {% if cookiecutter.add_pyup_badge == 'y' %} 26 |

27 | 28 | Updates 29 | 30 |

31 | {% endif %} 32 | {% endif %} 33 | 34 | {{ cookiecutter.project_short_description }} 35 | 36 | {% if is_open_source %} 37 | * Free software: {{ cookiecutter.open_source_license }} 38 | * Documentation: 39 | {% endif %} 40 | 41 | ## Features 42 | 43 | * TODO 44 | 45 | ## Credits 46 | 47 | This package was created with the [ppw](https://zillionare.github.io/python-project-wizard) tool. For more information, please visit the [project page](https://zillionare.github.io/python-project-wizard/). 48 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/api.md: -------------------------------------------------------------------------------- 1 | ::: {{ cookiecutter.project_slug }} 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/authors.md: -------------------------------------------------------------------------------- 1 | {% 2 | include-markdown "../AUTHORS.md" 3 | %} 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/contributing.md: -------------------------------------------------------------------------------- 1 | {% 2 | include-markdown "../CONTRIBUTING.md" 3 | %} 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/history.md: -------------------------------------------------------------------------------- 1 | {% 2 | include-markdown "../HISTORY.md" 3 | %} 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/index.md: -------------------------------------------------------------------------------- 1 | {% 2 | include-markdown "../README.md" 3 | %} 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Stable release 4 | 5 | To install {{ cookiecutter.project_name }}, run this command in your 6 | terminal: 7 | 8 | ``` console 9 | pip install {{ cookiecutter.project_slug }} 10 | ``` 11 | 12 | This is the preferred method to install {{ cookiecutter.project_name 13 | }}, as it will always install the most recent stable release. 14 | 15 | If you don't have [pip][] installed, this [Python installation guide][] 16 | can guide you through the process. 17 | 18 | ## From source 19 | 20 | The source for {{ cookiecutter.project_name }} can be downloaded from 21 | the [Github repo][]. 22 | 23 | You can either clone the public repository: 24 | 25 | ``` console 26 | git clone git://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }} 27 | ``` 28 | 29 | Or download the [tarball][]: 30 | 31 | ``` console 32 | curl -OJL https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/tarball/master 33 | ``` 34 | 35 | Once you have a copy of the source, you can install it with: 36 | 37 | ``` console 38 | pip install . 39 | ``` 40 | 41 | [pip]: https://pip.pypa.io 42 | [Python installation guide]: http://docs.python-guide.org/en/latest/starting/installation/ 43 | [Github repo]: https://github.com/%7B%7B%20cookiecutter.github_username%20%7D%7D/%7B%7B%20cookiecutter.project_slug%20%7D%7D 44 | [tarball]: https://github.com/%7B%7B%20cookiecutter.github_username%20%7D%7D/%7B%7B%20cookiecutter.project_slug%20%7D%7D/tarball/master 45 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | To use {{ cookiecutter.project_name }} in a project 4 | 5 | ``` 6 | import {{ cookiecutter.project_slug }} 7 | ``` 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: {{ cookiecutter.project_name }} 2 | # site_url: http://www.jieyu.ai 3 | repo_url: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }} 4 | repo_name: {{ cookiecutter.project_slug }} 5 | #strict: true 6 | nav: 7 | - home: index.md 8 | - installation: installation.md 9 | - usage: usage.md 10 | - modules: api.md 11 | - contributing: contributing.md 12 | {% if cookiecutter.create_author_file == 'y' -%} - authors: authors.md 13 | {% endif -%} 14 | - history: history.md 15 | theme: 16 | name: material 17 | language: en 18 | #logo: assets/logo.png 19 | palette: 20 | - media: "(prefers-color-scheme: light)" 21 | scheme: default 22 | toggle: 23 | icon: material/weather-night 24 | name: Switch to dark mode 25 | - media: "(prefers-color-scheme: dark)" 26 | scheme: slate 27 | toggle: 28 | icon: material/weather-sunny 29 | name: Switch to light mode 30 | features: 31 | - navigation.indexes 32 | - navigation.tabs 33 | - navigation.instant 34 | - navigation.tabs.sticky 35 | markdown_extensions: 36 | - pymdownx.emoji: 37 | emoji_index: !!python/name:materialx.emoji.twemoji 38 | emoji_generator: !!python/name:materialx.emoji.to_svg 39 | - pymdownx.critic 40 | - pymdownx.caret 41 | - pymdownx.mark 42 | - pymdownx.tilde 43 | - pymdownx.tabbed 44 | - attr_list 45 | - pymdownx.arithmatex: 46 | generic: true 47 | - pymdownx.highlight: 48 | linenums: true 49 | - pymdownx.superfences 50 | - pymdownx.details 51 | - admonition 52 | - toc: 53 | baselevel: 2 54 | permalink: true 55 | slugify: !!python/name:pymdownx.slugs.uslugify 56 | - meta 57 | plugins: 58 | - include-markdown 59 | - search: 60 | lang: en 61 | - mkdocstrings: 62 | watch: 63 | - {{ cookiecutter.project_slug }} 64 | extra: 65 | version: 66 | provider: mike 67 | social: 68 | - icon: fontawesome/brands/twitter 69 | # replace with your own tweet link below 70 | link: http://www.jieyu.ai 71 | name: Tweet 72 | - icon: fontawesome/brands/facebook 73 | # replace with your own facebook link below 74 | link: http://www.jieyu.ai 75 | name: Facebook 76 | - icon: fontawesome/brands/github 77 | link: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }} 78 | name: Github 79 | - icon: material/email 80 | link: "mailto:{{ cookiecutter.email }}" 81 | # to enable disqus, uncomment the following and put your disqus id below 82 | # disqus: disqus_id 83 | # uncomment the following and put your google tracking id below to enable GA 84 | #google_analytics: 85 | #- UA-xxx 86 | #- auto 87 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | # Ensure we know what we do 3 | warn_redundant_casts = true 4 | warn_unused_ignores = true 5 | warn_unused_configs = true 6 | 7 | # Imports management 8 | ignore_missing_imports = false 9 | 10 | # Ensure full coverage 11 | disallow_untyped_defs = true 12 | #disallow_incomplete_defs = true 13 | disallow_untyped_calls = true 14 | disallow_untyped_decorators = true 15 | # Restrict dynamic typing (a little) 16 | # e.g. `x: List[Any]` or x: List` 17 | disallow_any_generics = true 18 | 19 | # Show errors codes 20 | show_error_codes = true 21 | 22 | # From functions not declared to return Any 23 | warn_return_any = true 24 | 25 | [mypy-fire] 26 | ignore_missing_imports = true 27 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/pyproject.toml: -------------------------------------------------------------------------------- 1 | {%- set license_classifiers = { 2 | 'MIT': 'License :: OSI Approved :: MIT License', 3 | 'BSD-3-Clause': 'License :: OSI Approved :: BSD License', 4 | 'ISC': 'License :: OSI Approved :: ISC License (ISCL)', 5 | 'Apache-2.0': 'License :: OSI Approved :: Apache Software License', 6 | 'GPL-3.0-only': 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)' 7 | } -%} 8 | [tool] 9 | [tool.poetry] 10 | name = "{{ cookiecutter.project_slug }}" 11 | version = "{{ cookiecutter.version }}" 12 | homepage = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}" 13 | description = "{{ cookiecutter.project_short_description }}." 14 | authors = ["{{ cookiecutter.full_name.replace('\"', '\\\"') }} <{{ cookiecutter.email }}>"] 15 | readme = "README.md" 16 | {%- if cookiecutter.open_source_license in license_classifiers %} 17 | license = "{{ cookiecutter.open_source_license }}" 18 | {%- endif %} 19 | classifiers=[ 20 | 'Development Status :: 2 - Pre-Alpha', 21 | 'Intended Audience :: Developers', 22 | {%- if cookiecutter.open_source_license in license_classifiers %} 23 | '{{ license_classifiers[cookiecutter.open_source_license] }}', 24 | {%- endif %} 25 | 'Natural Language :: English', 26 | 'Programming Language :: Python :: 3', 27 | 'Programming Language :: Python :: 3.8', 28 | 'Programming Language :: Python :: 3.9', 29 | 'Programming Language :: Python :: 3.10', 30 | 'Programming Language :: Python :: 3.11', 31 | ] 32 | packages = [ 33 | { include = "{{ cookiecutter.project_slug }}" }, 34 | { include = "tests", format = "sdist" }, 35 | ] 36 | 37 | [tool.poetry.dependencies] 38 | python = ">=3.8,<4.0" 39 | {%- if cookiecutter.command_line_interface|lower == 'fire' %} 40 | fire = "0.4.0" 41 | {%- endif %} 42 | 43 | black = { version = "^22.3.0", optional = true} 44 | isort = { version = "5.10.1", optional = true} 45 | flake8 = { version = "4.0.1", optional = true} 46 | flake8-docstrings = { version = "^1.6.0", optional = true } 47 | pytest = { version = "^7.0.1", optional = true} 48 | pytest-cov = { version = "^3.0.0", optional = true} 49 | tox = { version = "^3.24.5", optional = true} 50 | mkdocs = { version = "^1.2.3", optional = true} 51 | mkdocs-include-markdown-plugin = { version = "^3.2.3", optional = true} 52 | mkdocs-material = { version = "^8.1.11", optional = true} 53 | mkdocstrings = { version = "^0.18.0", optional = true} 54 | mkdocs-material-extensions = { version = "^1.0.3", optional = true} 55 | twine = { version = "^3.8.0", optional = true} 56 | mkdocs-autorefs = {version = "^0.3.1", optional = true} 57 | pre-commit = {version = "^2.17.0", optional = true} 58 | toml = {version = "^0.10.2", optional = true} 59 | livereload = {version = "^2.6.3", optional = true} 60 | pyreadline = {version = "^2.1", optional = true} 61 | mike = { version="^1.1.2", optional=true} 62 | mypy = {version = "^1.5.1", optional = true} 63 | setuptools = {version="^68.0", optional = true} 64 | pkginfo = {version="^1.9", optional = true} 65 | virtualenv = {version="^20.0", optional = true} 66 | 67 | [tool.poetry.extras] 68 | test = [ 69 | "pytest", 70 | "pytest-cov" 71 | ] 72 | 73 | dev = ["tox", 74 | "pre-commit", 75 | "twine", 76 | "toml", 77 | "black", 78 | "isort", 79 | "flake8", 80 | "flake8-docstrings", 81 | "mypy" 82 | ] 83 | 84 | doc = [ 85 | "mkdocs", 86 | "mkdocs-include-markdown-plugin", 87 | "mkdocs-material", 88 | "mkdocstrings", 89 | "mkdocs-material-extension", 90 | "mkdocs-autorefs", 91 | "mike", 92 | "setuptools", 93 | "pkginfo", 94 | "virtualenv" 95 | ] 96 | 97 | {% if cookiecutter.command_line_interface|lower == 'fire' -%} 98 | [tool.poetry.scripts] 99 | {{ cookiecutter.project_slug }} = '{{ cookiecutter.project_slug }}.cli:main' 100 | {%- endif %} 101 | 102 | [build-system] 103 | requires = ["poetry-core>=1.0.0"] 104 | build-backend = "poetry.core.masonry.api" 105 | 106 | [tool.black] 107 | line-length = 88 108 | include = '\.pyi?$' 109 | exclude = ''' 110 | /( 111 | \.eggs 112 | | \.git 113 | | \.hg 114 | | \.mypy_cache 115 | | \.tox 116 | | \.venv 117 | | _build 118 | | buck-out 119 | | build 120 | | dist 121 | | \.history 122 | )/ 123 | ''' 124 | [tool.isort] 125 | profile = "black" 126 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": "This is for pylance extension of vscode. Remove me if you're not intented to use vscode.", 3 | "exclude": [ 4 | ".git", 5 | ".eggs", 6 | ".github", 7 | ".history", 8 | ".idea", 9 | ".tox", 10 | ".vscode", 11 | "docs", 12 | "**/__pycache__", 13 | "{{ cookiecutter.github_username }}_{{ cookiecutter.project_slug }}.egg-info" 14 | ], 15 | 16 | "ignore": [ 17 | "src/oldstuff" 18 | ], 19 | 20 | "reportMissingImports": true, 21 | "reportMissingTypeStubs": false, 22 | "pythonVersion": "3.8" 23 | } 24 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # !!!NOTICE!! 4 | # Personal token with full access rights is required to run this scripts 5 | # Once you got persona token, set enviroment variable GH_TOKEN with it 6 | 7 | # Create repo and push code to github 8 | gh repo create {{cookiecutter.project_slug}} --public 9 | git remote add origin git@github.com:{{cookiecutter.github_username}}/{{cookiecutter.project_slug}}.git 10 | git add . 11 | pre-commit run --all-files 12 | git add . 13 | git commit -m "Initial commit by ppw" 14 | git branch -M main 15 | 16 | # Config github secret used by github workflow. 17 | gh secret set PERSONAL_TOKEN --body $GH_TOKEN 18 | gh secret set PYPI_API_TOKEN --body $PYPI_API_TOKEN 19 | gh secret set TEST_PYPI_API_TOKEN --body $TEST_PYPI_API_TOKEN 20 | 21 | # uncomment the following if you need to setup email notification 22 | # gh secret set BUILD_NOTIFY_MAIL_SERVER --body $BUILD_NOTIFY_MAIL_SERVER 23 | # gh secret set BUILD_NOTIFY_MAIL_PORT --body $BUILD_NOTIFY_MAIL_PORT 24 | # gh secret set BUILD_NOTIFY_MAIL_FROM --body $BUILD_NOTIFY_MAIL_FROM 25 | # gh secret set BUILD_NOTIFY_MAIL_PASSWORD --body $BUILD_NOTIFY_MAIL_PASSWORD 26 | # gh secret set BUILD_NOTIFY_MAIL_RCPT --body $BUILD_NOTIFY_MAIL_RCPT 27 | 28 | git push -u origin main 29 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for {{ cookiecutter.project_slug }}.""" 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/tests/test_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Tests for `{{ cookiecutter.project_slug }}` package.""" 3 | # pylint: disable=redefined-outer-name 4 | 5 | {% if cookiecutter.use_pytest == 'y' -%} 6 | import pytest 7 | 8 | {%- else -%} 9 | import unittest 10 | 11 | {%- endif %} 12 | 13 | {%- if cookiecutter.use_pytest == 'y' %} 14 | 15 | 16 | @pytest.fixture 17 | def response(): 18 | """Sample pytest fixture. 19 | 20 | See more at: http://doc.pytest.org/en/latest/fixture.html 21 | """ 22 | # import requests 23 | # return requests.get('https://github.com/audreyr/cookiecutter-pypackage') 24 | 25 | 26 | def test_content(response): 27 | """Sample pytest test function with the pytest fixture as an argument.""" 28 | # from bs4 import BeautifulSoup 29 | # assert 'GitHub' in BeautifulSoup(response.content).title.string 30 | del response 31 | {%- if cookiecutter.command_line_interface|lower == 'click' %} 32 | 33 | 34 | def test_command_line_interface(): 35 | """Test the CLI.""" 36 | runner = CliRunner() 37 | result = runner.invoke(cli.main) 38 | assert result.exit_code == 0 39 | assert '{{ cookiecutter.project_slug }}.cli.main' in result.output 40 | help_result = runner.invoke(cli.main, ['--help']) 41 | assert help_result.exit_code == 0 42 | assert '--help Show this message and exit.' in help_result.output 43 | {%- endif %} 44 | {%- else %} 45 | 46 | 47 | class Test{{ cookiecutter.project_slug|title }}(unittest.TestCase): 48 | """Tests for `{{ cookiecutter.project_slug }}` package.""" 49 | 50 | def setUp(self): 51 | """Set up test fixtures, if any.""" 52 | 53 | def tearDown(self): 54 | """Tear down test fixtures, if any.""" 55 | 56 | def test_000_something(self): 57 | """Test something.""" 58 | {%- if cookiecutter.command_line_interface|lower == 'click' %} 59 | 60 | def test_command_line_interface(self): 61 | """Test the CLI.""" 62 | runner = CliRunner() 63 | result = runner.invoke(cli.main) 64 | assert result.exit_code == 0 65 | assert '{{ cookiecutter.project_slug }}.cli.main' in result.output 66 | help_result = runner.invoke(cli.main, ['--help']) 67 | assert help_result.exit_code == 0 68 | assert '--help Show this message and exit.' in help_result.output 69 | {%- endif %} 70 | {%- endif %} 71 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | isolated_build = true 3 | envlist = py38, py39, py310, py311, lint 4 | 5 | [gh-actions] 6 | python = 7 | 3.11: py311 8 | 3.10: py310 9 | 3.9: py39 10 | 3.8: py38 11 | 12 | [testenv:lint] 13 | extras = 14 | dev 15 | doc 16 | deps = 17 | poetry 18 | commands = 19 | poetry run isort {{ cookiecutter.project_slug }} 20 | poetry run black {{ cookiecutter.project_slug }} tests 21 | poetry run flake8 {{ cookiecutter.project_slug }} 22 | poetry run mypy -m {{ cookiecutter.project_slug }} --exclude ^tests 23 | poetry build 24 | poetry run mkdocs build 25 | poetry run twine check dist/* 26 | 27 | [testenv] 28 | passenv = * 29 | setenv = 30 | PYTHONPATH = {toxinidir} 31 | PYTHONWARNINGS = ignore 32 | deps = 33 | poetry 34 | extras = 35 | test 36 | commands = 37 | poetry run pytest -s --cov={{ cookiecutter.project_slug }} --cov-append --cov-report=xml --cov-report term-missing tests 38 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for {{ cookiecutter.project_name }}.""" 2 | 3 | __author__ = """{{ cookiecutter.full_name }}""" 4 | __email__ = "{{ cookiecutter.email }}" 5 | __version__ = "{{ cookiecutter.version }}" 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/app.py: -------------------------------------------------------------------------------- 1 | """Main module.""" 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py: -------------------------------------------------------------------------------- 1 | """Console script for {{cookiecutter.project_slug}}.""" 2 | 3 | {% if cookiecutter.command_line_interface|lower == 'fire' -%} 4 | import fire 5 | 6 | 7 | def help() -> None: 8 | print("{{ cookiecutter.project_slug }}") 9 | print("=" * len("{{ cookiecutter.project_slug }}")) 10 | print("{{ cookiecutter.project_short_description }}") 11 | 12 | def main() -> None: 13 | fire.Fire({ 14 | "help": help 15 | }) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() # pragma: no cover 20 | {%- endif %} 21 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zillionare/python-project-wizard/e16d2ff164f073b27c4bfa2be13daba26f05bb1f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/py.typed --------------------------------------------------------------------------------