├── test ├── __init__.py └── unit │ ├── __init__.py │ ├── conftest.py │ └── test_my_module.py ├── project_name ├── py.typed ├── _version.py ├── __init__.py └── my_module.py ├── .flake8 ├── CHANGELOG.md ├── .coveragerc ├── .gitignore ├── .github └── workflows │ ├── release.yml │ └── build.yml ├── LICENSE ├── README_project.md ├── pyproject.toml ├── justfile └── README.md /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project_name/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project_name/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | extend-ignore = E203 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v0.1.0 (2024-10-10) 4 | 5 | - Details go here 6 | -------------------------------------------------------------------------------- /test/unit/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def mock_function(): 6 | pass 7 | -------------------------------------------------------------------------------- /test/unit/test_my_module.py: -------------------------------------------------------------------------------- 1 | import project_name 2 | 3 | 4 | def test_main(): 5 | project_name.main() 6 | -------------------------------------------------------------------------------- /project_name/__init__.py: -------------------------------------------------------------------------------- 1 | from project_name.my_module import main 2 | 3 | 4 | __all__ = [ 5 | 'main', 6 | ] 7 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | if __name__ == '__main__': 4 | [run] 5 | omit = project_name/_version.py 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .coverage 3 | .DS_Store 4 | .env 5 | *.egg-info 6 | *.lcov 7 | build 8 | dist 9 | htmlcov 10 | venv 11 | -------------------------------------------------------------------------------- /project_name/my_module.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | """The main entrypoint for this script used in the setup.py file.""" 3 | pass 4 | 5 | 6 | if __name__ == '__main__': 7 | main() 8 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # name: release 2 | 3 | # on: 4 | # release: 5 | # types: [published] 6 | # workflow_dispatch: ~ 7 | 8 | # jobs: 9 | # release: 10 | # runs-on: ubuntu-latest 11 | # steps: 12 | # - uses: actions/checkout@v5 13 | # - uses: extractions/setup-just@v3 14 | # - uses: actions/setup-python@v6 15 | # with: 16 | # python-version: '3.14' 17 | # - name: Build package 18 | # run: just install build 19 | # - name: Publish to PyPI 20 | # uses: pypa/gh-action-pypi-publish@release/v1 21 | # with: 22 | # password: ${{ secrets.PYPI_API_TOKEN }} 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Justin Hammond 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 | -------------------------------------------------------------------------------- /README_project.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # PROJECT_NAME_URL 4 | 5 | A one-liner description of your project goes here. 6 | 7 | [![Build Status](https://github.com/USERNAME/PROJECT_NAME_URL/workflows/build/badge.svg)](https://github.com/USERNAME/PROJECT_NAME_URL/actions) 8 | [![Coverage Status](https://img.shields.io/codecov/c/github/USERNAME/PROJECT_NAME_URL)](https://app.codecov.io/github/USERNAME/PROJECT_NAME_URL) 9 | [![PyPi](https://img.shields.io/pypi/v/PROJECT_NAME_URL)](https://pypi.org/project/PROJECT_NAME_URL) 10 | [![Licence](https://img.shields.io/github/license/USERNAME/PROJECT_NAME_URL)](LICENSE) 11 | 12 | Showcase 13 | 14 |
15 | 16 | A longer paragraph description of your project goes here. 17 | 18 | ## Install 19 | 20 | ```bash 21 | # Install tool 22 | pip3 install project_name 23 | 24 | # Install locally 25 | just install 26 | ``` 27 | 28 | ## Usage 29 | 30 | Usage instructions go here. 31 | 32 | ```bash 33 | venv/bin/python my_script.py 34 | ``` 35 | 36 | ## Development 37 | 38 | ```bash 39 | # Get a comprehensive list of development tools 40 | just --list 41 | ``` 42 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "PROJECT_NAME_URL" 7 | description = "Your project description here" 8 | dynamic = ["version"] 9 | readme = "README.md" 10 | requires-python = ">=3.10,<4" 11 | license = { text = "MIT" } 12 | authors = [{ name = "USERNAME" }] 13 | urls = { Homepage = "http://github.com/USERNAME/PROJECT_NAME_URL" } 14 | scripts = { PROJECT_NAME_URL = "project_name.my_module:main" } 15 | dependencies = [ 16 | # Add your list of production dependencies here, eg: 17 | # "requests == 2.*", 18 | ] 19 | optional-dependencies = { dev = [ 20 | "bandit == 1.9.*", 21 | "black == 25.*", 22 | "build == 1.3.*", 23 | "flake8 == 7.*", 24 | "isort == 7.*", 25 | "mypy == 1.18.*", 26 | "pytest == 9.*", 27 | "pytest-cov == 7.*", 28 | ] } 29 | 30 | [tool.setuptools.dynamic] 31 | version = { attr = "project_name._version.__version__" } 32 | 33 | [tool.setuptools.packages.find] 34 | exclude = ["examples", "test"] 35 | 36 | [tool.setuptools.package-data] 37 | project_name = ["py.typed"] 38 | 39 | [tool.black] 40 | line-length = 120 41 | 42 | [tool.isort] 43 | profile = "black" 44 | line_length = 120 45 | indent = 4 46 | force_grid_wrap = 2 47 | multi_line_output = 3 48 | sections = "FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER" 49 | lines_after_imports = 2 50 | include_trailing_comma = true 51 | use_parentheses = true 52 | 53 | [tool.mypy] 54 | disable_error_code = "import-untyped" 55 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # name: build 2 | 3 | # on: 4 | # push: 5 | # paths: 6 | # - '.github/workflows/build.yml' 7 | # - '**/*.py' 8 | # branches: 9 | # - '**' 10 | # tags: 11 | # - '!**' 12 | # pull_request: 13 | # paths: 14 | # - '.github/workflows/build.yml' 15 | # - '**/*.py' 16 | # workflow_dispatch: ~ 17 | 18 | # jobs: 19 | # lint: 20 | # runs-on: ubuntu-latest 21 | # steps: 22 | # - uses: actions/checkout@v5 23 | # - uses: extractions/setup-just@v3 24 | # - uses: actions/setup-python@v6 25 | # with: 26 | # python-version: '3.14' 27 | # - run: just install lint 28 | # test: 29 | # runs-on: ubuntu-latest 30 | # strategy: 31 | # matrix: 32 | # pythonversion: ['3.10', '3.11', '3.12', '3.13', '3.14'] 33 | # steps: 34 | # - uses: actions/checkout@v5 35 | # - uses: extractions/setup-just@v3 36 | # - uses: actions/setup-python@v6 37 | # with: 38 | # python-version: ${{ matrix.pythonversion }} 39 | # - run: just install coverage 40 | # coverage: 41 | # if: github.ref == 'refs/heads/main' 42 | # runs-on: ubuntu-latest 43 | # steps: 44 | # - uses: actions/checkout@v5 45 | # - uses: extractions/setup-just@v3 46 | # - uses: actions/setup-python@v6 47 | # with: 48 | # python-version: '3.14' 49 | # - run: just install coverage 50 | # - uses: codecov/codecov-action@v5 51 | # with: 52 | # token: ${{ secrets.CODECOV_TOKEN }} 53 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | PYTHON_BINARY := "python3" 2 | VIRTUAL_ENV := "venv" 3 | VIRTUAL_BIN := VIRTUAL_ENV / "bin" 4 | PROJECT_NAME := "project_name" 5 | TEST_DIR := "test" 6 | 7 | # Scans the project for security vulnerabilities 8 | bandit: 9 | {{VIRTUAL_BIN}}/bandit -r {{PROJECT_NAME}}/ 10 | 11 | # Builds the project in preparation for release 12 | build: 13 | {{VIRTUAL_BIN}}/python -m build 14 | 15 | # Runs the Black Python formatter against the project 16 | black: 17 | {{VIRTUAL_BIN}}/black {{PROJECT_NAME}}/ {{TEST_DIR}}/ 18 | 19 | # Checks if the project is formatted correctly against the Black rules 20 | black-check: 21 | {{VIRTUAL_BIN}}/black {{PROJECT_NAME}}/ {{TEST_DIR}}/ --check 22 | 23 | # Test the project and generate an HTML coverage report 24 | coverage: 25 | {{VIRTUAL_BIN}}/pytest --cov={{PROJECT_NAME}} --cov-branch --cov-report=html --cov-report=lcov --cov-report=term-missing --cov-fail-under=90 26 | 27 | # Cleans the project 28 | clean: 29 | rm -rf {{VIRTUAL_ENV}} dist *.egg-info .coverage htmlcov .*cache 30 | find . -name '*.pyc' -delete 31 | 32 | # Run flake8 checks against the project 33 | flake8: 34 | {{VIRTUAL_BIN}}/flake8 {{PROJECT_NAME}}/ {{TEST_DIR}}/ 35 | 36 | # Lints the project 37 | lint: black-check isort-check flake8 mypy bandit 38 | 39 | # Runs all formatting tools against the project 40 | lint-fix: black isort 41 | 42 | # Install the project locally 43 | install: 44 | {{PYTHON_BINARY}} -m venv {{VIRTUAL_ENV}} 45 | {{VIRTUAL_BIN}}/pip install -e ."[dev]" 46 | 47 | # Sorts imports throughout the project 48 | isort: 49 | {{VIRTUAL_BIN}}/isort {{PROJECT_NAME}}/ {{TEST_DIR}}/ 50 | 51 | # Checks that imports throughout the project are sorted correctly 52 | isort-check: 53 | {{VIRTUAL_BIN}}/isort {{PROJECT_NAME}}/ {{TEST_DIR}}/ --check-only 54 | 55 | # Run mypy type checking on the project 56 | mypy: 57 | {{VIRTUAL_BIN}}/mypy --install-types --non-interactive {{PROJECT_NAME}}/ {{TEST_DIR}}/ 58 | 59 | # Test the project 60 | test: 61 | {{VIRTUAL_BIN}}/pytest 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Template 2 | 3 | A Python project template to save you time and energy. 4 | 5 | [![Licence](https://img.shields.io/github/license/justintime50/python-template)](LICENSE) 6 | 7 | Python projects take a long time to setup with all the various files, the virtual environment, and keeping things uniform across projects. With this Python template, you can quickly setup boilerplate code and miscellaneous items for your Python project saving you time and energy so you can get back to coding. 8 | 9 | ## Install 10 | 11 | Click the [Use this template](https://github.com/Justintime50/python-template/generate) button at the top of this project's GitHub page to get started. 12 | 13 | ## Usage 14 | 15 | ### Easy text replacements 16 | 17 | 1. Replace all instances of `project_name` with the name of your project 18 | * These are the Python snake_case references (eg: `project_name`) 19 | 1. Replace all instances of `PROJECT_NAME_URL` with the name of your project 20 | * These are the references to your project that will appear in URLs and are typically hyphenated (eg: `project-name`) 21 | 1. Replace all instances of `USERNAME` with the name of the author or owner of the project 22 | * These are references typically found in the URL of your project as it appears on GitHub 23 | 24 | ### File configuration 25 | 26 | 1. Configure the `setup.py` file 27 | 1. Configure the `justfile` targets 28 | 1. Update the name in the `LICENSE` or swap it out entirely 29 | 1. Configure the `.github/workflows/build.yml` file 30 | 1. Update the `CHANGELOG.md` with your own info 31 | 1. Rename other files/folders as needed and configure their content 32 | 1. Delete this `README` and rename `README_project.md` to `README.md` 33 | 34 | ### GitHub configuration 35 | 36 | 1. Add a `PYPI_API_TOKEN` GitHub secret to your project so that automated releasing can occur from GitHub Actions to PyPI and uncomment the final step on the `release` job in `.github/workflows/release.yml` 37 | 38 | ## Attribution 39 | 40 | * Watch [the video](https://youtu.be/ZMfcl3CnRhA) where I built this template. 41 | --------------------------------------------------------------------------------