├── template ├── requirements.txt ├── {{ package_dir_name }} │ ├── __init__.py │ └── test │ │ └── __init__.py ├── setup.cfg ├── requirements_test.txt ├── requirements_test_runner.txt ├── docker-compose.yml ├── Dockerfile.template ├── requirements_static_analysis.txt ├── .codeclimate.yml ├── tox.ini ├── .travis.yml.template ├── .gitignore ├── setup.py.template ├── LICENSE.template ├── .dockerignore ├── README.rst.template ├── tests.py.template └── .pylintrc ├── test ├── docker-compose.yml ├── integration │ ├── docker-entrypoint.sh │ ├── Dockerfile │ └── tests.bats └── run-integration-tests.sh ├── populate.ini ├── LICENSE ├── .gitignore ├── .dockerignore ├── README.md └── populate.py /template/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/{{ package_dir_name }}/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/{{ package_dir_name }}/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /template/requirements_test.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | -------------------------------------------------------------------------------- /template/requirements_test_runner.txt: -------------------------------------------------------------------------------- 1 | nose 2 | coverage 3 | -------------------------------------------------------------------------------- /template/docker-compose.yml: -------------------------------------------------------------------------------- 1 | tox: 2 | build: . 3 | volumes: 4 | - ".:/src:ro" 5 | -------------------------------------------------------------------------------- /template/Dockerfile.template: -------------------------------------------------------------------------------- 1 | FROM themattrix/tox 2 | 3 | MAINTAINER {{ author_name }} <{{ author_email }}> 4 | -------------------------------------------------------------------------------- /template/requirements_static_analysis.txt: -------------------------------------------------------------------------------- 1 | # Docs analysis, same versions used by PyPI 2 | docutils==0.12 3 | pygments==2.0.2 4 | 5 | # Code analysis 6 | flake8 7 | pylint 8 | -------------------------------------------------------------------------------- /test/docker-compose.yml: -------------------------------------------------------------------------------- 1 | integration_tests: 2 | privileged: true 3 | build: integration 4 | volumes: 5 | - "..:/src:ro" 6 | - "/var/run/docker.sock:/var/run/docker.sock" 7 | -------------------------------------------------------------------------------- /template/.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - python 8 | fixme: 9 | enabled: true 10 | radon: 11 | enabled: true 12 | ratings: 13 | paths: 14 | - "**.py" 15 | exclude_paths: [] 16 | -------------------------------------------------------------------------------- /test/integration/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit -o pipefail 4 | shopt -s dotglob 5 | 6 | # Copy our template files from /src/ to the pwd. 7 | cp -r /src/{populate.{ini,py},template} ./ 8 | chmod +x ./populate.py 9 | 10 | # Run tests! 11 | exec bats /src/test/integration/*.bats 12 | -------------------------------------------------------------------------------- /populate.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | 3 | ; Used to uniquely identify this package on PyPI. Pick a name that doesn't 4 | ; exist yet! 5 | package_name= 6 | 7 | ; Initial version of this package. I prefer Semantic Versioning: http://semver.org/ 8 | package_version= 9 | 10 | ; This description will appear in 'setup.py' and at the top of `README.rst`. 11 | short_description= 12 | -------------------------------------------------------------------------------- /template/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26,py27,py33,py34,py35,py36,pypy,pypy3 3 | skipsdist = {env:TOXBUILD:false} 4 | 5 | [testenv] 6 | passenv = LANG 7 | whitelist_externals = 8 | true 9 | setenv = 10 | {py27,py36}: STATIC_ANALYSIS = --static-analysis 11 | deps = 12 | {py27,py36}: -rrequirements_static_analysis.txt 13 | -rrequirements_test_runner.txt 14 | -rrequirements_test.txt 15 | commands = 16 | {env:TOXBUILD:python tests.py {env:STATIC_ANALYSIS:}} 17 | 18 | [flake8] 19 | max-line-length = 79 20 | -------------------------------------------------------------------------------- /test/run-integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | output_on_failure() { 6 | local message=${1}; shift 7 | local output 8 | local status 9 | printf '%s' "${message}" 10 | set +e 11 | output=$("${@}" 2>&1) 12 | status=$? 13 | set -e 14 | ! ((status)) || { printf 'FAIL\n\n%s\n' "${output}"; ! ((status)); } 15 | printf 'done\n' 16 | } 17 | 18 | cd "$(dirname ${BASH_SOURCE[0]})" 19 | 20 | output_on_failure "Cleaning..." docker-compose rm -f 21 | output_on_failure "Building..." docker-compose build 22 | docker-compose up # must use "up" instead of "run" for introspection by the tests 23 | -------------------------------------------------------------------------------- /template/.travis.yml.template: -------------------------------------------------------------------------------- 1 | language: python 2 | matrix: 3 | include: 4 | - python: 2.6 5 | env: 6 | - TOXENV=py26 7 | - python: 2.7 8 | env: 9 | - TOXENV=py27 10 | - python: 3.3 11 | env: 12 | - TOXENV=py33 13 | - python: 3.4 14 | env: 15 | - TOXENV=py34 16 | - python: 3.5 17 | env: 18 | - TOXENV=py35 19 | - python: 3.6 20 | env: 21 | - TOXENV=py36 22 | - python: pypy 23 | env: 24 | - TOXENV=pypy 25 | - python: pypy3 26 | env: 27 | - TOXENV=pypy3 28 | install: 29 | - pip install tox coveralls 30 | script: 31 | - tox 32 | after_success: 33 | - coveralls 34 | deploy: 35 | provider: pypi 36 | user: {{ github_user }} 37 | distributions: sdist bdist_wheel 38 | on: 39 | condition: $TOXENV == py27 40 | tags: true 41 | all_branches: true 42 | repo: {{ github_user }}/{{ repo_name }} 43 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # Virtual environment 57 | .env/ 58 | .venv/ 59 | venv/ 60 | 61 | # PyCharm 62 | .idea 63 | 64 | # Python mode for VIM 65 | .ropeproject 66 | 67 | # Vim swap files 68 | *.swp 69 | -------------------------------------------------------------------------------- /template/setup.py.template: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='{{ package_name }}', 5 | version='{{ package_version }}', 6 | packages=('{{ package_dir_name }}',), 7 | url='https://github.com/{{ github_user }}/{{ repo_name }}', 8 | license='MIT', 9 | author='{{ author_name }}', 10 | author_email='{{ author_email }}', 11 | install_requires=( 12 | {{ install_requires|pytuple }}), 13 | tests_require=( 14 | {{ tests_require|pytuple }}), 15 | description=( 16 | {{ short_description|pystring }}), 17 | classifiers=( 18 | 'License :: OSI Approved :: MIT License', 19 | 'Programming Language :: Python :: 2', 20 | 'Programming Language :: Python :: 2.6', 21 | 'Programming Language :: Python :: 2.7', 22 | 'Programming Language :: Python :: 3', 23 | 'Programming Language :: Python :: 3.2', 24 | 'Programming Language :: Python :: 3.3', 25 | 'Programming Language :: Python :: 3.4', 26 | 'Programming Language :: Python :: Implementation :: CPython', 27 | 'Programming Language :: Python :: Implementation :: PyPy')) 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Matthew Tardiff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # docker-in-docker 2 | .docker 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 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 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # Virtual environment 60 | .env/ 61 | .venv/ 62 | venv/ 63 | 64 | # PyCharm 65 | .idea 66 | 67 | # Python mode for VIM 68 | .ropeproject 69 | 70 | # Vim swap files 71 | *.swp 72 | .*.swp 73 | *~ 74 | .*~ 75 | -------------------------------------------------------------------------------- /template/LICENSE.template: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) {{ copyright_years }} {{ author_name }} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /template/.dockerignore: -------------------------------------------------------------------------------- 1 | # Git 2 | .git 3 | .gitignore 4 | 5 | # CI 6 | .codeclimate.yml 7 | .travis.yml 8 | 9 | # Docker 10 | docker-compose.yml 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | */__pycache__/ 15 | */*/__pycache__/ 16 | */*/*/__pycache__/ 17 | *.py[cod] 18 | */*.py[cod] 19 | */*/*.py[cod] 20 | */*/*/*.py[cod] 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | env/ 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | lib/ 34 | lib64/ 35 | parts/ 36 | sdist/ 37 | var/ 38 | *.egg-info/ 39 | .installed.cfg 40 | *.egg 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .coverage 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Virtual environment 74 | .env/ 75 | .venv/ 76 | venv/ 77 | 78 | # PyCharm 79 | .idea 80 | 81 | # Python mode for VIM 82 | .ropeproject 83 | */.ropeproject 84 | */*/.ropeproject 85 | */*/*/.ropeproject 86 | 87 | # Vim swap files 88 | *.swp 89 | */*.swp 90 | */*/*.swp 91 | */*/*/*.swp 92 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Git 2 | .git 3 | .gitignore 4 | 5 | # CI 6 | .codeclimate.yml 7 | .travis.yml 8 | .taskcluster.yml 9 | 10 | # Docker 11 | docker-compose.yml 12 | .docker 13 | 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | */__pycache__/ 17 | */*/__pycache__/ 18 | */*/*/__pycache__/ 19 | *.py[cod] 20 | */*.py[cod] 21 | */*/*.py[cod] 22 | */*/*/*.py[cod] 23 | 24 | # C extensions 25 | *.so 26 | 27 | # Distribution / packaging 28 | .Python 29 | env/ 30 | build/ 31 | develop-eggs/ 32 | dist/ 33 | downloads/ 34 | eggs/ 35 | lib/ 36 | lib64/ 37 | parts/ 38 | sdist/ 39 | var/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | 44 | # PyInstaller 45 | # Usually these files are written by a python script from a template 46 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 47 | *.manifest 48 | *.spec 49 | 50 | # Installer logs 51 | pip-log.txt 52 | pip-delete-this-directory.txt 53 | 54 | # Unit test / coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .coverage 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Virtual environment 76 | .env/ 77 | .venv/ 78 | venv/ 79 | 80 | # PyCharm 81 | .idea 82 | 83 | # Python mode for VIM 84 | .ropeproject 85 | */.ropeproject 86 | */*/.ropeproject 87 | */*/*/.ropeproject 88 | 89 | # Vim swap files 90 | *.swp 91 | */*.swp 92 | */*/*.swp 93 | */*/*/*.swp 94 | -------------------------------------------------------------------------------- /template/README.rst.template: -------------------------------------------------------------------------------- 1 | {{ package_name|capitalize }} |Version| |Build| |Coverage| |Health| 2 | =================================================================== 3 | 4 | |Compatibility| |Implementations| |Format| |Downloads| 5 | 6 | {{ short_description }} 7 | 8 | .. code:: python 9 | 10 | # TODO: add super short usage 11 | 12 | 13 | Installation: 14 | 15 | .. code:: console 16 | 17 | $ pip install {{ package_name }} 18 | 19 | .. TODO: longer description 20 | 21 | 22 | Example 23 | ------- 24 | 25 | .. code:: python 26 | 27 | # TODO: add example 28 | 29 | 30 | Changelog 31 | --------- 32 | 33 | **{{ package_version }}** 34 | 35 | - Initial release. 36 | 37 | 38 | .. |Build| image:: https://travis-ci.org/{{ github_user }}/{{ repo_name }}.svg?branch=master 39 | :target: https://travis-ci.org/{{ github_user }}/{{ repo_name }} 40 | .. |Coverage| image:: https://img.shields.io/coveralls/{{ github_user }}/{{ repo_name }}.svg 41 | :target: https://coveralls.io/r/{{ github_user }}/{{ repo_name }} 42 | .. |Health| image:: https://codeclimate.com/github/{{ github_user }}/{{ repo_name }}/badges/gpa.svg 43 | :target: https://codeclimate.com/github/{{ github_user }}/{{ repo_name }} 44 | .. |Version| image:: https://img.shields.io/pypi/v/{{ package_name }}.svg 45 | :target: https://pypi.python.org/pypi/{{ package_name }} 46 | .. |Downloads| image:: https://img.shields.io/pypi/dm/{{ package_name }}.svg 47 | :target: https://pypi.python.org/pypi/{{ package_name }} 48 | .. |Compatibility| image:: https://img.shields.io/pypi/pyversions/{{ package_name }}.svg 49 | :target: https://pypi.python.org/pypi/{{ package_name }} 50 | .. |Implementations| image:: https://img.shields.io/pypi/implementation/{{ package_name }}.svg 51 | :target: https://pypi.python.org/pypi/{{ package_name }} 52 | .. |Format| image:: https://img.shields.io/pypi/format/{{ package_name }}.svg 53 | :target: https://pypi.python.org/pypi/{{ package_name }} 54 | -------------------------------------------------------------------------------- /test/integration/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | 3 | MAINTAINER Matthew Tardiff 4 | 5 | ENV DEBIAN_FRONTEND noninteractive 6 | 7 | RUN apt-get update && \ 8 | apt-get install -y --no-install-recommends locales && \ 9 | apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ 10 | localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 11 | ENV LANG en_US.UTF-8 12 | 13 | RUN apt-get update && \ 14 | apt-get -y --no-install-recommends install \ 15 | iptables \ 16 | ca-certificates \ 17 | lxc \ 18 | apt-transport-https \ 19 | git \ 20 | wget \ 21 | curl \ 22 | python \ 23 | build-essential \ 24 | make \ 25 | ruby \ 26 | ruby-dev && \ 27 | apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 28 | 29 | # Install Docker 30 | ENV DOCKER_BUCKET get.docker.com 31 | ENV DOCKER_VERSION 1.11.1 32 | ENV DOCKER_SHA256 893e3c6e89c0cd2c5f1e51ea41bc2dd97f5e791fcfa3cee28445df277836339d 33 | RUN set -x && \ 34 | curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-$DOCKER_VERSION.tgz" -o docker.tgz && \ 35 | echo "${DOCKER_SHA256} *docker.tgz" | sha256sum -c - && \ 36 | tar -xzvf docker.tgz && \ 37 | mv docker/* /usr/local/bin/ && \ 38 | rmdir docker && \ 39 | rm docker.tgz && \ 40 | docker -v 41 | 42 | # Install Docker Compose 43 | ENV DOCKER_COMPOSE_VERSION 1.7.1 44 | RUN curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose && \ 45 | chmod +x /usr/local/bin/docker-compose 46 | 47 | # Install Travis CI Gem 48 | ENV TRAVIS_CI_VERSION 1.8.3.travis.745.4 49 | RUN gem install travis -v "${TRAVIS_CI_VERSION}" --no-rdoc --no-ri 50 | 51 | # Install BATS (Bash Automated Testing System) 52 | RUN mkdir /install && \ 53 | cd /install && \ 54 | git clone https://github.com/sstephenson/bats.git && \ 55 | cd bats && \ 56 | git checkout v0.4.0 && \ 57 | ./install.sh /usr/local 58 | 59 | VOLUME /src 60 | VOLUME /app 61 | WORKDIR /app 62 | 63 | COPY docker-entrypoint.sh / 64 | 65 | ENTRYPOINT ["/docker-entrypoint.sh"] 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python PyPI Template 2 | 3 | Template for quickly creating a new Python project and publishing it to [PyPI](https://pypi.python.org/pypi). 4 | 5 | 6 | ## Requirements 7 | 8 | - [Python](https://www.python.org/) 9 | - [Travis CI command-line tools](https://rubygems.org/gems/travis) 10 | - [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) 11 | - [Git](http://git-scm.com/) and a [GitHub](https://github.com/) account 12 | - [PyPI](https://pypi.python.org/pypi) account 13 | 14 | 15 | ## Initial Setup 16 | 17 | 1. Create a new, empty GitHub repo for your Python project. Search PyPI to find a name that doesn't exist! 18 | 2. Enable your repo in: 19 | - [Travis CI](https://travis-ci.org) for building, testing, and publishing to PyPI; 20 | - [Code Climate](https://codeclimate.com) for code metrics; and 21 | - [Coveralls](https://coveralls.io) for code coverage. 22 | 3. Clone your repo locally. 23 | 4. [Make sure you have `user.name` and `user.email` set in git](https://help.github.com/articles/setting-your-username-in-git/). 24 | 5. Clone this template repo locally and copy `populate.ini`, `populate.py`, and `template/` into your new repo. 25 | 6. Edit `populate.ini` and fill out the appropriate info for your project. 26 | 7. Commit. 27 | 8. Run `populate.py` to populate all templated files and filenames. This will delete all files useful only for the template, including itself. If something doesn't work out, you can always revert to commit you made in the previous step. 28 | 9. Add your encrypted PyPI password to `.travis.yml` by running: 29 | 30 | travis encrypt --add deploy.password 31 | 32 | 10. Commit. 33 | 34 | At this point, your library should be empty, but all set up. Let's test it out! 35 | 36 | 37 | ## Local Tests 38 | 39 | Docker, Compose, and [Tox](https://tox.readthedocs.org/en/latest/) are used to approximate the environment that Travis CI, Code Climate, and Coveralls all run when you push. This will allow you to test your code against multiple versions of Python (2.6, 2.7, 3.3, 3.4, 3.5, 3.6, PyPy, and PyPy3) locally before pushing it or even committing it. 40 | 41 | To run everything (this will take a while the first time you run it, but subsequent runs will be quick): 42 | 43 | ``` 44 | $ docker-compose build && docker-compose up 45 | ``` 46 | 47 | To run against a single environment (e.g., Python 3.4): 48 | 49 | ``` 50 | $ docker-compose build && docker-compose run tox tox -e py34 51 | ``` 52 | 53 | 54 | ## PyPI Deployment 55 | 56 | Travis CI will deploy a new version of your package to PyPI every time you push a tag of any name to any branch. My typical process for making changes is something like this: 57 | 58 | 1. Made some code changes, and [update the version number](http://semver.org/) in `setup.py`. 59 | 2. Test the changes locally (e.g., `docker-compose build && docker-compose up`). See previous section. 60 | 3. Commit. 61 | 4. Push your changes; make sure Travis CI succeeds. 62 | 5. Tag the successful commit with the newly-updated version (e.g., `git tag 1.0.2`). 63 | 6. Push the tag (e.g., `git push origin 1.0.2`). 64 | 65 | Then sit back and wait for Travis CI to push your new package version to PyPI! 66 | -------------------------------------------------------------------------------- /template/tests.py.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import glob 4 | import itertools 5 | import fnmatch 6 | import os 7 | import os.path 8 | import subprocess 9 | import sys 10 | 11 | PACKAGE_DIR = '{{ package_dir_name }}' 12 | 13 | 14 | def main(args): 15 | try: 16 | if args and args[0] == '--static-analysis': 17 | run_static_analysis() 18 | run_unit_tests() 19 | except ProcessError as e: 20 | print(str(e)) 21 | sys.exit(e.status) 22 | 23 | 24 | def run_static_analysis(): 25 | analyze_rst_files() 26 | analyze_setup_py() 27 | analyze_source_with_flake8() 28 | analyze_source_with_pylint() 29 | 30 | 31 | def run_unit_tests(): 32 | run(('nosetests', 33 | '--exe', 34 | '--with-doctest', 35 | '--doctest-options', '+NORMALIZE_WHITESPACE', 36 | '--with-coverage', 37 | '--cover-tests', 38 | '--cover-inclusive', 39 | '--cover-package', PACKAGE_DIR, 40 | PACKAGE_DIR)) 41 | 42 | 43 | def analyze_rst_files(): 44 | rst_iter = itertools.chain( 45 | glob.iglob('*.rst'), 46 | recursive_glob(PACKAGE_DIR, '*.rst')) 47 | 48 | for path in rst_iter: 49 | run(('rst2html.py', '--exit-status=2', path), display_stdout=False) 50 | 51 | 52 | def analyze_setup_py(): 53 | run((sys.executable, 54 | 'setup.py', 55 | 'check', 56 | '--strict', 57 | '--restructuredtext', 58 | '--metadata')) 59 | 60 | 61 | def analyze_source_with_flake8(): 62 | run(('flake8', 'setup.py', PACKAGE_DIR)) 63 | 64 | 65 | def analyze_source_with_pylint(): 66 | run(('pylint', '--reports=no', '--rcfile', '.pylintrc', PACKAGE_DIR)) 67 | 68 | 69 | def recursive_glob(top, pattern): 70 | for dirpath, _, filenames in os.walk(top): 71 | for f in fnmatch.filter(filenames, pattern): 72 | yield os.path.join(dirpath, f) 73 | 74 | 75 | # subprocess.CalledProcessError(...) has a different signature in Python 2.6 76 | class ProcessError(Exception): 77 | def __init__(self, args, status, stdout=None, stderr=None): 78 | self.args = args 79 | self.status = status 80 | self.stdout = (stdout or b'').decode('utf-8', 'ignore') 81 | self.stderr = (stderr or b'').decode('utf-8', 'ignore') 82 | 83 | def __str__(self): 84 | def format_output(o): 85 | return '' if not o else '\n\n[stdout]\n{}'.format(indent(o)) 86 | 87 | return ( 88 | 'The command {args!r} returned the non-zero exit code {status}.' 89 | '{stdout}{stderr}' 90 | ).format( 91 | args=self.args, 92 | status=self.status, 93 | stdout=format_output(self.stdout), 94 | stderr=format_output(self.stderr)) 95 | 96 | 97 | def run(args, display_stdout=True): 98 | p = subprocess.Popen( 99 | args, stdout=None if display_stdout else subprocess.PIPE) 100 | stdout, _ = p.communicate() 101 | if p.returncode != 0: 102 | raise ProcessError( 103 | args=args, 104 | status=p.returncode, 105 | stdout=stdout if display_stdout else None) 106 | 107 | 108 | # textwrap.indent() doesn't exist in Python 2 109 | def indent(text, prefix=' '): 110 | return prefix + prefix.join(text.splitlines(True)) 111 | 112 | 113 | if __name__ == '__main__': 114 | main(args=sys.argv[1:]) 115 | -------------------------------------------------------------------------------- /test/integration/tests.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | @test "ensure project created from template" { 4 | # Set all files and directories to a consistant, arbitrary date so that the 5 | # docker ADD command will invalidate the cache on checksum alone. 6 | find . -exec touch -t 200001010000.00 {} + 7 | 8 | git init 9 | git config user.name "Test User" 10 | git config user.email "test.email@localhost" 11 | 12 | cat >> .git/config <<"ORIGIN-AND-MASTER" 13 | [remote "origin"] 14 | url = git@github.com:test-github-user/test-github-project.git 15 | fetch = +refs/heads/*:refs/remotes/origin/* 16 | [branch "master"] 17 | remote = origin 18 | merge = refs/heads/master 19 | ORIGIN-AND-MASTER 20 | 21 | sed -e 's/^package_name=/&test-package/' \ 22 | -e 's/^package_version=/&1.0.0/' \ 23 | -e 's/^short_description=/&This is my test project!/' \ 24 | -i populate.ini 25 | 26 | git add . 27 | git commit -m "Pre-populated template." 28 | } 29 | 30 | @test "ensure populate.py runs successfully" { 31 | function debug { 32 | echo -e "\n[${1}]" 33 | find \( -type d -name ".git" -prune \) -o -ls 34 | } 35 | 36 | debug "Before" 37 | run ./populate.py 38 | echo "${output}" 39 | debug "After" 40 | 41 | [ "${status}" -eq 0 ] 42 | 43 | [ "${output}" == "$( 44 | echo "Using the following template values:" 45 | echo " package_name: 'test-package'" 46 | echo " install_requires: ()" 47 | echo " package_version: '1.0.0'" 48 | echo " author_name: 'Test User'" 49 | echo " tests_require: ()" 50 | echo " package_dir_name: 'test_package'" 51 | echo " author_email: 'test.email@localhost'" 52 | echo " github_user: 'test-github-user'" 53 | echo " copyright_years: '$(date +%Y)'" 54 | echo " short_description: 'This is my test project!'" 55 | echo " repo_name: 'test-github-project'")" ] 56 | 57 | git commit -m "Post-populated template." 58 | } 59 | 60 | @test "ensure .travis.yml looks valid" { 61 | travis lint 62 | } 63 | 64 | @test "ensure tox base image up-to-date" { 65 | docker pull themattrix/tox:latest 66 | } 67 | 68 | @test "ensure tox environments build correctly" { 69 | docker-compose rm -f --all 70 | run docker-compose build 71 | echo "${output}" 72 | 73 | [[ "${status}" -eq 0 ]] 74 | 75 | # No errors should appear in the output. 76 | ! grep -Ei "Error:" <<< "${output}" 77 | } 78 | 79 | @test "ensure pwd is visible to docker daemon" { 80 | # The docker daemon we're talking to is the same one in which we're 81 | # running. This allows us to create "sibling" containers, but it also 82 | # creates a problem for volumes. When we tell the daemon to mount the 83 | # working directory, it will mount an empty directory because it doesn't 84 | # have visibility into *this* container. 85 | # 86 | # Solution: We can employ a little trickery to find where the working 87 | # directory is actually stored on disk (as seen by the daemon). This 88 | # relies on the fact that the working directory happens to be a mountpoint 89 | # and that we can inspect mountpoints "docker inspect". 90 | 91 | find_pwd_on_disk() { 92 | docker inspect \ 93 | -f '{{ range .Mounts }}{{ if eq .Destination "'"${PWD}"'" }}{{ .Source }}{{ end }}{{ end }}' \ 94 | test_integration_tests_1 95 | } 96 | 97 | sed -e "s#.:/src:ro#$(find_pwd_on_disk):/src:ro#" \ 98 | -i "docker-compose.yml" 99 | 100 | # Expect the mount point to be updated. 101 | grep "/var/lib/docker" "docker-compose.yml" 102 | } 103 | 104 | @test "ensure tox environments run tests successfully" { 105 | run docker-compose up 106 | echo "${output}" 107 | 108 | # Expect one set of nosetests to be run per Python version. 109 | [[ "$(grep -F "Ran 0 tests" <<< "${output}" | wc -l)" -eq 8 ]] 110 | 111 | # Python 2.x and 3.x should run static analysis. 112 | [[ "$(grep ' python tests.py --static-analysis$' <<< "${output}" | wc -l)" -eq 2 ]] 113 | [[ "$(grep ' running check$' <<< "${output}" | wc -l)" -eq 2 ]] 114 | 115 | # Other runs should not run static analysis 116 | [[ "$(grep ' python tests.py$' <<< "${output}" | wc -l)" -eq 6 ]] 117 | 118 | # No errors should appear in the output. 119 | ! grep -Ei "Error:" <<< "${output}" 120 | } 121 | 122 | @test "ensure no files escaped tox container" { 123 | [ -z "$(git clean -xnd)" ] 124 | } 125 | -------------------------------------------------------------------------------- /populate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import fnmatch 4 | import os 5 | import re 6 | import sys 7 | import textwrap 8 | from datetime import date 9 | from string import capwords 10 | from subprocess import check_call, check_output 11 | from os.path import abspath, dirname, join 12 | 13 | try: 14 | from ConfigParser import SafeConfigParser as ConfigParser 15 | except ImportError: 16 | try: 17 | from configparser import SafeConfigParser as ConfigParser 18 | except ImportError: 19 | from configparser import ConfigParser 20 | assert ConfigParser # silence pyflakes 21 | 22 | 23 | this_dir = dirname(abspath(__file__)) 24 | template_dir = join(this_dir, 'template') 25 | populate_ini = join(this_dir, 'populate.ini') 26 | 27 | 28 | if sys.version_info < (3, 0): 29 | def items(x): 30 | return x.iteritems() 31 | else: 32 | def items(x): 33 | return x.items() 34 | 35 | 36 | def find_templated_files(): 37 | for root, _, filenames in os.walk(template_dir): # pylint: disable=W0612 38 | for f in fnmatch.filter(filenames, '*.template'): 39 | yield join(root, f) 40 | 41 | 42 | def find_templated_directories(): 43 | for root, dirnames, _ in os.walk(template_dir): # pylint: disable=W0612 44 | for d in fnmatch.filter(dirnames, '{{ * }}'): 45 | yield join(root, d) 46 | 47 | 48 | def git(*args): 49 | return check_output(('git',) + tuple(args)).strip() 50 | 51 | 52 | def read_requirements(requirements_file_basename): 53 | with open(join(template_dir, requirements_file_basename)) as f: 54 | content = f.read() 55 | 56 | for line in content.splitlines(): 57 | line = re.sub('#.*', '', line).strip() 58 | if not line: 59 | continue 60 | # Ignore included requirements files. 61 | if line.startswith('-r'): 62 | continue 63 | yield line 64 | 65 | 66 | def get_author_email(): 67 | return git('config', 'user.email') 68 | 69 | 70 | def get_author_name(): 71 | return git('config', 'user.name') 72 | 73 | 74 | def get_copyright_years(): 75 | return str(date.today().year) 76 | 77 | 78 | def get_github_info(): 79 | result = re.search( 80 | 'github.com[:/](?P[^/]+)/(?P.+)[.]git', 81 | git('config', '--get', 'remote.origin.url')) 82 | 83 | if not result: 84 | raise RuntimeError( 85 | 'Failed to find a GitHub user and/or repository name in the ' 86 | 'output of "git config --get remote.origin.url".') 87 | 88 | return result.group('user'), result.group('repo') 89 | 90 | 91 | def get_install_requires(): 92 | return tuple(read_requirements('requirements.txt')) 93 | 94 | 95 | def get_tests_require(): 96 | return tuple(read_requirements('requirements_test.txt')) 97 | 98 | 99 | def get_populate_ini_settings(): 100 | config = ConfigParser() 101 | config.readfp(open(populate_ini)) 102 | values = dict( 103 | package_name=config.get('global', 'package_name'), 104 | package_version=config.get('global', 'package_version'), 105 | short_description=config.get('global', 'short_description')) 106 | 107 | empty_values = [k for k, v in items(values) if not v] 108 | 109 | if empty_values: 110 | raise RuntimeError( 111 | 'Please specify values in "populate.ini" for the following: ' 112 | '{empty}'.format(empty=empty_values)) 113 | 114 | return values 115 | 116 | 117 | def get_template_values(): 118 | user, repo = get_github_info() 119 | 120 | values = dict( 121 | author_email=get_author_email(), 122 | author_name=get_author_name(), 123 | copyright_years=get_copyright_years(), 124 | github_user=user, 125 | repo_name=repo, 126 | install_requires=get_install_requires(), 127 | tests_require=get_tests_require()) 128 | 129 | values.update(get_populate_ini_settings()) 130 | 131 | # The actual package directory should not have dashes in it, but dashes are 132 | # pretty common for package names. 133 | values['package_dir_name'] = values['package_name'].replace('-', '_') 134 | 135 | print('Using the following template values:\n {values}'.format( 136 | values='\n '.join( 137 | '{k}: {v!r}'.format(k=k, v=v) 138 | for k, v in items(values)))) 139 | 140 | return values 141 | 142 | 143 | def replace_multiline(text, key, value, filter_name, replacement_fn): 144 | token = '{{{{ {k}|{f} }}}}'.format(k=key, f=filter_name) 145 | regex = re.compile('(?P[ \t]*){token}'.format( 146 | token=re.escape(token))) 147 | 148 | while True: 149 | match = regex.search(text) 150 | 151 | if not match: 152 | break 153 | 154 | indent = match.group('indent') 155 | replacement = replacement_fn(indent=indent, value=value) 156 | text = regex.sub(replacement, text, count=1) 157 | 158 | return text 159 | 160 | 161 | def replace_pystrings(text, key, value): 162 | return replace_multiline( 163 | text=text, key=key, value=value, 164 | filter_name='pystring', 165 | replacement_fn=lambda indent, value: indent + ('\n' + indent).join( 166 | repr(line) for line in textwrap.wrap( 167 | value, 168 | drop_whitespace=False, 169 | width=70 - len(indent)))) 170 | 171 | 172 | def replace_pytuples(text, key, value): 173 | text = replace_multiline( 174 | text=text, key=key, value=value, 175 | filter_name='pytuple', 176 | replacement_fn=lambda indent, value: '\n'.join( 177 | indent + repr(v) + ',' for v in value)) 178 | 179 | # squash empty tuples 180 | text = re.sub('[(]\s+[)]', '()', text) 181 | 182 | return text 183 | 184 | 185 | def replace_raw(text, key, value): 186 | return text.replace('{{{{ {k} }}}}'.format(k=key), value) 187 | 188 | 189 | def replace_capitalize(text, key, value): 190 | return text.replace( 191 | '{{{{ {k}|capitalize }}}}'.format(k=key), capwords(value)) 192 | 193 | 194 | def do_replacements(text, key, value, fns): 195 | for fn in fns: 196 | text = fn(text=text, key=key, value=value) 197 | return text 198 | 199 | 200 | def populate_files(template_values): 201 | for template_path in find_templated_files(): 202 | with open(template_path) as f: 203 | content = f.read() 204 | 205 | for k, v in items(template_values): 206 | if isinstance(v, basestring): 207 | fns = replace_raw, replace_capitalize, replace_pystrings 208 | else: 209 | fns = replace_pytuples, 210 | 211 | content = do_replacements(text=content, key=k, value=v, fns=fns) 212 | 213 | with open(template_path, 'wb') as f: 214 | f.write(content) 215 | 216 | populated_path = re.sub('[.]template$', '', template_path) 217 | git('mv', '-f', template_path, populated_path) 218 | 219 | 220 | def populate_directories(template_values): 221 | for template_dir in find_templated_directories(): 222 | 223 | for k, v in items(template_values): 224 | if not isinstance(v, basestring): 225 | continue 226 | 227 | renamed_dir = template_dir.replace('{{{{ {k} }}}}'.format(k=k), v) 228 | 229 | if renamed_dir != template_dir: 230 | git('mv', template_dir, renamed_dir) 231 | 232 | 233 | def main(): 234 | try: 235 | template_values = get_template_values() 236 | populate_files(template_values) 237 | populate_directories(template_values) 238 | 239 | # No longer need the template setup files! 240 | git('rm', '-f', populate_ini) 241 | git('rm', abspath(__file__)) 242 | 243 | # Move everything in template/ to the root of the project. 244 | for filename in os.listdir(template_dir): 245 | git('mv', '-f', join(template_dir, filename), this_dir) 246 | 247 | # The template dir is unneeded now and should be empty. 248 | check_call(('rmdir', template_dir)) 249 | 250 | # Stage the rest of the updated files. 251 | git('add', '-u') 252 | 253 | except RuntimeError as e: 254 | print('[ERROR] {e}'.format(e=e)) 255 | sys.exit(1) 256 | 257 | 258 | if __name__ == '__main__': 259 | main() 260 | -------------------------------------------------------------------------------- /template/.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Add files or directories to the blacklist. They should be base names, not 11 | # paths. 12 | ignore=CVS 13 | 14 | # Pickle collected data for later comparisons. 15 | persistent=yes 16 | 17 | # List of plugins (as comma separated values of python modules names) to load, 18 | # usually to register additional checkers. 19 | load-plugins= 20 | 21 | # Use multiple processes to speed up Pylint. 22 | jobs=1 23 | 24 | # Allow loading of arbitrary C extensions. Extensions are imported into the 25 | # active Python interpreter and may run arbitrary code. 26 | unsafe-load-any-extension=no 27 | 28 | # A comma-separated list of package or module names from where C extensions may 29 | # be loaded. Extensions are loading into the active Python interpreter and may 30 | # run arbitrary code 31 | extension-pkg-whitelist= 32 | 33 | # Allow optimization of some AST trees. This will activate a peephole AST 34 | # optimizer, which will apply various small optimizations. For instance, it can 35 | # be used to obtain the result of joining multiple strings with the addition 36 | # operator. Joining a lot of strings can lead to a maximum recursion error in 37 | # Pylint and this flag can prevent that. It has one side effect, the resulting 38 | # AST will be different than the one from reality. 39 | optimize-ast=no 40 | 41 | 42 | [MESSAGES CONTROL] 43 | 44 | # Only show warnings with the listed confidence levels. Leave empty to show 45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 46 | confidence= 47 | 48 | # Enable the message, report, category or checker with the given id(s). You can 49 | # either give multiple identifier separated by comma (,) or put this option 50 | # multiple time. See also the "--disable" option for examples. 51 | #enable= 52 | 53 | # Disable the message, report, category or checker with the given id(s). You 54 | # can either give multiple identifiers separated by comma (,) or put this 55 | # option multiple times (only on the command line, not in the configuration 56 | # file where it should appear only once).You can also use "--disable=all" to 57 | # disable everything first and then reenable specific checks. For example, if 58 | # you want to run only the similarities checker, you can use "--disable=all 59 | # --enable=similarities". If you want to run only the classes checker, but have 60 | # no Warning level messages displayed, use"--disable=all --enable=classes 61 | # --disable=W" 62 | disable=suppressed-message,dict-iter-method,getslice-method,range-builtin-not-iterating,useless-suppression,input-builtin,indexing-exception,reload-builtin,import-star-module-level,nonzero-method,print-statement,cmp-builtin,oct-method,metaclass-assignment,xrange-builtin,long-builtin,old-octal-literal,coerce-method,raising-string,basestring-builtin,old-division,old-ne-operator,round-builtin,old-raise-syntax,coerce-builtin,execfile-builtin,dict-view-method,raw_input-builtin,unichr-builtin,no-absolute-import,using-cmp-argument,hex-method,unicode-builtin,next-method-called,delslice-method,unpacking-in-except,standarderror-builtin,cmp-method,intern-builtin,backtick,reduce-builtin,map-builtin-not-iterating,apply-builtin,buffer-builtin,file-builtin,zip-builtin-not-iterating,filter-builtin-not-iterating,long-suffix,parameter-unpacking,setslice-method 63 | 64 | 65 | [REPORTS] 66 | 67 | # Set the output format. Available formats are text, parseable, colorized, msvs 68 | # (visual studio) and html. You can also give a reporter class, eg 69 | # mypackage.mymodule.MyReporterClass. 70 | output-format=text 71 | 72 | # Put messages in a separate file for each module / package specified on the 73 | # command line instead of printing them on stdout. Reports (if any) will be 74 | # written in a file name "pylint_global.[txt|html]". 75 | files-output=no 76 | 77 | # Tells whether to display a full report or only the messages 78 | reports=yes 79 | 80 | # Python expression which should return a note less than 10 (10 is the highest 81 | # note). You have access to the variables errors warning, statement which 82 | # respectively contain the number of errors / warnings messages and the total 83 | # number of statements analyzed. This is used by the global evaluation report 84 | # (RP0004). 85 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 86 | 87 | # Template used to display messages. This is a python new-style format string 88 | # used to format the message information. See doc for all details 89 | #msg-template= 90 | 91 | 92 | [SPELLING] 93 | 94 | # Spelling dictionary name. Available dictionaries: none. To make it working 95 | # install python-enchant package. 96 | spelling-dict= 97 | 98 | # List of comma separated words that should not be checked. 99 | spelling-ignore-words= 100 | 101 | # A path to a file that contains private dictionary; one word per line. 102 | spelling-private-dict-file= 103 | 104 | # Tells whether to store unknown words to indicated private dictionary in 105 | # --spelling-private-dict-file option instead of raising a message. 106 | spelling-store-unknown-words=no 107 | 108 | 109 | [FORMAT] 110 | 111 | # Maximum number of characters on a single line. 112 | max-line-length=79 113 | 114 | # Regexp for a line that is allowed to be longer than the limit. 115 | ignore-long-lines=^\s*(# )??$ 116 | 117 | # Allow the body of an if to be on the same line as the test if there is no 118 | # else. 119 | single-line-if-stmt=no 120 | 121 | # List of optional constructs for which whitespace checking is disabled. `dict- 122 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 123 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 124 | # `empty-line` allows space-only lines. 125 | no-space-check=trailing-comma,dict-separator 126 | 127 | # Maximum number of lines in a module 128 | max-module-lines=1000 129 | 130 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 131 | # tab). 132 | indent-string=' ' 133 | 134 | # Number of spaces of indent required inside a hanging or continued line. 135 | indent-after-paren=4 136 | 137 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 138 | expected-line-ending-format= 139 | 140 | 141 | [MISCELLANEOUS] 142 | 143 | # List of note tags to take in consideration, separated by a comma. 144 | notes=FIXME,XXX,TODO 145 | 146 | 147 | [TYPECHECK] 148 | 149 | # Tells whether missing members accessed in mixin class should be ignored. A 150 | # mixin class is detected if its name ends with "mixin" (case insensitive). 151 | ignore-mixin-members=yes 152 | 153 | # List of module names for which member attributes should not be checked 154 | # (useful for modules/projects where namespaces are manipulated during runtime 155 | # and thus existing member attributes cannot be deduced by static analysis. It 156 | # supports qualified module names, as well as Unix pattern matching. 157 | ignored-modules= 158 | 159 | # List of classes names for which member attributes should not be checked 160 | # (useful for classes with attributes dynamically set). This supports can work 161 | # with qualified names. 162 | ignored-classes= 163 | 164 | # List of members which are set dynamically and missed by pylint inference 165 | # system, and so shouldn't trigger E1101 when accessed. Python regular 166 | # expressions are accepted. 167 | generated-members= 168 | 169 | 170 | [BASIC] 171 | 172 | # List of builtins function names that should not be used, separated by a comma 173 | bad-functions=map,filter 174 | 175 | # Good variable names which should always be accepted, separated by a comma 176 | good-names=i,j,k,ex,Run,_ 177 | 178 | # Bad variable names which should always be refused, separated by a comma 179 | bad-names=foo,bar,baz,toto,tutu,tata 180 | 181 | # Colon-delimited sets of names that determine each other's naming style when 182 | # the name regexes allow several styles. 183 | name-group= 184 | 185 | # Include a hint for the correct naming format with invalid-name 186 | include-naming-hint=no 187 | 188 | # Regular expression matching correct method names 189 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 190 | 191 | # Naming hint for method names 192 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 193 | 194 | # Regular expression matching correct constant names 195 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 196 | 197 | # Naming hint for constant names 198 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 199 | 200 | # Regular expression matching correct argument names 201 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 202 | 203 | # Naming hint for argument names 204 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 205 | 206 | # Regular expression matching correct function names 207 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 208 | 209 | # Naming hint for function names 210 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 211 | 212 | # Regular expression matching correct variable names 213 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 214 | 215 | # Naming hint for variable names 216 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 217 | 218 | # Regular expression matching correct class names 219 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 220 | 221 | # Naming hint for class names 222 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 223 | 224 | # Regular expression matching correct attribute names 225 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 226 | 227 | # Naming hint for attribute names 228 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 229 | 230 | # Regular expression matching correct inline iteration names 231 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 232 | 233 | # Naming hint for inline iteration names 234 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 235 | 236 | # Regular expression matching correct class attribute names 237 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 238 | 239 | # Naming hint for class attribute names 240 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 241 | 242 | # Regular expression matching correct module names 243 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 244 | 245 | # Naming hint for module names 246 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 247 | 248 | # Regular expression which should only match function or class names that do 249 | # not require a docstring. 250 | no-docstring-rgx=^_ 251 | 252 | # Minimum line length for functions/classes that require docstrings, shorter 253 | # ones are exempt. 254 | docstring-min-length=-1 255 | 256 | 257 | [ELIF] 258 | 259 | # Maximum number of nested blocks for function / method body 260 | max-nested-blocks=5 261 | 262 | 263 | [SIMILARITIES] 264 | 265 | # Minimum lines number of a similarity. 266 | min-similarity-lines=4 267 | 268 | # Ignore comments when computing similarities. 269 | ignore-comments=yes 270 | 271 | # Ignore docstrings when computing similarities. 272 | ignore-docstrings=yes 273 | 274 | # Ignore imports when computing similarities. 275 | ignore-imports=no 276 | 277 | 278 | [VARIABLES] 279 | 280 | # Tells whether we should check for unused import in __init__ files. 281 | init-import=no 282 | 283 | # A regular expression matching the name of dummy variables (i.e. expectedly 284 | # not used). 285 | dummy-variables-rgx=_$|dummy 286 | 287 | # List of additional names supposed to be defined in builtins. Remember that 288 | # you should avoid to define new builtins when possible. 289 | additional-builtins= 290 | 291 | # List of strings which can identify a callback function by name. A callback 292 | # name must start or end with one of those strings. 293 | callbacks=cb_,_cb 294 | 295 | 296 | [LOGGING] 297 | 298 | # Logging modules to check that the string format arguments are in logging 299 | # function parameter format 300 | logging-modules=logging 301 | 302 | 303 | [IMPORTS] 304 | 305 | # Deprecated modules which should not be used, separated by a comma 306 | deprecated-modules=optparse 307 | 308 | # Create a graph of every (i.e. internal and external) dependencies in the 309 | # given file (report RP0402 must not be disabled) 310 | import-graph= 311 | 312 | # Create a graph of external dependencies in the given file (report RP0402 must 313 | # not be disabled) 314 | ext-import-graph= 315 | 316 | # Create a graph of internal dependencies in the given file (report RP0402 must 317 | # not be disabled) 318 | int-import-graph= 319 | 320 | 321 | [CLASSES] 322 | 323 | # List of method names used to declare (i.e. assign) instance attributes. 324 | defining-attr-methods=__init__,__new__,setUp 325 | 326 | # List of valid names for the first argument in a class method. 327 | valid-classmethod-first-arg=cls 328 | 329 | # List of valid names for the first argument in a metaclass class method. 330 | valid-metaclass-classmethod-first-arg=mcs 331 | 332 | # List of member names, which should be excluded from the protected access 333 | # warning. 334 | exclude-protected=_asdict,_fields,_replace,_source,_make 335 | 336 | 337 | [DESIGN] 338 | 339 | # Maximum number of arguments for function / method 340 | max-args=5 341 | 342 | # Argument names that match this expression will be ignored. Default to name 343 | # with leading underscore 344 | ignored-argument-names=_.* 345 | 346 | # Maximum number of locals for function / method body 347 | max-locals=15 348 | 349 | # Maximum number of return / yield for function / method body 350 | max-returns=6 351 | 352 | # Maximum number of branch for function / method body 353 | max-branches=12 354 | 355 | # Maximum number of statements in function / method body 356 | max-statements=50 357 | 358 | # Maximum number of parents for a class (see R0901). 359 | max-parents=7 360 | 361 | # Maximum number of attributes for a class (see R0902). 362 | max-attributes=7 363 | 364 | # Minimum number of public methods for a class (see R0903). 365 | min-public-methods=2 366 | 367 | # Maximum number of public methods for a class (see R0904). 368 | max-public-methods=20 369 | 370 | # Maximum number of boolean expressions in a if statement 371 | max-bool-expr=5 372 | 373 | 374 | [EXCEPTIONS] 375 | 376 | # Exceptions that will emit a warning when being caught. Defaults to 377 | # "Exception" 378 | overgeneral-exceptions=Exception 379 | --------------------------------------------------------------------------------