Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ project_long_description:
help: "A long description of the project. It will be added to the README.md of this project"
default: ""

project_short_description:
type: str
help: "A short description of the project. Used in the documentation and in the package metadata."

project_getting_started:
type: str
help: "A short description of how to get started with the project"
Expand All @@ -26,6 +30,24 @@ logo_path_gallery:
help: "What is the path to the logo image for the pyfar gallery?"
default: "resources/logos/pyfar_logos_fixed_size_{{ project_slug }}.png"

version:
type: str
help: "What is the version of the project."
default: 0.1.0
validator: >-
{% if not (version | regex_search(
'^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)'
'(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)'
'(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?'
'(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$')) %}
Package version should use Semantic Versioning 2.0.0 (MAJOR.MINOR.PATCH)
{% endif %}

keywords:
type: str
help: "Keywords to help categorize the project (e.g., on PyPI)."
default: "acoustics,pyfar"

minimum_python_version:
type: str
help: "The minimum Python version required"
Expand Down Expand Up @@ -60,3 +82,36 @@ copyright_statement:
type: str
help: "What is the copyright statement? For example publication year, copyright holder."
default: "{{ '%Y' | strftime }}, {{ author }}"


dependencies:
type: str
help: |
Enter the dependencies of this project, separate them with commas and do not leave any
spaces between them. These dependencies will be included in the pyproject.toml file.
default: "numpy,scipy"

valid_python_versions:
when: false
type: yaml
default: >
{% set versions = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] %}
{% set min_index = versions.index(minimum_python_version) %}
{{ versions[min_index:] }}

pypi_license_classifiers_list:
when: false
type: yaml
default:
MIT: "License :: OSI Approved :: MIT License"
BSD-3-Clause: "License :: OSI Approved :: BSD License"
Apache-2.0: "License :: OSI Approved :: Apache Software License"
GPL-3.0-only: "License :: OSI Approved :: GNU General Public License v3 (GPLv3)"
EUPL-1.2: "License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)"
MPL-2.0: "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)"

pypi_license_classifier:
when: false
type: str
default: >
{{ pypi_license_classifiers_list[license] }}
156 changes: 156 additions & 0 deletions template/pyproject.toml.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
[project]
name = "{{ project_slug }}"
version = "{{ version }}"
description = "{{ project_short_description }}"
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">={{ minimum_python_version }}"

authors = [
{ name = "The pyfar developers", email = "info@pyfar.org" }
]
keywords = [
{%- for key in keywords.split(',') %}
"{{ key.strip() }}",
{%- endfor %}
]

classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"{{ pypi_license_classifier }}",
"Natural Language :: English",
"Programming Language :: Python :: 3",
{%- for version in valid_python_versions %}
"Programming Language :: Python :: {{ version }}",
{%- endfor %}
]

dependencies = [
{%- for key in dependencies.split(',') %}
"{{ key.strip() }}",
{%- endfor %}
]

[project.optional-dependencies]
deploy = [
"twine",
"wheel",
"build",
"setuptools",
"bump-my-version",
]
tests = [
"pytest",
"pytest-cov",
"watchdog",
"ruff==0.8.3",
"coverage",
]
docs = [
"sphinx",
"autodocsumm>=0.2.14",
"pydata-sphinx-theme",
"sphinx_mdinclude",
"sphinx-design",
"sphinx-favicon",
"sphinx-reredirects",
]
dev = ["{{ project_slug }}[deploy,tests,docs]"]

[project.urls]
Tracker = "https://github.com/{{ git_username }}/{{ project_slug }}/issues"
Documentation = "https://{{ project_slug }}.readthedocs.io/"
Download = "https://pypi.org/project/{{ project_slug }}/"
Homepage = "https://pyfar.org/"
Source = "https://github.com/{{ git_username }}/{{ project_slug }}"
Changelog = "https://github.com/{{ git_username }}/{{ project_slug }}/blob/main/HISTORY.rst"

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages]
find = {} # Scan the project directory with the default parameters


[tool.ruff]
exclude = [
".git",
"docs",
]
line-length = 79
lint.ignore = [
"D200", # One-line docstring should fit on one line with quotes
"D202", # No blank lines allowed after function docstring
"D205", # 1 blank line required between summary line and description
"D401", # First line should be in imperative mood
"D404", # First word of the docstring should not be "This"
"B006", # Do not use mutable data structures for argument defaults
"B008", # Do not perform calls in argument defaults
"PT018", # Assertion should be broken down into multiple parts
"PT019", # Fixture `_` without value is injected as parameter
]
lint.select = [
"B", # bugbear extension
"ARG", # Remove unused function/method arguments
"C4", # Check for common security issues
"E", # PEP8 errors
"F", # Pyflakes
"W", # PEP8 warnings
"D", # Docstring guidelines
"NPY", # Check all numpy related deprecations
"D417", # Missing argument descriptions in the docstring
"PT", # Pytest style
"A", # Avoid builtin function and type shadowing
"ERA", # No commented out code
"NPY", # Check all numpy related deprecations
"COM", # trailing comma rules
"I002", # missing required import
"TID252", # Use absolute over relative imports
"FIX", # Code should not contain FIXME, TODO, etc
]

# Ignore missing docstrings in tests
[tool.ruff.lint.per-file-ignores]
"tests/*" = [
"D100",
"D101",
"D103",
"D104",
]

[tool.ruff.lint.pydocstyle]
convention = "numpy"


[tool.bumpversion]
current_version = "{{ version }}"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
replace = "{new_version}"
regex = true
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = false
commit = true
message = "Bump version: {current_version} → {new_version}"
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []

[[tool.bumpversion.files]]
filename = "pyproject.toml"
search = '\nversion = "{current_version}"'
replace = '\nversion = "{new_version}"'

[[tool.bumpversion.files]]
filename = "{{ project_slug }}/__init__.py"
search = "__version__ = '{current_version}'"
replace = "__version__ = '{new_version}'"
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def copier_project_defaults():
"minimum_python_version": "3.11",
"license": "MIT",
"copyright_statement": "2025, The pyfar developers",
"project_short_description": "my_project_short_description",
}

@pytest.fixture(scope="session")
Expand Down
33 changes: 33 additions & 0 deletions tests/test_copier.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def test_project_folder(default_project):
"README.md",
"LICENSE",
"CONTRIBUTING.rst",
"pyproject.toml",
])
def test_generated_file_exists(default_project, file_name):
assert default_project.project_dir.joinpath(file_name).exists()
Expand Down Expand Up @@ -64,3 +65,35 @@ def test_content_contributing(default_project, desired):
content = default_project.project_dir.joinpath(
"CONTRIBUTING.rst").read_text()
assert desired in content


@pytest.mark.parametrize("desired", [
'name = "my_project"',
'version = "0.1.0"',
'description = "my_project_short_description"',
'requires-python = ">=3.11"',
'\n "acoustics",\n',
'\n "pyfar",\n',
'\n "numpy",\n',
'\n "scipy",\n',
'"License :: OSI Approved :: MIT License"',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Tracker = "https://github.com/pyfar/my_project/issues"',
])
def test_content_pyproject(default_project, desired):
content = default_project.project_dir.joinpath(
"pyproject.toml").read_text()
assert desired in content, f"{desired!r} is not in content"


@pytest.mark.parametrize("not_desired", [
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
])
def test_incorrect_content_pyproject(default_project, not_desired):
content = default_project.project_dir.joinpath(
"pyproject.toml").read_text()
assert not_desired not in content, f"{not_desired!r} is in content"