Giter Club home page Giter Club logo

versioningit's Introduction

Project Status: Active — The project has reached a stable, usable
state and is being actively developed. CI Status coverage pyversions Conda Version MIT License

GitHub | PyPI | Documentation | Issues | Changelog

versioningitVersioning It with your Version In Git

versioningit is yet another Python packaging plugin for automatically determining your package's version based on your version control repository's tags. Unlike others, it allows easy customization of the version format and even lets you easily override the separate functions used for version extraction & calculation.

Features:

  • Works with both setuptools and Hatch
  • Installed & configured through 518's pyproject.toml
  • Supports Git, modern Git archives, and Mercurial
  • Formatting of the final version uses format template strings, with fields for basic VCS information and separate template strings for distanced vs. dirty vs. distanced-and-dirty repository states
  • Can optionally write the final version and other details to a file for loading at runtime
  • Provides custom hooks for inserting the final version and other details into a source file at build time
  • The individual methods for VCS querying, tag-to-version calculation, version bumping, version formatting, and writing the version to a file can all be customized using either functions defined alongside one's project code or via publicly-distributed entry points
  • Can alternatively be used as a library for use in setup.py or the like, in case you don't want to or can't configure it via pyproject.toml
  • The only thing it does is calculate your version and optionally write it to a file; there's no overriding of your sdist contents based on what's in your Git repository, especially not without a way to turn it off, because that would just be rude.

Installation & Setup

versioningit requires Python 3.8 or higher. Just use pip for Python 3 (You have pip, right?) to install versioningit and its dependencies:

python3 -m pip install versioningit

However, usually you won't need to install versioningit in your environment directly. Instead, you specify it in your project's pyproject.toml file in the build-system.requires key, like so:

# If using Setuptools:
[build-system]
requires = [
    "setuptools",
    "versioningit",
]
build-backend = "setuptools.build_meta"

# If using Hatch:
[build-system]
requires = [
    "hatchling",
    "versioningit",
]
build-backend = "hatchling.build"

# This setting is also required if you're using Hatch:
[tool.hatch.version]
source = "versioningit"

Then, you configure versioningit by adding a [tool.versioningit] table to your pyproject.toml. See the documentation for details, but you can get up & running with just the minimal configuration, an empty table:

[tool.versioningit]

versioningit eliminates the need to list an explicit version in setup.py, setup.cfg, or pyproject.toml (and any explicit version you do list will be ignored when using versioningit), so you should remove any such settings in order to reduce confusion.

Note: If you're specifying your project metadata via a [project] table in pyproject.toml, you need to set project.dynamic = ["version"] in order for versioningit to work.

Once you have a [tool.versioningit] table in your pyproject.toml — and once your repository has at least one tag — building your project with build or similar will result in your project's version automatically being set based on the latest tag in your Git repository. You can test your configuration and see what the resulting version will be using the versioningit command (see the documentation).

Example Configurations

One of versioningit's biggest strengths is its ability to configure the version format using placeholder strings. The default format configuration looks like this:

[tool.versioningit.format]
# Format used when there have been commits since the most recent tag:
distance = "{base_version}.post{distance}+{vcs}{rev}"
# Example formatted version: 1.2.3.post42+ge174a1f

# Format used when there are uncommitted changes:
dirty = "{base_version}+d{build_date:%Y%m%d}"
# Example formatted version: 1.2.3+d20230922

# Format used when there are both commits and uncommitted changes:
distance-dirty = "{base_version}.post{distance}+{vcs}{rev}.d{build_date:%Y%m%d}"
# Example formatted version: 1.2.3.post42+ge174a1f.d20230922

Other format configurations of interest include:

  • The default format used by setuptools_scm:

    [tool.versioningit.next-version]
    method = "smallest"
    
    [tool.versioningit.format]
    distance = "{next_version}.dev{distance}+{vcs}{rev}"
    # Example formatted version: 1.2.4.dev42+ge174a1f
    
    dirty = "{base_version}+d{build_date:%Y%m%d}"
    # Example formatted version: 1.2.3+d20230922
    
    distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.d{build_date:%Y%m%d}"
    # Example formatted version: 1.2.4.dev42+ge174a1f.d20230922
  • The format used by versioneer:

    [tool.versioningit.format]
    distance = "{base_version}+{distance}.{vcs}{rev}"
    # Example formatted version: 1.2.3+42.ge174a1f
    
    dirty = "{base_version}+{distance}.{vcs}{rev}.dirty"
    # Example formatted version: 1.2.3+42.ge174a1f.dirty
    
    distance-dirty = "{base_version}+{distance}.{vcs}{rev}.dirty"
    # Example formatted version: 1.2.3+42.ge174a1f.dirty
  • The format used by vcversioner:

    [tool.versioningit.format]
    distance = "{base_version}.post{distance}"
    # Example formatted version: 1.2.3.post42
    
    dirty = "{base_version}"
    # Example formatted version: 1.2.3
    
    distance-dirty = "{base_version}.post{distance}"
    # Example formatted version: 1.2.3.post42

versioningit's People

Contributors

abravalheri avatar dependabot[bot] avatar jenshnielsen avatar jwodder avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

versioningit's Issues

config file name can be supplied to get_version() but not to command-line program

Because we use setup.py I renamed pyproject.toml to versioningit.toml to avoid any possibility of the CICD server using the toml file to build the project.

Even though I am experiencing an import error in setup.py for the line from versioningit import get_version, I feel better about debugging it because I have the file renamed.

But there is no way to test the new file via the command-line program because it lacks an option to specify the config file (unlike get_version() which does.

Add a beginner-friendly quickstart to docs

I think we can all agree that versioningit's documentation can be a bit overwhelming for new users. The only parts of the docs that are directly aimed at new users are the "Installation & Setup" and "Example Configurations" sections from the README & docs landing page, and I fear that these sections don't convey everything a new user would want/need to know. The rest of the docs, meanwhile, are an information-dense feature reference which a new user can easily get lost in. Unfortunately, as the tool's author, I have a blind spot as to what information new users should be presented with first (and how) and what information should be left to the comprehensive reference pages.

I therefore call upon you, dear versioningit users, both experienced & new: If the docs were to gain a "quickstart" page covering (hopefully) all the basic functionality that a new user would need or want to know, what should be in it?


Things that probably belong in a quickstart:

  • A table/list of all available format fields
  • How to use the write method?
  • Useful example configurations with explanatory comments
  • ???

Things that probably don't belong in a quickstart:

  • The library API
  • Writing custom methods
  • Git archive support?
  • Mercurial support?
  • How to use the onbuild method?
  • ???

Feature request: Write version as tuple to `tool.versioningit.write` -> `file`

setuptools_scm writes the version both as a str and as a tuple (called _version_as_tuple of type tuple[int | str, ...]) similar to sys.version_info. This is especially useful when doing programmatic comparisons such as:

if sys.version_info >= (3, 8):
  …

Please consider offering a similar format when writing to tool.versioningit.write -> file. I realize versioningit's flexibility on formats might frustrate this, but one could do something like the following and probably satisfy most use cases:

import re
from typing import Iterator

# split_on allows for possible end-user configuration, if desirable
def version_as_tuple(version: str, split_on = r"\.|\+") -> tuple[int | str, ...]:
  def _parts_as_int_or_str() -> Iterator[int | str]:
    for part in re.split(split_on, version):
      try:
        yield int(part)
      except ValueError:
        yield part
  return tuple(_parts_as_int_or_str())

In what way does versioningit replace the need for (and will overwrite) the version keyword to the setup() function?

In the docs we read:

versioningit replaces the need for (and will overwrite) the version keyword to the setup() function, so you should remove any such keyword from your setup.py/setup.cfg to reduce confusion.

But it has not been my experience that the versioningit command-line program overwrites the version keyword in setup.py.

I have the following setup.py


from setuptools import setup

from versioningit import get_version

setup(
    name='mlm_sim',
    version=get_version(write=True),
    packages=['mlm_sim'],
    install_requires=[
        'requests',
    ],
)

and python setup.py --version returns a version but without overwriting anything....

In Python 3.12, a DeprecationWarning about tarfile filtering by default in Python 3.14

When using Python 3.12, a number of tests fail due to DeprecationWarnings of the following form:

DeprecationWarning: Python 3.14 will, by default, filter extracted tar archives and reject files or modify their metadata. Use the filter argument to control this behavior.

Please consider reading https://docs.python.org/3.12/library/tarfile.html#tarfile-extraction-filter and, when running on Python 3.12 or later, explicitly configuring a filter for tar archives.

Unable to create versioningit "plugin" - i.e. issues wiring methods

Hi!

I am trying to create my own versioningit "plugin", "versionista". It has its own custom format() function. My goal is to use "versionista" in other projects.

I'm following these instructions:

However, when I try to install my other project, using the same "venv" where "versionista" is installed, it fails:

$ pip install .
...
Collecting versioningit
    Using cached versioningit-3.0.0-py3-none-any.whl (37 kB)
ERROR: Could not find a version that satisfies the requirement versionista (from versions: none)
ERROR: No matching distribution found for versionista
...

My other project ("dummy"):

# pyproject.toml

[build-system]
requires = [
  "setuptools >= 61",
  "wheel",
  # "setuptools_scm[toml]"
  "versioningit",
  "versionista",  # per instrunctions?!
]
build-backend = "setuptools.build_meta"

...

[tool.versioningit.format]
# distance = "{base_version}.post{distance}+{branch}"
# distance-dirty = "{base_version}.post{distance}+{branch}.{build_date:%Y%m%d%H%M}"
# method = { module = "versionista.main", value = "format"}
method = "format"  # per instructions?!

In my "versionista" project:

# pyproject.toml

[build-system]
requires = [
  "setuptools >= 61",
  "wheel",
  "setuptools_scm[toml]"
]
build-backend = "setuptools.build_meta"

...

[options.entry_points]
# NOTE: documentation asks for the following but that doesn't work:
#    [options.entry_points]
#    versioningit.vcs =
#        foobar = mypackage.vcs:foobar_vcs
# 
# ... I guess it meant this instead?
versioningit.format = { format = "versionista.main:format" }
> pip list
Package            Version
------------------ ----------------------
build              1.0.3
dummy              0.0.0.post6+versiongit
importlib-metadata 7.0.1
packaging          23.2
pip                23.0.1
pyproject_hooks    1.0.0
setuptools         56.0.0
tomli              2.0.1
versioningit       3.0.0
versionista        0.1.dev11+dirty
zipp               3.17.0

Am I missing anything obvious?
Can I test my "versionista" plugin wired onto "versioningit" outside a build? (e.g. https://versioningit.readthedocs.io/en/stable/command.html#command)

how to "package" source distributions from PyPI distribution (e.g. for Debian)...?

Now that heudiconv released with use of versioningit, I have tried to update its debian package (packaging at https://salsa.debian.org/med-team/heudiconv/) where it would get new sources from pypi's tar.gz .

versioningit relies exclusively on the distributed .tar.gz shipping heudiconv.egg-info with PKG-INFO which has the version specified.
The "tricky" issue I encountered is that from that .tar.gz, if "derived" distribution (e.g. via python setup.py sdist) is attempted, it overwrites heudiconv.egg-info/PKG-INFO with new information containing dummy 0.0.0 version!

detailed log
lena:/tmp/heudiconv-0.11.1
$> cat heudiconv.egg-info/PKG-INFO 
Metadata-Version: 2.1
Name: heudiconv
Version: 0.11.1
Summary: Heuristic DICOM Converter
Author: HeuDiConv team and contributors
License: Apache 2.0
Classifier: Environment :: Console
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.7
Provides-Extra: tests
Provides-Extra: extras
Provides-Extra: datalad
Provides-Extra: all
License-File: LICENSE

Convert DICOM dirs based on heuristic info - HeuDiConv
uses the dcmstack package and dcm2niix tool to convert DICOM directories or
tarballs into collections of NIfTI files following pre-defined heuristic(s).

$> python3 setup.py sdist
running sdist
running egg_info
writing heudiconv.egg-info/PKG-INFO
writing dependency_links to heudiconv.egg-info/dependency_links.txt
writing entry points to heudiconv.egg-info/entry_points.txt
writing requirements to heudiconv.egg-info/requires.txt
writing top-level names to heudiconv.egg-info/top_level.txt
listing git files failed - pretending there aren't any
reading manifest file 'heudiconv.egg-info/SOURCES.txt'
adding license file 'LICENSE'
writing manifest file 'heudiconv.egg-info/SOURCES.txt'
running check
warning: Check: missing required meta-data: url

warning: Check: missing meta-data: if 'author' supplied, 'author_email' should be supplied too

creating heudiconv-0.0.0
creating heudiconv-0.0.0/heudiconv
creating heudiconv-0.0.0/heudiconv.egg-info
creating heudiconv-0.0.0/heudiconv/cli
creating heudiconv-0.0.0/heudiconv/external
creating heudiconv-0.0.0/heudiconv/external/tests
creating heudiconv-0.0.0/heudiconv/heuristics
creating heudiconv-0.0.0/heudiconv/tests
creating heudiconv-0.0.0/heudiconv/tests/data
creating heudiconv-0.0.0/heudiconv/tests/data/01-anat-scout
creating heudiconv-0.0.0/heudiconv/tests/data/01-fmap_acq-3mm
creating heudiconv-0.0.0/heudiconv/tests/data/b0dwiForFmap
copying files to heudiconv-0.0.0...
copying LICENSE -> heudiconv-0.0.0
copying README.rst -> heudiconv-0.0.0
copying pyproject.toml -> heudiconv-0.0.0
copying setup.cfg -> heudiconv-0.0.0
copying setup.py -> heudiconv-0.0.0
copying heudiconv/__init__.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/bids.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/convert.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/dicoms.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/due.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/info.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/main.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/parser.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/queue.py -> heudiconv-0.0.0/heudiconv
copying heudiconv/utils.py -> heudiconv-0.0.0/heudiconv
copying heudiconv.egg-info/PKG-INFO -> heudiconv-0.0.0/heudiconv.egg-info
copying heudiconv.egg-info/SOURCES.txt -> heudiconv-0.0.0/heudiconv.egg-info
...
copying heudiconv/tests/data/b0dwiForFmap/b0dwi_for_fmap+00001.dcm -> heudiconv-0.0.0/heudiconv/tests/data/b0dwiForFmap
copying heudiconv/tests/data/b0dwiForFmap/b0dwi_for_fmap+00002.dcm -> heudiconv-0.0.0/heudiconv/tests/data/b0dwiForFmap
copying heudiconv/tests/data/b0dwiForFmap/b0dwi_for_fmap+00003.dcm -> heudiconv-0.0.0/heudiconv/tests/data/b0dwiForFmap
Writing heudiconv-0.0.0/setup.cfg
creating dist
Creating tar archive
removing 'heudiconv-0.0.0' (and everything under it)

$> cat heudiconv.egg-info/PKG-INFO 
Metadata-Version: 2.1
Name: heudiconv
Version: 0.0.0
...

something like that is happening during building debian package distribution of heudiconv -- we get a new heudiconv.egg-info/PKG-INFO with new 0.0.0 version .

Do you see @jwodder some most legit way to preserve .egg-info/PKG-INFO if it already exists? shouldn't it be versioningit taking care about picking up known version from that file during that egg_info step somehow or producing some other persistent file in source distribution which would be picked up by python machinery somehow..?

just trying to figure out how to update debian packaging now

Support "onbuild" step in Hatch

There is preliminary support for using the "onbuild" step with Hatch in #53, but merging is currently blocked by a bug in Hatchling. Currently, using Hatch and PR #53 to build a wheel directly from the source repository — as happens with python3 -m build --wheel or hatch build (even without specifying --target) — results in warnings from Python's zipfile module and a malformed wheel. Pip seems to be fine with the resulting wheels (though I didn't check very thoroughly), but that doesn't seem like something to rely on, and the warnings are just a bad UX. I am not willing to release the feature in this state. See pypa/hatch#1030 for more information.

How to format version if the repository’s current state is an exact tag match

Is there an option to format the version like the [tool.versioningit.format] does, but when there is no distance and is no dirty?

I need to do something like:

[tool.versioningit.format]
base = "{base_version}+{vcs}{rev}.{author_date:%Y%m%dT%H%M%S}"
distance = "{base_version}.post{distance}+{vcs}{rev}.{author_date:%Y%m%dT%H%M%S}"
dirty = "{base_version}.dev+{vcs}{rev}.{author_date:%Y%m%dT%H%M%S}"
distance-dirty = "{base_version}.dev{distance}+{vcs}{rev}.{author_date:%Y%m%dT%H%M%S}"

git-archive method should ignore lightweight tags

The git-archive VCS method makes use of the %(describe) format placeholder added in Git 2.32 in order to retrieve tag & distance information when installing from a Git archive. When installing from a full Git repository, the method endeavors to provide the same information as %(describe) would provide.

However, %(describe) only honors annotated tags, ignoring lightweight tags, yet the git-archive method (as of versioningit 0.3.2) honors all tags. This can lead to the same commit of a project having different versions depending on whether it's installed from a repository or an archive.

(Note that Git 2.35 introduced an option for making %(describe) honor all tags; see #13 for supporting this.)

3.1.0: missing functionality to allow build module without git repo

Trying to package pg8000 I fond that one functionality is missing.

In case of building package which uses versioningit from sdist tar ball and from autogenerated tar ball from git tag all other modules like setuptools-scm, pdm-backend or hatch-vcs is possible to inject module version over env variable.
setuptools-scm uses $SETUPTOOLS_SCM_PRETEND_VERSION, pdm-backend uses $PDM_BUILD_SCM_VERSION and hatch-vcs uses $PBR_VERSION.

Looking on versioningit code I cannot find similar functionality 🤔
It causes that for example on building pg8000 using pep517 based build procedure it fails with

+ /usr/bin/python3 -sBm build -w --no-isolation
* Getting build dependencies for wheel...
Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/versioningit/git.py", line 152, in ensure_is_repo
    self.read(
  File "/usr/lib/python3.9/site-packages/versioningit/git.py", line 184, in read
    return readcmd("git", *args, cwd=str(self.path), **kwargs)
  File "/usr/lib/python3.9/site-packages/versioningit/util.py", line 71, in readcmd
    s = runcmd(*args, stdout=subprocess.PIPE, text=True, **kwargs).stdout
  File "/usr/lib/python3.9/site-packages/versioningit/util.py", line 66, in runcmd
    return subprocess.run(arglist, **kwargs)
  File "/usr/lib64/python3.9/subprocess.py", line 528, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['git', 'rev-parse', '--is-inside-work-tree']' returned non-zero exit status 128.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/versioningit/core.py", line 260, in run
    description = self.do_vcs()
  File "/usr/lib/python3.9/site-packages/versioningit/core.py", line 324, in do_vcs
    description = self.vcs(project_dir=self.project_dir)
  File "/usr/lib/python3.9/site-packages/versioningit/methods.py", line 162, in __call__
    return self.method(params=self.params, **kwargs)
  File "/usr/lib/python3.9/site-packages/versioningit/git.py", line 229, in describe_git
    repo.ensure_is_repo()
  File "/usr/lib/python3.9/site-packages/versioningit/git.py", line 162, in ensure_is_repo
    raise NotVCSError(f"{self.path} is not in a Git repository")
versioningit.errors.NotVCSError: /home/tkloczko/rpmbuild/BUILD/pg8000-1.31.0 is not in a Git repository

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/versioningit/core.py", line 548, in get_version_from_pkg_info
    Path(project_dir, "PKG-INFO").read_text(encoding="utf-8")
  File "/usr/lib64/python3.9/pathlib.py", line 1266, in read_text
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
  File "/usr/lib64/python3.9/pathlib.py", line 1252, in open
    return io.open(self, mode, buffering, encoding, errors, newline,
  File "/usr/lib64/python3.9/pathlib.py", line 1120, in _opener
    return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory: '/home/tkloczko/rpmbuild/BUILD/pg8000-1.31.0/PKG-INFO'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/versioningit/hook.py", line 23, in setuptools_finalizer
    report = vgit.run(write=True, fallback=True)
  File "/usr/lib/python3.9/site-packages/versioningit/core.py", line 283, in run
    version=get_version_from_pkg_info(self.project_dir)
  File "/usr/lib/python3.9/site-packages/versioningit/core.py", line 551, in get_version_from_pkg_info
    raise NotSdistError(f"{project_dir} does not contain a PKG-INFO file")
versioningit.errors.NotSdistError: /home/tkloczko/rpmbuild/BUILD/pg8000-1.31.0 does not contain a PKG-INFO file

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
    main()
  File "/usr/lib/python3.9/site-packages/pyproject_hooks/_in_process/_in_process.py", line 335, in main
    json_out['return_val'] = hook(**hook_input['kwargs'])
  File "/usr/lib/python3.9/site-packages/pyproject_hooks/_in_process/_in_process.py", line 118, in get_requires_for_build_wheel
    return hook(config_settings)
  File "/usr/lib/python3.9/site-packages/setuptools/build_meta.py", line 325, in get_requires_for_build_wheel
    return self._get_build_requires(config_settings, requirements=['wheel'])
  File "/usr/lib/python3.9/site-packages/setuptools/build_meta.py", line 295, in _get_build_requires
    self.run_setup()
  File "/usr/lib/python3.9/site-packages/setuptools/build_meta.py", line 311, in run_setup
    exec(code, locals())
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.9/site-packages/setuptools/__init__.py", line 103, in setup
    return distutils.core.setup(**attrs)
  File "/usr/lib/python3.9/site-packages/setuptools/_distutils/core.py", line 147, in setup
    _setup_distribution = dist = klass(attrs)
  File "/usr/lib/python3.9/site-packages/setuptools/dist.py", line 303, in __init__
    _Distribution.__init__(self, dist_attrs)
  File "/usr/lib/python3.9/site-packages/setuptools/_distutils/dist.py", line 283, in __init__
    self.finalize_options()
  File "/usr/lib/python3.9/site-packages/setuptools/dist.py", line 654, in finalize_options
    ep(self)
  File "/usr/lib/python3.9/site-packages/versioningit/hook.py", line 28, in setuptools_finalizer
    raise RuntimeError(
RuntimeError:
versioningit could not find a version for the project in /home/tkloczko/rpmbuild/BUILD/pg8000-1.31.0!

You may be installing from a shallow clone, in which case you need to unshallow it first.

Alternatively, you may be installing from a Git archive, which is not supported by default.  Install from a git+https://... URL instead.



ERROR Backend subprocess exited when trying to invoke get_requires_for_build_wheel

Support Hatch

Support for the onbuild method with hatch may have to be delayed a bit.

Add a "today" `next-version` method

The method should return the current date in a given format (and given timezone offset?).

  • Add a "normalize: bool" (default True?) parameter that does str(Version(next_version)) in order to get rid of leading zeroes in components
    • Don't use canonicalize_version, as that strips trailing zero components

Default version if no tags

Hi, thanks for your work on versioningit. Would it be possible to have a default version, say 0.0.0 if there are no tags found in the repository? The reason is that for testing, a shallow copy is often taken which doesn't have a tag, and so the install fails.

Feature request: override/pretend a version by environment variable

I'm looking for the equivalent of SETUPTOOLS_SCM_PRETEND_VERSION environment variable as available in setuptools-scm, for versioningit.

Would be great if that's possible without the need to write a custom versioningit method. My use case is a build in Docker where I can exclude the (huge) .git/ folder from the Docker context, have no dependency on git tooling in the image and just supply the version from the host as Docker build argument.

Give `tag2version` a `replace: dict[str, str]` parameter

This would enable dealing with tags like like rel_1_0 (used by SQLAlchemy and Alembic) by mapping the underscores to periods.

Alternatively, tag2version could gain a dot: str parameter for specifying the version segment separator in tags.

Allow writing cmdclasses in `setup.cfg`

Hi,

I noticed that in order to use the onbuild step, I need to explicitly create a setup.py and invoke setup() by passing the cmclasses returned by versioningit.get_cmdclasses().

Since setuptools supports the definition of cmdclasses in setup.cfg (and potentially pyproject.toml, I didn't check, see pypa/setuptools#2570 for reference), would it be possible to expose the needed cmdclasses in the cmdclasses.py module so that they can be used in this form, avoiding having to create a setup.py specifically for this use case?

Thank you for this tool, it's quite helpful!

Version not marked dirty when tag is on latest commit

I've just migrated over from versioneer to your package as it looked so much simpler to use and doesn't cause problems with a local install.

However, I can't seem to get it to mark the version as dirty when there are uncommitted changes and the latest commit has a tag. Is this possible without writing a custom method?

Should look for PKG-INFO in {package}.egg-info

satellite to #28

pypi distributed source .tar.gz carries {package}.egg-info with PKG-INFO (so PKG-INFO is not in the root of the project where setup.py is). versioningit seems to look for it in the root of the project

detailed run of python3 setup.py --fullname on heudiconv
root@smaug:/build/heudiconv-0.11.1# grep Vers heudiconv.egg-info/PKG-INFO
Metadata-Version: 2.1
Version: 0.11.1

root@smaug:/build/heudiconv-0.11.1# python3 setup.py --fullname
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/versioningit/git.py", line 145, in ensure_is_repo
    self.read(
  File "/usr/local/lib/python3.10/dist-packages/versioningit/git.py", line 177, in read
    return readcmd("git", *args, cwd=str(self.path), **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/versioningit/util.py", line 56, in readcmd
    s = runcmd(*args, stdout=subprocess.PIPE, universal_newlines=True, **kwargs).stdout
  File "/usr/local/lib/python3.10/dist-packages/versioningit/util.py", line 51, in runcmd
    return subprocess.run(arglist, **kwargs)
  File "/usr/lib/python3.10/subprocess.py", line 524, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['git', 'rev-parse', '--is-inside-work-tree']' returned non-zero exit status 128.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/versioningit/core.py", line 308, in get_version
    version = vgit.get_version()
  File "/usr/local/lib/python3.10/dist-packages/versioningit/core.py", line 149, in get_version
    description = self.do_vcs()
  File "/usr/local/lib/python3.10/dist-packages/versioningit/core.py", line 188, in do_vcs
    description = self.vcs(project_dir=self.project_dir)
  File "/usr/local/lib/python3.10/dist-packages/versioningit/methods.py", line 154, in __call__
    return self.method(params=self.params, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/versioningit/git.py", line 226, in describe_git
    repo.ensure_is_repo()
  File "/usr/local/lib/python3.10/dist-packages/versioningit/git.py", line 155, in ensure_is_repo
    raise NotVCSError(f"{self.path} is not in a Git repository")
versioningit.errors.NotVCSError: /build/heudiconv-0.11.1 is not in a Git repository

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/versioningit/core.py", line 370, in get_version_from_pkg_info
    Path(project_dir, "PKG-INFO").read_text(encoding="utf-8")
  File "/usr/lib/python3.10/pathlib.py", line 1132, in read_text
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
  File "/usr/lib/python3.10/pathlib.py", line 1117, in open
    return self._accessor.open(self, mode, buffering, encoding, errors,
FileNotFoundError: [Errno 2] No such file or directory: '/build/heudiconv-0.11.1/PKG-INFO'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/versioningit/hook.py", line 18, in setuptools_finalizer
    version = get_version(PROJECT_ROOT, write=True, fallback=True)
  File "/usr/local/lib/python3.10/dist-packages/versioningit/core.py", line 313, in get_version
    version = get_version_from_pkg_info(project_dir)
  File "/usr/local/lib/python3.10/dist-packages/versioningit/core.py", line 373, in get_version_from_pkg_info
    raise NotSdistError(f"{project_dir} does not contain a PKG-INFO file")
versioningit.errors.NotSdistError: /build/heudiconv-0.11.1 does not contain a PKG-INFO file

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/build/heudiconv-0.11.1/setup.py", line 66, in <module>
    main()
  File "/build/heudiconv-0.11.1/setup.py", line 40, in main
    setup(
  File "/usr/lib/python3/dist-packages/setuptools/__init__.py", line 153, in setup
    return distutils.core.setup(**attrs)
  File "/usr/lib/python3.10/distutils/core.py", line 108, in setup
    _setup_distribution = dist = klass(attrs)
  File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 459, in __init__
    _Distribution.__init__(
  File "/usr/lib/python3.10/distutils/dist.py", line 292, in __init__
    self.finalize_options()
  File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 837, in finalize_options
    ep(self)
  File "/usr/local/lib/python3.10/dist-packages/versioningit/hook.py", line 23, in setuptools_finalizer
    raise RuntimeError(
RuntimeError:
versioningit could not find a version for the project in /build/heudiconv-0.11.1!

You may be installing from a shallow clone, in which case you need to unshallow it first.

Alternatively, you may be installing from a Git archive, which is not supported by default.  Install from a git+https://... URL instead.


dirty workaround symlinking that file shows that it works then o pick up version from it

root@smaug:/build/heudiconv-0.11.1# ln -s heudiconv.egg-info/PKG-INFO .
root@smaug:/build/heudiconv-0.11.1# python3 setup.py --fullname
heudiconv-0.11.1

3.1.0: sphinx warnings `reference target not found`

First of all currently it is not possible to use straight sphinx-build command to build documentation out of source tree

+ /usr/bin/sphinx-build -n -T -b man docs build/sphinx/man
Running Sphinx v7.2.6

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/sphinx/config.py", line 358, in eval_config_file
    exec(code, namespace)  # NoQA: S102
  File "/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/conf.py", line 1, in <module>
    from versioningit import __version__
ModuleNotFoundError: No module named 'versioningit'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/sphinx/cmd/build.py", line 293, in build_main
    app = Sphinx(args.sourcedir, args.confdir, args.outputdir,
  File "/usr/lib/python3.9/site-packages/sphinx/application.py", line 211, in __init__
    self.config = Config.read(self.confdir, confoverrides or {}, self.tags)
  File "/usr/lib/python3.9/site-packages/sphinx/config.py", line 181, in read
    namespace = eval_config_file(filename, tags)
  File "/usr/lib/python3.9/site-packages/sphinx/config.py", line 371, in eval_config_file
    raise ConfigError(msg % traceback.format_exc()) from exc
sphinx.errors.ConfigError: There is a programmable error in your configuration file:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/sphinx/config.py", line 358, in eval_config_file
    exec(code, namespace)  # NoQA: S102
  File "/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/conf.py", line 1, in <module>
    from versioningit import __version__
ModuleNotFoundError: No module named 'versioningit'


Configuration error:
There is a programmable error in your configuration file:

Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/sphinx/config.py", line 358, in eval_config_file
    exec(code, namespace)  # NoQA: S102
  File "/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/conf.py", line 1, in <module>
    from versioningit import __version__
ModuleNotFoundError: No module named 'versioningit'

This can be fixed by patch like below:

--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,3 +1,7 @@
+import sys
+import os
+sys.path.insert(0, os.path.abspath("../src"))
+
 from versioningit import __version__

 project = "versioningit"

This patch fixes what is in the comment and that can of fix is suggested in sphinx example copy.py https://www.sphinx-doc.org/en/master/usage/configuration.html#example-of-configuration-file

Than .. on building my packages I'm using sphinx-build command with -n switch which shows warmings about missing references. These are not critical issues.

+ /usr/bin/sphinx-build -n -T -b man docs build/sphinx/man
Running Sphinx v7.2.6
making output directory... done
loading intersphinx inventory from https://docs.python.org/3/objects.inv...
building [mo]: targets for 0 po files that are out of date
writing output...
building [man]: all manpages
updating environment: [new config] 10 added, 0 changed, 0 removed
reading sources... [100%] writing-methods
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
writing... python-versioningit.3 { how configuration runtime-version hatch command api writing-methods notes changelog } /home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/configuration.rst:36: WARNING: py:obj reference target not found: my_next_version()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/configuration.rst:585: WARNING: py:obj reference target not found: setup()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/configuration.rst:752: WARNING: 'envvar' reference target not found: VERSIONINGIT_LOG_LEVEL
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/runtime-version.rst:8: WARNING: py:obj reference target not found: importlib.metadata.version()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/command.rst:38: WARNING: 'envvar' reference target not found: VERSIONINGIT_LOG_LEVEL
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/command.rst:38: WARNING: 'envvar' reference target not found: VERSIONINGIT_LOG_LEVEL
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.get_version:1: WARNING: py:class reference target not found: Path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.get_next_version:1: WARNING: py:class reference target not found: Path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/get_cmdclasses.py:docstring of versioningit.get_cmdclasses.get_cmdclasses:1: WARNING: py:class reference target not found: Command
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/get_cmdclasses.py:docstring of versioningit.get_cmdclasses.get_cmdclasses:1: WARNING: py:class reference target not found: Command
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/get_cmdclasses.py:docstring of versioningit.get_cmdclasses.get_cmdclasses:3: WARNING: py:obj reference target not found: Command
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/get_cmdclasses.py:docstring of versioningit.get_cmdclasses.get_cmdclasses:3: WARNING: py:obj reference target not found: setuptools.setup()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/get_cmdclasses.py:docstring of versioningit.get_cmdclasses.get_cmdclasses:3: WARNING: py:obj reference target not found: setuptools.command.sdist.sdist
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/get_cmdclasses.py:docstring of versioningit.get_cmdclasses.get_cmdclasses:3: WARNING: py:obj reference target not found: setuptools.command.build_py.build_py
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/api.rst:17: WARNING: py:obj reference target not found: setuptools.command.sdist.sdist
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/api.rst:26: WARNING: py:obj reference target not found: setuptools.command.build_py.build_py
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.Versioningit.from_project_dir:1: WARNING: py:class reference target not found: Path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.get_version_from_pkg_info:1: WARNING: py:class reference target not found: Path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.run_onbuild:1: WARNING: py:class reference target not found: Path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.run_onbuild:1: WARNING: py:class reference target not found: Path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.get_template_fields_from_distribution:1: WARNING: py:class reference target not found: Distribution
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/core.py:docstring of versioningit.core.get_template_fields_from_distribution:1: WARNING: py:obj reference target not found: setuptools.Distribution
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:29: WARNING: py:obj reference target not found: my_vcs()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:38: WARNING: py:obj reference target not found: setup()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:38: WARNING: py:obj reference target not found: setup()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:55: WARNING: py:class reference target not found: path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:152: WARNING: py:class reference target not found: path
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/onbuild.py:docstring of versioningit.onbuild.OnbuildFileProvider.get_file:1: WARNING: py:class reference target not found: PurePath
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/onbuild.py:docstring of versioningit.onbuild.OnbuildFileProvider.get_file:1: WARNING: py:class reference target not found: PurePath
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/onbuild.py:docstring of versioningit.onbuild.OnbuildFile.open:1: WARNING: py:class reference target not found: TextMode
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/src/versioningit/onbuild.py:docstring of versioningit.onbuild.OnbuildFile.open:1: WARNING: py:class reference target not found: BinaryMode
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:205: WARNING: py:obj reference target not found: foobar_vcs()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/notes.rst:21: WARNING: py:obj reference target not found: importlib.metadata.version()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/notes.rst:21: WARNING: py:obj reference target not found: importlib.metadata.version()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/changelog.rst:102: WARNING: 'envvar' reference target not found: SOURCE_DATE_EPOCH
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/changelog.rst:102: WARNING: 'envvar' reference target not found: SOURCE_DATE_EPOCH
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/changelog.rst:139: WARNING: 'envvar' reference target not found: VERSIONINGIT_LOG_LEVEL
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/changelog.rst:187: WARNING: py:obj reference target not found: importlib.metadata.entry_points()
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:215: WARNING: "unsupported "label"
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/writing-methods.rst:223: WARNING: "unsupported "label"
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/index.rst:79: WARNING: "unsupported "label"
/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/docs/index.rst:90: WARNING: "unsupported "label"
done
build succeeded, 42 warnings.

You can peak on fixes that kind of issues in other projects
RDFLib/rdflib-sqlalchemy#95
RDFLib/rdflib#2036
click-contrib/sphinx-click@abc31069
frostming/unearth#14
jaraco/cssutils#21
latchset/jwcrypto#289
latchset/jwcrypto#289
pypa/distlib@98b9b89f
pywbem/pywbem#2895
sissaschool/elementpath@bf869d9e
sissaschool/xmlschema@42ea98f2
sqlalchemy/sqlalchemy@5e88e6e8

Best way of getting the version from vcs in editable install falling back to metadata for regular install

I would like to achieve something similar to how versioneer works such that I get the version number from git describe if I am in an editable install using imporlib_metadata.version if not

Currently I ended up doing something like this:

from importlib.metadata import version
from importlib.resources import files

def get_package_version(package_name: str) -> str:
    maybe_package_root = files(package_name).parent
    if maybe_package_root / "pyproject.toml").exists():
        import versioningit
        __version__ = versioningit.get_version(project_dir=maybe_package_root)
    else:
        __version__ = version(package_name)
    return __version__

So my questions are

  1. Is there a better way of doing this that I have overlooked. #3 could be one
    way of solving this since I could put the editable install flow into a file and let the custom cmd_class overwrite it when generating a package.
  2. If not is there any interest in putting a cleaner version of this into versioningit (would need to handle different source layouts at least)

Make the method APIs more resistant to breaking changes

Idea: Require method implementations to be subclasses of base classes defined by versioningit, one base class per step. Instances of these classes will have step arguments provided as instance attributes (and thus additions to the arguments should not require updating any code), and they are executed via a run() or __call__() method.

  • Other possible instance attributes of method classes:

    • the path to the project root
    • the complete versioningit configuration? (before or after processing by Config.parse_*?)
    • a Versioningit instance?
  • Possible ways to supply the user parameters:

    • As a dict passed to the run() method
    • As a dict attribute on instances
      • A method class could then validate user parameters at construction (which happens while parsing the configuration, before execution) by overloading __init__.
  • Cf. Hatch's plugin classes

  • Problem: This won't address the need to make breaking changes to do_STEP() methods whenever a new argument is added.

3.1.0: pytest fails in two units

I'm packaging your module as an rpm package so I'm using the typical PEP517 based build, install and test cycle used on building packages from non-root account.

  • python3 -sBm build -w --no-isolation
  • because I'm calling build with --no-isolation I'm using during all processes only locally installed modules
  • install .whl file in </install/prefix> using installer module
  • run pytest with $PYTHONPATH pointing to sitearch and sitelib inside </install/prefix>
  • build is performed in env which is cut off from access to the public network (pytest is executed with -m "not network")
Here is pytest output:
+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-versioningit-3.1.0-2.fc36.x86_64/usr/lib64/python3.9/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-versioningit-3.1.0-2.fc36.x86_64/usr/lib/python3.9/site-packages
+ /usr/bin/pytest -ra -m 'not network'
==================================================================================== test session starts ====================================================================================
platform linux -- Python 3.9.18, pytest-8.1.1, pluggy-1.4.0
rootdir: /home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0
configfile: tox.ini
plugins: mock-3.14.0
collected 534 items

test/test_config.py ....................                                                                                                                                              [  3%]
test/test_end2end.py .............s.................................sssssssss...........F........F..                                                                                  [ 18%]
test/test_get_version.py ..                                                                                                                                                           [ 18%]
test/test_logging.py ...........................................                                                                                                                      [ 26%]
test/test_main.py ..........................                                                                                                                                          [ 31%]
test/test_methods/test_format.py .........                                                                                                                                            [ 33%]
test/test_methods/test_git.py ............................s                                                                                                                           [ 38%]
test/test_methods/test_hg.py sssssss.sssssss......                                                                                                                                    [ 42%]
test/test_methods/test_next_version.py .....................................................................................................                                          [ 61%]
test/test_methods/test_onbuild.py .......................................                                                                                                             [ 69%]
test/test_methods/test_tag2version.py .............                                                                                                                                   [ 71%]
test/test_methods/test_template_fields.py ................                                                                                                                            [ 74%]
test/test_methods/test_write.py ........                                                                                                                                              [ 76%]
test/test_util.py ...........................................s....................................................................................                                    [100%]

========================================================================================= FAILURES ==========================================================================================
___________________________________________________________________________ test_end2end_error[errors/hg-no-tag] ____________________________________________________________________________

tmp_path = PosixPath('/tmp/pytest-of-tkloczko/pytest-228/test_end2end_error_errors_hg_n0')
repozip = PosixPath('/home/tkloczko/rpmbuild/BUILD/versioningit-3.1.0/test/data/repos/errors/hg-no-tag.zip')
details = ErrorDetails(type='NoTagError', message="No latest tag in Mercurial repository (pattern = 're:^v')")

    @pytest.mark.parametrize(
        "repozip,details", mkcases("errors", [needs_git], details_cls=ErrorDetails)
    )
    def test_end2end_error(tmp_path: Path, repozip: Path, details: ErrorDetails) -> None:
        shutil.unpack_archive(repozip, tmp_path)
        with pytest.raises(Error) as excinfo:
            get_version(project_dir=tmp_path, write=False, fallback=True)
>       assert type(excinfo.value).__name__ == details.type
E       AssertionError: assert 'NotSdistError' == 'NoTagError'
E
E         - NoTagError
E         + NotSdistError

test/test_end2end.py:249: AssertionError
------------------------------------------------------------------------------------- Captured log call -------------------------------------------------------------------------------------
DEBUG    versioningit:methods.py:60 Loading entry point 'hg' in group versioningit.vcs
DEBUG    versioningit:methods.py:60 Loading entry point 'basic' in group versioningit.tag2version
DEBUG    versioningit:methods.py:60 Loading entry point 'minor' in group versioningit.next_version
DEBUG    versioningit:methods.py:60 Loading entry point 'basic' in group versioningit.format
DEBUG    versioningit:methods.py:60 Loading entry point 'basic' in group versioningit.template_fields
DEBUG    versioningit:util.py:64 Running: hg --cwd /tmp/pytest-of-tkloczko/pytest-228/test_end2end_error_errors_hg_n0 files .
INFO     versioningit:core.py:280 Could not get VCS data from /tmp/pytest-of-tkloczko/pytest-228/test_end2end_error_errors_hg_n0: hg not installed; assuming this isn't a Mercurial repository
INFO     versioningit:core.py:281 Falling back to reading from PKG-INFO
_________________________________________________________________________________ test_editable_mode[cmd1] __________________________________________________________________________________

cmd = ['setup.py', 'develop'], tmp_path = PosixPath('/tmp/pytest-of-tkloczko/pytest-228/test_editable_mode_cmd1_0')

    @needs_git
    @pytest.mark.parametrize(
        "cmd",
        [
            ["-m", "pip", "install", "--no-build-isolation", "-e", "."],
            ["setup.py", "develop"],
        ],
    )
    def test_editable_mode(cmd: list[str], tmp_path: Path) -> None:
        repozip = DATA_DIR / "repos" / "git" / "onbuild-write.zip"
        details = CaseDetails.model_validate_json(
            repozip.with_suffix(".json").read_text(encoding="utf-8")
        )
        srcdir = tmp_path / "src"
        shutil.unpack_archive(repozip, srcdir)
        status = get_repo_status(srcdir)
>       subprocess.run([sys.executable, *cmd], cwd=str(srcdir), check=True)

test/test_end2end.py:374:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = False, timeout = None, check = True, popenargs = (['/usr/bin/python3', 'setup.py', 'develop'],)
kwargs = {'cwd': '/tmp/pytest-of-tkloczko/pytest-228/test_editable_mode_cmd1_0/src'}, process = <Popen: returncode: 1 args: ['/usr/bin/python3', 'setup.py', 'develop']>, stdout = None
stderr = None, retcode = 1

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.

        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them.

        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.

        If timeout is given, and the process takes too long, a TimeoutExpired
        exception will be raised.

        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.

        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.

        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE

        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE

        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['/usr/bin/python3', 'setup.py', 'develop']' returned non-zero exit status 1.

/usr/lib64/python3.9/subprocess.py:528: CalledProcessError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
running develop
----------------------------------------------------------------------------------- Captured stderr call ------------------------------------------------------------------------------------
/usr/lib/python3.9/site-packages/setuptools/command/develop.py:40: EasyInstallDeprecationWarning: easy_install command is deprecated.
!!

        ********************************************************************************
        Please avoid running ``setup.py`` and ``easy_install``.
        Instead, use pypa/build, pypa/installer or other
        standards-based tools.

        See https://github.com/pypa/setuptools/issues/917 for details.
        ********************************************************************************

!!
  easy_install.initialize_options(self)
/usr/lib/python3.9/site-packages/setuptools/_distutils/cmd.py:66: SetuptoolsDeprecationWarning: setup.py install is deprecated.
!!

        ********************************************************************************
        Please avoid running ``setup.py`` directly.
        Instead, use pypa/build, pypa/installer or other
        standards-based tools.

        See https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html for details.
        ********************************************************************************

!!
  self.initialize_options()
error: can't create or remove files in install directory

The following error occurred while trying to add or remove files in the
installation directory:

    [Errno 13] Permission denied: '/usr/lib/python3.9/site-packages/test-easy-install-3661377.write-test'

The installation directory you specified (via --install-dir, --prefix, or
the distutils default setting) was:

    /usr/lib/python3.9/site-packages/

Perhaps your account does not have write access to this directory?  If the
installation directory is a system-owned directory, you may need to sign in
as the administrator or "root" account.  If you do not have administrative
access to this machine, you may wish to choose a different installation
directory, preferably one that is listed in your PYTHONPATH environment
variable.

For information on other options, you may wish to consult the
documentation at:

  https://setuptools.pypa.io/en/latest/deprecated/easy_install.html

Please make the appropriate changes for your system and try again.

------------------------------------------------------------------------------------- Captured log call -------------------------------------------------------------------------------------
DEBUG    versioningit:util.py:64 Running: git status --porcelain
================================================================================== short test summary info ==================================================================================
SKIPPED [1] test/test_end2end.py:96: Only run when --oldsetup is given
SKIPPED [9] test/test_end2end.py:96: Mercurial not installed
SKIPPED [1] test/test_methods/test_git.py:309: Git must not be installed
SKIPPED [6] test/test_methods/test_hg.py:34: Mercurial not installed
SKIPPED [1] test/test_methods/test_hg.py:70: Mercurial not installed
SKIPPED [1] test/test_methods/test_hg.py:84: Mercurial not installed
SKIPPED [2] test/test_methods/test_hg.py:91: Mercurial not installed
SKIPPED [1] test/test_methods/test_hg.py:100: Mercurial not installed
SKIPPED [1] test/test_methods/test_hg.py:110: Mercurial not installed
SKIPPED [1] test/test_methods/test_hg.py:139: Mercurial not installed
SKIPPED [1] test/test_methods/test_hg.py:149: Mercurial not installed
SKIPPED [1] test/test_util.py:146: Windows only
FAILED test/test_end2end.py::test_end2end_error[errors/hg-no-tag] - AssertionError: assert 'NotSdistError' == 'NoTagError'
FAILED test/test_end2end.py::test_editable_mode[cmd1] - subprocess.CalledProcessError: Command '['/usr/bin/python3', 'setup.py', 'develop']' returned non-zero exit status 1.
=================================================================== 2 failed, 506 passed, 26 skipped in 84.23s (0:01:24) ====================================================================
List of installed modules in build env:
Package                       Version
----------------------------- -----------
alabaster                     0.7.16
annotated-types               0.6.0
Babel                         2.14.0
build                         1.1.1
charset-normalizer            3.3.2
docutils                      0.20.1
editables                     0.5
exceptiongroup                1.1.3
hatchling                     1.21.1
idna                          3.6
imagesize                     1.4.1
importlib_metadata            7.1.0
iniconfig                     2.0.0
installer                     0.7.0
Jinja2                        3.1.3
MarkupSafe                    2.1.5
packaging                     24.0
pathspec                      0.12.1
pluggy                        1.4.0
pydantic                      2.6.4
pydantic_core                 2.16.3
Pygments                      2.17.2
pyproject_hooks               1.0.0
pytest                        8.1.1
pytest-mock                   3.14.0
python-dateutil               2.9.0.post0
requests                      2.31.0
setuptools                    69.1.1
snowballstemmer               2.2.0
Sphinx                        7.2.6
sphinx-copybutton             0.5.2
sphinx_inline_tabs            2023.4.21
sphinxcontrib-applehelp       1.0.8
sphinxcontrib-devhelp         1.0.5
sphinxcontrib-htmlhelp        2.0.5
sphinxcontrib-jsmath          1.0.1
sphinxcontrib-qthelp          1.0.7
sphinxcontrib-serializinghtml 1.1.10
tokenize_rt                   5.2.0
tomli                         2.0.1
trove-classifiers             2024.3.25
typing_extensions             4.10.0
urllib3                       1.26.18
wheel                         0.43.0
zipp                          3.18.1

Please let me know if you need more details or want me to perform some diagnostics.

Failure during tests

While updating the openSUSE rpm package to the new 3.0.0 version I'm seeing an error that occur during the %check part of the building

[   22s] + mv _build.python39 build
[   22s] + echo python39
[   22s] + python_flavor=python39
[   22s] + PYTHONPATH=/home/abuild/rpmbuild/BUILDROOT/python-versioningit-3.0.0-0.x86_64/usr/lib/python3.9/site-packages
[   22s] + PYTHONDONTWRITEBYTECODE=1
[   22s] + pytest-3.9 --ignore=_build.python39 --ignore=_build.python310 --ignore=_build.python312 --ignore=_build.python311 -v test -k 'not test_editable_mode'
[   22s] ============================= test session starts ==============================
[   22s] platform linux -- Python 3.9.18, pytest-7.4.4, pluggy-1.3.0 -- /usr/bin/python3.9
[   22s] cachedir: .pytest_cache
[   22s] rootdir: /home/abuild/rpmbuild/BUILD/versioningit-3.0.0
[   22s] configfile: tox.ini
[   22s] plugins: cov-4.1.0, mock-3.12.0
[   23s] collecting ... collected 454 items / 1 error
[   23s]
[   23s] ==================================== ERRORS ====================================
[   23s] ____________________ ERROR collecting test/test_end2end.py _____________________
[   23s] test/test_end2end.py:98: in <module>
[   23s]     [
[   23s] test/test_end2end.py:106: in <listcomp>
[   23s]     for c in mkcases(subdir, marks)
[   23s] test/test_end2end.py:79: in mkcases
[   23s]     details = details_cls.model_validate_json(
[   23s] E   AttributeError: type object 'CaseDetails' has no attribute 'model_validate_json'
[   23s] =========================== short test summary info ============================
[   23s] ERROR test/test_end2end.py - AttributeError: type object 'CaseDetails' has no...
[   23s] !!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
[   23s] =============================== 1 error in 0.64s ===============================
[   23s] error: Bad exit status from /var/tmp/rpm-tmp.RTHAjE (%check)

This is with openSUSE Tumbleweed (python 3.9 3.10 3.11 3.12 are build in a fresh build isolated vm.
Any ideas, I'm maybe missing a dependency ?

Build log place: (tests are deactivated here to get the package built)
https://build.opensuse.org/package/live_build_log/home:bruno_friedmann:branches:devel:languages:python/python-versioningit/openSUSE_Tumbleweed/x86_64

can you calculate the 'next_version' ?

love the project, but feel like I might be missing something ..
I'd like to be able to print or write the 'next_version' ( or the next release version )
e.g.
current tag = 0.2.0
distance version on branch (format "{version}.post{distance}") = 0.2.0.post7
next_version (smallest-release) = 0.2.1

how do I print (or write) the 'next version' .. for example if I wanted to tag a repo after merge into the main branch
e.g git tag $(versioningit -n)

I'm happy to open a PR to support this, but wanted to check, am I missing something about how this package should be used ?

Add the ability to update a __version__ string in code in-place

Doing this the same way as the write step (i.e., updating the file in the source tree on build) would mean that building a package would make the repository dirty, which could lead to all sorts of subtle issues. The best strategy I am aware of is to create a custom setuptools cmdclass that updates the copy of the source code in build/ while building it, thereby leaving the source tree untouched. I should look into how versioneer implements its cmdclass for ideas.


Sketch for an addition to the API to support a cmdclass:

  • Add a sixth step & table, "onbuild", that configures an action to take when a project is built using a custom versioningit cmdclass.
    • The default method will be "replace-version", which takes a path to a file and a regex and edits the first line matching the regex in order to make it contain the project's version.
      • Parameters:
        • source-file (required): /-separated path to the file when building an sdist (cf. versioneer's versionfile_source)
        • build-file (required): /-separated path to the file when building a wheel (cf. versioneer's versionfile_build)
        • encoding: Defaults to "utf-8"
        • regex: The regular expression to use; matched with re.search
          • Defaults to, in essence, r'^\s*__version__\s*=\s*(?P<version>.*)'
          • If it contains a capturing group named "version", that group is replaced with the project version; otherwise, the entire matched text is replaced.
        • require-match (bool): Whether to error if the regex is not found in the file; defaults to false
        • replacement: template string containing {version} describing what to replace the string with; may contain backreferences to the regex(?); defaults to '"{version}"'
        • append-line: Optional line with {version} placeholder to append to the file if no lines matching the regex are found
    • "onbuild" step arguments:
      • build_dir: Union[str, Path] — directory in which the build is being performed
      • version: str — The project version
      • is_source: bool — whether this is a build of an sdist or other source-like non-final distribution, i.e., whether to prepend package-dir to the file path
  • Export a run_onbuild(project_dir, build_dir, version, is_source, config=None) (tentative name) function that takes a build root path and a project dir and/or config structure and runs the "onbuild" step for the given project
  • Export a get_cmdclasses() function that is similar to versioneer's get_cmdclass(), except the new command classes call run_onbuild()

Using the example configurations produces warnings

I followed the example configuration for versioneer format:

https://versioningit.readthedocs.io/en/stable/index.html#example-configurations

[tool.versioningit]
# The format used by versioneer
distance = '{base_version}+{distance}.{vcs}{rev}'
dirty = '{base_version}+{distance}.{vcs}{rev}.dirty'
distance-dirty = '{base_version}+{distance}.{vcs}{rev}.dirty'

THis produces warnings, but it works correctly:

$ python setup.py --version
[WARNING ] versioningit: Ignoring unknown parameter 'distance' in versioningit configuration
[WARNING ] versioningit: Ignoring unknown parameter 'dirty' in versioningit configuration
[WARNING ] versioningit: Ignoring unknown parameter 'distance-dirty' in versioningit configuration
[WARNING ] versioningit: Ignoring unknown parameter 'match' in versioningit's vcs
0.1.0

git-archive method should fetch match, exclude, and tags options from describe-subst field

The git-archive VCS method requires the user to set a describe-subst field in pyproject.toml to a properly-formatted $Format:%(describe)$ string. If the user wishes to match or exclude certain tags, they currently must provide a match/exclude field as well as repeat the information in the $Format$ string. This redundancy is unnecessary and should be eliminated; versioningit should parse the match & exclude options from the $Format$ string instead of requiring the user to repeat them. While at it, support for the tags option added to %(describe) in Git 2.35 should be added as well.

Note that this is a breaking change, as it means the match and exclude parameters of the git-archive method will be eliminated; if they are set, a warning will be produced, and their values will be ignored.

versioningit and project metadata in pyproject.toml (PEP 621)

Hi, first off, thanks for creating this project! 👍

I've been trying on switching our project to PEP 518 build system declarations via pyproject.toml, and versioneer was a road blocker for us the entire time because of its lack of support, and setuptools_scm didn't fit because of its strict version string format which we don't want. versioningit on the other hand does everything right and is customizable, and so far, (almost) everything's working well.

One thing that required a bit of extra research was getting the right version in editable installs via tool.versioningit.build and setting up the version template replacement (#8), but everything else was dead simple.

However, the one final issue I'm facing is my attempt to move project metadata from setup.cfg to pyproject.toml (PEP 621). Adding metadata to the [project] table in pyproject.toml also requires setting the project.version property with its value being a string compatible with PEP 440, otherwise it won't build and it returns the following errors:

configuration error: project must contain ['version'] properties
configuration error: project.version must be pep440

That's why I set it to 0.0.0+unknown in my expectation that this static version string would get replaced by versioningit when building. Unfortunately though, having the project.version property set results in having a static version string when building instead of the one calculated by versioningit. Editable installs via pip install -e . don't have this problem because the template file does not get replaced with the static string and the version gets calculated during runtime, but pip will still log the static version string when creating the editable install. When the metadata is left in setup.cfg (no mandatory version field here), this obviously won't cause any issues and everything works as expected.

I could of course leave the project metadata in setup.cfg and stop here, but according to setuptools, they are planning on deprecating the setup.cfg file format in favor of having a single pyproject.toml configuration:
https://github.com/pypa/setuptools/milestone/7

So my question is, is this a bug in versioningit, or is this an issue with how setuptools reads the metadata which needs to get fixed upstream?

Thanks for your help!

Integrate with GitHub Actions

Hi,

I am trying to use versioningit to bump my pkg version while deploying with GitHub Actions.
The first push successfully created version 0.0.0 at PyPI: https://test.pypi.org/project/geotils/0.0.0/

However, version is not being incremented in subsequent pushes. What did I miss in the following configuration?

pyproject.toml:

[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "geotils"
dynamic = ["version"]
dependencies = [
                ...,
		'versioningit',
]
requires-python = ">=3.10"
description = "geotils by GEOAI group"
readme = "README.md"
license = {text = "MIT License"}
keywords = ["geotils", "geospatial", "earth observation"]
classifiers = [
  "Development Status :: 3 - Alpha",
  "Intended Audience :: Developers",
  "Topic :: Software Development :: Build Tools",
  "License :: OSI Approved :: MIT License",
  "Programming Language :: Python",
]

[tool.versioningit.format]
distance = "{base_version}.post{distance}+{vcs}{rev}"
dirty = "{base_version}+d{build_date:%Y%m%d}"
distance-dirty = "{base_version}.post{distance}+{vcs}{rev}.d{build_date:%Y%m%d}"

[project.urls]
Homepage = "https://geogroup.ai/"
Documentation = "https://readthedocs.org/geotils"
Repository = "https://github.com/geoaigroup/geotils"
"Bug Tracker" = "https://github.com/geoaigroup/geotils/issues"
Changelog = "https://github.com/geoaigroup/geotils/blob/master/CHANGELOG.md"
[project.scripts]
geotils = "geotils.module:function"

GitHub CI/CD workflow code: https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#the-whole-ci-cd-workflow

Move documentation to a Read the Docs site

The versioningit README is huge, and the planned addition of a cmdclass in #3 is only going to make it bigger. Everything after the "Installation & Setup" section should be moved into a Sphinx site for hosting on Read the Docs.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.