From 609b740dc3cc38de7ac9597563d9fe038745acd1 Mon Sep 17 00:00:00 2001 From: Geoffrey Chambers Date: Mon, 18 May 2026 11:21:09 -0700 Subject: [PATCH 1/5] First Round literalinclude some wordsmithing in pyproject-toml-python-oacjage-metadata.md updated declare-dependencies.md with literalinclude updated pyproject.toml to reflect the previous intention with the hard-coded examples as close as possible --- examples/pure-hatch/pyproject.toml | 16 +++- .../declare-dependencies.md | 85 +++++++++---------- .../pyproject-toml-python-package-metadata.md | 2 +- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/examples/pure-hatch/pyproject.toml b/examples/pure-hatch/pyproject.toml index c6ef1931..6a0bc748 100644 --- a/examples/pure-hatch/pyproject.toml +++ b/examples/pure-hatch/pyproject.toml @@ -19,24 +19,32 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "dependency-package-name-1", - "dependency-package-name-2", + "pandas", + "matplotlib" ] -[project.optional-dependencies] +[development-group] tests = [ "pytest", "pytest-cov" ] + lint = [ "black", - "flake8" + "ruff" ] + docs = [ "sphinx", "pydata-sphinx-theme" ] +[optional.dependencies] +plot = ["bokeh"] +test = ["pytest", "pytest-cov"] +docs = ["sphinx", "pydata-sphinx-theme"] +dev = ["examplePy[test,docs,plot]", "build", "twine"] # "examplePy" is the name of the package in [project] + [tool.ruff] select = [ "E", # pycodestyle errors diff --git a/package-structure-code/declare-dependencies.md b/package-structure-code/declare-dependencies.md index 56519a44..9505dacc 100644 --- a/package-structure-code/declare-dependencies.md +++ b/package-structure-code/declare-dependencies.md @@ -32,25 +32,21 @@ While `pyproject.toml` is now the standard, you may sometimes encounter older ap Specifying dependencies in the [project.dependency] array of your `pyproject.toml` file ensures that libraries needed to run your package are correctly installed into a user's environment. For instance, if your package requires Pandas to run properly, and you add Pandas to the `project.dependency` array, Pandas will be installed into the users' environment when they install your package using uv, pip, or conda. -```toml -[project] -... -... -... -dependencies = [ - "pandas", -] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n..." +:start-at: dependencies = [ +:end-at: ] +::: Development dependencies make it easier for contributors to work on your package. You can set up instructions for running specific workflows, such as tests, linting, and even typing, that automatically install groups of development dependencies. These dependencies can be stored in arrays (lists of dependencies) within a `[development-group]` table. -```toml -[development-group] -tests = [ - "pytest", - "pytest-cov" -] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [development-group] +:end-before: lint +::: ### Types of dependencies @@ -76,17 +72,13 @@ You can add your required dependencies to the `dependencies` array in the your package with uv, pip, or conda, these dependencies will be automatically installed alongside your package in their environment. -```toml -[project] -name = "examplePy" -authors = [ - {name = "Some Maintainer", email = "some-email@pyopensci.org"}, -] -dependencies = [ - "pandas", - "matplotlib", -] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n..." +:start-at: dependencies = [ +:end-at: ] +::: :::{tip} Try your best to minimize dependencies whenever possible. Remember that @@ -143,14 +135,13 @@ Optional (also referred to as feature) dependencies can be installed by users as Place these dependencies in the `[project.optional-dependencies]` table. -```toml -[project] -... -... -... -[optional.dependencies] -plot = ["bokeh"] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n..." +:start-at: [optional.dependencies] +:end-at: plot = ["bokeh"] +::: When a user installs your package, uv, pip, or conda automatically installs all required dependencies. Optional dependencies are only installed if the user explicitly requests them. @@ -208,12 +199,12 @@ within a `[development-groups]` table. Similar to optional-dependencies, you can create separate subgroups or arrays with names using the syntax: `group-name = ["dep1", "dep2"]` -```toml -[development-groups] -tests = ["pytest", "pytest-cov"] -docs = ["sphinx", "pydata-sphinx-theme"] -lint = ["ruff", "black"] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [development-group] +:end-before: [optional.dependencies] +::: + :::{dropdown} How to Add [development.group] using UV :icon: eye @@ -361,12 +352,12 @@ installation conflicts. You can also create combined groups that reference other groups: -```toml -[project.optional-dependencies] -test = ["pytest", "pytest-cov"] -docs = ["sphinx", "pydata-sphinx-theme"] -dev = ["your-package[test,docs]", "build", "twine"] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [optional.dependencies] +:end-before: [tool.ruff] +::: Then install everything with pip install or uv sync as needed: diff --git a/package-structure-code/pyproject-toml-python-package-metadata.md b/package-structure-code/pyproject-toml-python-package-metadata.md index a35a0faa..1aa7f7dd 100644 --- a/package-structure-code/pyproject-toml-python-package-metadata.md +++ b/package-structure-code/pyproject-toml-python-package-metadata.md @@ -7,7 +7,7 @@ 2. There are two _required_ fields in the **[project]** table: **name=** and **version=**. 3. Add metadata to the classifiers section of your `pyproject.toml` file to make it easier for users to find your project on PyPI. 4. When you are adding classifiers to the [project] table, only use valid values from [PyPI’s classifier page](https://PyPI.org/classifiers/). An invalid value here will raise an error when you build your package or publish to PyPI. -5. There is no specific order for tables in the `pyproject.toml` file. However fields need to be placed within the correct table sections. For example `requires =` always need to be associated with the **[build-system]** table. +5. There is no specific order for tables in the `pyproject.toml` file. However, fields need to be placed within the correct table sections. For example `requires =` always needs to be associated with the **[build-system]** table. ::: From 0d948319e0cf32e7d5b5711f0864899d4f696945 Mon Sep 17 00:00:00 2001 From: Geoffrey Chambers Date: Mon, 18 May 2026 13:46:29 -0700 Subject: [PATCH 2/5] Literal Include for TOML references added literalinclude pointing to examples/pure-hatch/pyproject.toml add ipa pronunciations for PyPI --- examples/pure-hatch/pyproject.toml | 17 +++++++- .../publish-python-package-pypi-conda.md | 2 +- ...-package-distribution-files-sdist-wheel.md | 40 ++++++++----------- .../python-package-versions.md | 21 +++++----- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/examples/pure-hatch/pyproject.toml b/examples/pure-hatch/pyproject.toml index 6a0bc748..b66d5111 100644 --- a/examples/pure-hatch/pyproject.toml +++ b/examples/pure-hatch/pyproject.toml @@ -1,3 +1,4 @@ +# pyproject.toml example build setup to use hatchling and hatch_vcs [build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -15,11 +16,19 @@ keywords = ["pyOpenSci", "python packaging"] readme = "README.md" license = "BSD-3-Clause" classifiers = [ - "Programming Language :: Python :: 3", - "Operating System :: OS Independent", + # How mature is this project? Common values are + "Development Status :: 4 - Beta", + + # Indicate who your project is intended for + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ] dependencies = [ "pandas", + "geopandas", "matplotlib" ] @@ -58,3 +67,7 @@ known-first-party = ["examplePy"] [tool.pytest.ini_options] pythonpath = "src" + +# Example hatch vcs setup in the pyproject.toml file +[tool.hatch.build.hooks.vcs] +version-file = "_version.py" diff --git a/package-structure-code/publish-python-package-pypi-conda.md b/package-structure-code/publish-python-package-pypi-conda.md index 881d321e..4b064e10 100644 --- a/package-structure-code/publish-python-package-pypi-conda.md +++ b/package-structure-code/publish-python-package-pypi-conda.md @@ -28,7 +28,7 @@ Once you have published both package distributions (the source distribution and (publish-pypi-conda)= ## What is PyPI -[PyPI](https://pypi.org/) is an online Python package repository that +[PyPI](https://pypi.org/) (p/aɪ/.pi/aɪ/ or p/aɪ/p/aɪ/) is an online Python package repository that you can use to both find and install and publish your Python package. There is also a test PyPI repository where you can test publishing your package prior to the final publication on PyPI. diff --git a/package-structure-code/python-package-distribution-files-sdist-wheel.md b/package-structure-code/python-package-distribution-files-sdist-wheel.md index 323c6a08..c015d1c1 100644 --- a/package-structure-code/python-package-distribution-files-sdist-wheel.md +++ b/package-structure-code/python-package-distribution-files-sdist-wheel.md @@ -36,38 +36,30 @@ The metadata that both build tools and PyPI uses to describe and understand your - The `[build-system]` table in your pyproject.toml file tells pip what [build backend tool](build_backends) you wish to use for creating your sdist and wheel distributions. -```toml -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [build-system] +:end-before: [project] +::: - And the dependencies section of your project table tells the build tool and PyPI what dependencies your project requires. -``` -dependencies = [ - "numpy", - "geopandas", -] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: dependencies = [ +:end-before: [development-group] +::: 2. When the build tool creates your package distribution file (the file that you publish on PyPI), it also creates a METADATA file which PyPI can read and use to help users find your package. For example: - The `classifiers = ` section of your `[project]` table in the pyproject.toml file provides information that users on PyPI can use to filter for packages that address different topics or that support specific versions of python. -```toml -classifiers = [ - # How mature is this project? Common values are - "Development Status :: 4 - Beta", - - # Indicate who your project is intended for - "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", -] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: classifiers = [ +:end-before: dependencies = [ +::: :::{admonition} What happened to setup.py and setup.cfg for metadata? :class: note diff --git a/package-structure-code/python-package-versions.md b/package-structure-code/python-package-versions.md index c835046c..574e0269 100644 --- a/package-structure-code/python-package-versions.md +++ b/package-structure-code/python-package-versions.md @@ -188,21 +188,20 @@ day workflow. #### Hatch example setup in your pyproject.toml -```toml -# pyproject.toml example build setup to use hatchling and hatch_vcs -[build-system] -requires = ["hatchling", "hatch-vcs"] -build-backend = "hatchling.build" -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: "# pyproject.toml example build setup to use hatchling and hatch_vcs" +:end-before: [project] +::: **Hatch_vcs** supports a fully automated package release and build, and push to PyPI workflow on GitHub. -```toml -# Example hatch vcs setup in the pyproject.toml file -[tool.hatch.build.hooks.vcs] -version-file = "_version.py" -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: "# Example hatch vcs setup in the pyproject.toml file" +:end-at: version-file = "_version.py" +::: :::{tip} If you use **setuptools_scm**, then you might find **hatch_vcs** and **hatchling** to be the modern equivalent to your current setuptools / build workflow. From 6cd39dac809a9bbada2b7abbb1dec14451ac2561 Mon Sep 17 00:00:00 2001 From: Geoffrey Chambers Date: Tue, 19 May 2026 09:14:17 -0700 Subject: [PATCH 3/5] Re-wired examples to existing files changed literalinclude to reference existing files add additional commentary on dependencies and dependency-groups --- examples/pure-hatch/pyproject.toml | 24 ++- examples/pure-hatch/src/examplePy/numbers.py | 18 ++ .../pure-hatch/src/examplePy/temperature.py | 42 ++++- .../tests/examplePy/test_numbers.py | 17 ++ .../tests/examplePy/test_temperature.py | 38 ++++ .../declare-dependencies.md | 12 +- .../pyproject-toml-python-package-metadata.md | 58 ++++-- tests/run-tests.md | 59 +++--- tests/test-types.md | 176 +++--------------- tests/write-tests.md | 41 +--- 10 files changed, 239 insertions(+), 246 deletions(-) create mode 100644 examples/pure-hatch/src/examplePy/numbers.py create mode 100644 examples/pure-hatch/tests/examplePy/test_numbers.py diff --git a/examples/pure-hatch/pyproject.toml b/examples/pure-hatch/pyproject.toml index b66d5111..64649897 100644 --- a/examples/pure-hatch/pyproject.toml +++ b/examples/pure-hatch/pyproject.toml @@ -28,12 +28,13 @@ classifiers = [ ] dependencies = [ "pandas", + "xarray", "geopandas", "matplotlib" ] -[development-group] -tests = [ +[dependency-groups] +test = [ "pytest", "pytest-cov" ] @@ -48,11 +49,15 @@ docs = [ "pydata-sphinx-theme" ] -[optional.dependencies] +dev = [ + {include-group = "test"}, + {include-group = "lint"} +] + +[project.optional-dependencies] plot = ["bokeh"] test = ["pytest", "pytest-cov"] docs = ["sphinx", "pydata-sphinx-theme"] -dev = ["examplePy[test,docs,plot]", "build", "twine"] # "examplePy" is the name of the package in [project] [tool.ruff] select = [ @@ -71,3 +76,14 @@ pythonpath = "src" # Example hatch vcs setup in the pyproject.toml file [tool.hatch.build.hooks.vcs] version-file = "_version.py" + +[tool.hatch.envs.test] +development-group= [ + "tests", +] + +[tool.hatch.envs.test.scripts] +run = "pytest {args:--cov=test --cov-report=term-missing --cov-report=xml}" + +[[tool.hatch.envs.test.matrix]] +python = ["3.10", "3.11", "3.12"] \ No newline at end of file diff --git a/examples/pure-hatch/src/examplePy/numbers.py b/examples/pure-hatch/src/examplePy/numbers.py new file mode 100644 index 00000000..3073b3eb --- /dev/null +++ b/examples/pure-hatch/src/examplePy/numbers.py @@ -0,0 +1,18 @@ +# src/examplePy/numbers.py +def add_numbers(a: float, b: float) -> float: + """ + Add two numbers together and return the result. + + Parameters + ---------- + a : float + The first number to add. + b : float + The second number to add. + + Returns + ------- + float + The sum of the two numbers. + """ + return a + b \ No newline at end of file diff --git a/examples/pure-hatch/src/examplePy/temperature.py b/examples/pure-hatch/src/examplePy/temperature.py index e19f96a9..6264661d 100644 --- a/examples/pure-hatch/src/examplePy/temperature.py +++ b/examples/pure-hatch/src/examplePy/temperature.py @@ -1,4 +1,5 @@ -def celsius_to_fahrenheit(celsius): +# src/examplePy/temperature.py +def celsius_to_fahrenheit(celsius: float) -> float: """ Convert temperature from Celsius to Fahrenheit. @@ -12,7 +13,7 @@ def celsius_to_fahrenheit(celsius): return fahrenheit -def fahrenheit_to_celsius(fahrenheit): +def fahrenheit_to_celsius(fahrenheit: float) -> float: """ Convert temperature from Fahrenheit to Celsius. @@ -24,3 +25,40 @@ def fahrenheit_to_celsius(fahrenheit): """ celsius = (fahrenheit - 32) * 5 / 9 return celsius + + +def average_temperature(temps: list[float]) -> float: + """ + Calculate average temperature from a list. + + Parameters + ---------- + temps : list + List of temperatures. + + Returns + ------- + float + Average temperature. + """ + return sum(temps) / len(temps) + + +def convert_and_average(temps_celsius: list[float]) -> float: + """ + Convert list of Celsius temps to Fahrenheit and + calculate the average. + + Parameters + ---------- + temps_celsius : list + List of Celsius temperatures. + + Returns + ------- + float + Average temperature in Fahrenheit. + """ + temps_fahrenheit = [celsius_to_fahrenheit(t) + for t in temps_celsius] + return average_temperature(temps_fahrenheit) \ No newline at end of file diff --git a/examples/pure-hatch/tests/examplePy/test_numbers.py b/examples/pure-hatch/tests/examplePy/test_numbers.py new file mode 100644 index 00000000..69bd7994 --- /dev/null +++ b/examples/pure-hatch/tests/examplePy/test_numbers.py @@ -0,0 +1,17 @@ +# tests/examplePy/test_numbers.py +from examplePy.numbers import add_numbers + + +def test_add_numbers(): + """Test the add_numbers function.""" + # test with positive numbers + result = add_numbers(2, 3) + assert result == 5, f"Expected 5, but got {result}" + + # test with negative numbers + result2 = add_numbers(-1, 4) + assert result2 == 3, f"Expected 3, but got {result2}" + + # test with zero + result3 = add_numbers(0, 5) + assert result3 == 5, f"Expected 5, but got {result3}" \ No newline at end of file diff --git a/examples/pure-hatch/tests/examplePy/test_temperature.py b/examples/pure-hatch/tests/examplePy/test_temperature.py index 9c6e3d5a..c8ac1eef 100644 --- a/examples/pure-hatch/tests/examplePy/test_temperature.py +++ b/examples/pure-hatch/tests/examplePy/test_temperature.py @@ -5,6 +5,44 @@ from examplePy import temperature +def test_convert_and_average(): + """ + Test that convert_and_average correctly combines conversion + and averaging. + """ + # Test with known values: [0, 10, 20] Celsius + # Should average to 10 Celsius = 50 Fahrenheit + temps_celsius = [0, 10, 20] + result = temperature.convert_and_average(temps_celsius) + assert abs(result - 50.0) < 0.01 + + # Test with different values + temps_celsius = [0, 100] + result = temperature.convert_and_average(temps_celsius) + # Average of 32 and 212 Fahrenheit = 122 + assert abs(result - 122.0) < 0.01 + + +def test_temperature_workflow(): + """ + Test the complete temperature processing workflow. + + This end-to-end test provides sample temperature data in + Celsius, processes it through the full workflow + (conversion and averaging), and verifies the output is + correct. + """ + # Sample temperature data in Celsius + temps_celsius = [0, 10, 20] + + # Run the complete workflow + result = temperature.convert_and_average(temps_celsius) + + # Verify the output + # Average of 32, 50, and 68 Fahrenheit = 50 Fahrenheit + assert abs(result - 50.0) < 0.01 + + def test_fahrenheit_to_celsius_positive(): """Test F to C calculation for positive values""" value = 95 diff --git a/package-structure-code/declare-dependencies.md b/package-structure-code/declare-dependencies.md index 9505dacc..18501232 100644 --- a/package-structure-code/declare-dependencies.md +++ b/package-structure-code/declare-dependencies.md @@ -53,7 +53,7 @@ Development dependencies make it easier for contributors to work on your package There are three different types of dependencies that you will learn about on this page: 1. **Required dependencies:** These are dependencies that need to be installed for your package to work correctly in a user's environment. You add these dependencies to the `[project.dependencies]` table in your pyproject.toml file. -2. **Feature Dependencies:** These are dependencies that are required if a user wants to access additional functionality (that is not core) to your package. Store these in the `[project.optional.dependencies]` table or your pyproject.toml file. +2. **Feature Dependencies:** These are dependencies that are required if a user wants to access additional functionality (that is not core) to your package. Store these in the `[project.project.optional-dependencies]` table or your pyproject.toml file. 3. **Development Dependencies:** These dependencies are required if someone wants to develop or work on your package. These include instance linters, testing tools like pytest and mypy are examples of development dependencies. Store these in the `[project.dependency.groups]` table or your pyproject.toml file. :::{tip} @@ -139,13 +139,13 @@ Place these dependencies in the `[project.optional-dependencies]` table. :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml :prepend: "[project]\n...\n...\n..." -:start-at: [optional.dependencies] +:start-at: [project.optional-dependencies] :end-at: plot = ["bokeh"] ::: When a user installs your package, uv, pip, or conda automatically installs all required dependencies. Optional dependencies are only installed if the user explicitly requests them. -:::{dropdown} How to Add optional.dependencies using UV +:::{dropdown} How to Add project.optional-dependencies using UV :icon: eye :color: primary @@ -160,7 +160,7 @@ uv add --optional feature pandas Will add this to your pyproject.toml file: ```toml -[optional.dependencies] +[project.optional-dependencies] feature = [ "pandas>=2.3.3", ] @@ -202,7 +202,7 @@ Similar to optional-dependencies, you can create separate subgroups or arrays wi :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml :start-at: [development-group] -:end-before: [optional.dependencies] +:end-before: [project.optional-dependencies] ::: @@ -355,7 +355,7 @@ You can also create combined groups that reference other groups: :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml -:start-at: [optional.dependencies] +:start-at: [project.optional-dependencies] :end-before: [tool.ruff] ::: diff --git a/package-structure-code/pyproject-toml-python-package-metadata.md b/package-structure-code/pyproject-toml-python-package-metadata.md index 1aa7f7dd..f4026b24 100644 --- a/package-structure-code/pyproject-toml-python-package-metadata.md +++ b/package-structure-code/pyproject-toml-python-package-metadata.md @@ -161,29 +161,65 @@ what dependencies your package requires. ## Add dependencies to your pyproject.toml file -The `pyproject.toml` file is a modern replacement for the `requirements.txt` file, which has been traditionally used to store development dependencies and also configuration for tools such as pytest, black, and others. +### Required dependencies +A `requirements.txt` file has been traditionally used to specify dependencies, but modern practice puts these +in the `pyproject.toml` file. Required dependencies are specified under the `[project]` section as a list of strings: -To add development dependencies to your build, add a `[dependency-groups]` array to your pyproject.toml file. +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n...\n" +:start-at: "dependencies = [" +:end-at: ] +::: -Then specify dependency groups as follows: +### Optional dependencies +Optional dependencies are specified under the `[project.optional-dependencies]` section and are intended to give users +options for including additional dependencies with their installation. Optional dependencies are collected together +as a list of strings and assigned to a name: :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml -:start-at: [project.optional-dependencies] -:end-before: [tool.ruff] +:start-at: "[project.optional-dependencies]" +:end-at: docs ::: -Following the above example, you install dependencies like this: +Named dependency lists can be invoked by users on installation to include those specific dependencies on installation. +Here is an example installing the test and docs dependencies using pip: + +- `python -m pip install examplePy[test,docs]` + +Other installation tools tend to follow a similar pattern where optional dependencies are concatenated as +a list to the package name. -- `python -m pip install -e .[tests]` +### Dependency Groups +Dependency groups are a way to group package requirements in the `pyproject.toml` file but exclude them in the +project metadata when it is built. Because it is not included in the project metadadata, users will not be able to +invoke dependency groups when installing from a package index such as PyPI, but they can be accessed by developers +who have all the project data (e.g. through cloning a repository). Optional dependencies are intended for package +consumers and dependency groups are intended for package maintainers and contributors. -- pip install --group test _# requires pip 25.1 or greater_ +To add development dependencies to your build, add a `[dependency-groups]` array to your pyproject.toml file. +Then specify dependency groups as follows: -The above will install both your package in editable mode and all of the dependencies declared in the tests section of your `[project.optional-dependencies]` table. +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [dependency-groups] +:end-before: "dev = [" +::: + +One of the capabilities that dependency groups have is the ability to make composition groups. +For example, `dev` dependency group could be composed of a `test` dependency group and a `lint` dependency group: + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[dependency-groups]\n...\n...\n...\n" +:start-at: "dev = [" +:end-at: ] +::: -To install all dependencies and also your package, you'd use: +To access groups using pip (requires pip 25.1 or higher), you can invoke them during installation like this: -`python -m pip install -e .[tests,lint,docs]` +- `python -m pip install --group dev` :::{admonition} Recursive dependencies :class: tip diff --git a/tests/run-tests.md b/tests/run-tests.md index b7d8b766..6951e731 100644 --- a/tests/run-tests.md +++ b/tests/run-tests.md @@ -181,29 +181,27 @@ using Hatch for packaging workflows. ### Setting up Hatch environments -Hatch environments are defined in your `pyproject.toml`. Rather than -duplicating dependencies, use `dependency-groups` to reference your test -dependencies: +Hatch environments can be defined in `pyproject.toml`. These environments can be used to specify what Hatch needs to run the +tests for the package. Test dependencies can be listed individually under `[tool.hatch.envs.test]`, +but if packages have already been grouped together under a name, those groups can be listed here instead. +Using named groups keeps the dependencies in one place and avoids duplication. -```toml -[dependency-groups] -tests = [ - "pytest>=7.0", - "pytest-cov", -] +The additional tools or options to run with the tests is specified under `[tool.hatch.envs.test.scripts]`. -[tool.hatch.envs.test] -dependency-groups = [ - "tests", -] +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [development-group] +:end-before: lint +::: -[tool.hatch.envs.test.scripts] -run = "pytest {args:--cov=test --cov-report=term-missing --cov-report=xml}" +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [tool.hatch.envs.test] +:end-at: run +::: -``` -This approach keeps your test dependencies in one place and avoids -duplication. For a complete example, see our +For a complete example, see our [packaging template tutorial](https://www.pyopensci.org/tutorials/create-python-package.html) which shows a full `pyproject.toml` configuration. @@ -228,23 +226,14 @@ hatch run test:run ### Testing across Python versions To test across multiple Python versions, define a matrix in your -`pyproject.toml`: - -```toml -[dependency-groups] -tests = [ - "pytest>=7.0", - "pytest-cov", -] - -[tool.hatch.envs.test] -dependency-groups = [ - "tests", -] - -[[tool.hatch.envs.test.matrix]] -python = ["3.10", "3.11", "3.12"] -``` +`pyproject.toml` under `[[tool.hatch.envs.test.matrix]]`: + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[tool.hatch.envs.test]\n...\n...\n" +:start-at: [[tool.hatch.envs.test.matrix]] +:end-at: python +::: Then run all versions with a single command: diff --git a/tests/test-types.md b/tests/test-types.md index c4bd7a55..349ddd01 100644 --- a/tests/test-types.md +++ b/tests/test-types.md @@ -30,48 +30,17 @@ numbers together. A unit test for that function ensures that when provided with two numbers, it returns the correct sum. This is a unit test because it checks a single unit (function) in isolation. -```python -# src/mypackage/math_utils.py -def add_numbers(a, b): - """ - Add two numbers together. - - Parameters - ---------- - a : float - First number. - b : float - Second number. - - Returns - ------- - float - Sum of a and b. - """ - return a + b -``` +:::{literalinclude} ../examples/pure-hatch/src/examplePy/numbers.py +:language: python +::: Example unit test for the above function. You'd run this test using the `pytest` command in your **tests/** directory. -```python -# tests/test_math_utils.py -from mypackage.math_utils import add_numbers - - -def test_add_numbers(): - """ - Test the add_numbers function. - """ - # Test with positive numbers - assert add_numbers(2, 3) == 5 - - # Test with negative numbers - assert add_numbers(-1, 4) == 3 - # Test with zero - assert add_numbers(0, 5) == 5 -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_numbers.py +:language: python +::: Notice that the tests above don't just test one case where numbers are added together. Instead, they test multiple scenarios: adding positive @@ -102,104 +71,19 @@ calculate statistics. An integration test would ensure that these functions work together correctly in a workflow where you convert temperatures and then analyze them. -```python -# src/mypackage/temperature_utils.py -def celsius_to_fahrenheit(celsius): - """ - Convert temperature from Celsius to Fahrenheit. - - Parameters - ---------- - celsius : float - Temperature in Celsius. - - Returns - ------- - float - Temperature in Fahrenheit. - """ - return (celsius * 9 / 5) + 32 - - -def fahrenheit_to_celsius(fahrenheit): - """ - Convert temperature from Fahrenheit to Celsius. - - Parameters - ---------- - fahrenheit : float - Temperature in Fahrenheit. - - Returns - ------- - float - Temperature in Celsius. - """ - return (fahrenheit - 32) * 5 / 9 - - -def average_temperature(temps): - """ - Calculate average temperature from a list. - - Parameters - ---------- - temps : list - List of temperatures. - - Returns - ------- - float - Average temperature. - """ - return sum(temps) / len(temps) - - -def convert_and_average(temps_celsius): - """ - Convert list of Celsius temps to Fahrenheit and - calculate the average. - - Parameters - ---------- - temps_celsius : list - List of Celsius temperatures. - - Returns - ------- - float - Average temperature in Fahrenheit. - """ - temps_fahrenheit = [celsius_to_fahrenheit(t) - for t in temps_celsius] - return average_temperature(temps_fahrenheit) -``` +:::{literalinclude} ../examples/pure-hatch/src/examplePy/temperature.py +:language: python +::: Here's an integration test that checks how the conversion and statistics functions work together: -```python -# tests/test_temperature_integration.py -from mypackage.temperature_utils import convert_and_average - - -def test_convert_and_average(): - """ - Test that convert_and_average correctly combines conversion - and averaging. - """ - # Test with known values: [0, 10, 20] Celsius - # Should average to 10 Celsius = 50 Fahrenheit - temps_celsius = [0, 10, 20] - result = convert_and_average(temps_celsius) - assert abs(result - 50.0) < 0.01 - - # Test with different values - temps_celsius = [0, 100] - result = convert_and_average(temps_celsius) - # Average of 32 and 212 Fahrenheit = 122 - assert abs(result - 122.0) < 0.01 -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_temperature.py +:language: python +:prepend: "# tests/examplePy/test_temperature.py" +:start-at: "from examplePy" +:end-at: "assert abs(result - 122.0) < 0.01" +::: This integration test verifies that the conversion and averaging functions work together as expected in a real workflow. @@ -246,30 +130,12 @@ temperature data and returns a summary average value. An end-to-end test would provide sample data, run the entire workflow, and verify that the final output is correct. -```python -# tests/test_temperature_e2e.py -from mypackage.temperature_utils import convert_and_average - - -def test_temperature_workflow(): - """ - Test the complete temperature processing workflow. - - This end-to-end test provides sample temperature data in - Celsius, processes it through the full workflow - (conversion and averaging), and verifies the output is - correct. - """ - # Sample temperature data in Celsius - temps_celsius = [0, 10, 20] - - # Run the complete workflow - result = convert_and_average(temps_celsius) - - # Verify the output - # Average of 32, 50, and 68 Fahrenheit = 50 Fahrenheit - assert abs(result - 50.0) < 0.01 -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_temperature.py +:language: python +:prepend: "# tests/examplePy/test_temperature.py" +:start-at: "def test_temperature_workflow" +:end-at: assert +::: This end-to-end test exercises the entire user workflow: providing sample data, converting and averaging it, and verifying the output diff --git a/tests/write-tests.md b/tests/write-tests.md index adae75f7..766bef22 100644 --- a/tests/write-tests.md +++ b/tests/write-tests.md @@ -61,43 +61,18 @@ For a good introduction to testing, see [this Software Carpentry lesson](https:/ Let's say you have a Python function that adds two numbers together. -```python -def add_numbers(a: float, b: float) -> float: - """ - Add two numbers together and return the result. - - Parameters - ---------- - a : float - The first number to add. - b : float - The second number to add. - - Returns - ------- - float - The sum of the two numbers. - """ - return a + b -``` +:::{literalinclude} ../examples/pure-hatch/src/examplePy/numbers.py +:language: python +:start-at: def add_numbers +::: A test to ensure that function runs as you might expect when provided with different numbers might look like this: -```python -def test_add_numbers(): - result = add_numbers(2, 3) - assert result == 5, f"Expected 5, but got {result}" - - result2 = add_numbers(-1, 4) - assert result2 == 3, f"Expected 3, but got {result2}" - - result3 = add_numbers(0, 0) - assert result3 == 0, f"Expected 0, but got {result3}" - -test_add_numbers() - -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_numbers.py +:language: python +:start-at: def test_add_numbers +::: ```` ### 🧩🐍 How do you know what type of tests to write? From 375f150167dde4a4fd6b04360f74fb8b0949d6dc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 18:40:04 +0000 Subject: [PATCH 4/5] =?UTF-8?q?'[pre-commit.ci=20=F0=9F=A4=96]=20Apply=20c?= =?UTF-8?q?ode=20format=20tools=20to=20PR'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/pure-hatch/pyproject.toml | 2 +- examples/pure-hatch/src/examplePy/numbers.py | 2 +- examples/pure-hatch/src/examplePy/temperature.py | 2 +- examples/pure-hatch/tests/examplePy/test_numbers.py | 2 +- .../pyproject-toml-python-package-metadata.md | 12 ++++++------ tests/run-tests.md | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/pure-hatch/pyproject.toml b/examples/pure-hatch/pyproject.toml index 64649897..8efda3a7 100644 --- a/examples/pure-hatch/pyproject.toml +++ b/examples/pure-hatch/pyproject.toml @@ -86,4 +86,4 @@ development-group= [ run = "pytest {args:--cov=test --cov-report=term-missing --cov-report=xml}" [[tool.hatch.envs.test.matrix]] -python = ["3.10", "3.11", "3.12"] \ No newline at end of file +python = ["3.10", "3.11", "3.12"] diff --git a/examples/pure-hatch/src/examplePy/numbers.py b/examples/pure-hatch/src/examplePy/numbers.py index 3073b3eb..94bccf8e 100644 --- a/examples/pure-hatch/src/examplePy/numbers.py +++ b/examples/pure-hatch/src/examplePy/numbers.py @@ -15,4 +15,4 @@ def add_numbers(a: float, b: float) -> float: float The sum of the two numbers. """ - return a + b \ No newline at end of file + return a + b diff --git a/examples/pure-hatch/src/examplePy/temperature.py b/examples/pure-hatch/src/examplePy/temperature.py index 6264661d..f553f890 100644 --- a/examples/pure-hatch/src/examplePy/temperature.py +++ b/examples/pure-hatch/src/examplePy/temperature.py @@ -61,4 +61,4 @@ def convert_and_average(temps_celsius: list[float]) -> float: """ temps_fahrenheit = [celsius_to_fahrenheit(t) for t in temps_celsius] - return average_temperature(temps_fahrenheit) \ No newline at end of file + return average_temperature(temps_fahrenheit) diff --git a/examples/pure-hatch/tests/examplePy/test_numbers.py b/examples/pure-hatch/tests/examplePy/test_numbers.py index 69bd7994..f0368f3f 100644 --- a/examples/pure-hatch/tests/examplePy/test_numbers.py +++ b/examples/pure-hatch/tests/examplePy/test_numbers.py @@ -14,4 +14,4 @@ def test_add_numbers(): # test with zero result3 = add_numbers(0, 5) - assert result3 == 5, f"Expected 5, but got {result3}" \ No newline at end of file + assert result3 == 5, f"Expected 5, but got {result3}" diff --git a/package-structure-code/pyproject-toml-python-package-metadata.md b/package-structure-code/pyproject-toml-python-package-metadata.md index f4026b24..b2ec3f66 100644 --- a/package-structure-code/pyproject-toml-python-package-metadata.md +++ b/package-structure-code/pyproject-toml-python-package-metadata.md @@ -174,7 +174,7 @@ in the `pyproject.toml` file. Required dependencies are specified under the `[pr ### Optional dependencies Optional dependencies are specified under the `[project.optional-dependencies]` section and are intended to give users -options for including additional dependencies with their installation. Optional dependencies are collected together +options for including additional dependencies with their installation. Optional dependencies are collected together as a list of strings and assigned to a name: :::{literalinclude} ../examples/pure-hatch/pyproject.toml @@ -183,16 +183,16 @@ as a list of strings and assigned to a name: :end-at: docs ::: -Named dependency lists can be invoked by users on installation to include those specific dependencies on installation. -Here is an example installing the test and docs dependencies using pip: +Named dependency lists can be invoked by users on installation to include those specific dependencies on installation. +Here is an example installing the test and docs dependencies using pip: - `python -m pip install examplePy[test,docs]` -Other installation tools tend to follow a similar pattern where optional dependencies are concatenated as +Other installation tools tend to follow a similar pattern where optional dependencies are concatenated as a list to the package name. ### Dependency Groups -Dependency groups are a way to group package requirements in the `pyproject.toml` file but exclude them in the +Dependency groups are a way to group package requirements in the `pyproject.toml` file but exclude them in the project metadata when it is built. Because it is not included in the project metadadata, users will not be able to invoke dependency groups when installing from a package index such as PyPI, but they can be accessed by developers who have all the project data (e.g. through cloning a repository). Optional dependencies are intended for package @@ -207,7 +207,7 @@ Then specify dependency groups as follows: :end-before: "dev = [" ::: -One of the capabilities that dependency groups have is the ability to make composition groups. +One of the capabilities that dependency groups have is the ability to make composition groups. For example, `dev` dependency group could be composed of a `test` dependency group and a `lint` dependency group: :::{literalinclude} ../examples/pure-hatch/pyproject.toml diff --git a/tests/run-tests.md b/tests/run-tests.md index 6951e731..eb8b31f5 100644 --- a/tests/run-tests.md +++ b/tests/run-tests.md @@ -181,12 +181,12 @@ using Hatch for packaging workflows. ### Setting up Hatch environments -Hatch environments can be defined in `pyproject.toml`. These environments can be used to specify what Hatch needs to run the -tests for the package. Test dependencies can be listed individually under `[tool.hatch.envs.test]`, +Hatch environments can be defined in `pyproject.toml`. These environments can be used to specify what Hatch needs to run the +tests for the package. Test dependencies can be listed individually under `[tool.hatch.envs.test]`, but if packages have already been grouped together under a name, those groups can be listed here instead. -Using named groups keeps the dependencies in one place and avoids duplication. +Using named groups keeps the dependencies in one place and avoids duplication. -The additional tools or options to run with the tests is specified under `[tool.hatch.envs.test.scripts]`. +The additional tools or options to run with the tests is specified under `[tool.hatch.envs.test.scripts]`. :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml From 97056d1c0481356cbe1e957d0cf88c8e70688f29 Mon Sep 17 00:00:00 2001 From: gchambers11 <67167140+gchambers11@users.noreply.github.com> Date: Wed, 20 May 2026 19:03:48 -0600 Subject: [PATCH 5/5] Update package-structure-code/declare-dependencies.md Co-authored-by: Jonny Saunders --- package-structure-code/declare-dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-structure-code/declare-dependencies.md b/package-structure-code/declare-dependencies.md index 18501232..ecdf68a9 100644 --- a/package-structure-code/declare-dependencies.md +++ b/package-structure-code/declare-dependencies.md @@ -53,7 +53,7 @@ Development dependencies make it easier for contributors to work on your package There are three different types of dependencies that you will learn about on this page: 1. **Required dependencies:** These are dependencies that need to be installed for your package to work correctly in a user's environment. You add these dependencies to the `[project.dependencies]` table in your pyproject.toml file. -2. **Feature Dependencies:** These are dependencies that are required if a user wants to access additional functionality (that is not core) to your package. Store these in the `[project.project.optional-dependencies]` table or your pyproject.toml file. +2. **Feature Dependencies:** These are dependencies that are required if a user wants to access additional functionality (that is not core) to your package. Store these in the `[project.optional-dependencies]` table or your pyproject.toml file. 3. **Development Dependencies:** These dependencies are required if someone wants to develop or work on your package. These include instance linters, testing tools like pytest and mypy are examples of development dependencies. Store these in the `[project.dependency.groups]` table or your pyproject.toml file. :::{tip}