setuptools-us4us 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ """
2
+ setuptools-us4us: shared build-time helpers for the us4us Python packages.
3
+ """
4
+ from setuptools_us4us.setup_helpers import (
5
+ is_dev_version,
6
+ read_version_file,
7
+ setup_version,
8
+ write_version_file,
9
+ )
10
+ from setuptools_us4us.version import __version__
11
+ from setuptools_us4us.versioning import (
12
+ VersioningError,
13
+ find_parent_release_branch,
14
+ get_version,
15
+ get_version_for_release_branch,
16
+ )
17
+
18
+ __all__ = [
19
+ "VersioningError",
20
+ "__version__",
21
+ "find_parent_release_branch",
22
+ "get_version",
23
+ "get_version_for_release_branch",
24
+ "is_dev_version",
25
+ "read_version_file",
26
+ "setup_version",
27
+ "write_version_file",
28
+ ]
@@ -0,0 +1,36 @@
1
+ import subprocess
2
+ from typing import Tuple, Union
3
+
4
+
5
+ def run_cmd(cmd: str, return_code: bool = False,
6
+ cwd: str = None) -> Union[str, Tuple[str, str, int]]:
7
+ """
8
+ Runs the given command and returns its output.
9
+
10
+ :param cmd: command to run, split by whitespaces
11
+ :param return_code: when true, returns the ``(stdout, stderr, returncode)``
12
+ tuple instead of raising an exception on a non-zero exit code
13
+ :param cwd: working directory in which the command should be run,
14
+ ``None`` means the current working directory
15
+ :return: command's stdout (stripped), or the
16
+ ``(stdout, stderr, returncode)`` tuple when ``return_code`` is true
17
+ :raises RuntimeError: when the command exited with a non-zero code and
18
+ ``return_code`` is false
19
+ """
20
+ argv = cmd.strip().split(" ")
21
+ result = subprocess.run(
22
+ argv,
23
+ capture_output=True,
24
+ text=True,
25
+ check=False,
26
+ cwd=cwd,
27
+ )
28
+ stdout = result.stdout.strip()
29
+ stderr = result.stderr.strip()
30
+ if return_code:
31
+ return stdout, stderr, result.returncode
32
+ if result.returncode != 0:
33
+ raise RuntimeError(
34
+ f"The process {argv} exited with code {result.returncode}, "
35
+ f"stdout: '{stdout}', stderr: '{stderr}'")
36
+ return stdout
@@ -0,0 +1,108 @@
1
+ """
2
+ Helpers to be used directly from a project's ``setup.py``.
3
+
4
+ Typical usage::
5
+
6
+ import setuptools
7
+ from setuptools_us4us import setup_version
8
+
9
+ version = setup_version("us4r_vandv")
10
+
11
+ setuptools.setup(
12
+ name="us4r_vandv",
13
+ version=version,
14
+ ...
15
+ )
16
+ """
17
+ import os
18
+ import re
19
+ from typing import Optional
20
+
21
+ from setuptools_us4us.versioning import VersioningError, get_version
22
+
23
+ VERSION_FILE_TEMPLATE = '''\
24
+ # This file is generated by setuptools-us4us, DO NOT EDIT.
25
+ __version__ = "{version}"
26
+
27
+
28
+ def is_dev() -> bool:
29
+ """
30
+ Returns true if this version is a development (non-release) build.
31
+ """
32
+ return {is_dev}
33
+
34
+
35
+ def is_stable() -> bool:
36
+ """
37
+ Returns true if this version is a stable (release) build.
38
+ """
39
+ return not is_dev()
40
+ '''
41
+
42
+
43
+ def is_dev_version(version: str) -> bool:
44
+ """
45
+ Returns true when the given PEP 440 version is a development version,
46
+ i.e. it carries a ``.devN`` segment and/or a local version label.
47
+ """
48
+ return ".dev" in version or "+" in version
49
+
50
+
51
+ def write_version_file(path: str, version: str) -> None:
52
+ """
53
+ Writes the ``version.py`` file with the given version number.
54
+
55
+ :param path: path of the file to write, e.g. ``us4r_vandv/version.py``
56
+ :param version: version number to write
57
+ """
58
+ directory = os.path.dirname(path)
59
+ if directory:
60
+ os.makedirs(directory, exist_ok=True)
61
+ with open(path, "w") as f:
62
+ f.write(VERSION_FILE_TEMPLATE.format(
63
+ version=version,
64
+ is_dev=is_dev_version(version),
65
+ ))
66
+
67
+
68
+ def read_version_file(path: str) -> Optional[str]:
69
+ """
70
+ Reads the ``__version__`` value from the given ``version.py`` file.
71
+ Returns ``None`` when the file does not exist or has no ``__version__``.
72
+ """
73
+ if not os.path.exists(path):
74
+ return None
75
+ with open(path) as f:
76
+ match = re.search(r'^__version__\s*=\s*["\'](.+)["\']\s*$',
77
+ f.read(), re.MULTILINE)
78
+ return match.group(1) if match is not None else None
79
+
80
+
81
+ def setup_version(package_name: str, root: str = ".") -> str:
82
+ """
83
+ Determines the package version from git, writes it to
84
+ ``<root>/<package_name>/version.py`` and returns it.
85
+
86
+ When the version cannot be determined from git (e.g. the package is built
87
+ from an sdist, outside of a git repository), the version already present in
88
+ ``version.py`` is used instead; the file is then left untouched.
89
+
90
+ :param package_name: name of the Python package (directory) that holds
91
+ the ``version.py`` file, e.g. ``us4r_vandv``
92
+ :param root: project root directory
93
+ :return: the version number
94
+ :raises VersioningError: when the version can be determined neither from
95
+ git nor from an existing ``version.py``
96
+ """
97
+ version_file = os.path.join(root, package_name, "version.py")
98
+ try:
99
+ version = get_version(cwd=root)
100
+ except (VersioningError, RuntimeError, OSError) as e:
101
+ fallback = read_version_file(version_file)
102
+ if fallback is None:
103
+ raise VersioningError(
104
+ f"Could not determine the version of '{package_name}' from "
105
+ f"git ({e}), and there is no usable '{version_file}'.")
106
+ return fallback
107
+ write_version_file(version_file, version)
108
+ return version
@@ -0,0 +1,16 @@
1
+ # This file is generated by setuptools-us4us, DO NOT EDIT.
2
+ __version__ = "0.1.0"
3
+
4
+
5
+ def is_dev() -> bool:
6
+ """
7
+ Returns true if this version is a development (non-release) build.
8
+ """
9
+ return False
10
+
11
+
12
+ def is_stable() -> bool:
13
+ """
14
+ Returns true if this version is a stable (release) build.
15
+ """
16
+ return not is_dev()
@@ -0,0 +1,188 @@
1
+ """
2
+ The rules implemented here are the ones used across the us4us Python
3
+ projects:
4
+
5
+ - a commit tagged with a release tag (``va.b.c``) marks the stable
6
+ version ``a.b.c``,
7
+ - a commit on a **release branch** (``va.b.x`` or the legacy ``va.b.0-dev``)
8
+ produces a development version of the next major.minor, i.e.
9
+ ``a.b.(c+1).devYYYYMMDD``, where ``va.b.c`` is the latest release tag
10
+ reachable from that branch; when there is no reachable release tag,
11
+ ``a.b.0.devYYYYMMDD`` is used,
12
+ - a commit on a feature branch produces the dev version of its
13
+ parent release branch, with the branch name local
14
+ version label, e.g. ``0.7.0.dev20260823+ref.mvandv.194``.
15
+ """
16
+ import datetime
17
+ import re
18
+ from typing import Optional, Sequence, Tuple
19
+
20
+ from setuptools_us4us.cmd import run_cmd
21
+
22
+ RELEASE_BRANCH_PATTERNS = (
23
+ # Example: v0.7.x
24
+ r"^v(?P<major>\d+)\.(?P<minor>\d+)\.x$",
25
+ # Legacy example: v0.3.0-dev
26
+ r"^v(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)-dev$",
27
+ )
28
+
29
+ #: Tag names denoting a stable release, e.g. v0.7.1
30
+ RELEASE_TAG_PATTERN = r"^v(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$"
31
+
32
+
33
+ class VersioningError(RuntimeError):
34
+ """
35
+ Raised when the version number cannot be determined from git.
36
+ """
37
+ pass
38
+
39
+
40
+ def get_today() -> str:
41
+ """
42
+ Returns the current date, formatted as ``YYYYMMDD``.
43
+ """
44
+ return datetime.datetime.now().strftime("%Y%m%d")
45
+
46
+
47
+ def match_release_branch(branch: str) -> Optional[Tuple[int, int]]:
48
+ """
49
+ Returns the ``(major, minor)`` pair for the given release branch name,
50
+ or ``None`` when the name is not a release branch name.
51
+ """
52
+ for pattern in RELEASE_BRANCH_PATTERNS:
53
+ match = re.match(pattern, branch.strip())
54
+ if match is not None:
55
+ return int(match.group("major")), int(match.group("minor"))
56
+ return None
57
+
58
+
59
+ def match_release_tag(tag: str) -> Optional[Tuple[int, int, int]]:
60
+ """
61
+ Returns the ``(major, minor, patch)`` triple for the given release tag,
62
+ or ``None`` when the name is not a release tag name.
63
+ """
64
+ match = re.match(RELEASE_TAG_PATTERN, tag.strip())
65
+ if match is None:
66
+ return None
67
+ return (int(match.group("major")), int(match.group("minor")),
68
+ int(match.group("patch")))
69
+
70
+
71
+ def normalize_local_label(branch: str) -> str:
72
+ """
73
+ Converts a branch name to a PEP 440 local version label
74
+ e.g. ref-MVANDV-194 -> ref.mvandv.194.
75
+ """
76
+ label = re.sub(r"[^a-z0-9]+", ".", branch.strip().lower())
77
+ return label.strip(".")
78
+
79
+
80
+ def _strip_v(version: str) -> str:
81
+ return version[1:] if version.startswith("v") else version
82
+
83
+
84
+ def get_version_for_release_branch(branch: str, cwd: str = None,
85
+ today: str = None) -> str:
86
+ """
87
+ Returns the development version for the given release branch.
88
+
89
+ :param branch: release branch name e.g. v0.7.x
90
+ :param cwd: repository directory
91
+ :param today: date timestamp to use, YYYYMMDD; default: current date
92
+ :return: version string (e.g. v0.7.1)
93
+ """
94
+ today = today if today is not None else get_today()
95
+ major_minor = match_release_branch(branch)
96
+ if major_minor is None:
97
+ raise VersioningError(f"Not a release branch name: '{branch}'")
98
+ major, minor = major_minor
99
+ base = f"v{major}.{minor}"
100
+ tag, _, ret = run_cmd(
101
+ cmd=f"git describe --tags --match {base}.* --abbrev=0",
102
+ return_code=True, cwd=cwd,
103
+ )
104
+ if ret == 0 and tag:
105
+ release = match_release_tag(tag)
106
+ if release is not None:
107
+ # There already is a stable release on this branch: the next
108
+ # thing we are going to release is the next patch version.
109
+ tag_major, tag_minor, tag_patch = release
110
+ return f"v{tag_major}.{tag_minor}.{tag_patch + 1}.dev{today}"
111
+ # Non-conformant tag (e.g. v0.7.0-rc1): ignore it.
112
+ return f"{base}.0.dev{today}"
113
+
114
+
115
+ def _list_branches(cwd: str = None) -> Sequence[str]:
116
+ """Returns the names of all local and remote-tracking branches."""
117
+ stdout, _, ret = run_cmd(
118
+ cmd="git branch --all --format=%(refname:short)",
119
+ return_code=True, cwd=cwd,
120
+ )
121
+ if ret != 0:
122
+ return ()
123
+ branches = []
124
+ for line in stdout.split("\n"):
125
+ name = line.strip()
126
+ if not name or "->" in name:
127
+ continue
128
+ # Drop the remote prefix, e.g. origin/v0.7.x -> v0.7.x
129
+ if "/" in name:
130
+ name = name.split("/", 1)[1]
131
+ branches.append(name)
132
+ return branches
133
+
134
+
135
+ def find_parent_release_branch(cwd: str = None) -> str:
136
+ """
137
+ Returns the release branch the current ``HEAD`` was most likely branched
138
+ off from: out of all the release branches sharing a common ancestor with
139
+ ``HEAD``, the one with the highest ``major.minor``.
140
+
141
+ :raises VersioningError: when no such branch exists
142
+ """
143
+ candidates = []
144
+ for branch in set(_list_branches(cwd=cwd)):
145
+ major_minor = match_release_branch(branch)
146
+ if major_minor is None:
147
+ continue
148
+ _, _, ret = run_cmd(cmd=f"git merge-base {branch} HEAD",
149
+ return_code=True, cwd=cwd)
150
+ if ret == 0:
151
+ candidates.append((major_minor, branch))
152
+ if len(candidates) == 0:
153
+ raise VersioningError(
154
+ "Could not determine the version number: the current HEAD has no "
155
+ "parent release branch (expected a branch named e.g. 'v0.7.x'). "
156
+ "Make sure the release branches are fetched "
157
+ "(e.g. 'git fetch --unshallow' in a CI checkout).")
158
+ candidates = sorted(candidates)
159
+ return candidates[-1][1]
160
+
161
+
162
+ def get_version(cwd: str = None, today: str = None) -> str:
163
+ """
164
+ Determines the package version from the state of the git repository.
165
+
166
+ :param cwd: repository directory, the current working directory by default
167
+ :param today: date stamp to use, ``YYYYMMDD``; the current date by default
168
+ :return: PEP 440 version string, e.g. ``0.7.1.dev20260823``
169
+ :raises VersioningError: when the version number cannot be determined
170
+ """
171
+ today = today if today is not None else get_today()
172
+ current_branch = run_cmd(cmd="git rev-parse --abbrev-ref HEAD", cwd=cwd)
173
+ current_tags = run_cmd(cmd="git tag --points-at HEAD", cwd=cwd)
174
+
175
+ # 1. A tagged commit is a stable release.
176
+ for tag in current_tags.split("\n"):
177
+ if match_release_tag(tag) is not None:
178
+ return _strip_v(tag.strip())
179
+
180
+ # 2. A release branch: the development version of the next patch release.
181
+ if match_release_branch(current_branch) is not None:
182
+ return _strip_v(get_version_for_release_branch(
183
+ current_branch, cwd=cwd, today=today))
184
+
185
+ # 3. A feature branch: the parent release branch version + local label.
186
+ parent = find_parent_release_branch(cwd=cwd)
187
+ version = get_version_for_release_branch(parent, cwd=cwd, today=today)
188
+ return _strip_v(f"{version}+{normalize_local_label(current_branch)}")
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: setuptools_us4us
3
+ Version: 0.1.0
4
+ Summary: Shared build-time (setuptools) helpers for the us4us Python packages.
5
+ Home-page: https://us4us.eu
6
+ Author: us4us Ltd.
7
+ Author-email: support@us4us.eu
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Topic :: Software Development :: Build Tools
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ # setuptools-us4us
25
+
26
+ Common setuptools extensions used by the us4us Python packages, e.g.
27
+ determining the package version from the state of the git repository
28
+ (release tags and branches).
29
+
30
+ ## Installation
31
+
32
+ ```
33
+ pip install setuptools_us4us
34
+ ```
35
+
36
+ Usually it is needed only at build time, e.g. in the project's `pyproject.toml`:
37
+
38
+ ```toml
39
+ [build-system]
40
+ requires = ["setuptools>=61", "wheel", "setuptools_us4us"]
41
+ build-backend = "setuptools.build_meta"
42
+ ```
43
+
44
+ and `setup.py`:
45
+
46
+ ```python
47
+ import setuptools
48
+ from setuptools_us4us import setup_version
49
+
50
+ setuptools.setup(
51
+ name="my_package",
52
+ version=setup_version("my_package"), # also writes my_package/version.py
53
+ ...
54
+ )
55
+ ```
56
+
57
+ ## Repository structure
58
+
59
+ - `setuptools_us4us/versioning.py`: git-based version numbers
60
+ (`get_version`, `get_version_for_release_branch`,
61
+ `find_parent_release_branch`):
62
+ - tagged commit `va.b.c` -> `a.b.c`,
63
+ - release branch `va.b.x` -> `a.b.(c+1).devYYYYMMDD`,
64
+ - feature branch -> parent release branch version + branch name as local
65
+ label, e.g. `0.7.0.dev20260823+ref.mvandv.194`.
66
+ - `setuptools_us4us/setup_helpers.py`: helpers for `setup.py`
67
+ (`setup_version`, `write_version_file`, `read_version_file`,
68
+ `is_dev_version`).
69
+ - `setuptools_us4us/cmd.py`: running shell commands (`run_cmd`).
@@ -0,0 +1,9 @@
1
+ setuptools_us4us/__init__.py,sha256=NWOIXdYO2V4epz8yHjBGLZOSpvLv496mUC2c_h3Uwek,657
2
+ setuptools_us4us/cmd.py,sha256=40OpqVJyfYAPrNnD6DDoABGzQKZVFM4r4tRhZzcsTlY,1299
3
+ setuptools_us4us/setup_helpers.py,sha256=yZSmz51zt21goozx4F1M_UdpQ3ke1XkINuuaEH0u_qU,3305
4
+ setuptools_us4us/version.py,sha256=xPY9lfp6N_vdi4fTqGroHseX11_-TIsbW0_QlWD6HR8,338
5
+ setuptools_us4us/versioning.py,sha256=VzQeV3KyGGWCYHHy-T4WOgfvQf6JuBNUP_hC-FNhu2Y,6934
6
+ setuptools_us4us-0.1.0.dist-info/METADATA,sha256=TBdqp9DJuYZwdTh0ebEV0cBnbaqnUyxDUHrBpgpH_Kw,1974
7
+ setuptools_us4us-0.1.0.dist-info/WHEEL,sha256=a-udL2m4V_6Yn8b-uKzWb12RjRxwwMo6ohYFM24tpaY,91
8
+ setuptools_us4us-0.1.0.dist-info/top_level.txt,sha256=NrGCQnrB2gB687Wy8oH5TqCpucfu8rIDkQj4Jth0WGw,17
9
+ setuptools_us4us-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ setuptools_us4us