diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f84a9d..77be1f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v3 @@ -29,7 +29,7 @@ jobs: with: path: ~/.cache/pip # This path is specific to Ubuntu # Check for a cache hit for the corresponding dev requirements file - key: ${{ runner.os }}-pip-${{ hashFiles('requirements.dev.txt') }}-${{ hashFiles('requirements.txt') }} + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- @@ -39,8 +39,8 @@ jobs: - name: Check example project run: | cd example - pip install -r requirements.dev.txt - pip install . + make venv + . venv/bin/activate make help make style make check-style diff --git a/README.rst b/README.rst index 94fb6ca..28574a6 100644 --- a/README.rst +++ b/README.rst @@ -34,8 +34,7 @@ the following items: developer documentation. - An empty ``CHANGELOG.rst`` file. This file gets included in the user documentation. -- A ``LICENSE`` file (or ``COPYING`` for GNU licenses) that defaults - to the Apache License version 2.0. +- An option ``LICENSE`` file (or ``COPYING`` for GNU licenses). - An ``examples`` directory with a minimal quickstart example script. This script imports the package and prints the package version. It is also called by the unit test suite to ensure it always works. @@ -77,9 +76,9 @@ cookiecutter using ``pip``. The example below shows how to do this. .. code-block:: console - $ python -m venv ccvenv --prompt cc + $ python -m venv --prompt cc ccvenv $ source ccvenv/bin/activate - (cc) $ pip install pip -U # update pip to avoid any warnings + (cc) $ pip install -U pip # update pip to avoid any warnings (cc) $ pip install cookiecutter You are now ready to create a new Python project from the Cookiecutter @@ -132,11 +131,12 @@ using the new project. ReadTheDocs then remove any links to those sites. Affected files are: - README.rst - - setup.py - docs/source/index.rst + - pyproject.toml -- Update any additional useful classifiers in ``setup.py``. The list of - available classifiers can be found `here `_. +- Update any additional useful classifiers in ``pyproject.toml``. The + list of available classifiers can be found `here + `_. Example @@ -162,24 +162,26 @@ Python package name. .. code-block:: console (cc) $ cookiecutter ../cookiecutter-python-project/ - package_display_name [Package-Name]: abc 123 - package_name [abc_123]: - package_short_description [A description of the package]: This is my abc 123 package. - version [0.0.1]: - full_name [Your Name]: First Last - email []: - github_user_name [GithubUserName]: flast - github_repo_name [abc_123]: - Select license: - 1 - Apache License, Version 2.0 - 2 - Expat License - 3 - GNU GPL version 2 - 4 - GNU GPL version 3 - 5 - GNU AGPL version 3 - 6 - Modified BSD license (3-clause) - 7 - Not licensed for distribution (no license) - Choose from 1, 2, 3, 4, 5, 6, 7 [1]: - year [2023]: + [1/10] package_display_name (Package-Name): abc 123 + [2/10] package_name (abc_123): + [3/10] package_short_description (A description of the package): This is my abc 123 package. + [4/10] version (0.0.1): + [5/10] full_name (Your Name): First Last + [6/10] email (): + [7/10] github_user_name (GithubUserName): flast + [8/10] github_repo_name (abc_123): + [9/10] Select license + 1 - Not licensed for distribution (no license) + 2 - AGPL-3.0-only + 3 - AGPL-3.0-or-later + 4 - Apache-2.0 + 5 - BSD-3-Clause + 6 - GPL-2.0-only + 7 - GPL-2.0-or-later + 8 - GPL-3.0-only + 9 - GPL-3.0-or-later + Choose from [1/2/3/4/5/6/7/8/9] (1): + [10/10] year (2024): The project has been created in the ``abc_123`` directory. diff --git a/cookiecutter.json b/cookiecutter.json index 9a8d332..82e2b94 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -8,13 +8,15 @@ "github_user_name": "GithubUserName", "github_repo_name": "{{cookiecutter.package_name}}", "license": [ - "Apache License, Version 2.0", - "Expat License", - "GNU GPL version 2", - "GNU GPL version 3", - "GNU AGPL version 3", - "Modified BSD license (3-clause)", - "Not licensed for distribution (no license)" + "Not licensed for distribution (no license)", + "AGPL-3.0-only", + "AGPL-3.0-or-later", + "Apache-2.0", + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-2.0-or-later", + "GPL-3.0-only", + "GPL-3.0-or-later" ], "year": "{% now 'utc', '%Y' %}" } diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index e83167b..c28a194 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -13,20 +13,26 @@ LICENSE_SELECTED = "{{ cookiecutter.license }}" -def deploy_license(): +def deploy_license() -> None: """Move the selected license file into the project root Licenses not selected for the project are removed. """ + sources: set[Path] = set() + selected_source = None with LICENSE_CONFIG.open(mode="r", encoding="utf-8") as config_file: config = json.load(config_file) for license_option, license_data in config.items(): + source_path = LICENSE_DIR.joinpath(license_data["source"]) if license_option == LICENSE_SELECTED: - LICENSE_DIR.joinpath(license_data["source"]).rename( - PROJECT_DIR.joinpath(license_data["destination"]) - ) - else: - LICENSE_DIR.joinpath(license_data["source"]).unlink() + selected_source = source_path + destination_path = PROJECT_DIR.joinpath(license_data["destination"]) + source_path.rename(destination_path) + sources.discard(source_path) + elif license_data["source"] != selected_source: + sources.add(source_path) + for source in sources: + source.unlink() LICENSE_CONFIG.unlink() LICENSE_DIR.rmdir() diff --git a/{{cookiecutter.package_name}}/.github/workflows/ci.yml b/{{cookiecutter.package_name}}/.github/workflows/ci.yml index 4ec428d..f9f3315 100644 --- a/{{cookiecutter.package_name}}/.github/workflows/ci.yml +++ b/{{cookiecutter.package_name}}/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v3 @@ -23,15 +23,14 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r requirements.dev.txt - pip install -r requirements.txt + python -m pip install -U pip + python -m pip install .[dev] - name: Cache pip dependencies uses: actions/cache@v3 with: path: ~/.cache/pip # This path is specific to Ubuntu - # Check for a cache hit for the corresponding dev requirements file - key: ${{ runner.os }}-pip-${{ hashFiles('requirements.dev.txt') }}-${{ hashFiles('requirements.txt') }} + # Check for a cache hit for the corresponding pyproject.toml file + key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- diff --git a/{{cookiecutter.package_name}}/Makefile b/{{cookiecutter.package_name}}/Makefile index b8c95e9..c44afa1 100644 --- a/{{cookiecutter.package_name}}/Makefile +++ b/{{cookiecutter.package_name}}/Makefile @@ -3,6 +3,14 @@ # python command links to the appropriate virtual environment Python. MAKEFLAGS += --no-print-directory +GIT := $(shell command -v git) + + +define CHECK_GIT +$(if $(GIT),,$(error "git command not available.")) +$(if $(wildcard .git),,$(error "Not a git repository.")) +endef + # Do not remove this block. It is used by the 'help' rule when # constructing the help output. @@ -10,6 +18,7 @@ MAKEFLAGS += --no-print-directory # help: {{cookiecutter.package_display_name}} Makefile help # help: + # help: help - display makefile help information .PHONY: help help: @@ -21,10 +30,9 @@ help: # help: venv - create a dev virtual environment PIP_CMDS = \ - source venv/bin/activate && \ - pip install pip --upgrade && \ - pip install -r requirements.dev.txt && \ - pip install -e . + source venv/bin/activate && \ + pip install -U pip && \ + pip install -e .[dev] .PHONY: venv venv: @rm -Rf venv @@ -34,17 +42,18 @@ venv: "\n\n\t$ source venv/bin/activate\n" - # help: clean - clean all files using .gitignore rules .PHONY: clean clean: - @git clean -X -f -d + $(call CHECK_GIT) + @$(GIT) clean -X -f -d # help: scrub - clean all files, even untracked files .PHONY: scrub scrub: - git clean -x -f -d + $(call CHECK_GIT) + @$(GIT) clean -x -f -d # help: test - run tests @@ -114,8 +123,7 @@ check-lint: @pylint --rcfile=.pylintrc \ {{cookiecutter.package_name}} \ examples \ - tests \ - setup.py + tests # help: check-static-analysis - check code style compliance @@ -149,7 +157,7 @@ serve-docs: # help: dist - create a wheel distribution package .PHONY: dist dist: - @python setup.py bdist_wheel + @hatch build # help: dist-test - test a wheel distribution package diff --git a/{{cookiecutter.package_name}}/licenses/EXPAT b/{{cookiecutter.package_name}}/licenses/EXPAT deleted file mode 100644 index 5ee27cb..0000000 --- a/{{cookiecutter.package_name}}/licenses/EXPAT +++ /dev/null @@ -1,20 +0,0 @@ -Copyright {{cookiecutter.year}} {{cookiecutter.full_name}} - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/{{cookiecutter.package_name}}/licenses/GPL2 b/{{cookiecutter.package_name}}/licenses/GPL2 index d159169..9efa6fb 100644 --- a/{{cookiecutter.package_name}}/licenses/GPL2 +++ b/{{cookiecutter.package_name}}/licenses/GPL2 @@ -2,7 +2,7 @@ Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -304,8 +304,7 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + with this program; if not, see . Also add information on how to contact you by electronic and paper mail. @@ -329,8 +328,8 @@ necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. - , 1 April 1989 - Ty Coon, President of Vice + , 1 April 1989 + Moe Ghoul, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may diff --git a/{{cookiecutter.package_name}}/licenses/config.json b/{{cookiecutter.package_name}}/licenses/config.json index 519608f..3203fc5 100644 --- a/{{cookiecutter.package_name}}/licenses/config.json +++ b/{{cookiecutter.package_name}}/licenses/config.json @@ -1,8 +1,10 @@ { - "Apache License, Version 2.0": {"source": "APACHE-2.0", "destination": "LICENSE"}, - "Expat License": {"source": "EXPAT", "destination": "LICENSE"}, - "GNU GPL version 3": {"source": "GPL3", "destination": "COPYING"}, - "GNU GPL version 2": {"source": "GPL2", "destination": "COPYING"}, - "GNU AGPL version 3": {"source": "AGPL3", "destination": "COPYING"}, - "Modified BSD license (3-clause)": {"source": "BSD-3-CLAUSE", "destination": "LICENSE"} + "AGPL-3.0-only": {"source": "AGPL3", "destination": "COPYING"}, + "AGPL-3.0-or-later": {"source": "AGPL3", "destination": "COPYING"}, + "Apache-2.0": {"source": "APACHE-2.0", "destination": "LICENSE"}, + "BSD-3-Clause": {"source": "BSD-3-CLAUSE", "destination": "LICENSE"}, + "GPL-2.0-only": {"source": "GPL2", "destination": "COPYING"}, + "GPL-2.0-or-later": {"source": "GPL2", "destination": "COPYING"}, + "GPL-3.0-only": {"source": "GPL3", "destination": "COPYING"}, + "GPL-3.0-or-later": {"source": "GPL3", "destination": "COPYING"} } diff --git a/{{cookiecutter.package_name}}/mypi.ini b/{{cookiecutter.package_name}}/mypi.ini deleted file mode 100644 index ef615ee..0000000 --- a/{{cookiecutter.package_name}}/mypi.ini +++ /dev/null @@ -1,7 +0,0 @@ -[mypy] -show_column_numbers = true -disallow_untyped_calls = true -disallow_untyped_defs = true -follow_imports = skip -no_implicit_optional = true -warn_no_return = true diff --git a/{{cookiecutter.package_name}}/pyproject.toml b/{{cookiecutter.package_name}}/pyproject.toml index 5c2a51c..0e8ae61 100644 --- a/{{cookiecutter.package_name}}/pyproject.toml +++ b/{{cookiecutter.package_name}}/pyproject.toml @@ -1,3 +1,71 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{cookiecutter.package_name}}" +dynamic = ["version"] +requires-python = ">= 3.11" +dependencies = [] +authors = [ + {name = "{{cookiecutter.full_name}}", email = "{{cookiecutter.email}}"}, +] +keywords=[ + "{{cookiecutter.package_name}}" +] +description = "{{cookiecutter.package_short_description}}" +readme = "README.rst" +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Developers", +{%- if cookiecutter.license == "Not licensed for distribution (no license)" %} + "License :: Other/Proprietary License", +{%- elif cookiecutter.license == "AGPL-3.0-only" %} + "License :: OSI Approved :: GNU Affero General Public License v3", +{%- elif cookiecutter.license == "AGPL-3.0-or-later" %} + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", +{%- elif cookiecutter.license == "Apache-2.0" %} + "License :: OSI Approved :: Apache Software License", +{%- elif cookiecutter.license == "BSD-3-Clause" %} + "License :: OSI Approved :: BSD License", +{%- elif cookiecutter.license == "GPL-2.0-only" %} + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", +{%- elif cookiecutter.license == "GPL-2.0-or-later" %} + "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", +{%- elif cookiecutter.license == "GPL-3.0-only" %} + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", +{%- elif cookiecutter.license == "GPL-3.0-or-later" %} + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", +{%- endif %} + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[project.optional-dependencies] +dev = [ + "black", + "coverage", + "hatch", + "isort", + "mypy", + "pylint", + "sphinx", + "twine", + "wheel", +] + +[project.urls] +Documentation = "http://{{cookiecutter.package_name}}.readthedocs.io" +"Source code" = "https://github.com/{{cookiecutter.github_user_name}}/{{cookiecutter.github_repo_name}}" + +[tool.hatch.version] +path = "{{cookiecutter.package_name}}/__init__.py" + [tool.black] line-length = 79 @@ -10,3 +78,11 @@ force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true line_length = 79 + +[tool.mypy] +disallow_untyped_calls = true +disallow_untyped_defs = true +follow_imports = "skip" +no_implicit_optional = true +show_column_numbers = true +warn_no_return = true diff --git a/{{cookiecutter.package_name}}/requirements.dev.txt b/{{cookiecutter.package_name}}/requirements.dev.txt deleted file mode 100644 index 2a426ec..0000000 --- a/{{cookiecutter.package_name}}/requirements.dev.txt +++ /dev/null @@ -1,8 +0,0 @@ -black -coverage -isort -mypy -pylint -sphinx -twine -wheel diff --git a/{{cookiecutter.package_name}}/requirements.txt b/{{cookiecutter.package_name}}/requirements.txt deleted file mode 100644 index e69de29..0000000 diff --git a/{{cookiecutter.package_name}}/setup.py b/{{cookiecutter.package_name}}/setup.py deleted file mode 100644 index c284105..0000000 --- a/{{cookiecutter.package_name}}/setup.py +++ /dev/null @@ -1,70 +0,0 @@ -import re -from pathlib import PurePath - -from setuptools import setup - -regexp = re.compile(r".*__version__ = [\'\"](.*?)[\'\"]", re.S) - -base_package = "{{cookiecutter.package_name}}" -base_path = PurePath(__file__).parent - -init_file = PurePath(base_path).joinpath( - "{{cookiecutter.package_name}}", "__init__.py" -) -with open(init_file, "r", encoding="utf-8") as f: - module_content = f.read() - - match = regexp.match(module_content) - if match: - version = match.group(1) - else: - raise RuntimeError(f"Cannot find __version__ in {init_file}") - -with open("README.rst", "r", encoding="utf-8") as f: - readme = f.read() - -with open("CHANGELOG.rst", "r", encoding="utf-8") as f: - changes = f.read() - - -def parse_requirements(filename): - """Load requirements from a pip requirements file""" - with open(filename, "r", encoding="utf-8") as fd: - lines = [] - for line in fd: - line.strip() - if line and not line.startswith("#"): - lines.append(line) - return lines - - -requirements = parse_requirements("requirements.txt") - - -if __name__ == "__main__": - setup( - name="{{cookiecutter.package_name}}", - description="{{cookiecutter.package_short_description}}", - long_description="\n\n".join([readme, changes]), - license="{{cookiecutter.license}}", - url="https://github.com/{{cookiecutter.github_user_name}}/{{cookiecutter.github_repo_name}}", - version=version, - author="{{cookiecutter.full_name}}", - author_email="{{cookiecutter.email}}", - maintainer="{{cookiecutter.full_name}}", - maintainer_email="{{cookiecutter.email}}", - install_requires=requirements, - keywords=["{{cookiecutter.package_name}}"], - packages=["{{cookiecutter.package_name}}"], - zip_safe=False, - # https://pypi.org/classifiers/ - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - ], - )