# Dunamai
Dunamai is a Python 3.5+ library and command line tool for producing dynamic,
standards-compliant version strings, derived from tags in your version control system.
This facilitates uniquely identifying nightly or per-commit builds in continuous integration
and releasing new versions of your software simply by creating a tag.
Dunamai is also available as a [GitHub Action](https://github.com/marketplace/actions/run-dunamai).
## Features
* Version control system support:
* [Git](https://git-scm.com) (2.7.0+ is recommended, but versions as old as 1.8.2.3 will work with some reduced functionality)
* [Mercurial](https://www.mercurial-scm.org)
* [Darcs](http://darcs.net)
* [Subversion](https://subversion.apache.org)
* [Bazaar](https://bazaar.canonical.com/en)
* [Fossil](https://www.fossil-scm.org/home/doc/trunk/www/index.wiki)
* [Pijul](https://pijul.org)
* Version styles:
* [PEP 440](https://www.python.org/dev/peps/pep-0440)
* [Semantic Versioning](https://semver.org)
* [Haskell Package Versioning Policy](https://pvp.haskell.org)
* Custom output formats
* Can be used for projects written in any programming language.
For Python, this means you do not need a setup.py.
## Usage
### Installation
```
pip install dunamai
```
### CLI
```console
# Suppose you are on commit g29045e8, 7 commits after the v0.2.0 tag.
# Auto-detect the version control system and generate a version:
$ dunamai from any
0.2.0.post7.dev0+g29045e8
# Or use an explicit VCS and style:
$ dunamai from git --no-metadata --style semver
0.2.0-post.7
# Custom formats:
$ dunamai from any --format "v{base}+{distance}.{commit}"
v0.2.0+7.g29045e8
# If you'd prefer to frame the version in terms of progress toward the next
# release rather than distance from the latest one, you can bump it:
$ dunamai from any --bump
0.2.1.dev7+g29045e8
# Validation of custom formats:
$ dunamai from any --format "v{base}" --style pep440
Version 'v0.2.0' does not conform to the PEP 440 style
# Validate your own freeform versions:
$ dunamai check 0.01.0 --style semver
Version '0.01.0' does not conform to the Semantic Versioning style
# More info:
$ dunamai --help
$ dunamai from --help
$ dunamai from git --help
```
### Library
```python
from dunamai import Version, Style
# Let's say you're on commit g644252b, which is tagged as v0.1.0.
version = Version.from_git()
assert version.serialize() == "0.1.0"
# Let's say there was a v0.1.0rc5 tag 44 commits ago
# and you have some uncommitted changes.
version = Version.from_any_vcs()
assert version.serialize() == "0.1.0rc5.post44.dev0+g644252b"
assert version.serialize(metadata=False) == "0.1.0rc5.post44.dev0"
assert version.serialize(dirty=True) == "0.1.0rc5.post44.dev0+g644252b.dirty"
assert version.serialize(style=Style.SemVer) == "0.1.0-rc.5.post.44+g644252b"
```
The `serialize()` method gives you an opinionated, PEP 440-compliant default
that ensures that versions for untagged commits are compatible with Pip's `--pre` flag.
The individual parts of the version are also available for you to use and inspect as you please:
```python
assert version.base == "0.1.0"
assert version.stage == "rc"
assert version.revision == 5
assert version.distance == 44
assert version.commit == "g644252b"
assert version.dirty is True
# Available if the latest tag includes metadata, like v0.1.0+linux:
assert version.tagged_metadata == "linux"
```
### Tips
By default, the "v" prefix on the tag is required,
unless you specify a custom tag pattern.
You can either write a regular expression:
* Console:
```console
$ dunamai from any --pattern "(?P<base>\d+\.\d+\.\d+)"
```
* Python:
```python
from dunamai import Version
version = Version.from_any_vcs(pattern=r"(?P<base>\d+\.\d+\.\d+)")
```
...or use a named preset:
* Console:
```console
$ dunamai from any --pattern default-unprefixed
```
* Python:
```python
from dunamai import Version, Pattern
version = Version.from_any_vcs(pattern=Pattern.DefaultUnprefixed)
```
You can also keep the default pattern and just specify a prefix.
For example, this would match tags like `some-package-v1.2.3`:
* Console:
```console
$ dunamai from any --pattern-prefix some-package-
```
* Python:
```python
from dunamai import Version
version = Version.from_any_vcs(pattern_prefix="some-package-")
```
### VCS archives
Sometimes, you may only have access to an archive of a repository (e.g., a zip file) without the full history.
Dunamai can still detect a version in some of these cases:
* For Git, you can configure `git archive` to produce a file with some metadata for Dunamai.
Add a `.git_archival.json` file to the root of your repository with this content:
```
{
"hash-full": "$Format:%H$",
"hash-short": "$Format:%h$",
"timestamp": "$Format:%cI$",
"refs": "$Format:%D$",
"describe": "$Format:%(describe:tags=true,match=v[0-9]*)$"
}
```
Add this line to your `.gitattributes` file.
If you don't already have this file, add it to the root of your repository:
```
.git_archival.json export-subst
```
* For Mercurial, Dunamai will detect and use an `.hg_archival.txt` file created by `hg archive`.
It will also recognize `.hgtags` if present.
### Custom formats
Here are the available substitutions for custom formats.
If you have a tag like `v9!0.1.2-beta.3+other`, then:
* `{base}` = `0.1.2`
* `{stage}` = `beta`
* `{revision}` = `3`
* `{distance}` is the number of commits since the last
* `{commit}` is the commit hash (defaults to short form, unless you use `--full-commit`)
* `{dirty}` expands to either "dirty" or "clean" if you have uncommitted modified files
* `{tagged_metadata}` = `other`
* `{epoch}` = `9`
* `{branch}` = `feature/foo`
* `{branch_escaped}` = `featurefoo`
* `{timestamp}` is in the format `YYYYmmddHHMMSS` as UTC
If you specify a substitution, its value will always be included in the output.
For conditional formatting, you can do something like this (Bash):
```bash
distance=$(dunamai from any --format "{distance}")
if [ "$distance" = "0" ]; then
dunamai from any --format "v{base}"
else
dunamai from any --format "v{base}+{distance}.{dirty}"
fi
```
## Comparison to Versioneer
[Versioneer](https://github.com/warner/python-versioneer)
is another great library for dynamic versions,
but there are some design decisions that prompted the creation of Dunamai as an alternative:
* Versioneer requires a setup.py file to exist, or else `versioneer install` will fail,
rendering it incompatible with non-setuptools-based projects such as those using Poetry or Flit.
Dunamai can be used regardless of the project's build system.
* Versioneer has a CLI that generates Python code which needs to be committed into your repository,
whereas Dunamai is just a normal importable library
with an optional CLI to help statically include your version string.
* Versioneer produces the version as an opaque string,
whereas Dunamai provides a Version class with discrete parts
that can then be inspected and serialized separately.
* Versioneer provides customizability through a config file,
whereas Dunamai aims to offer customizability through its library API and CLI
for both scripting support and use in other libraries.
## Integration
* Setting a `__version__` statically:
```console
$ echo "__version__ = '$(dunamai from any)'" > your_library/_version.py
```
```python
# your_library/__init__.py
from your_library._version import __version__
```
Or dynamically (but Dunamai becomes a runtime dependency):
```python
# your_library/__init__.py
import dunamai as _dunamai
__version__ = _dunamai.get_version("your-library", third_choice=_dunamai.Version.from_any_vcs).serialize()
```
* setup.py (no install-time dependency on Dunamai as long as you use wheels):
```python
from setuptools import setup
from dunamai import Version
setup(
name="your-library",
version=Version.from_any_vcs().serialize(),
)
```
Or you could use a static inclusion approach as in the prior example.
* [Poetry](https://poetry.eustace.io):
```console
$ poetry version $(dunamai from any)
```
Or you can use the [poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning) plugin.
## Other notes
* Dunamai needs access to the full version history to find tags and compute distance.
Be careful if your CI system does a shallow clone by default.
* For GitHub workflows, invoke `actions/checkout@v3` with `fetch-depth: 0`.
* For GitLab pipelines, set the `GIT_DEPTH` variable to 0.
* For Docker builds, copy the VCS history (e.g., `.git` folder) into the container.
For Git, you can also avoid doing a full clone by specifying a remote branch for tags
(e.g., `--tag-branch remotes/origin/master`).
* When using Git, remember that lightweight tags do not store their creation time.
Therefore, if a commit has multiple lightweight tags,
we cannot reliably determine which one should be considered the newest.
The solution is to use annotated tags instead.
* When using Git, the initial commit must **not** be both tagged and empty
(i.e., created with `--allow-empty`).
This is related to a reporting issue in Git.
For more info, [click here](https://github.com/mtkennerly/dunamai/issues/14).
Raw data
{
"_id": null,
"home_page": "https://github.com/mtkennerly/dunamai",
"name": "dunamai",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.5",
"maintainer_email": null,
"keywords": "version, versioning, dynamic",
"author": "Matthew T. Kennerly",
"author_email": "mtkennerly@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/a0/fe/aee602f08765de4dd753d2e5d6cbd480857182e345f161f7a19ad1979e4d/dunamai-1.22.0.tar.gz",
"platform": null,
"description": "\n# Dunamai\nDunamai is a Python 3.5+ library and command line tool for producing dynamic,\nstandards-compliant version strings, derived from tags in your version control system.\nThis facilitates uniquely identifying nightly or per-commit builds in continuous integration\nand releasing new versions of your software simply by creating a tag.\n\nDunamai is also available as a [GitHub Action](https://github.com/marketplace/actions/run-dunamai).\n\n## Features\n* Version control system support:\n * [Git](https://git-scm.com) (2.7.0+ is recommended, but versions as old as 1.8.2.3 will work with some reduced functionality)\n * [Mercurial](https://www.mercurial-scm.org)\n * [Darcs](http://darcs.net)\n * [Subversion](https://subversion.apache.org)\n * [Bazaar](https://bazaar.canonical.com/en)\n * [Fossil](https://www.fossil-scm.org/home/doc/trunk/www/index.wiki)\n * [Pijul](https://pijul.org)\n* Version styles:\n * [PEP 440](https://www.python.org/dev/peps/pep-0440)\n * [Semantic Versioning](https://semver.org)\n * [Haskell Package Versioning Policy](https://pvp.haskell.org)\n * Custom output formats\n* Can be used for projects written in any programming language.\n For Python, this means you do not need a setup.py.\n\n## Usage\n### Installation\n```\npip install dunamai\n```\n\n### CLI\n```console\n# Suppose you are on commit g29045e8, 7 commits after the v0.2.0 tag.\n\n# Auto-detect the version control system and generate a version:\n$ dunamai from any\n0.2.0.post7.dev0+g29045e8\n\n# Or use an explicit VCS and style:\n$ dunamai from git --no-metadata --style semver\n0.2.0-post.7\n\n# Custom formats:\n$ dunamai from any --format \"v{base}+{distance}.{commit}\"\nv0.2.0+7.g29045e8\n\n# If you'd prefer to frame the version in terms of progress toward the next\n# release rather than distance from the latest one, you can bump it:\n$ dunamai from any --bump\n0.2.1.dev7+g29045e8\n\n# Validation of custom formats:\n$ dunamai from any --format \"v{base}\" --style pep440\nVersion 'v0.2.0' does not conform to the PEP 440 style\n\n# Validate your own freeform versions:\n$ dunamai check 0.01.0 --style semver\nVersion '0.01.0' does not conform to the Semantic Versioning style\n\n# More info:\n$ dunamai --help\n$ dunamai from --help\n$ dunamai from git --help\n```\n\n### Library\n\n```python\nfrom dunamai import Version, Style\n\n# Let's say you're on commit g644252b, which is tagged as v0.1.0.\nversion = Version.from_git()\nassert version.serialize() == \"0.1.0\"\n\n# Let's say there was a v0.1.0rc5 tag 44 commits ago\n# and you have some uncommitted changes.\nversion = Version.from_any_vcs()\nassert version.serialize() == \"0.1.0rc5.post44.dev0+g644252b\"\nassert version.serialize(metadata=False) == \"0.1.0rc5.post44.dev0\"\nassert version.serialize(dirty=True) == \"0.1.0rc5.post44.dev0+g644252b.dirty\"\nassert version.serialize(style=Style.SemVer) == \"0.1.0-rc.5.post.44+g644252b\"\n```\n\nThe `serialize()` method gives you an opinionated, PEP 440-compliant default\nthat ensures that versions for untagged commits are compatible with Pip's `--pre` flag.\nThe individual parts of the version are also available for you to use and inspect as you please:\n\n```python\nassert version.base == \"0.1.0\"\nassert version.stage == \"rc\"\nassert version.revision == 5\nassert version.distance == 44\nassert version.commit == \"g644252b\"\nassert version.dirty is True\n\n# Available if the latest tag includes metadata, like v0.1.0+linux:\nassert version.tagged_metadata == \"linux\"\n```\n\n### Tips\nBy default, the \"v\" prefix on the tag is required,\nunless you specify a custom tag pattern.\nYou can either write a regular expression:\n\n* Console:\n ```console\n $ dunamai from any --pattern \"(?P<base>\\d+\\.\\d+\\.\\d+)\"\n ```\n* Python:\n ```python\n from dunamai import Version\n version = Version.from_any_vcs(pattern=r\"(?P<base>\\d+\\.\\d+\\.\\d+)\")\n ```\n\n...or use a named preset:\n\n* Console:\n ```console\n $ dunamai from any --pattern default-unprefixed\n ```\n* Python:\n ```python\n from dunamai import Version, Pattern\n version = Version.from_any_vcs(pattern=Pattern.DefaultUnprefixed)\n ```\n\nYou can also keep the default pattern and just specify a prefix.\nFor example, this would match tags like `some-package-v1.2.3`:\n\n* Console:\n ```console\n $ dunamai from any --pattern-prefix some-package-\n ```\n* Python:\n ```python\n from dunamai import Version\n version = Version.from_any_vcs(pattern_prefix=\"some-package-\")\n ```\n\n### VCS archives\nSometimes, you may only have access to an archive of a repository (e.g., a zip file) without the full history.\nDunamai can still detect a version in some of these cases:\n\n* For Git, you can configure `git archive` to produce a file with some metadata for Dunamai.\n\n Add a `.git_archival.json` file to the root of your repository with this content:\n ```\n {\n \"hash-full\": \"$Format:%H$\",\n \"hash-short\": \"$Format:%h$\",\n \"timestamp\": \"$Format:%cI$\",\n \"refs\": \"$Format:%D$\",\n \"describe\": \"$Format:%(describe:tags=true,match=v[0-9]*)$\"\n }\n ```\n\n Add this line to your `.gitattributes` file.\n If you don't already have this file, add it to the root of your repository:\n ```\n .git_archival.json export-subst\n ```\n\n* For Mercurial, Dunamai will detect and use an `.hg_archival.txt` file created by `hg archive`.\n It will also recognize `.hgtags` if present.\n\n### Custom formats\nHere are the available substitutions for custom formats.\nIf you have a tag like `v9!0.1.2-beta.3+other`, then:\n\n* `{base}` = `0.1.2`\n* `{stage}` = `beta`\n* `{revision}` = `3`\n* `{distance}` is the number of commits since the last\n* `{commit}` is the commit hash (defaults to short form, unless you use `--full-commit`)\n* `{dirty}` expands to either \"dirty\" or \"clean\" if you have uncommitted modified files\n* `{tagged_metadata}` = `other`\n* `{epoch}` = `9`\n* `{branch}` = `feature/foo`\n* `{branch_escaped}` = `featurefoo`\n* `{timestamp}` is in the format `YYYYmmddHHMMSS` as UTC\n\nIf you specify a substitution, its value will always be included in the output.\nFor conditional formatting, you can do something like this (Bash):\n\n```bash\ndistance=$(dunamai from any --format \"{distance}\")\nif [ \"$distance\" = \"0\" ]; then\n dunamai from any --format \"v{base}\"\nelse\n dunamai from any --format \"v{base}+{distance}.{dirty}\"\nfi\n```\n\n## Comparison to Versioneer\n[Versioneer](https://github.com/warner/python-versioneer)\nis another great library for dynamic versions,\nbut there are some design decisions that prompted the creation of Dunamai as an alternative:\n\n* Versioneer requires a setup.py file to exist, or else `versioneer install` will fail,\n rendering it incompatible with non-setuptools-based projects such as those using Poetry or Flit.\n Dunamai can be used regardless of the project's build system.\n* Versioneer has a CLI that generates Python code which needs to be committed into your repository,\n whereas Dunamai is just a normal importable library\n with an optional CLI to help statically include your version string.\n* Versioneer produces the version as an opaque string,\n whereas Dunamai provides a Version class with discrete parts\n that can then be inspected and serialized separately.\n* Versioneer provides customizability through a config file,\n whereas Dunamai aims to offer customizability through its library API and CLI\n for both scripting support and use in other libraries.\n\n## Integration\n* Setting a `__version__` statically:\n\n ```console\n $ echo \"__version__ = '$(dunamai from any)'\" > your_library/_version.py\n ```\n ```python\n # your_library/__init__.py\n from your_library._version import __version__\n ```\n\n Or dynamically (but Dunamai becomes a runtime dependency):\n\n ```python\n # your_library/__init__.py\n import dunamai as _dunamai\n __version__ = _dunamai.get_version(\"your-library\", third_choice=_dunamai.Version.from_any_vcs).serialize()\n ```\n\n* setup.py (no install-time dependency on Dunamai as long as you use wheels):\n\n ```python\n from setuptools import setup\n from dunamai import Version\n\n setup(\n name=\"your-library\",\n version=Version.from_any_vcs().serialize(),\n )\n ```\n\n Or you could use a static inclusion approach as in the prior example.\n\n* [Poetry](https://poetry.eustace.io):\n\n ```console\n $ poetry version $(dunamai from any)\n ```\n\n Or you can use the [poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning) plugin.\n\n## Other notes\n* Dunamai needs access to the full version history to find tags and compute distance.\n Be careful if your CI system does a shallow clone by default.\n\n * For GitHub workflows, invoke `actions/checkout@v3` with `fetch-depth: 0`.\n * For GitLab pipelines, set the `GIT_DEPTH` variable to 0.\n * For Docker builds, copy the VCS history (e.g., `.git` folder) into the container.\n\n For Git, you can also avoid doing a full clone by specifying a remote branch for tags\n (e.g., `--tag-branch remotes/origin/master`).\n* When using Git, remember that lightweight tags do not store their creation time.\n Therefore, if a commit has multiple lightweight tags,\n we cannot reliably determine which one should be considered the newest.\n The solution is to use annotated tags instead.\n* When using Git, the initial commit must **not** be both tagged and empty\n (i.e., created with `--allow-empty`).\n This is related to a reporting issue in Git.\n For more info, [click here](https://github.com/mtkennerly/dunamai/issues/14).\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Dynamic version generation",
"version": "1.22.0",
"project_urls": {
"Homepage": "https://github.com/mtkennerly/dunamai",
"Repository": "https://github.com/mtkennerly/dunamai"
},
"split_keywords": [
"version",
" versioning",
" dynamic"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "b8010c3ba3dcf946e884ad8a153f6ac277e38cc8e87038059dbb751f89b9e258",
"md5": "a7de2d86455c1a891698a8d08ae99d2d",
"sha256": "eab3894b31e145bd028a74b13491c57db01986a7510482c9b5fff3b4e53d77b7"
},
"downloads": -1,
"filename": "dunamai-1.22.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a7de2d86455c1a891698a8d08ae99d2d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 26187,
"upload_time": "2024-08-07T04:09:55",
"upload_time_iso_8601": "2024-08-07T04:09:55.018840Z",
"url": "https://files.pythonhosted.org/packages/b8/01/0c3ba3dcf946e884ad8a153f6ac277e38cc8e87038059dbb751f89b9e258/dunamai-1.22.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "a0feaee602f08765de4dd753d2e5d6cbd480857182e345f161f7a19ad1979e4d",
"md5": "1b3ca932416961ea6953dbbc21e6d42c",
"sha256": "375a0b21309336f0d8b6bbaea3e038c36f462318c68795166e31f9873fdad676"
},
"downloads": -1,
"filename": "dunamai-1.22.0.tar.gz",
"has_sig": false,
"md5_digest": "1b3ca932416961ea6953dbbc21e6d42c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 44453,
"upload_time": "2024-08-07T04:09:56",
"upload_time_iso_8601": "2024-08-07T04:09:56.681316Z",
"url": "https://files.pythonhosted.org/packages/a0/fe/aee602f08765de4dd753d2e5d6cbd480857182e345f161f7a19ad1979e4d/dunamai-1.22.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-07 04:09:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "mtkennerly",
"github_project": "dunamai",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "dunamai"
}