├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.rst ├── cookiecutter.json ├── requirements.txt └── {{cookiecutter.package_name}} ├── .coveragerc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .pylintrc ├── CHANGELOG.rst ├── CONTRIBUTING.rst ├── License ├── Makefile ├── README.rst ├── docs ├── Makefile └── source │ ├── _static │ └── .gitignore │ ├── api │ └── index.rst │ ├── conf.py │ ├── dev │ └── index.rst │ ├── index.rst │ └── user │ └── index.rst ├── examples └── quickstart.py ├── mypi.ini ├── requirements.dev.txt ├── requirements.txt ├── setup.py ├── src └── {{cookiecutter.package_name}} │ └── __init__.py └── tests ├── __init__.py ├── test-dist.bash ├── test_examples.py └── test_version.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Python Package Workflow 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [3.6, 3.7, 3.8] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v1 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install -r requirements.txt 27 | - name: Cache pip dependencies 28 | uses: actions/cache@v1 29 | with: 30 | path: ~/.cache/pip # This path is specific to Ubuntu 31 | # Check for a cache hit for the corresponding dev requirements file 32 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.dev.txt') }}-${{ hashFiles('requirements.txt') }} 33 | restore-keys: | 34 | ${{ runner.os }}-pip- 35 | ${{ runner.os }}- 36 | - name: Generate example project 37 | run: | 38 | cookiecutter --no-input . package_name=example 39 | - name: Check example project 40 | run: | 41 | cd example 42 | pip install -r requirements.dev.txt 43 | pip install . 44 | make help 45 | make style 46 | make check-style 47 | make check-static-analysis 48 | make test 49 | make test-verbose 50 | make coverage 51 | make check-docs 52 | make docs 53 | make dist 54 | make dist-test 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 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 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | docs/build/ 69 | docs/source/api/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | # VSCode 109 | .vscode/ 110 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Cookiecutter Python Project 2 | ########################### 3 | 4 | This project contains a Cookiecutter template that helps you create new Python 5 | 3.6+ package projects by automatically generating most of the boiler plate 6 | content for you. 7 | 8 | Cookiecutter is a command-line utility that creates projects from templates. 9 | Cookiecutter lets you to easily and quickly bootstrap a new project from a 10 | template which allows you to skip all manual setup and common mistakes when 11 | starting a new project. 12 | 13 | Cookiecutter takes a source directory tree and copies it into your new project. 14 | It replaces all the names that it finds surrounded by templating tags ``{{`` 15 | and ``}}`` with names that it finds in the file ``cookiecutter.json``. 16 | 17 | The Python project structure produced by this Cookiecutter template contains 18 | the following items: 19 | 20 | - A minimal README.rst file. 21 | - A Makefile that automates many common developer tasks, such as: 22 | 23 | - Creating a Virtual environment 24 | - Checking code style. 25 | - Performing static analysis checks. 26 | - Running unit tests. 27 | - Checking code coverage. 28 | - Generating documentation. 29 | - Generating, testing and uploading a project release to PyPI. 30 | 31 | - A ``setup.py`` file used to generate project install and releases. 32 | - A ``CONTRIBUTING.rst`` guide. On Github this file is shown when sending 33 | a pull request or an issue. This file also gets included in the generated 34 | developer documentation. 35 | - An empty ``CHANGELOG.rst`` file. This file gets included in the user 36 | documentation. 37 | - A ``License`` file that defaults to the MIT License. Change this if 38 | you choose a license other than MIT. 39 | - An ``examples`` directory with a minimal quickstart example script. This 40 | script imports the package and prints the package version. It is also 41 | called by the unit test suite to ensure it always works. 42 | - A ``tests`` directory containing a basic unit test (using unittest) and 43 | a shell script that can be used to test a wheel distribution of the 44 | package. 45 | - A Github Actions continuous integration configuration. 46 | - A ``docs`` directory with pre-configured Sphinx documentation containing: 47 | 48 | - A minimal ``index.rst`` page 49 | 50 | - A user focused page containing information such as installation 51 | instructions, API docs, a link to the change log and instructions 52 | about how to raise a bug. 53 | 54 | - A developer focused page containing information such as contributing, 55 | testing, code coverage, style compliance, type annotations and 56 | documentation. 57 | 58 | It is assumed that the new Python package will eventually be: 59 | 60 | - hosted on Github (or perhaps GitLab) 61 | - published to PyPI 62 | - linked to ReadTheDocs. 63 | 64 | The generated docs have some references and links to those sites. 65 | 66 | 67 | Getting Started 68 | =============== 69 | 70 | One Time Setup Steps 71 | -------------------- 72 | 73 | The process for using Cookiecutter to create a new Python package project 74 | starts with installing Cookiecutter. This is best done by creating a new 75 | virtual environment specifically for cookiecutter and then installing 76 | cookiecutter using ``pip``. The example below shows how to do this. 77 | 78 | .. code-block:: console 79 | 80 | $ python -m venv ccvenv --prompt cc 81 | $ source ccvenv/bin/activate 82 | (cc) $ pip install pip -U # update pip to avoid any warnings 83 | (cc) $ pip install cookiecutter 84 | 85 | You are now ready to create a new Python project from the Cookiecutter 86 | template provided by this project. 87 | 88 | 89 | Create a new project 90 | -------------------- 91 | 92 | To create a new Python package project based on this cookiecutter template 93 | simply navigate to a directory where you want to create the new project, then 94 | run the ``cookiecutter`` command with a command line argument referencing this 95 | template. 96 | 97 | The easiest method is to reference this template via its Github URL (where 'gh' 98 | is a shortened form for Github): 99 | 100 | .. code-block:: console 101 | 102 | (cc) $ cookiecutter gh:claws/cookiecutter-python-project 103 | 104 | Alternatively, if you have cloned a local copy of this template you can 105 | reference it directly: 106 | 107 | .. code-block:: console 108 | 109 | (cc) $ cookiecutter path/to/cookiecutter-python-project 110 | 111 | You will be prompted for user input to configure the project. Prompts are the 112 | keys in 'cookiecutter.json' and default responses are the values. Prompts are 113 | shown in order. 114 | 115 | Once you have generated your new Python package project you can exit the 116 | cookiecutter virtual environment as it is no longer required. 117 | 118 | .. code-block:: console 119 | 120 | (cc) $ deactivate 121 | $ 122 | 123 | 124 | Manual Modifications 125 | -------------------- 126 | 127 | Some aspects of generating a project in a generic approach are not practical 128 | to completely automate so there may be a few steps remaining before you begin 129 | using the new project. 130 | 131 | - If you specify a license other than MIT then you will need to update the 132 | ``LICENSE`` file to contain your license content. By default it contains 133 | a MIT License. 134 | 135 | - If you do not plan to publish project artifacts at GitHub, PyPI or 136 | ReadTheDocs then remove any links to those sites. Affected files are: 137 | 138 | - README.rst 139 | - setup.py 140 | - docs/source/index.rst 141 | 142 | - Update any additional useful classifiers in ``setup.py``. The list of 143 | available classifiers can be found `here `_. 144 | 145 | 146 | Example 147 | ======= 148 | 149 | Below is an example showing exactly how to create a new Python project using 150 | the template in this project. In this scenario the project is called 151 | ``abc 123`` and the Python package is called ``abc_123``. 152 | 153 | It is assumed that you have performed the actions outlined in the One Time 154 | Setup Steps section above which provides a virtual environment with 155 | cookiecutter installed into it. 156 | 157 | After running the cookiecutter command and passing it a reference to this 158 | template the first question it asks for is the package display name. This is 159 | the human friendly label that will be used in docs to refer to the project. It 160 | is also used to create the package name so it should not contain special 161 | characters that are invalid when used in a Python attribute. It can have spaces 162 | and hyphens in it. The package display name is first converted to lowercase 163 | text and then any spaces or hyphens are converted to underscores to produce a 164 | Python package name. 165 | 166 | .. code-block:: console 167 | 168 | (ccenv) $ cookiecutter ../cookiecutter-python-project/ 169 | package_display_name [Package-Name]: abc 123 170 | package_name [abc_123]: 171 | package_short_description [A description of the package]: This is my abc 123 package. 172 | version [0.0.1]: 173 | full_name [Your Name]: First Last 174 | email []: 175 | github_user_name [GithubUserName]: flast 176 | github_repo_name [abc_123]: 177 | Select license: 178 | 1 - MIT license 179 | 2 - BSD license 180 | 3 - Apache Software License 2.0 181 | 4 - GNU General Public License v3 182 | 5 - Not open source 183 | Choose from 1, 2, 3, 4, 5 [1]: 184 | year [2018]: 185 | 186 | The project has been created in the ``abc_123`` directory. 187 | 188 | .. code-block:: console 189 | 190 | $ cd abc_123 191 | 192 | We can now kick the tires of this new project by performing some initial 193 | project checks. 194 | 195 | First, let's create a project specific virtual environment and activate it. 196 | This will install all of the project's development dependencies as well as 197 | the project itself. The project will be installed as an editable package (by 198 | using the ``-e`` flag to ``pip``). 199 | 200 | .. code-block:: console 201 | 202 | $ make venv 203 | ... 204 | Enter virtual environment using: 205 | 206 | source venv/abc_123/bin/activate 207 | 208 | $ source venv/abc_123/bin/activate 209 | (abc_123) $ 210 | 211 | Now that we have a virtual environment we can check the remaining convenience 212 | functions provided by the Makefile. 213 | 214 | .. code-block:: console 215 | 216 | (abc_123) $ make style 217 | (abc_123) $ make check-style 218 | (abc_123) $ make check-static-analysis 219 | (abc_123) $ make test 220 | (abc_123) $ make test-verbose 221 | (abc_123) $ make coverage 222 | (abc_123) $ make check-docs 223 | (abc_123) $ make docs 224 | (abc_123) $ make serve-docs # in browser navigate to http://localhost:8000/html 225 | (abc_123) $ make dist 226 | (abc_123) $ make dist-test 227 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "package_display_name": "Package-Name", 3 | "package_name": "{{cookiecutter.package_display_name.lower().replace(' ', '_').replace('-', '_')}}", 4 | "package_short_description": "A description of the package", 5 | "version": "0.0.1", 6 | "full_name": "Your Name", 7 | "email": "", 8 | "github_user_name": "GithubUserName", 9 | "github_repo_name": "{{cookiecutter.package_name}}", 10 | "license": ["MIT license", "BSD license", "Apache Software License 2.0", "GNU General Public License v3", "Not open source"], 11 | "year": "{% now 'utc', '%Y' %}", 12 | "_extensions": ["jinja2_time.TimeExtension"] 13 | } 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cookiecutter 2 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = 4 | {{cookiecutter.package_name}} 5 | 6 | [paths] 7 | source = 8 | src/{{cookiecutter.package_name}} 9 | **/site-packages/{{cookiecutter.package_name}} 10 | 11 | [report] 12 | exclude_lines = 13 | pragma: no cover 14 | def __repr__ 15 | if self.debug 16 | raise AssertionError 17 | raise NotImplentedError 18 | if 0: 19 | if __name__ == .__main__.: 20 | 21 | ignore_errors = True 22 | 23 | [html] 24 | title = {{cookiecutter.package_name}} Coverage Test Report 25 | directory = docs/source/_static/coverage 26 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Python Package Workflow 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | {% raw -%} 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: [3.6, 3.7, 3.8] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v1 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install -r requirements.dev.txt 28 | pip install -r requirements.txt 29 | - name: Cache pip dependencies 30 | uses: actions/cache@v1 31 | with: 32 | path: ~/.cache/pip # This path is specific to Ubuntu 33 | # Check for a cache hit for the corresponding dev requirements file 34 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.dev.txt') }}-${{ hashFiles('requirements.txt') }} 35 | restore-keys: | 36 | ${{ runner.os }}-pip- 37 | ${{ runner.os }}- 38 | - name: Check code style 39 | run: | 40 | make check-style 41 | - name: Check static analysis 42 | run: | 43 | make check-static-analysis 44 | - name: Install package 45 | run: | 46 | pip install . 47 | - name: Run unit tests 48 | run: | 49 | make test 50 | - name: Generate code coverage report 51 | run: | 52 | make coverage 53 | - name: Generate docs 54 | run: | 55 | make docs 56 | - name: Generate package 57 | run: | 58 | make dist 59 | {%- endraw %} 60 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 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 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | docs/build/ 69 | docs/source/api/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | # VSCode 109 | .vscode/ 110 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Use multiple processes to speed up Pylint. 4 | jobs=4 5 | 6 | # Pickle collected data for later comparisons. 7 | persistent=yes 8 | 9 | # When enabled, pylint would attempt to guess common misconfiguration and emit 10 | # user-friendly hints instead of false-positive error messages. 11 | suggestion-mode=yes 12 | 13 | 14 | [MESSAGES CONTROL] 15 | 16 | disable=print-statement, 17 | parameter-unpacking, 18 | unpacking-in-except, 19 | old-raise-syntax, 20 | backtick, 21 | long-suffix, 22 | old-ne-operator, 23 | old-octal-literal, 24 | import-star-module-level, 25 | non-ascii-bytes-literal, 26 | raw-checker-failed, 27 | bad-inline-option, 28 | locally-disabled, 29 | file-ignored, 30 | suppressed-message, 31 | useless-suppression, 32 | deprecated-pragma, 33 | use-symbolic-message-instead, 34 | apply-builtin, 35 | basestring-builtin, 36 | buffer-builtin, 37 | cmp-builtin, 38 | coerce-builtin, 39 | execfile-builtin, 40 | file-builtin, 41 | long-builtin, 42 | raw_input-builtin, 43 | reduce-builtin, 44 | standarderror-builtin, 45 | unicode-builtin, 46 | xrange-builtin, 47 | coerce-method, 48 | delslice-method, 49 | getslice-method, 50 | setslice-method, 51 | no-absolute-import, 52 | old-division, 53 | dict-iter-method, 54 | dict-view-method, 55 | next-method-called, 56 | metaclass-assignment, 57 | indexing-exception, 58 | raising-string, 59 | reload-builtin, 60 | oct-method, 61 | hex-method, 62 | nonzero-method, 63 | cmp-method, 64 | input-builtin, 65 | round-builtin, 66 | intern-builtin, 67 | unichr-builtin, 68 | map-builtin-not-iterating, 69 | zip-builtin-not-iterating, 70 | range-builtin-not-iterating, 71 | filter-builtin-not-iterating, 72 | using-cmp-argument, 73 | eq-without-hash, 74 | div-method, 75 | idiv-method, 76 | rdiv-method, 77 | exception-message-attribute, 78 | invalid-str-codec, 79 | sys-max-int, 80 | bad-python3-import, 81 | deprecated-string-function, 82 | deprecated-str-translate-call, 83 | deprecated-itertools-function, 84 | deprecated-types-field, 85 | next-method-defined, 86 | dict-items-not-iterating, 87 | dict-keys-not-iterating, 88 | dict-values-not-iterating, 89 | deprecated-operator-function, 90 | deprecated-urllib-function, 91 | xreadlines-attribute, 92 | deprecated-sys-function, 93 | exception-escape, 94 | comprehension-escape 95 | bad-continuation, 96 | invalid-name, 97 | too-many-instance-attributes, 98 | too-many-arguments, 99 | unused-argument, 100 | wrong-import-order, 101 | missing-module-docstring, 102 | missing-class-docstring, 103 | missing-function-docstring, 104 | too-many-branches, 105 | too-many-nested-blocks, 106 | no-self-use, 107 | no-else-break, 108 | broad-except, 109 | pointless-string-statement 110 | 111 | enable=useless-object-inheritance, 112 | unused-variable, 113 | unused-import, 114 | undefined-variable 115 | not-callable, 116 | arguments-differ, 117 | redefined-outer-name 118 | 119 | [REPORTS] 120 | 121 | # Set the output format. Available formats are text, parseable, colorized, json 122 | # and msvs (visual studio). You can also give a reporter class, e.g. 123 | # mypackage.mymodule.MyReporterClass. 124 | output-format=text 125 | 126 | # Tells whether to display a full report or only the messages. 127 | reports=no 128 | 129 | # Activate the evaluation score. 130 | score=yes 131 | 132 | 133 | [LOGGING] 134 | 135 | # Format style used to check logging format string. `old` means using % 136 | # formatting, `new` is for `{}` formatting,and `fstr` is for f-strings. 137 | logging-format-style=new 138 | 139 | # Logging modules to check that the string format arguments are in logging 140 | # function parameter format. 141 | logging-modules=logging 142 | 143 | 144 | [VARIABLES] 145 | 146 | # List of additional names supposed to be defined in builtins. Remember that 147 | # you should avoid defining new builtins when possible. 148 | additional-builtins= 149 | 150 | # Tells whether unused global variables should be treated as a violation. 151 | allow-global-unused-variables=yes 152 | 153 | # List of strings which can identify a callback function by name. A callback 154 | # name must start or end with one of those strings. 155 | callbacks=cb_, 156 | _cb 157 | 158 | # A regular expression matching the name of dummy variables (i.e. expected to 159 | # not be used). 160 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 161 | 162 | # Argument names that match this expression will be ignored. Default to name 163 | # with leading underscore. 164 | ignored-argument-names=_.*|^ignored_|^unused_ 165 | 166 | # Tells whether we should check for unused import in __init__ files. 167 | init-import=no 168 | 169 | # List of qualified module names which can have objects that can redefine 170 | # builtins. 171 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 172 | 173 | 174 | [FORMAT] 175 | 176 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 177 | expected-line-ending-format= 178 | 179 | # Regexp for a line that is allowed to be longer than the limit. 180 | ignore-long-lines=^\s*(# )??$ 181 | 182 | # Number of spaces of indent required inside a hanging or continued line. 183 | indent-after-paren=4 184 | 185 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 186 | # tab). 187 | indent-string=' ' 188 | 189 | # Maximum number of characters on a single line. 190 | max-line-length=160 191 | 192 | # Maximum number of lines in a module. 193 | max-module-lines=1000 194 | 195 | # List of optional constructs for which whitespace checking is disabled. `dict- 196 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 197 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 198 | # `empty-line` allows space-only lines. 199 | no-space-check=trailing-comma, 200 | dict-separator 201 | 202 | # Allow the body of a class to be on the same line as the declaration if body 203 | # contains single statement. 204 | single-line-class-stmt=no 205 | 206 | # Allow the body of an if to be on the same line as the test if there is no 207 | # else. 208 | single-line-if-stmt=no 209 | 210 | 211 | [BASIC] 212 | 213 | # Naming style matching correct argument names. 214 | argument-naming-style=snake_case 215 | 216 | # Regular expression matching correct argument names. Overrides argument- 217 | # naming-style. 218 | #argument-rgx= 219 | 220 | # Naming style matching correct attribute names. 221 | attr-naming-style=snake_case 222 | 223 | # Regular expression matching correct attribute names. Overrides attr-naming- 224 | # style. 225 | #attr-rgx= 226 | 227 | # Bad variable names which should always be refused, separated by a comma. 228 | bad-names=foo, 229 | bar, 230 | baz, 231 | toto, 232 | tutu, 233 | tata 234 | 235 | # Naming style matching correct class attribute names. 236 | class-attribute-naming-style=any 237 | 238 | # Naming style matching correct class names. 239 | class-naming-style=PascalCase 240 | 241 | # Minimum line length for functions/classes that require docstrings, shorter 242 | # ones are exempt. 243 | docstring-min-length=-1 244 | 245 | # Naming style matching correct function names. 246 | function-naming-style=snake_case 247 | 248 | # Good variable names which should always be accepted, separated by a comma. 249 | good-names=i, 250 | j, 251 | k, 252 | ex, 253 | Run, 254 | _ 255 | 256 | # Include a hint for the correct naming format with invalid-name. 257 | include-naming-hint=no 258 | 259 | # Naming style matching correct inline iteration names. 260 | inlinevar-naming-style=any 261 | 262 | # Naming style matching correct method names. 263 | method-naming-style=snake_case 264 | 265 | # Naming style matching correct module names. 266 | module-naming-style=snake_case 267 | 268 | # List of decorators that produce properties, such as abc.abstractproperty. Add 269 | # to this list to register other decorators that produce valid properties. 270 | # These decorators are taken in consideration only for invalid-name. 271 | property-classes=abc.abstractproperty 272 | 273 | # Naming style matching correct variable names. 274 | variable-naming-style=snake_case 275 | 276 | 277 | [STRING] 278 | 279 | # This flag controls whether the implicit-str-concat-in-sequence should 280 | # generate a warning on implicit string concatenation in sequences defined over 281 | # several lines. 282 | check-str-concat-over-line-jumps=no 283 | 284 | 285 | [IMPORTS] 286 | 287 | # List of modules that can be imported at any level, not just the top level 288 | # one. 289 | allow-any-import-level= 290 | 291 | # Allow wildcard imports from modules that define __all__. 292 | allow-wildcard-with-all=no 293 | 294 | # Analyse import fallback blocks. This can be used to support both Python 2 and 295 | # 3 compatible code, which means that the block might have code that exists 296 | # only in one or another interpreter, leading to false positives when analysed. 297 | analyse-fallback-blocks=no 298 | 299 | # Deprecated modules which should not be used, separated by a comma. 300 | deprecated-modules=optparse,tkinter.tix 301 | 302 | # Create a graph of external dependencies in the given file (report RP0402 must 303 | # not be disabled). 304 | ext-import-graph= 305 | 306 | # Create a graph of every (i.e. internal and external) dependencies in the 307 | # given file (report RP0402 must not be disabled). 308 | import-graph= 309 | 310 | # Create a graph of internal dependencies in the given file (report RP0402 must 311 | # not be disabled). 312 | int-import-graph= 313 | 314 | # Force import order to recognize a module as part of the standard 315 | # compatibility libraries. 316 | known-standard-library= 317 | 318 | # Force import order to recognize a module as part of a third party library. 319 | known-third-party=enchant 320 | 321 | # Couples of modules and preferred modules, separated by a comma. 322 | preferred-modules= 323 | 324 | 325 | [CLASSES] 326 | 327 | # List of method names used to declare (i.e. assign) instance attributes. 328 | defining-attr-methods=__init__, 329 | __new__, 330 | setUp, 331 | __post_init__ 332 | 333 | # List of member names, which should be excluded from the protected access 334 | # warning. 335 | exclude-protected=_asdict, 336 | _fields, 337 | _replace, 338 | _source, 339 | _make 340 | 341 | # List of valid names for the first argument in a class method. 342 | valid-classmethod-first-arg=cls 343 | 344 | # List of valid names for the first argument in a metaclass class method. 345 | valid-metaclass-classmethod-first-arg=cls 346 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | .. _change-log-label: 2 | 3 | Change Log 4 | ========== 5 | 6 | Version History 7 | --------------- 8 | 9 | 0.0.1 10 | * Project created. 11 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing Guide 2 | ================== 3 | 4 | Contributions are welcome and greatly appreciated! 5 | 6 | 7 | .. _contributing-workflow-label: 8 | 9 | Workflow 10 | -------- 11 | 12 | A bug-fix or enhancement is delivered using a pull request. A good pull request 13 | should cover one bug-fix or enhancement feature. This strategy ensures the 14 | change set is easier to review and less likely to need major re-work or even be 15 | rejected. 16 | 17 | The workflow that developers typically use to fix a bug or add enhancements 18 | is as follows. 19 | 20 | * Fork the ``{{cookiecutter.github_repo_name}}`` repo into your account. 21 | 22 | * Obtain the source by cloning it onto your development machine. 23 | 24 | .. code-block:: console 25 | 26 | $ git clone git@github.com:your_name_here/{{cookiecutter.github_repo_name}}.git 27 | $ cd {{cookiecutter.package_name}} 28 | 29 | * Create a branch for local development: 30 | 31 | .. code-block:: console 32 | 33 | $ git checkout -b name-of-your-bugfix-or-feature 34 | 35 | Now you can make your changes locally. 36 | 37 | * Familiarize yourself with the developer convenience rules in the Makefile. 38 | 39 | .. code-block:: console 40 | 41 | $ make help 42 | 43 | * Create and activate a Python virtual environment for local development. This 44 | rule also specifies a project specific prompt label to use once the virtual 45 | environment is activated. 46 | 47 | .. code-block:: console 48 | 49 | $ make venv 50 | $ source venv/bin/activate 51 | ({{cookiecutter.package_name}}) $ 52 | 53 | The 'venv' directory is is created under the project root directory and is 54 | also listed in the '.gitignore' file so that its contents never accidentally 55 | get added to a git change set. 56 | 57 | .. note:: 58 | 59 | ({{cookiecutter.package_name}}) is used to indicate when the commands 60 | should be run within the virtual environment containing the development 61 | dependencies. 62 | 63 | * Develop fix or enhancement: 64 | 65 | * Make a fix or enhancement (e.g. modify a class, method, function, module, 66 | etc). 67 | 68 | * Update an existing unit test or create a new unit test module to verify 69 | the change works as expected. 70 | 71 | * Run the test suite. 72 | 73 | .. code-block:: console 74 | 75 | ({{cookiecutter.package_name}}) $ make test 76 | 77 | See the :ref:`testing-label` section for more information on testing. 78 | 79 | * Check code coverage of the area of code being modified. 80 | 81 | .. code-block:: console 82 | 83 | ({{cookiecutter.package_name}}) $ make coverage 84 | 85 | Review the output produced in ``docs/source/_static/coverage/coverage.html``. 86 | Add additional test steps, where practical, to improve coverage. 87 | 88 | * The change should be style compliant. Perform style check. 89 | 90 | .. code-block:: console 91 | 92 | ({{cookiecutter.package_name}}) $ make check-style 93 | 94 | Run 'make style' to automatically apply style fixes if needed. See the 95 | :ref:`style-compliance-label` section for more information. 96 | 97 | * The change should pass static analysis checks (linting and type annotations 98 | where appropriate). Perform static analysis check. 99 | 100 | .. code-block:: console 101 | 102 | ({{cookiecutter.package_name}}) $ make check-static-analysis 103 | 104 | See the :ref:`static-analysis-label` section for more information. 105 | 106 | * Fix any errors or regressions. 107 | 108 | * The docs and the change log should be updated for anything but trivial bug 109 | fixes. Perform docs check. 110 | 111 | .. code-block:: console 112 | 113 | ({{cookiecutter.package_name}}) $ make docs 114 | 115 | See the :ref:`documentation-label` section for more information. 116 | 117 | * Commit and push changes to your fork. 118 | 119 | .. code-block:: console 120 | 121 | $ git add . 122 | $ git commit -m "A detailed description of the changes." 123 | $ git push origin name-of-your-bugfix-or-feature 124 | 125 | A pull request should preferably only have one commit upon the current 126 | master HEAD, (via rebases and squash). 127 | 128 | * Submit a pull request through the service website (e.g. Github, Gitlab). 129 | 130 | * Check automated continuous integration steps all pass. Fix any problems 131 | if necessary and update the pull request. 132 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) {{cookiecutter.full_name}} {{cookiecutter.year}} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/Makefile: -------------------------------------------------------------------------------- 1 | # This makefile has been created to help developers perform common actions. 2 | # Most actions assume it is operating in a virtual environment where the 3 | # python command links to the appropriate virtual environment Python. 4 | 5 | MAKEFLAGS += --no-print-directory 6 | 7 | # Do not remove this block. It is used by the 'help' rule when 8 | # constructing the help output. 9 | # help: 10 | # help: {{cookiecutter.package_display_name}} Makefile help 11 | # help: 12 | 13 | # help: help - display this makefile's help information 14 | .PHONY: help 15 | help: 16 | @grep "^# help\:" Makefile | grep -v grep | sed 's/\# help\: //' | sed 's/\# help\://' 17 | 18 | # help: venv - create a virtual environment for development 19 | .PHONY: venv 20 | venv: 21 | @rm -Rf venv 22 | @python3 -m venv venv --prompt {{cookiecutter.package_name}} 23 | @/bin/bash -c "source venv/bin/activate && pip install pip --upgrade && pip install -r requirements.dev.txt && pip install -e ." 24 | @echo "Enter virtual environment using:\n\n\t$ source venv/bin/activate\n" 25 | 26 | 27 | # help: clean - clean all files using .gitignore rules 28 | .PHONY: clean 29 | clean: 30 | @git clean -X -f -d 31 | 32 | 33 | # help: scrub - clean all files, even untracked files 34 | .PHONY: scrub 35 | scrub: 36 | git clean -x -f -d 37 | 38 | 39 | # help: test - run tests 40 | .PHONY: test 41 | test: 42 | @python -m unittest discover -s tests 43 | 44 | 45 | # help: test-verbose - run tests [verbosely] 46 | .PHONY: test-verbose 47 | test-verbose: 48 | @python -m unittest discover -s tests -v 49 | 50 | 51 | # help: coverage - perform test coverage checks 52 | .PHONY: coverage 53 | coverage: 54 | @coverage erase 55 | @PYTHONPATH=src coverage run -m unittest discover -s tests -v 56 | @coverage html 57 | @coverage report 58 | 59 | 60 | # help: format - perform code style format 61 | .PHONY: format 62 | format: 63 | @black src/{{cookiecutter.package_name}} tests examples 64 | 65 | 66 | # help: check-format - check code format compliance 67 | .PHONY: check-format 68 | check-format: 69 | @black --check src/{{cookiecutter.package_name}} tests examples 70 | 71 | 72 | # help: sort-imports - apply import sort ordering 73 | .PHONY: sort-imports 74 | sort-imports: 75 | @isort . --profile black 76 | 77 | 78 | # help: check-sort-imports - check imports are sorted 79 | .PHONY: check-sort-imports 80 | check-sort-imports: 81 | @isort . --check-only --profile black 82 | 83 | 84 | # help: style - perform code style format 85 | .PHONY: style 86 | style: sort-imports format 87 | 88 | 89 | # help: check-style - check code style compliance 90 | .PHONY: check-style 91 | check-style: check-sort-imports check-format 92 | 93 | 94 | # help: check-types - check type hint annotations 95 | .PHONY: check-types 96 | check-types: 97 | @cd src; mypy -p {{cookiecutter.package_name}} --ignore-missing-imports 98 | 99 | 100 | # help: check-lint - run static analysis checks 101 | .PHONY: check-lint 102 | check-lint: 103 | @pylint --rcfile=.pylintrc {{cookiecutter.package_name}} ./tests setup.py ./examples 104 | 105 | 106 | # help: check-static-analysis - check code style compliance 107 | .PHONY: check-static-analysis 108 | check-static-analysis: check-lint check-types 109 | 110 | 111 | # help: docs - generate project documentation 112 | .PHONY: docs 113 | docs: coverage 114 | @cd docs; rm -rf source/api/{{cookiecutter.package_name}}*.rst source/api/modules.rst build/* 115 | @cd docs; make html 116 | 117 | 118 | # help: check-docs - quick check docs consistency 119 | .PHONY: check-docs 120 | check-docs: 121 | @cd docs; make dummy 122 | 123 | 124 | # help: serve-docs - serve project html documentation 125 | .PHONY: serve-docs 126 | serve-docs: 127 | @cd docs/build; python -m http.server --bind 127.0.0.1 128 | 129 | 130 | # help: dist - create a wheel distribution package 131 | .PHONY: dist 132 | dist: 133 | @python setup.py bdist_wheel 134 | 135 | 136 | # help: dist-test - test a whell distribution package 137 | .PHONY: dist-test 138 | dist-test: dist 139 | @cd dist && ../tests/test-dist.bash ./{{cookiecutter.package_name}}-*-py3-none-any.whl 140 | 141 | 142 | # help: dist-upload - upload a wheel distribution package 143 | .PHONY: dist-upload 144 | dist-upload: 145 | @twine upload dist/{{cookiecutter.package_name}}-*-py3-none-any.whl 146 | 147 | 148 | # Keep these lines at the end of the file to retain nice help 149 | # output formatting. 150 | # help: 151 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/README.rst: -------------------------------------------------------------------------------- 1 | {{cookiecutter.package_display_name}} 2 | {{cookiecutter.package_display_name|length * '#' }} 3 | 4 | {{cookiecutter.package_short_description}} 5 | 6 | 7 | Quickstart 8 | ========== 9 | 10 | {{cookiecutter.package_display_name}} is available on PyPI and can be installed with `pip `_. 11 | 12 | .. code-block:: console 13 | 14 | $ pip install {{cookiecutter.package_name}} 15 | 16 | After installing {{cookiecutter.package_display_name}} you can use it like any other Python module. 17 | 18 | Here is a simple example: 19 | 20 | .. code-block:: python 21 | 22 | import {{cookiecutter.package_name}} 23 | # Fill this section in with the common use-case. 24 | 25 | The `API Reference `_ provides API-level documentation. 26 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | 3 | # You can set these variables from the command line. 4 | SPHINXOPTS = 5 | SPHINXBUILD = sphinx-build 6 | SPHINXPROJ = {{cookiecutter.package_display_name}} 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | .PHONY: help 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | # Catch-all target: route all unknown targets to Sphinx using the new 16 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 17 | %: 18 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 19 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/docs/source/_static/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claws/cookiecutter-python-project/5d1e2f1407edb92e90b95ce9bfc477af161c4e65/{{cookiecutter.package_name}}/docs/source/_static/.gitignore -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/docs/source/api/index.rst: -------------------------------------------------------------------------------- 1 | API 2 | ### 3 | 4 | This section provides you with information on specific functions, classes and methods. 5 | 6 | .. toctree:: 7 | 8 | modules.rst 9 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | 5 | # If extensions (or modules to document with autodoc) are in another directory, 6 | # add these directories to sys.path here. If the directory is relative to the 7 | # documentation root, use os.path.abspath to make it absolute, like shown here. 8 | # 9 | import os 10 | import re 11 | import sys 12 | 13 | from sphinx.ext import apidoc 14 | 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | regexp = re.compile(r'.*__version__ = [\'\"](.*?)[\'\"]', re.S) 19 | repo_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 20 | pkg_root = os.path.join(repo_root, 'src', '{{cookiecutter.package_name}}') 21 | init_file = os.path.join(pkg_root, '__init__.py') 22 | with open(init_file, 'r') as f: 23 | module_content = f.read() 24 | match = regexp.match(module_content) 25 | if match: 26 | version = match.group(1) 27 | else: 28 | raise RuntimeError( 29 | 'Cannot find __version__ in {}'.format(init_file)) 30 | 31 | 32 | # -- General configuration ------------------------------------------------ 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | 'sphinx.ext.autodoc', 39 | 'sphinx.ext.viewcode', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # The suffix(es) of source filenames. 46 | # You can specify multiple suffix as a list of string: 47 | # 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = '.rst' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = '{{cookiecutter.package_display_name}}' 56 | copyright = '{{cookiecutter.year}}, {{cookiecutter.full_name}}' 57 | author = '{{cookiecutter.full_name}}' 58 | 59 | # The version info for the project you're documenting, acts as replacement for 60 | # |version| and |release|, also used in various other places throughout the 61 | # built documents. 62 | # 63 | # The short X.Y version. 64 | version = version 65 | # The full version, including alpha/beta/rc tags. 66 | release = version 67 | 68 | # The language for content autogenerated by Sphinx. Refer to documentation 69 | # for a list of supported languages. 70 | # 71 | # This is also used if you do content translation via gettext catalogs. 72 | # Usually you set "language" from the command line for these cases. 73 | language = None 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | # This patterns also effect to html_static_path and html_extra_path 78 | exclude_patterns = [] 79 | 80 | # The name of the Pygments (syntax highlighting) style to use. 81 | pygments_style = 'sphinx' 82 | 83 | # If true, `todo` and `todoList` produce output, else they produce nothing. 84 | todo_include_todos = False 85 | 86 | # If true, links to the reST sources are added to the pages. 87 | # 88 | html_show_sourcelink = False 89 | 90 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 91 | # 92 | html_show_copyright = False 93 | 94 | # Suppress nonlocal image warnings 95 | suppress_warnings = ['image.nonlocal_uri'] 96 | 97 | 98 | # -- Options for HTML output ---------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | # 103 | html_theme = 'alabaster' 104 | 105 | # Theme options are theme-specific and customize the look and feel of a theme 106 | # further. For a list of options available for each theme, see the 107 | # documentation. 108 | # 109 | html_theme_options = { 110 | 'description': '{{cookiecutter.package_short_description}}', 111 | 'show_powered_by': False, 112 | # 'logo': 'my-logo.png', 113 | 'logo_name': False, 114 | 'page_width': '80%', 115 | } 116 | 117 | # Custom sidebar templates, maps document names to template names. 118 | # 119 | html_sidebars = { 120 | '**': [ 121 | 'about.html', 122 | 'navigation.html', 123 | ] 124 | } 125 | 126 | 127 | # -- Custom config to work around readthedocs.org #1139 ------------------- 128 | 129 | def run_apidoc(_): 130 | output_path = os.path.join(repo_root, 'docs', 'source', 'api') 131 | apidoc.main(['-o', output_path, '-f', pkg_root]) 132 | 133 | 134 | def setup(app): 135 | app.connect('builder-inited', run_apidoc) 136 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/docs/source/dev/index.rst: -------------------------------------------------------------------------------- 1 | Developers Guide 2 | ################ 3 | 4 | .. include:: ../../../CONTRIBUTING.rst 5 | 6 | 7 | .. _testing-label: 8 | 9 | Testing 10 | ======= 11 | 12 | The {{cookiecutter.package_display_name}} project implements a regression 13 | test suite that improves developer productivity by identifying capability 14 | regressions early. 15 | 16 | Developers implementing fixes or enhancements must ensure that they have 17 | not broken existing functionality. The {{cookiecutter.package_display_name}} 18 | project provides some convenience tools so this testing step can be quickly 19 | performed. 20 | 21 | Use the Makefile convenience rules to run the tests. 22 | 23 | .. code-block:: console 24 | 25 | ({{cookiecutter.package_name}}) $ make test 26 | 27 | To run tests verbosely use: 28 | 29 | .. code-block:: console 30 | 31 | ({{cookiecutter.package_name}}) $ make test-verbose 32 | 33 | Alternatively, you may want to run the test suite directly. The following 34 | steps assume you are running in a virtual environment in which the 35 | ``{{cookiecutter.package_name}}`` package has been installed. If this is 36 | not the case then you will likely need to set the ``PYTHONPATH`` environment 37 | variable so that the ``{{cookiecutter.package_name}}`` package can be found. 38 | 39 | .. code-block:: console 40 | 41 | ({{cookiecutter.package_name}}) $ cd tests 42 | ({{cookiecutter.package_name}}) $ python -m unittest 43 | 44 | Individual unit tests can be run also. 45 | 46 | .. code-block:: console 47 | 48 | ({{cookiecutter.package_name}}) $ python -m test_version 49 | 50 | 51 | .. _test-coverage-label: 52 | 53 | Coverage 54 | ======== 55 | 56 | The ``coverage`` tool is used to collect code test coverage metrics. Use the 57 | Makefile convenience rule to run the code coverage checks. 58 | 59 | .. code-block:: console 60 | 61 | ({{cookiecutter.package_name}}) $ make coverage 62 | 63 | The test code coverage report can be found `here <../_static/coverage/index.html>`_ 64 | 65 | 66 | .. _style-compliance-label: 67 | 68 | Code Style 69 | ========== 70 | 71 | Adopting a consistent code style assists with maintenance. This project uses 72 | Black to format code and isort to sort imports. Use the Makefile convenience rule 73 | to apply code style fixes. 74 | 75 | .. code-block:: console 76 | 77 | ({{cookiecutter.package_name}}) $ make style 78 | 79 | .. _format-label: 80 | 81 | Code Formatting 82 | --------------- 83 | 84 | A Makefile convenience rule exists to perform just code format fixes. 85 | 86 | .. code-block:: console 87 | 88 | ({{cookiecutter.package_name}}) $ make format 89 | 90 | .. _import-sort-label: 91 | 92 | Import Sorting 93 | -------------- 94 | 95 | A Makefile convenience rule exists to perform just module import sorting fixes. 96 | 97 | .. code-block:: console 98 | 99 | ({{cookiecutter.package_name}}) $ make sort-imports 100 | 101 | 102 | .. _static-analysis-label: 103 | 104 | Static Analysis 105 | =============== 106 | 107 | A Makefile convenience rule exists to simplify performing static analysis 108 | checks. This will perform linting and type annotations checks. 109 | 110 | .. code-block:: console 111 | 112 | ({{cookiecutter.package_name}}) $ make check-static-analysis 113 | 114 | 115 | .. _code-linting-label: 116 | 117 | Code Linting 118 | ------------ 119 | 120 | A Makefile convenience rule exists to perform code linting checks. 121 | 122 | .. code-block:: console 123 | 124 | ({{cookiecutter.package_name}}) $ make check-lint 125 | 126 | 127 | .. _annotations-label: 128 | 129 | Type Annotations 130 | ---------------- 131 | 132 | The code base contains type annotations to provide helpful type information 133 | that can improve code maintenance. A Makefile convenience rule exists to check 134 | no type annotations issues are reported. 135 | 136 | .. code-block:: console 137 | 138 | ({{cookiecutter.package_name}}) $ make check-types 139 | 140 | 141 | .. _documentation-label: 142 | 143 | Documentation 144 | ============= 145 | 146 | To rebuild this project's documentation, developers should use the Makefile 147 | in the top level directory. It performs a number of steps to create a new 148 | set of `sphinx `_ html content. 149 | 150 | .. code-block:: console 151 | 152 | ({{cookiecutter.package_name}}) $ make docs 153 | 154 | To quickly check consistency of ReStructuredText files use the dummy run which 155 | does not actually generate HTML content. 156 | 157 | .. code-block:: console 158 | 159 | ({{cookiecutter.package_name}}) $ make check-docs 160 | 161 | To quickly view the HTML rendered docs, start a simple web server and open a 162 | browser to http://127.0.0.1:8000/. 163 | 164 | .. code-block:: console 165 | 166 | ({{cookiecutter.package_name}}) $ make serve-docs 167 | 168 | 169 | .. _release-label: 170 | 171 | Release Process 172 | =============== 173 | 174 | The following steps are used to make a new software release. 175 | 176 | The steps assume they are executed from within a development virtual 177 | environment. 178 | 179 | - Check that the package version label in ``__init__.py`` is correct. 180 | 181 | - Create and push a repo tag to Github. As a convention use the package 182 | version number (e.g. YY.MM.MICRO) as the tag. 183 | 184 | .. code-block:: console 185 | 186 | $ git checkout master 187 | $ git tag YY.MM.MICRO -m "A meaningful release tag comment" 188 | $ git tag # check release tag is in list 189 | $ git push --tags origin master 190 | 191 | - This will trigger Github to create a release at: 192 | 193 | :: 194 | 195 | https://github.com/{username}/{{cookiecutter.package_name}}/releases/{tag} 196 | 197 | - Create the release distribution. This project produces an artefact called a 198 | pure Python wheel. The wheel file will be created in the ``dist`` directory. 199 | 200 | .. code-block:: console 201 | 202 | ({{cookiecutter.package_name}}) $ make dist 203 | 204 | - Test the release distribution. This involves creating a virtual environment, 205 | installing the distribution into it and running project tests against the 206 | installed distribution. These steps have been captured for convenience in a 207 | Makefile rule. 208 | 209 | .. code-block:: console 210 | 211 | ({{cookiecutter.package_name}}) $ make dist-test 212 | 213 | - Upload the release to PyPI using 214 | 215 | .. code-block:: console 216 | 217 | ({{cookiecutter.package_name}}) $ make dist-upload 218 | 219 | The package should now be available at https://pypi.org/project/{{cookiecutter.package_name}}/ 220 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | {{cookiecutter.package_display_name}} 2 | {{cookiecutter.package_display_name|length * '#' }} 3 | 4 | {{cookiecutter.package_short_description}} 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :numbered: 9 | :hidden: 10 | 11 | user/index 12 | api/index 13 | dev/index 14 | 15 | 16 | Quick Start 17 | =========== 18 | 19 | {{cookiecutter.package_display_name}} is available on PyPI and can be installed with `pip `_. 20 | 21 | .. code-block:: console 22 | 23 | $ pip install {{cookiecutter.package_name}} 24 | 25 | After installing {{cookiecutter.package_display_name}} you can use it like any other Python module. 26 | 27 | Here is a simple example: 28 | 29 | .. code-block:: python 30 | 31 | import {{cookiecutter.package_name}} 32 | # Fill this section in with the common use-case. 33 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/docs/source/user/index.rst: -------------------------------------------------------------------------------- 1 | User Guide 2 | ########## 3 | 4 | This section of the documentation provides user focused information such as 5 | installing and quickly using this package. 6 | 7 | .. _install-guide-label: 8 | 9 | Install Guide 10 | ============= 11 | 12 | .. note:: 13 | 14 | It is best practice to install Python projects in a virtual environment, 15 | which can be created and activated as follows using Python 3.6+. 16 | 17 | .. code-block:: console 18 | 19 | $ python -m venv venv --prompt myvenv 20 | $ source venv/bin/activate 21 | (myvenv) $ 22 | 23 | The simplest way to install {{cookiecutter.package_display_name}} is using Pip. 24 | 25 | .. code-block:: console 26 | 27 | $ pip install {{cookiecutter.package_name}} 28 | 29 | This will install ``{{cookiecutter.package_name}}`` and all of its dependencies. 30 | 31 | 32 | .. _api-reference-label: 33 | 34 | API Reference 35 | ============= 36 | 37 | The `API Reference `_ provides API-level documentation. 38 | 39 | 40 | .. include:: ../../../CHANGELOG.rst 41 | 42 | 43 | .. _report-bugs-label: 44 | 45 | Report Bugs 46 | =========== 47 | 48 | Report bugs at the `issue tracker `_. 49 | 50 | Please include: 51 | 52 | - Operating system name and version. 53 | - Any details about your local setup that might be helpful in troubleshooting. 54 | - Detailed steps to reproduce the bug. 55 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/examples/quickstart.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example script imports the {{cookiecutter.package_name}} package and 3 | prints out the version. 4 | """ 5 | 6 | import {{cookiecutter.package_name}} 7 | 8 | 9 | def main(): 10 | print( 11 | f"{{cookiecutter.package_name}} version: {% raw -%}{{%- endraw %}{{cookiecutter.package_name}}.__version__{% raw -%}}{%- endraw %}" 12 | ) 13 | 14 | 15 | if __name__ == "__main__": 16 | main() 17 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/mypi.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | show_column_numbers = true 3 | disallow_untyped_calls = true 4 | disallow_untyped_defs = true 5 | follow_imports = skip 6 | no_implicit_optional = true 7 | warn_no_return = true 8 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/requirements.dev.txt: -------------------------------------------------------------------------------- 1 | black 2 | coverage 3 | isort 4 | mypy 5 | pylint 6 | sphinx 7 | twine 8 | wheel 9 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claws/cookiecutter-python-project/5d1e2f1407edb92e90b95ce9bfc477af161c4e65/{{cookiecutter.package_name}}/requirements.txt -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from setuptools import find_packages, setup 5 | 6 | regexp = re.compile(r'.*__version__ = [\'\"](.*?)[\'\"]', re.S) 7 | 8 | base_package = '{{cookiecutter.package_name}}' 9 | base_path = os.path.dirname(__file__) 10 | 11 | init_file = os.path.join(base_path, 'src', '{{cookiecutter.package_name}}', '__init__.py') 12 | with open(init_file, 'r') as f: 13 | module_content = f.read() 14 | 15 | match = regexp.match(module_content) 16 | if match: 17 | version = match.group(1) 18 | else: 19 | raise RuntimeError( 20 | 'Cannot find __version__ in {}'.format(init_file)) 21 | 22 | with open('README.rst', 'r') as f: 23 | readme = f.read() 24 | 25 | with open('CHANGELOG.rst', 'r') as f: 26 | changes = f.read() 27 | 28 | def parse_requirements(filename): 29 | ''' Load requirements from a pip requirements file ''' 30 | with open(filename, 'r') as fd: 31 | lines = [] 32 | for line in fd: 33 | line.strip() 34 | if line and not line.startswith("#"): 35 | lines.append(line) 36 | return lines 37 | 38 | requirements = parse_requirements('requirements.txt') 39 | 40 | 41 | if __name__ == '__main__': 42 | setup( 43 | name='{{cookiecutter.package_name}}', 44 | description='{{cookiecutter.package_short_description}}', 45 | long_description='\n\n'.join([readme, changes]), 46 | license='{{cookiecutter.license}}', 47 | url='https://github.com/{{cookiecutter.github_user_name}}/{{cookiecutter.github_repo_name}}', 48 | version=version, 49 | author='{{cookiecutter.full_name}}', 50 | author_email='{{cookiecutter.email}}', 51 | maintainer='{{cookiecutter.full_name}}', 52 | maintainer_email='{{cookiecutter.email}}', 53 | install_requires=requirements, 54 | keywords=['{{cookiecutter.package_name}}'], 55 | package_dir={'': 'src'}, 56 | packages=find_packages('src'), 57 | zip_safe=False, 58 | classifiers=['Development Status :: 3 - Alpha', 59 | 'Intended Audience :: Developers', 60 | 'Programming Language :: Python :: 3.6', 61 | 'Programming Language :: Python :: 3.7', 62 | 'Programming Language :: Python :: 3.8'] 63 | ) 64 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/src/{{cookiecutter.package_name}}/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | {{cookiecutter.package_short_description}} 3 | """ 4 | 5 | __version__ = "{{cookiecutter.version}}" 6 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ This file exists solely to support running pylint on tests """ 2 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/tests/test-dist.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script creates a virtual environment and installs a distribution of 4 | # this project into the environment and then executes the unit tests. This 5 | # provides a crude check that the distribution is installable and that the 6 | # package is minimally functional. 7 | # 8 | 9 | if [ -z "$1" ]; then 10 | echo "usage: $0 {{cookiecutter.package_name}}-YY.MM.MICRO-py3-none-any.whl" 11 | exit 12 | fi 13 | 14 | RELEASE_ARCHIVE="$1" 15 | 16 | echo "Release archive: $RELEASE_ARCHIVE" 17 | 18 | echo "Removing any old artefacts" 19 | rm -rf test_venv 20 | 21 | echo "Creating test virtual environment" 22 | python -m venv test_venv 23 | 24 | echo "Entering test virtual environment" 25 | source test_venv/bin/activate 26 | 27 | echo "Upgrading pip" 28 | pip install pip --upgrade 29 | 30 | echo "Installing $RELEASE_ARCHIVE" 31 | pip install $RELEASE_ARCHIVE 32 | 33 | echo "Running tests" 34 | cd ../tests 35 | python -m unittest discover -s . 36 | 37 | echo "Exiting test virtual environment" 38 | deactivate 39 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/tests/test_examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | To avoid bit-rot in the examples they are tested as part of the unit tests 3 | suite. 4 | """ 5 | 6 | import os 7 | import shlex 8 | import subprocess 9 | import unittest 10 | 11 | # Use the current virtual environment when executing the example scripts. 12 | VENV_DIR = os.environ.get("VIRTUAL_ENV") 13 | 14 | REPO_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 15 | 16 | 17 | @unittest.skipIf(VENV_DIR is None, "VIRTUAL_ENV environment variable is not set") 18 | class ExamplesTestCase(unittest.TestCase): 19 | """ Check example scripts function. 20 | 21 | This test case assumes it is running in a virtual environment. The same 22 | virtual environment is activated prior to running the example script 23 | in a subprocess. 24 | """ 25 | 26 | def run_in_venv(self, filepath, timeout=5.0, **kwargs): 27 | """ Run a Python script in a virtual env in a subprocess. 28 | 29 | filepath references must be relative to the repo root directory. 30 | """ 31 | original_cwd = os.getcwd() 32 | script_dir = os.path.join(REPO_DIR, os.path.dirname(filepath)) 33 | filename = os.path.basename(filepath) 34 | args = shlex.split( 35 | f'/bin/bash -c "source {VENV_DIR}/bin/activate && python {filename}"' 36 | ) 37 | 38 | env = {} 39 | if os.environ["PATH"]: 40 | env["PATH"] = os.environ["PATH"] 41 | if "LD_LIBRARY_PATH" in os.environ: 42 | env["LD_LIBRARY_PATH"] = os.environ["LD_LIBRARY_PATH"] 43 | 44 | try: 45 | os.chdir(script_dir) 46 | proc = subprocess.Popen( 47 | args, 48 | env=env, 49 | stdout=subprocess.PIPE, 50 | stderr=subprocess.PIPE, 51 | shell=False, 52 | **kwargs, 53 | ) 54 | _out, _err = proc.communicate(timeout=timeout) 55 | returncode = proc.returncode 56 | finally: 57 | os.chdir(original_cwd) 58 | 59 | success = returncode == 0 60 | return success 61 | 62 | def test_quickstart_example(self): 63 | """ check quickstart example """ 64 | self.assertTrue(self.run_in_venv(os.path.join("examples", "quickstart.py"))) 65 | 66 | 67 | if __name__ == "__main__": 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/tests/test_version.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import {{cookiecutter.package_name}} 4 | 5 | 6 | class VersionTestCase(unittest.TestCase): 7 | """ Version tests """ 8 | 9 | def test_version(self): 10 | """ check {{cookiecutter.package_name}} exposes a version attribute """ 11 | self.assertTrue(hasattr({{cookiecutter.package_name}}, "__version__")) 12 | self.assertIsInstance({{cookiecutter.package_name}}.__version__, str) 13 | 14 | 15 | if __name__ == "__main__": 16 | unittest.main() 17 | --------------------------------------------------------------------------------