Skip to content

Commit be4cf53

Browse files
authored
fix coversioning tests and document (#11)
Hatch only runs hooks if there's something dynamic in the project table, so add an arbitrary element to the project table in the test pyproject.tomls. Also, add some docs about making sure dynamic is in there, and add some little stuff found from online examples.
1 parent c804887 commit be4cf53

File tree

6 files changed

+104
-4
lines changed

6 files changed

+104
-4
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Development Documentation
2+
3+
`hatch-dependency-coversion`is a python package developed using [Hatch](https://github.com/pypa/hatch) as its environment manager, task runner, and build frontend. Generally, any command that inspects or interacts with the package source is done through hatch.
4+
5+
## Contributing
6+
7+
Contributions should come through pull request. If done via a fork, a maintainer will run the CI checks after a quick review. Open source contributions are welcome!
8+
9+
Pull requests should contain
10+
- A descriptive title
11+
- that is prefixed with `hatch-dependency-coversion:`, since this repository has multiple packages
12+
- A descriptive body that notes what the problem/missing feature is that the PR fixes and how the PR fixes it
13+
- A note on how the PR should be tested, if testing is required, and descriptions of how you tested it
14+
- A news fragment in `changelog.d` that adheres to [towncrier news fragment format](https://towncrier.readthedocs.io/en/stable/tutorial.html#creating-news-fragments) describing your change
15+
- If you're not already in it and you want to be, an addition of yourself to CONTRIBUTORS.md
16+
17+
## Linting/formatting/typechecking
18+
19+
Static analysis tools are run from the default hatch environment. Typechecking is via [mypy](http://mypy-lang.org/), linting and formatting is via [ruff](https://github.com/astral-sh/ruff). You can run these commands with `hatch run` without further qualification:
20+
- Typecheck: `hatch run check`
21+
- Format: `hatch run format`
22+
- Lint: `hatch run lint`
23+
- Auto lint fixes: `hatch run lint --fix`
24+
25+
These all must pass in CI before a PR can be merged.
26+
27+
## Tests
28+
29+
Tests are defined both in the default environment (in which case they will run just in whatever python environment and dependency set you happen to have installed) and in a special `test` environment endowed with matrix definitions for multiple python versions and multiple versions of the `hatch-vcs` plugin that `hatch-dependency-coversion` extends.
30+
31+
- Run quick tests: `hatch run test`
32+
- Run full test matrix: `hatch run test:test`
33+
34+
Tests must pass in CI before a PR can be merged.
35+
36+
## Maintenance and Releasing
37+
38+
Changelogs are generated using [towncrier](https://towncrier.readthedocs.io/en/stable/index.html). When opening a PR that has a newsworthy change, that PR should include a news fragment in `changelog.d`. Changelog generation happens during the release flow, which is automated via github actions that can be run by project maintainers.
39+
40+

hatch-dependency-coversion/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ dependencies = [
3131
# 0.0.0 is chosen at random and will be overwritten
3232
"my-dependency==0.0.0"
3333
]
34+
# the dynamic entry must be present and the array must not be empty, otherwise hatch
35+
# will not invoke the plugin. however, something in dynamic cannot be in the rest of
36+
# the metadata, and only top-level keys can appear here - so if you did
37+
# dynamic = ['dependencies'], then it (a) would not be true since it's not all the
38+
# dependencies, just the versions of some of them and (b) you couldn't have a
39+
# dependencies entry in the project table. so put an arbitrary string here, and the
40+
# name of the plugin is as good an arbitrary string as any.
41+
dynamic = ['dependency-coversioning']
3442
[tool.hatch.metadata.hooks.dependency-coversion]
3543
# this list contains the names of dependencies to override
3644
override-versions-of = ["my-dependency"]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Added new project hatch-dependency-coversion
2+
3+
`hatch-dependency-coversion` is a plugin for [Hatch](https://github.com/pypa/hatch) that that allows you to rewrite the versions in selected dependency specifiers to be exactly the same as the current version of the project configured in `pyproject.toml`'s `[project]` table, requiring exact coversioning of those dependencies with the project. This is useful for projects that are developed in lockstep but distributed as independent python packages rather than as subcomponents of the same namespace package.
4+
5+
To install `hatch-dependency-coversion`, list it as a PEP-517 dependency in your `pyproject.toml`'s `build-system` section alongside `hatchling` (you have to be using `hatchling` as your builder for this plugin to work):
6+
7+
```
8+
[build-system]
9+
requires = ["hatchling", "hatch-dependency-coversion"]
10+
build-backend = "hatchling.build"
11+
```
12+
13+
From there, you can configure the plugin by
14+
- adding a `dynamic` entry to project metadata (containing an arbitrary string, such as the plugin name - the `dynamic` entry just needs to exist for hatch to call the plugin:
15+
```toml
16+
[project]
17+
dynamic = ['hatch-dependency-coversion']
18+
```
19+
- The plugin name is `dependency-coversion`, and you set which dependency versions should be controlled with the config element `override-versions-of`, which should be an array of package names of dependencies from your `project.dependencies`:
20+
``` toml
21+
22+
[project]
23+
dependencies = ['my-favorite-package==0.1.0']
24+
25+
[tool.hatch.build.hooks.dependency-coversion]
26+
override-versions-of=['my-favorite-package']
27+
```

hatch-dependency-coversion/src/hatch_dependency_coversion/hooks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
"""Registers a hatch metadata hook for altering dependency versions."""
66

7+
from typing import Type
78
from hatchling.plugin import hookimpl
89
from .metadata_hook import DependencyCoversionMetadataHook
910

1011

1112
@hookimpl
12-
def hatch_register_metadata_hook():
13+
def hatch_register_metadata_hook() -> Type[DependencyCoversionMetadataHook]:
1314
return DependencyCoversionMetadataHook

hatch-dependency-coversion/src/hatch_dependency_coversion/metadata_hook.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
"""A metadata hook for hatchling that can force dependencies to be coversioned."""
66
from __future__ import annotations
7+
import inspect
78
from typing import Any
89

910
from packaging.requirements import Requirement, SpecifierSet
@@ -14,6 +15,8 @@
1415

1516
class DependencyCoversionMetadataHook(MetadataHookInterface):
1617
PLUGIN_NAME = _PLUGIN_NAME
18+
root: str
19+
config: dict
1720

1821
def _maybe_update_dep(
1922
self, depspec: str, version: str, which_dependencies: list[str]
@@ -38,8 +41,23 @@ def _update_dependency_versions(
3841

3942
def update(self, metadata: dict[str, Any]) -> None:
4043
"""Update metadata for coversioning."""
44+
# this is from https://github.com/flying-sheep/hatch-docstring-description/blob/main/src/hatch_docstring_description/read_description.py
45+
# and would prevent surprise recursions, and if that author is worried about it then so am I
46+
if ("update", __file__) in (
47+
(frame.function, frame.filename) for frame in inspect.stack()[1:]
48+
):
49+
return
50+
if "override-versions-of" not in self.config:
51+
return
52+
if not isinstance(self.config["override-versions-of"], list):
53+
raise RuntimeError(
54+
"tool.hatch.metadata.hooks.dependency-coversion.override-versions-of must be an array of strings"
55+
)
56+
override_of: list[str] = self.config["override-versions-of"]
4157
metadata["dependencies"] = self._update_dependency_versions(
42-
metadata.get("dependencies", []),
43-
metadata["version"],
44-
self.config.get("override-versions-of", []),
58+
metadata.get("dependencies", []), metadata["version"], override_of
4559
)
60+
61+
def get_known_classifiers(self) -> list[str]:
62+
"""Dummy function that is part of the hook interface."""
63+
return []

hatch-dependency-coversion/tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def unconfigured_project(tmp_path: Path, static_requirements: list[str]) -> Path
2929
name = "unconfigured-project"
3030
version = "0.1.0"
3131
dependencies = [{','.join([f'"{requirement}"' for requirement in static_requirements])}]
32+
dynamic = ['dependency-coversion']
3233
[tool.hatch.metadata.hooks.dependency-coversion]
3334
"""
3435
)
@@ -52,6 +53,7 @@ def requests_zero_coversions_project(
5253
name = "zero-coversions-project"
5354
version = "0.1.0"
5455
dependencies = [{','.join([f'"{requirement}"' for requirement in static_requirements])}]
56+
dynamic = ['dependency-coversion']
5557
[tool.hatch.metadata.hooks.dependency-coversion]
5658
override-versions-of=[]
5759
"""
@@ -76,6 +78,7 @@ def requests_coversion_of_open_version_project(
7678
name = "coversion-of-open-version-project"
7779
version = "0.1.0"
7880
dependencies = [{','.join([f'"{requirement}"' for requirement in static_requirements])}]
81+
dynamic = ['dependency-coversion']
7982
[tool.hatch.metadata.hooks.dependency-coversion]
8083
override-versions-of=["dependency1"]
8184
"""
@@ -100,6 +103,7 @@ def requests_coversion_of_specified_version_project(
100103
name = "coversion-of-specified-version-project"
101104
version = "0.1.0"
102105
dependencies = [{','.join([f'"{requirement}"' for requirement in static_requirements])}]
106+
dynamic = ['dependency-coversion']
103107
[tool.hatch.metadata.hooks.dependency-coversion]
104108
override-versions-of=["dependency2"]
105109
"""
@@ -124,6 +128,7 @@ def requests_coversion_of_marked_version_project(
124128
name = "coversion-of-marked-version-project"
125129
version = "0.1.0"
126130
dependencies = [{','.join([f'"{requirement}"' for requirement in static_requirements])}]
131+
dynamic = ['dependency-coversion']
127132
[tool.hatch.metadata.hooks.dependency-coversion]
128133
override-versions-of=["dependency3"]
129134
"""
@@ -146,6 +151,7 @@ def requests_multiple_project(tmp_path: Path, static_requirements: list[str]) ->
146151
name = "coversion-of-multiple-project"
147152
version = "0.1.0"
148153
dependencies = [{','.join([f'"{requirement}"' for requirement in static_requirements])}]
154+
dynamic = ['dependency-coversion']
149155
[tool.hatch.metadata.hooks.dependency-coversion]
150156
override-versions-of=["dependency2", "dependency1", "dependency3"]
151157
"""

0 commit comments

Comments
 (0)