diff --git a/README.rst b/README.rst
index 6f5c925..e8c0126 100644
--- a/README.rst
+++ b/README.rst
@@ -9,55 +9,74 @@ Incremental is a small library that versions your Python projects.
API documentation can be found `here `_.
+.. contents::
Quick Start
-----------
-In your ``pyproject.toml``, add Incremental to your build requirements:
+Using setuptools
+~~~~~~~~~~~~~~~~
+
+Add Incremental to your ``pyproject.toml``:
.. code-block:: toml
[build-system]
- requires = ["setuptools", "incremental>=NEXT"]
+ requires = [
+ "setuptools",
+ "incremental>=NEXT", # ← Add incremental as a build dependency
+ ]
build-backend = "setuptools.build_meta"
-Specify the project's version as dynamic:
-
-.. code-block:: toml
-
[project]
name = ""
- dynamic = ["version"]
+ dynamic = ["version"] # ← Mark the version dynamic
+ dependencies = [
+ "incremental>=NEXT", # ← Depend on incremental at runtime
+ ]
+ # ...
-Remove any ``version`` line and any ``[tool.setuptools.dynamic] version = `` entry.
+ [tool.incremental] # ← Activate Incremental's setuptools plugin
-Add this empty block to activate Incremental's setuptools plugin:
-.. code-block:: toml
+It's fine if the ``[tool.incremental]`` table is empty, but it must be present.
- [tool.incremental]
+Remove any ``[project] version =`` entry and any ``[tool.setuptools.dynamic] version =`` entry.
-Install Incremental to your local environment with ``pip install incremental[scripts]``.
-Then run ``python -m incremental.update --create``.
-It will create a file in your package named ``_version.py`` and look like this:
+Next, `initialize the project`_.
-.. code:: python
+Using Hatchling
+~~~~~~~~~~~~~~~
- from incremental import Version
-
- __version__ = Version("", 24, 1, 0)
- __all__ = ["__version__"]
+If you're using `Hatchling `_ to package your project,
+activate Incremental's Hatchling plugin by altering your ``pyproject.toml``:
+.. code:: toml
-Then, so users of your project can find your version, in your root package's ``__init__.py`` add:
+ [build-system]
+ requires = [
+ "hatchling",
+ "incremental>=NEXT", # ← Add incremental as a build dependency
+ ]
+ build-backend = "hatchling.build"
-.. code:: python
+ [project]
+ name = ""
+ dynamic = ["version"] # ← Mark the version dynamic
+ dependencies = [
+ "incremental>=NEXT", # ← Depend on incremental at runtime
+ ]
+ # ...
- from ._version import __version__
+ [tool.hatch.version]
+ source = "incremental" # ← Activate Incremental's Hatchling plugin
+Incremental can be configured as usual in an optional ``[tool.incremental]`` table.
-Subsequent installations of your project will then use Incremental for versioning.
+The ``hatch version`` command will report the Incremental-managed version.
+Use the ``python -m incremental.update`` command to change the version (setting it with ``hatch version`` is not supported).
+Next, `initialize the project`_.
Using ``setup.py``
~~~~~~~~~~~~~~~~~~
@@ -74,7 +93,34 @@ Add this to your ``setup()`` call, removing any other versioning arguments:
...
}
-Then proceed with the ``incremental.update`` command above.
+Then `initialize the project`_.
+
+
+Initialize the project
+~~~~~~~~~~~~~~~~~~~~~~
+
+Install Incremental to your local environment with ``pip install incremental[scripts]``.
+Then run ``python -m incremental.update --create``.
+It will create a file in your package named ``_version.py`` like this:
+
+.. code:: python
+
+ from incremental import Version
+
+ __version__ = Version("", 24, 1, 0)
+ __all__ = ["__version__"]
+
+
+Then, so users of your project can find your version, in your root package's ``__init__.py`` add:
+
+.. code:: python
+
+ from ._version import __version__
+
+
+Subsequent installations of your project will then use Incremental for versioning.
+
+
Incremental Versions
--------------------
@@ -90,7 +136,7 @@ It is made up of the following elements (which are given during instantiation):
You can extract a PEP-440 compatible version string by using the ``.public()`` method, which returns a ``str`` containing the full version. This is the version you should provide to users, or publicly use. An example output would be ``"13.2.0"``, ``"17.1.2dev1"``, or ``"18.8.0rc2"``.
-Calling ``repr()`` with a ``Version`` will give a Python-source-code representation of it, and calling ``str()`` with a ``Version`` will provide a string similar to ``'[Incremental, version 16.10.1]'``.
+Calling ``repr()`` with a ``Version`` will give a Python-source-code representation of it, and calling ``str()`` on a ``Version`` produces a string like ``'[Incremental, version 16.10.1]'``.
Updating
diff --git a/pyproject.toml b/pyproject.toml
index d80bd8d..193e54a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,6 +16,7 @@ maintainers = [
classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
+ "Framework :: Hatch",
"Framework :: Setuptools Plugin",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
@@ -36,9 +37,6 @@ dependencies = [
scripts = [
"click>=6.0",
]
-mypy = [
- "mypy==0.812",
-]
[project.urls]
Homepage = "https://github.com/twisted/incremental"
@@ -50,6 +48,8 @@ Changelog = "https://github.com/twisted/incremental/blob/trunk/NEWS.rst"
use_incremental = "incremental:_get_distutils_version"
[project.entry-points."setuptools.finalize_distribution_options"]
incremental = "incremental:_get_setuptools_version"
+[project.entry-points.hatch]
+incremental = "incremental._hatch"
[tool.incremental]
diff --git a/requirements_mypy.in b/requirements_mypy.in
new file mode 100644
index 0000000..1c3f8f8
--- /dev/null
+++ b/requirements_mypy.in
@@ -0,0 +1,3 @@
+mypy==0.812
+twisted
+hatchling # for types
diff --git a/requirements_mypy.txt b/requirements_mypy.txt
new file mode 100644
index 0000000..9228c07
--- /dev/null
+++ b/requirements_mypy.txt
@@ -0,0 +1,51 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# pip-compile --output-file=requirements_mypy.txt requirements_mypy.in
+#
+attrs==23.2.0
+ # via
+ # automat
+ # twisted
+automat==22.10.0
+ # via twisted
+constantly==23.10.4
+ # via twisted
+hatchling==1.25.0
+ # via -r requirements_mypy.in
+hyperlink==21.0.0
+ # via twisted
+idna==3.7
+ # via hyperlink
+incremental==22.10.0
+ # via twisted
+mypy==0.812
+ # via -r requirements_mypy.in
+mypy-extensions==0.4.4
+ # via mypy
+packaging==24.1
+ # via hatchling
+pathspec==0.12.1
+ # via hatchling
+pluggy==1.5.0
+ # via hatchling
+six==1.16.0
+ # via automat
+tomli==2.0.1
+ # via hatchling
+trove-classifiers==2024.7.2
+ # via hatchling
+twisted==24.3.0
+ # via -r requirements_mypy.in
+typed-ast==1.4.3
+ # via mypy
+typing-extensions==4.12.2
+ # via
+ # mypy
+ # twisted
+zope-interface==6.4.post2
+ # via twisted
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools
diff --git a/requirements_tests.in b/requirements_tests.in
new file mode 100644
index 0000000..8e00e35
--- /dev/null
+++ b/requirements_tests.in
@@ -0,0 +1,5 @@
+build
+coverage
+coverage-p
+twisted
+hatch
diff --git a/requirements_tests.txt b/requirements_tests.txt
new file mode 100644
index 0000000..7d812d6
--- /dev/null
+++ b/requirements_tests.txt
@@ -0,0 +1,153 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# pip-compile --output-file=requirements_tests.txt requirements_tests.in
+#
+anyio==4.4.0
+ # via httpx
+attrs==23.2.0
+ # via
+ # automat
+ # twisted
+automat==22.10.0
+ # via twisted
+backports-tarfile==1.2.0
+ # via jaraco-context
+build==1.2.1
+ # via -r requirements_tests.in
+certifi==2024.7.4
+ # via
+ # httpcore
+ # httpx
+cffi==1.16.0
+ # via cryptography
+click==8.1.7
+ # via
+ # hatch
+ # userpath
+constantly==23.10.4
+ # via twisted
+coverage==7.5.4
+ # via
+ # -r requirements_tests.in
+ # coverage-p
+coverage-p==24.7.0
+ # via -r requirements_tests.in
+cryptography==42.0.8
+ # via secretstorage
+distlib==0.3.8
+ # via virtualenv
+exceptiongroup==1.2.1
+ # via anyio
+filelock==3.15.4
+ # via virtualenv
+h11==0.14.0
+ # via httpcore
+hatch==1.12.0
+ # via -r requirements_tests.in
+hatchling==1.25.0
+ # via hatch
+httpcore==1.0.5
+ # via httpx
+httpx==0.27.0
+ # via hatch
+hyperlink==21.0.0
+ # via
+ # hatch
+ # twisted
+idna==3.7
+ # via
+ # anyio
+ # httpx
+ # hyperlink
+importlib-metadata==8.0.0
+ # via keyring
+incremental==22.10.0
+ # via twisted
+jaraco-classes==3.4.0
+ # via keyring
+jaraco-context==5.3.0
+ # via keyring
+jaraco-functools==4.0.1
+ # via keyring
+jeepney==0.8.0
+ # via
+ # keyring
+ # secretstorage
+keyring==25.2.1
+ # via hatch
+markdown-it-py==3.0.0
+ # via rich
+mdurl==0.1.2
+ # via markdown-it-py
+more-itertools==10.3.0
+ # via
+ # jaraco-classes
+ # jaraco-functools
+packaging==24.1
+ # via
+ # build
+ # hatch
+ # hatchling
+pathspec==0.12.1
+ # via hatchling
+pexpect==4.9.0
+ # via hatch
+platformdirs==4.2.2
+ # via
+ # hatch
+ # virtualenv
+pluggy==1.5.0
+ # via hatchling
+ptyprocess==0.7.0
+ # via pexpect
+pycparser==2.22
+ # via cffi
+pygments==2.18.0
+ # via rich
+pyproject-hooks==1.1.0
+ # via build
+rich==13.7.1
+ # via hatch
+secretstorage==3.3.3
+ # via keyring
+shellingham==1.5.4
+ # via hatch
+six==1.16.0
+ # via automat
+sniffio==1.3.1
+ # via
+ # anyio
+ # httpx
+tomli==2.0.1
+ # via
+ # build
+ # hatchling
+tomli-w==1.0.0
+ # via hatch
+tomlkit==0.13.0
+ # via hatch
+trove-classifiers==2024.7.2
+ # via hatchling
+twisted==24.3.0
+ # via -r requirements_tests.in
+typing-extensions==4.12.2
+ # via
+ # anyio
+ # twisted
+userpath==1.9.2
+ # via hatch
+uv==0.2.24
+ # via hatch
+virtualenv==20.26.3
+ # via hatch
+zipp==3.19.2
+ # via importlib-metadata
+zope-interface==6.4.post2
+ # via twisted
+zstandard==0.22.0
+ # via hatch
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools
diff --git a/src/incremental/__init__.py b/src/incremental/__init__.py
index d804924..62bcf88 100644
--- a/src/incremental/__init__.py
+++ b/src/incremental/__init__.py
@@ -400,7 +400,7 @@ def _get_setuptools_version(dist): # type: (_Distribution) -> None
[1]: https://setuptools.pypa.io/en/latest/userguide/extension.html#customizing-distribution-options
"""
config = _load_pyproject_toml("./pyproject.toml")
- if not config:
+ if not config or not config.has_tool_incremental:
return
dist.metadata.version = _existing_version(config.path).public()
@@ -448,20 +448,26 @@ def _load_toml(f): # type: (BinaryIO) -> Any
@dataclass(frozen=True)
class _IncrementalConfig:
"""
- @ivar package: The package name, capitalized as in the package metadata.
+ Configuration loaded from a ``pyproject.toml`` file.
+ """
- @ivar path: Path to the package root
+ has_tool_incremental: bool
+ """
+ Does the pyproject.toml file contain a [tool.incremental]
+ section? This indicates that the package has explicitly
+ opted-in to Incremental versioning.
"""
package: str
+ """The package name, capitalized as in the package metadata."""
+
path: str
+ """Path to the package root"""
def _load_pyproject_toml(toml_path): # type: (str) -> Optional[_IncrementalConfig]
"""
- Does the pyproject.toml file contain a [tool.incremental]
- section? This indicates that the package has opted-in to Incremental
- versioning.
+ Load Incremental configuration from a ``pyproject.toml``
If the [tool.incremental] section is empty we take the project name
from the [project] section. Otherwise we require only a C{name} key
@@ -474,16 +480,9 @@ def _load_pyproject_toml(toml_path): # type: (str) -> Optional[_IncrementalConf
except FileNotFoundError:
return None
- if "tool" not in data:
- return None
- if "incremental" not in data["tool"]:
- return None
+ tool_incremental = _extract_tool_incremental(data)
- tool_incremental = data["tool"]["incremental"]
- if not isinstance(tool_incremental, dict):
- raise ValueError("[tool.incremental] must be a table")
-
- if tool_incremental == {}:
+ if tool_incremental is None or tool_incremental == {}:
try:
package = data["project"]["name"]
except KeyError:
@@ -509,11 +508,27 @@ def _load_pyproject_toml(toml_path): # type: (str) -> Optional[_IncrementalConf
)
return _IncrementalConfig(
+ has_tool_incremental=tool_incremental is not None,
package=package,
path=_findPath(os.path.dirname(toml_path), package),
)
+def _extract_tool_incremental(data): # type: (Dict[str, object]) -> Optional[Dict[str, object]]
+ if "tool" not in data:
+ return None
+ if not isinstance(data["tool"], dict):
+ raise ValueError("[tool] must be a table")
+ if "incremental" not in data["tool"]:
+ return None
+
+ tool_incremental = data["tool"]["incremental"]
+ if not isinstance(tool_incremental, dict):
+ raise ValueError("[tool.incremental] must be a table")
+
+ return tool_incremental
+
+
from ._version import __version__ # noqa: E402
diff --git a/src/incremental/_hatch.py b/src/incremental/_hatch.py
new file mode 100644
index 0000000..8d677c4
--- /dev/null
+++ b/src/incremental/_hatch.py
@@ -0,0 +1,37 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import os
+import shlex
+from typing import Any, Dict, List, Type, TypedDict
+
+from hatchling.version.source.plugin.interface import VersionSourceInterface
+from hatchling.plugin import hookimpl
+
+from incremental import _load_pyproject_toml, _existing_version
+
+
+class _VersionData(TypedDict):
+ version: str
+
+
+class IncrementalVersionSource(VersionSourceInterface):
+ PLUGIN_NAME = "incremental"
+
+ def get_version_data(self) -> _VersionData: # type: ignore[override]
+ path = os.path.join(self.root, "./pyproject.toml")
+ config = _load_pyproject_toml(path)
+ assert config is not None, "Failed to read {}".format(path)
+ return {"version": _existing_version(config.path).public()}
+
+ def set_version(self, version: str, version_data: Dict[Any, Any]) -> None:
+ raise NotImplementedError(
+ f"Run `python -m incremental.version --newversion"
+ f" {shlex.quote(version)}` to set the version.\n\n"
+ f" See `python -m incremental.version --help` for more options."
+ )
+
+
+@hookimpl
+def hatch_register_version_source() -> List[Type[VersionSourceInterface]]:
+ return [IncrementalVersionSource]
diff --git a/src/incremental/newsfragments/93.feature b/src/incremental/newsfragments/93.feature
new file mode 100644
index 0000000..85e76cc
--- /dev/null
+++ b/src/incremental/newsfragments/93.feature
@@ -0,0 +1 @@
+Incremental now provides a read-only `Hatchling version source plugin `_.
diff --git a/src/incremental/tests/test_pyproject.py b/src/incremental/tests/test_pyproject.py
index de3b269..df5b0ca 100644
--- a/src/incremental/tests/test_pyproject.py
+++ b/src/incremental/tests/test_pyproject.py
@@ -4,6 +4,8 @@
"""Test handling of ``pyproject.toml`` configuration"""
import os
+from typing import cast, Optional, Union
+from pathlib import Path
from twisted.trial.unittest import TestCase
from incremental import _load_pyproject_toml, _IncrementalConfig
@@ -12,106 +14,132 @@
class VerifyPyprojectDotTomlTests(TestCase):
"""Test the `_load_pyproject_toml` helper function"""
- def test_fileNotFound(self):
+ def _loadToml(
+ self, toml: str, *, path: Union[Path, str, None] = None
+ ) -> Optional[_IncrementalConfig]:
"""
- Verification fails when no ``pyproject.toml`` file exists.
+ Read a TOML snipped from a temporary file with `_load_pyproject_toml`
+
+ @param toml: TOML content of the temporary file
+
+ @param path: Path to which the TOML is written
"""
- path = os.path.join(self.mktemp(), "pyproject.toml")
- self.assertFalse(_load_pyproject_toml(path))
+ path_: str
+ if path is None:
+ path_ = self.mktemp() # type: ignore
+ else:
+ path_ = str(path)
+
+ with open(path_, "w") as f:
+ f.write(toml)
+
+ try:
+ return _load_pyproject_toml(path_)
+ except Exception as e:
+ if hasattr(e, "add_note"):
+ e.add_note( # type: ignore[attr-defined]
+ f"While loading:\n\n{toml}"
+ ) # pragma: no coverage
+ raise
- def test_noToolIncrementalSection(self):
+ def test_fileNotFound(self):
"""
- Verification fails when there isn't a ``[tool.incremental]`` section.
+ An absent ``pyproject.toml`` file produces no result
"""
- path = self.mktemp()
- for toml in [
- "\n",
- "[tool]\n",
- "[tool.notincremental]\n",
- '[project]\nname = "foo"\n',
- ]:
- with open(path, "w") as f:
- f.write(toml)
- self.assertIsNone(_load_pyproject_toml(path))
+ path = os.path.join(cast(str, self.mktemp()), "pyproject.toml")
+ self.assertIsNone(_load_pyproject_toml(path))
def test_nameMissing(self):
"""
`ValueError` is raised when ``[tool.incremental]`` is present but
he project name isn't available.
"""
- path = self.mktemp()
for toml in [
+ "\n",
+ "[tool.notincremental]\n",
"[tool.incremental]\n",
"[project]\n[tool.incremental]\n",
]:
- with open(path, "w") as f:
- f.write(toml)
-
- self.assertRaises(ValueError, _load_pyproject_toml, path)
+ self.assertRaises(ValueError, self._loadToml, toml)
def test_nameInvalid(self):
"""
`TypeError` is raised when the project name isn't a string.
"""
- path = self.mktemp()
for toml in [
"[tool.incremental]\nname = -1\n",
"[tool.incremental]\n[project]\nname = 1.0\n",
]:
- with open(path, "w") as f:
- f.write(toml)
-
- self.assertRaises(TypeError, _load_pyproject_toml, path)
+ self.assertRaises(TypeError, self._loadToml, toml)
def test_toolIncrementalInvalid(self):
"""
- `ValueError` is raised when the ``[tool.incremental]`` section isn't
- a dict.
+ `ValueError` is raised when the ``[tool]`` or ``[tool.incremental]``
+ isn't a table.
"""
- path = self.mktemp()
for toml in [
+ "tool = false\n",
"[tool]\nincremental = false\n",
"[tool]\nincremental = 123\n",
"[tool]\nincremental = null\n",
]:
- with open(path, "w") as f:
- f.write(toml)
-
- self.assertRaises(ValueError, _load_pyproject_toml, path)
+ self.assertRaises(ValueError, self._loadToml, toml)
def test_toolIncrementalUnexpecteKeys(self):
"""
Raise `ValueError` when the ``[tool.incremental]`` section contains
keys other than ``"name"``
"""
- path = self.mktemp()
for toml in [
"[tool.incremental]\nfoo = false\n",
'[tool.incremental]\nname = "OK"\nother = false\n',
]:
- with open(path, "w") as f:
- f.write(toml)
-
- self.assertRaises(ValueError, _load_pyproject_toml, path)
+ self.assertRaises(ValueError, self._loadToml, toml)
- def test_ok(self):
+ def test_setuptoolsOptIn(self):
"""
The package has opted-in to Incremental version management when
- the ``[tool.incremental]`` section is an empty dict.
+ the ``[tool.incremental]`` section is a dict. The project name
+ is taken from ``[tool.incremental] name`` or ``[project] name``.
"""
- root = self.mktemp()
- path = os.path.join(root, "src", "foo")
- os.makedirs(path)
- toml_path = os.path.join(root, "pyproject.toml")
+ root = Path(self.mktemp())
+ pkg = root / "src" / "foo"
+ pkg.mkdir(parents=True)
for toml in [
'[project]\nname = "Foo"\n[tool.incremental]\n',
'[tool.incremental]\nname = "Foo"\n',
]:
- with open(toml_path, "w") as f:
- f.write(toml)
+ config = self._loadToml(toml, path=root / "pyproject.toml")
self.assertEqual(
- _load_pyproject_toml(toml_path),
- _IncrementalConfig(package="Foo", path=path),
+ config,
+ _IncrementalConfig(
+ has_tool_incremental=True,
+ package="Foo",
+ path=str(pkg),
+ ),
)
+
+ def test_noToolIncrementalSection(self):
+ """
+ The ``has_tool_incremental`` flag is false when there
+ isn't a ``[tool.incremental]`` section.
+ """
+ root = Path(self.mktemp())
+ pkg = root / "foo"
+ pkg.mkdir(parents=True)
+
+ config = self._loadToml(
+ '[project]\nname = "foo"\n',
+ path=root / "pyproject.toml",
+ )
+
+ self.assertEqual(
+ config,
+ _IncrementalConfig(
+ has_tool_incremental=False,
+ package="foo",
+ path=str(pkg),
+ ),
+ )
diff --git a/tests/example_hatchling/example_hatchling/__init__.py b/tests/example_hatchling/example_hatchling/__init__.py
new file mode 100644
index 0000000..26d23ba
--- /dev/null
+++ b/tests/example_hatchling/example_hatchling/__init__.py
@@ -0,0 +1,3 @@
+from ._version import __version__
+
+__all__ = ["__version__"]
diff --git a/tests/example_hatchling/example_hatchling/_version.py b/tests/example_hatchling/example_hatchling/_version.py
new file mode 100644
index 0000000..6f1e6b0
--- /dev/null
+++ b/tests/example_hatchling/example_hatchling/_version.py
@@ -0,0 +1,11 @@
+"""
+Provides example_hatchling version information.
+"""
+
+# This file is auto-generated! Do not edit!
+# Use `python -m incremental.update example_hatchling` to change this file.
+
+from incremental import Version
+
+__version__ = Version("example_hatchling", 24, 7, 0)
+__all__ = ["__version__"]
diff --git a/tests/example_hatchling/pyproject.toml b/tests/example_hatchling/pyproject.toml
new file mode 100644
index 0000000..fa01745
--- /dev/null
+++ b/tests/example_hatchling/pyproject.toml
@@ -0,0 +1,16 @@
+[build-system]
+requires = [
+ "hatchling",
+ "incremental",
+]
+build-backend = "hatchling.build"
+
+[project]
+name = "example_hatchling"
+dependencies = [
+ "incremental",
+]
+dynamic = ["version"]
+
+[tool.hatch.version]
+source = "incremental"
diff --git a/tests/test_examples.py b/tests/test_examples.py
index 4004850..5d83e3c 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -13,6 +13,8 @@
from twisted.python.filepath import FilePath
from twisted.trial.unittest import TestCase
+from incremental import Version
+
TEST_DIR = FilePath(os.path.abspath(os.path.dirname(__file__)))
@@ -47,3 +49,51 @@ def test_setuptools_version(self):
self.assertEqual(example_setuptools.__version__.base(), "2.3.4")
self.assertEqual(metadata.version("example_setuptools"), "2.3.4")
+
+ def test_hatchling_get_version(self):
+ """
+ example_hatchling has a version of 24.7.0.
+ """
+ build_and_install(TEST_DIR.child("example_hatchling"))
+
+ import example_hatchling
+
+ self.assertEqual(
+ example_hatchling.__version__,
+ Version("example_hatchling", 24, 7, 0),
+ )
+ self.assertEqual(metadata.version("example_hatchling"), "24.7.0")
+
+ def test_hatch_version(self):
+ """
+ The ``hatch version`` command reports the version of a package
+ packaged with hatchling.
+ """
+ proc = run(
+ ["hatch", "version"],
+ cwd=TEST_DIR.child("example_hatchling").path,
+ check=True,
+ capture_output=True,
+ )
+
+ self.assertEqual(proc.stdout, b"24.7.0\n")
+
+ def test_hatch_version_set(self):
+ """
+ The ``hatch version`` command can't set the version so its output
+ tells the user to use ``incremental.update`` instead.
+ """
+ proc = run(
+ ["hatch", "--no-color", "version", "24.8.0"],
+ cwd=TEST_DIR.child("example_hatchling").path,
+ check=False,
+ capture_output=True,
+ )
+ suggestion = b"Run `python -m incremental.version --newversion 24.8.0` to set the version."
+
+ self.assertGreater(proc.returncode, 0)
+ self.assertRegex(
+ proc.stdout,
+ # Hatch may wrap the output, so we are flexible about the specifics of whitespace.
+ suggestion.replace(b".", rb"\.").replace(b" ", b"\\s+"),
+ )
diff --git a/tox.ini b/tox.ini
index 948d0fb..c1d2fc7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -14,15 +14,16 @@ envlist =
[testenv]
wheel = true
wheel_build_env = build
+skip_install =
+ pindeps: true
deps =
- tests: build
- tests: coverage
- tests: coverage-p
- tests,mypy: twisted
+ tests: -rrequirements_tests.txt
+ mypy: -rrequirements_mypy.txt
apidocs: pydoctor
lint: pre-commit
+ pindeps: pip-tools
extras =
- mypy: mypy,scripts
+ mypy: scripts
tests: scripts
setenv =
@@ -52,6 +53,9 @@ commands =
mypy: mypy src
+ pindeps: pip-compile -o requirements_tests.txt requirements_tests.in {posargs}
+ pindeps: pip-compile -o requirements_mypy.txt requirements_mypy.in {posargs}
+
[testenv:build]
# empty environment to build universal wheel once per tox invocation