Skip to content

Commit 80ed5a8

Browse files
authored
feat(feature-groups): add feature group support to template and simplify (#148)
* fix: tests dep group * fix: simplify * fix: dep groups * more cleanup * more cleanup * fix: docs * fix
1 parent c77ad63 commit 80ed5a8

3 files changed

Lines changed: 31 additions & 42 deletions

File tree

copier.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ template_mode:
66
"Welcome to the pyOpenSci package template.
77
Please select the type of template that you want to create today."
88
choices:
9-
"MINIMAL: I want a minimal setup (package, tests and docs; typing); linting not included).": "minimal"
9+
"MINIMAL: I want a minimal setup (package, tests and docs); linting & typing not included).": "minimal"
1010
"FULL DEFAULT: I want to use the default settings but I want it setup for me.": "full"
1111
"FULL CUSTOM: I want it all but I want to fully customize all template elements.": "custom"
1212

template/pyproject.toml.jinja

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ authors = [
3030
{ name = "{{ author_name }}"{% if author_email %}, email = "{{ author_email }}"{% endif %} },
3131
]
3232
license = "{{ license }}"
33-
requires-python = ">= 3.10" # Adjust based on the minimum version of Python that you support
33+
requires-python = ">= 3.11" # Adjust based on the minimum version of Python that you support
3434
readme = {"file" = "README.md", "content-type" = "text/markdown"}
3535
# Please consult https://pypi.org/classifiers/ for a full list.
3636
classifiers = [
@@ -67,12 +67,10 @@ Documentation = "https://{{ project_slug }}.readthedocs.io"
6767
Documentation = "{{ dev_platform_url }}/{{ username }}/{{ project_slug }}/blob/main/README.md"
6868
{%- endif %}
6969
{%- endif %}
70+
# Note: update this url when you have published your package to pypi. (or delete it)
7071
Download = "https://pypi.org/project/{{ project_slug }}/#files"
7172

72-
[project.optional-dependencies]
73-
# The groups below should be in the [development-groups] table
74-
# They are here now because hatch hasn't released support for them but plans to
75-
# in Mid November 2025.
73+
[dependency-groups]
7674
dev = [
7775
"hatch",
7876
"pre-commit",
@@ -90,11 +88,11 @@ docs = [
9088
{%- if documentation == "sphinx" %}
9189
"sphinx~=8.0",
9290
"myst-parser>=4.0",
93-
"pydata-sphinx-theme~=0.16",
91+
"pydata-sphinx-theme>=0.16",
9492
"sphinx-autobuild>=2024.10.3",
9593
"sphinx-autoapi>=3.6.0",
96-
"sphinx_design>=0.6.1",
9794
"sphinx-copybutton>=0.5.2",
95+
"sphinx-design>=0.6.1",
9896
{%- elif documentation == "mkdocs" %}
9997
"mkdocs-material ~=9.5",
10098
"mkdocstrings[python] ~=0.24",
@@ -111,9 +109,6 @@ build = [
111109
tests = [
112110
"pytest",
113111
"pytest-cov",
114-
"pytest-raises",
115-
"pytest-randomly",
116-
"pytest-xdist",
117112
]
118113
{%- endif %}
119114

@@ -243,23 +238,7 @@ exclude = "_version.py"
243238
{%- endif -%}
244239

245240
{% if use_types %}
246-
# TODO: Adjust mypy configuration.
247-
#[tool.mypy]
248-
#plugins = [
249-
# "pydantic.mypy",
250-
#]
251-
252-
# Stop mypy from complaining about missing types from imports.
253-
#[[tool.mypy.overrides]]
254-
#module = [
255-
# "pandas",
256-
#]
257-
#ignore_missing_imports = true
258-
259-
#[tool.pydantic-mypy]
260-
#init_forbid_extra = true
261-
#init_typed = true
262-
#warn_required_dynamic_aliases = true
241+
# Typing is not supported yet.
263242
{%- endif %}
264243

265244
{%- if use_hatch_envs %}
@@ -276,10 +255,11 @@ installer = "uv"
276255
# This table installs the tools you need to test and build your package
277256
[tool.hatch.envs.build]
278257
description = """Test the installation the package."""
279-
features = [
258+
dependency-groups = [
280259
"build",
281260
]
282261
detached = true
262+
builder = true
283263

284264
# This table installs created the command hatch run install:check which will build and check your package.
285265
[tool.hatch.envs.build.scripts]
@@ -293,12 +273,12 @@ check = [
293273
{%- if use_test %}
294274
[tool.hatch.envs.test]
295275
description = """Run the test suite."""
296-
features = [
297-
"tests",
276+
dependency-groups = [
277+
"tests",
298278
]
299279

300280
[[tool.hatch.envs.test.matrix]]
301-
python = ["3.10", "3.11", "3.12", "3.13"]
281+
python = ["3.11", "3.12", "3.13"]
302282

303283
[tool.hatch.envs.test.scripts]
304284
run = "pytest {args:--cov={{ package_name }} --cov-report=term-missing --cov-report=xml}"
@@ -310,7 +290,7 @@ run = "pytest {args:--cov={{ package_name }} --cov-report=term-missing --cov-rep
310290
[tool.hatch.envs.docs]
311291
description = """Build or serve the documentation."""
312292
# Install optional dependency test for docs
313-
features = [
293+
dependency-groups = [
314294
"docs",
315295
]
316296

@@ -333,7 +313,7 @@ serve = ["sphinx-autobuild docs --watch src/{{ package_name }} {args:-b html doc
333313

334314
[tool.hatch.envs.style]
335315
description = """Check the code and documentation style."""
336-
features = [
316+
dependency-groups = [
337317
"style",
338318
]
339319
detached = true
@@ -349,7 +329,7 @@ check = ["docstrings", "code"]
349329

350330
[tool.hatch.envs.audit]
351331
description = """Check dependencies for security vulnerabilities."""
352-
features = [
332+
dependency-groups = [
353333
"build",
354334
]
355335

@@ -361,7 +341,7 @@ check = ["pip-audit"]
361341
#--------------- Typing ---------------#
362342
[tool.hatch.envs.types]
363343
description = """Check the static types of the codebase."""
364-
features = ["types"]
344+
dependency-groups = ["types"]
365345

366346
[tool.hatch.envs.types.scripts]
367347
check = "mypy src/{{ package_name }}"

tests/test_template_init.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,17 @@ def init_git(path: Path):
113113

114114
def run_command(command: str, cwd: Path, **kwargs) -> None:
115115
"""Run a command, showing stdout/stderr on errors."""
116+
# Strip HATCH_ENV_ACTIVE so inner hatch calls don't inherit the outer
117+
# matrix environment name (e.g. test.py3.10), which would cause hatch
118+
# to fail with "Unknown environment" in the generated project.
119+
env = os.environ.copy()
120+
env.pop("HATCH_ENV_ACTIVE", None)
116121
default_kwargs = {
117122
"shell": True,
118123
"stdout": subprocess.PIPE,
119124
"stderr": subprocess.PIPE,
120125
"text": True,
126+
"env": env,
121127
}
122128
kwargs = {**default_kwargs, **kwargs}
123129
try:
@@ -170,7 +176,10 @@ def test_template_suite(
170176

171177
# Run the local test suite.
172178
run_command("hatch build --clean", project_dir)
173-
run_command(f"hatch run +py={sys.version_info.major}.{sys.version_info.minor} test:run", project_dir)
179+
# The template requires Python >= 3.11; clamp to that minimum so this test
180+
# works even when the outer test suite runs on an older interpreter.
181+
py = max(sys.version_info[:2], (3, 11))
182+
run_command(f"hatch run +py={py[0]}.{py[1]} test:run", project_dir)
174183
run_command("hatch run style:check", project_dir)
175184

176185

@@ -225,7 +234,7 @@ def test_non_hatch_deps(
225234
documentation: str,
226235
generated: Callable[..., Path],
227236
) -> None:
228-
"""When we aren't using hatch, we should still get the optional dependencies."""
237+
"""When we aren't using hatch, we should still get the dependency groups."""
229238
project = generated(
230239
use_hatch_envs=False,
231240
use_lint=True,
@@ -242,15 +251,15 @@ def test_non_hatch_deps(
242251
# validate pyproject.toml file if present
243252
validator_api.Validator()(pyproject)
244253

245-
optional_deps = pyproject["project"]["optional-dependencies"]
254+
dep_groups = pyproject["dependency-groups"]
246255
groups = ("dev", "tests", "style", "types", "build")
247-
assert all(group in optional_deps for group in groups)
256+
assert all(group in dep_groups for group in groups)
248257

249258
# we don't want to hardcode all our deps here,
250259
# bc that would be a very fragile test indeed.
251260
# Instead, we just assume their presence and that the validity of the pyproject file
252261
# means that they have been correctly specified.
253262
# except for the docs, where we want to test our switch works :)
254263
if documentation != "no":
255-
assert "docs" in optional_deps
256-
assert any(dep.startswith(documentation) for dep in optional_deps["docs"])
264+
assert "docs" in dep_groups
265+
assert any(dep.startswith(documentation) for dep in dep_groups["docs"])

0 commit comments

Comments
 (0)