ofek / hatch-vcs

Hatch plugin for versioning with your preferred VCS
MIT License
104 stars 15 forks source link

[Feature Request] Allow specifying a custom version scheme #59

Open dhirschfeld opened 11 months ago

dhirschfeld commented 11 months ago

In setuptools_scm you can supply a callable (ScmVersion) -> str

image

https://setuptools-scm.readthedocs.io/en/latest/config/#configuration-parameters

However, it appears that the only way to use a custom callable is by defining it in setup.py and passing it to setup via the use_scm_version parameter:

image

https://setuptools-scm.readthedocs.io/en/latest/customizing/

If I add a dummy setup.py which defined a myversion_func and specify it in my pyproject.toml as:

[tool.hatch.version.raw-options]
version_scheme = "myversion_func"

hatch apppears to not pick it up:

❯ hatch version
Traceback (most recent call last):
  File "C:\python\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\python\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "<snip>\lib\site-packages\hatchling\__main__.py", line 6, in <module>
    sys.exit(hatchling())
  File "<snip>\lib\site-packages\hatchling\cli\__init__.py", line 26, in hatchling
    command(**kwargs)
  File "<snip>\lib\site-packages\hatchling\cli\version\__init__.py", line 29, in version_impl
    version_data = source.get_version_data()
  File "<snip>\lib\site-packages\hatch_vcs\version_source.py", line 70, in get_version_data
    version = get_version(**self.construct_setuptools_scm_config())
  File "<snip>\lib\site-packages\setuptools_scm\_get_version_impl.py", line 155, in get_version
    maybe_version = _get_version(config, force_write_version_files=True)
  File "<snip>\lib\site-packages\setuptools_scm\_get_version_impl.py", line 96, in _get_version
    version_string = _format_version(parsed_version)
  File "<snip>\lib\site-packages\setuptools_scm\version.py", line 435, in format_version
    assert main_version is not None
AssertionError

Is there any way with hatch-vcs to use a custom version format?

ofek commented 11 months ago

Try this:

[tool.hatch.version.raw-options]
version_scheme = "setup.myversion_func"

Also get rid of the setup call

RonnyPfannschmidt commented 11 months ago

There's no magic import,so far hatch has no equivalent

One basically has to implement a custom version scheme hook

dhirschfeld commented 11 months ago

There's no magic import,so far hatch has no equivalent

One basically has to implement a custom version scheme hook

Hmm, yeah - what I suspected :(

version_scheme = "setup.myversion_func" also did not work.

dhirschfeld commented 11 months ago

My version scheme is pretty close to the post-release one. It's enforced by CI and (currently) injected via SETUPTOOLS_SCM_PRETEND_VERSION.

It's a little awkward having hatch version disagree with what the actual version name is so I'd ideally like to have a way to easily tell hatch the correct format is to use.

For reference, my version scheme is:

In [31]: def custom_post_release(version: 'ScmVersion') -> str:
    ...:     version_str = f"{version.tag}"
    ...:     if version.distance != 0:
    ...:         version_str += f".post{version.distance:03d}+{version.node[1:]}"
    ...:     if version.dirty:
    ...:         version_str += ".dirty"
    ...: 
    ...:     return version_str
    ...: 

In [32]: version
Out[32]: <ScmVersion 0.0.1 dist=1 node=gfabaff6 dirty=True branch=None>

In [33]: custom_post_release(version)
Out[33]: '0.0.1.post001+fabaff6.dirty'

It can be implemented by injecting version into a format-string:

In [34]: f"""{str(version.tag) + (f".post{version.distance:03d}+{version.node[1:]}" if version.distance > 0 else '') + ('.dirty' if version.dirty else '')}"""
Out[34]: '0.0.1.post001+fabaff6.dirty'

It would be ideal if I could specify the format-string to use in the pyproject.toml:

[tool.hatch.version.raw-options]
version_scheme = '{str(version.tag) + (f".post{version.distance:03d}+{version.node[1:]}" if version.distance > 0 else "") + (".dirty" if version.dirty else "")}'

...but I'm not sure that's possible?

dhirschfeld commented 11 months ago

One basically has to implement a custom version scheme hook

I might give it a go. Let me know if you know of any good examples :pray:

RonnyPfannschmidt commented 11 months ago

PS the post release version scheme Will get deprecated, the post scheme is incorrect for the dev bump usage

RonnyPfannschmidt commented 11 months ago

Also custom format Strings will not get allowed, people get it wrong way too easy

Even your own post scheme is technically incorrect as the canonical version would drop the leading digits

dhirschfeld commented 11 months ago

Even your own post scheme is technically incorrect as the canonical version would drop the leading digits

Yeah, but it's better because no tool cares about the leading digits and it leads to packages which sort alphabetically. It's a conscious design decision for a better DX - at least for those who manage package repositories.

It's the same reason I always use %Y-%m-%d for timestamps in filenames.

If it's good enough to pass the packaging regex, it's good enough for me:

In [18]: import re
    ...: from packaging.version import VERSION_PATTERN

In [19]: re.match(VERSION_PATTERN, '0.0.1.post001+fabaff6.dirty', re.VERBOSE).groupdict()
Out[19]:
{'epoch': None,
 'release': '0.0.1',
 'pre': None,
 'pre_l': None,
 'pre_n': None,
 'post': '.post001',
 'post_n1': None,
 'post_l': 'post',
 'post_n2': '001',
 'dev': None,
 'dev_l': None,
 'dev_n': None,
 'local': 'fabaff6.dirty'}

Anyway, that's beside the point - irrespective of whatever some group consider correct or deprecated, it would be good if hatch-vcs allowed users to choose whatever format they'd like in their own workflows.

dhirschfeld commented 11 months ago

custom format Strings will not get allowed,

There are probably other ways of solving the problem that don't involve users having to create and publish their own python package.

Perhaps hatch-vcs could import functions from a hatch_vcs.py file in the repo root, if it existed... though I think a format-string template would be less of a security concern.

RonnyPfannschmidt commented 11 months ago

Non normalized leading digits are a topic I want to bring into standardisation

It makes sense for Calver as well and I'd happily integrate it's into core

However currently not normalizing creates numerous issues due to how different tools enforce normalization

As for DX, if one uses parsed Versions as order key, the DX is not sabotaged

dhirschfeld commented 11 months ago

As for DX, if one uses parsed Versions as order key, the DX is not sabotaged

As someone who manages package repositories I want to be able to view the files in my file browser in alphabetical order. The same reason it's important to name files by year/month/date - so they sort alphabetically and are easy to identify in your file browser. It makes it much easier to identify any issues and reduces the human-error component - it's all about being able to eyeball the state of the repository, as a human.

Having to use a custom tool to parse versions/filenames before being able to sort them is multiple orders of magnitude more difficult than opening your file browser or S3/Blob Storage web console and clicking on the sort by name column.

dhirschfeld commented 11 months ago

Non normalized leading digits are a topic I want to bring into standardisation

For the above mentioned reasons, I think they're a good idea. A package repository (web) browser can obviously parse the pacakge versions and sort in a custom order but there's benefit to having them also sort naturally by the filesystem too - especially for those who manage package repositories.

There's the obvious question of what happens if you exceed 999 commits since your last release, but in my own experience that has never happened. Maybe at a certain scale that might be problematic but then, in such a company, they could choose to use 4-digit versions. They are dev versions after all, so there's no need to be consistent between packages - so long as the install tool does't care about leading zeros.

m-birke commented 7 months ago

I would love to have the possiblity w/o writing python code like poetry-dynamic-versioning is providing it

https://pypi.org/project/poetry-dynamic-versioning/

eg. format = "{base}.{distance}+g{commit}"

RonnyPfannschmidt commented 7 months ago

Version scheme parameters need some consideration

It's very easy to make seemingly correct but actually incorrect version numbers

I'm opposed to handling loaded and aimed footguns

dhirschfeld commented 7 months ago

I kind of think that goes against the philosophy of Python that we're all consenting adults. Provide sensible defaults but allow people to customise things as they see fit.

I've had to fork this project to provide the functionality I want, but I'd rather not have to resort to that.

RonnyPfannschmidt commented 7 months ago

@dhirschfeld when providing a api there is a difference between easy accidentally wrong by default and you need to work hard and commit to multiple red flags to get things wrong

for me this is the difference between informed consent and uninformed consent - im opposed to being baby trapped in a bad easy misused api i have to support for the years to come

in particular im hellbent on not consenting to the type of support request that demonstrably will come in when people start to misuse a easy misused api and suddenly have consequences - i already had my fair share of it

im happy to include the proposed mechanism under a name that makes its shortcomings clear to the person using it, since then we have informed consent

but giving out the easy misused thing under a name that doesn't reflect the danger is pretty much deception - you cant claim consenting adults while requesting a feature that is likely to misinform

so let me restate - im happy to include the requested feature under a name that appropriately reflects its potential for wrong usage so we can have consenting adults based on actual information

im absolutely unwillig to use a simple deceptive name for it as that would create consent under false pretenses