easybind 0.2.3__tar.gz → 0.2.4__tar.gz

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.
Files changed (32) hide show
  1. {easybind-0.2.3 → easybind-0.2.4}/PKG-INFO +38 -1
  2. {easybind-0.2.3 → easybind-0.2.4}/README.md +37 -0
  3. {easybind-0.2.3 → easybind-0.2.4}/RELEASING.md +13 -0
  4. {easybind-0.2.3 → easybind-0.2.4}/pyproject.toml +3 -0
  5. easybind-0.2.4/scripts/release_tag.py +197 -0
  6. easybind-0.2.4/src/easybind/devtools/__init__.py +19 -0
  7. easybind-0.2.4/src/easybind/devtools/pin_pyproject.py +170 -0
  8. {easybind-0.2.3 → easybind-0.2.4}/.clangd +0 -0
  9. {easybind-0.2.3 → easybind-0.2.4}/.github/workflows/publish.yml +0 -0
  10. {easybind-0.2.3 → easybind-0.2.4}/.gitignore +0 -0
  11. {easybind-0.2.3 → easybind-0.2.4}/.vscode/tasks.json +0 -0
  12. {easybind-0.2.3 → easybind-0.2.4}/CMakeLists.txt +0 -0
  13. {easybind-0.2.3 → easybind-0.2.4}/LICENSE +0 -0
  14. {easybind-0.2.3 → easybind-0.2.4}/NOTICE +0 -0
  15. {easybind-0.2.3 → easybind-0.2.4}/cmake/easybind_dependencies.cmake +0 -0
  16. {easybind-0.2.3 → easybind-0.2.4}/cmake/easybind_pip.cmake +0 -0
  17. {easybind-0.2.3 → easybind-0.2.4}/scripts/clangd-update.sh +0 -0
  18. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/__init__.cpp +0 -0
  19. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/bind.hpp +0 -0
  20. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/export.hpp +0 -0
  21. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/module/__init__.cpp +0 -0
  22. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/module/node.cpp +0 -0
  23. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/module/node.hpp +0 -0
  24. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/module/node__init__.cpp +0 -0
  25. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/module/ns_module.hpp +0 -0
  26. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/py.typed +0 -0
  27. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/sample/__init__.cpp +0 -0
  28. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/sample/sample.cpp +0 -0
  29. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/sample/sample.hpp +0 -0
  30. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/sample/test1.py +0 -0
  31. {easybind-0.2.3 → easybind-0.2.4}/src/easybind/sample/test2/__init__.py +0 -0
  32. {easybind-0.2.3 → easybind-0.2.4}/tests/test_sample.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easybind
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Self-registering nanobind helpers (import as easybind)
5
5
  Keywords: easybind,nanobind,bindings,pymergetic
6
6
  Author-Email: PymergeticOS Maintainers <raudzus@pymergetic.com>
@@ -83,6 +83,43 @@ easybind_fetch_magic_enum() # if you include easybind headers that need magic_
83
83
 
84
84
  When developing **inside** this repository, `easybind_add_extension(...)` is defined in the top-level `CMakeLists.txt` (not shipped in the wheel).
85
85
 
86
+ ## Bumping `{distribution}~=…` pins (downstream projects)
87
+
88
+ Use **`easybind.devtools`** or the **`easybind-pin-pyproject`** CLI to rewrite every compatible-release line **`{name}~=X.Y.Z`** in a **`pyproject.toml`** for any PyPI **distribution** name **`name`** (not only **easybind**). Examples:
89
+
90
+ - **cppdantic** pinning **easybind** in several tables.
91
+ - A future project pinning **cppdantic** the same way: pass **`--distribution cppdantic`**.
92
+
93
+ **CLI** (defaults: **`distribution=easybind`**, version = latest on PyPI for that distribution; run from the tree that contains **`pyproject.toml`**):
94
+
95
+ ```bash
96
+ easybind-pin-pyproject --dry-run
97
+ easybind-pin-pyproject
98
+ easybind-pin-pyproject --installed
99
+ easybind-pin-pyproject --version 0.2.3
100
+ easybind-pin-pyproject --distribution cppdantic
101
+ easybind-pin-pyproject --pyproject /path/to/pyproject.toml
102
+ ```
103
+
104
+ **Library:**
105
+
106
+ ```python
107
+ from easybind.devtools import (
108
+ bump_compatible_pins_in_file,
109
+ fetch_pypi_version,
110
+ )
111
+
112
+ # Pin all easybind~= lines to latest PyPI easybind
113
+ ver = fetch_pypi_version("easybind")
114
+ bump_compatible_pins_in_file("pyproject.toml", "easybind", ver)
115
+
116
+ # Pin all cppdantic~= lines to latest PyPI cppdantic (e.g. downstream of cppdantic)
117
+ ver = fetch_pypi_version("cppdantic")
118
+ bump_compatible_pins_in_file("pyproject.toml", "cppdantic", ver)
119
+ ```
120
+
121
+ Shorthands **`bump_easybind_compatible_pins`** / **`bump_easybind_compatible_pins_in_file`** remain for **`distribution=\"easybind\"`** only.
122
+
86
123
  ## Core idea
87
124
  - Each namespace/module defines a `ModuleNode` and a bind callback.
88
125
  - The module entry point calls `apply_init` to run the callback and recurse.
@@ -54,6 +54,43 @@ easybind_fetch_magic_enum() # if you include easybind headers that need magic_
54
54
 
55
55
  When developing **inside** this repository, `easybind_add_extension(...)` is defined in the top-level `CMakeLists.txt` (not shipped in the wheel).
56
56
 
57
+ ## Bumping `{distribution}~=…` pins (downstream projects)
58
+
59
+ Use **`easybind.devtools`** or the **`easybind-pin-pyproject`** CLI to rewrite every compatible-release line **`{name}~=X.Y.Z`** in a **`pyproject.toml`** for any PyPI **distribution** name **`name`** (not only **easybind**). Examples:
60
+
61
+ - **cppdantic** pinning **easybind** in several tables.
62
+ - A future project pinning **cppdantic** the same way: pass **`--distribution cppdantic`**.
63
+
64
+ **CLI** (defaults: **`distribution=easybind`**, version = latest on PyPI for that distribution; run from the tree that contains **`pyproject.toml`**):
65
+
66
+ ```bash
67
+ easybind-pin-pyproject --dry-run
68
+ easybind-pin-pyproject
69
+ easybind-pin-pyproject --installed
70
+ easybind-pin-pyproject --version 0.2.3
71
+ easybind-pin-pyproject --distribution cppdantic
72
+ easybind-pin-pyproject --pyproject /path/to/pyproject.toml
73
+ ```
74
+
75
+ **Library:**
76
+
77
+ ```python
78
+ from easybind.devtools import (
79
+ bump_compatible_pins_in_file,
80
+ fetch_pypi_version,
81
+ )
82
+
83
+ # Pin all easybind~= lines to latest PyPI easybind
84
+ ver = fetch_pypi_version("easybind")
85
+ bump_compatible_pins_in_file("pyproject.toml", "easybind", ver)
86
+
87
+ # Pin all cppdantic~= lines to latest PyPI cppdantic (e.g. downstream of cppdantic)
88
+ ver = fetch_pypi_version("cppdantic")
89
+ bump_compatible_pins_in_file("pyproject.toml", "cppdantic", ver)
90
+ ```
91
+
92
+ Shorthands **`bump_easybind_compatible_pins`** / **`bump_easybind_compatible_pins_in_file`** remain for **`distribution=\"easybind\"`** only.
93
+
57
94
  ## Core idea
58
95
  - Each namespace/module defines a `ModuleNode` and a bind callback.
59
96
  - The module entry point calls `apply_init` to run the callback and recurse.
@@ -20,6 +20,19 @@ twine upload dist/* # or TestPyPI: twine upload --repository testpypi dist/*
20
20
 
21
21
  Configure credentials with **API token** (`~/.pypirc` or environment variables) or use **trusted publishing** (OIDC) from GitHub Actions.
22
22
 
23
+ ## Helper: next semver tag + push
24
+
25
+ `scripts/release_tag.py` picks the latest **`vMAJOR.MINOR.PATCH`** tag, bumps **patch** by default (or **`--minor`** / **`--major`**), then **`fetch` / `checkout` / `pull`** (unless **`--no-pull`**), creates an **annotated** tag, and **`push`**es branch + tag (unless **`--no-push`** or **`--dry-run`**).
26
+
27
+ ```bash
28
+ ./scripts/release_tag.py --dry-run
29
+ ./scripts/release_tag.py
30
+ ./scripts/release_tag.py --minor
31
+ ./scripts/release_tag.py --major
32
+ ```
33
+
34
+ Run from the **easybind** repo root. Requires a **clean** working tree.
35
+
23
36
  ## CI upload
24
37
 
25
38
  Pushing a tag matching `v*` triggers `.github/workflows/publish.yml`, which builds and uploads to PyPI.
@@ -32,6 +32,9 @@ Homepage = "https://github.com/pymergetic/easybind"
32
32
  Repository = "https://github.com/pymergetic/easybind"
33
33
  Issues = "https://github.com/pymergetic/easybind/issues"
34
34
 
35
+ [project.scripts]
36
+ easybind-pin-pyproject = "easybind.devtools.pin_pyproject:main"
37
+
35
38
  [project.optional-dependencies]
36
39
  dev = [
37
40
  "pytest>=7.0.0",
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ """Create and push the next easybind release tag from the latest ``v*`` git tag.
3
+
4
+ Default: bump **patch** (0.2.3 → 0.2.4). Use ``--minor`` or ``--major`` to bump those
5
+ segments instead (resets lower segments to 0).
6
+
7
+ If there is no ``v*`` tag yet, starts from **v0.0.0** as the notional baseline, then bumps.
8
+
9
+ Examples::
10
+
11
+ ./scripts/release_tag.py --dry-run
12
+ ./scripts/release_tag.py
13
+ ./scripts/release_tag.py --minor
14
+ ./scripts/release_tag.py --major --no-push
15
+
16
+ Requires a clean ``main`` sync (fetch/checkout/pull) unless ``--no-pull``.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import re
23
+ import subprocess
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ _REPO = Path(__file__).resolve().parents[1]
28
+ _TAG_RE = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")
29
+
30
+
31
+ def _run(
32
+ cmd: list[str],
33
+ *,
34
+ cwd: Path,
35
+ check: bool = True,
36
+ ) -> subprocess.CompletedProcess[str]:
37
+ return subprocess.run(
38
+ cmd,
39
+ cwd=cwd,
40
+ check=check,
41
+ text=True,
42
+ capture_output=True,
43
+ )
44
+
45
+
46
+ def _latest_version_tag(repo: Path) -> str | None:
47
+ """Newest ``vMAJOR.MINOR.PATCH`` tag by version tuple (no reliance on git sort)."""
48
+ p = _run(["git", "tag", "-l", "v*"], cwd=repo, check=False)
49
+ if p.returncode != 0:
50
+ return None
51
+ tags: list[str] = []
52
+ for line in p.stdout.splitlines():
53
+ line = line.strip()
54
+ if line and _TAG_RE.match(line):
55
+ tags.append(line)
56
+ if not tags:
57
+ return None
58
+ return max(tags, key=lambda t: _parse_semver(t))
59
+
60
+
61
+ def _parse_semver(tag: str) -> tuple[int, int, int]:
62
+ m = _TAG_RE.fullmatch(tag.strip())
63
+ if not m:
64
+ raise ValueError(f"not a vMAJOR.MINOR.PATCH tag: {tag!r}")
65
+ return int(m.group(1)), int(m.group(2)), int(m.group(3))
66
+
67
+
68
+ def _bump(
69
+ major: int,
70
+ minor: int,
71
+ patch: int,
72
+ *,
73
+ level: str,
74
+ ) -> tuple[int, int, int]:
75
+ if level == "major":
76
+ return major + 1, 0, 0
77
+ if level == "minor":
78
+ return major, minor + 1, 0
79
+ return major, minor, patch + 1
80
+
81
+
82
+ def _format_tag(t: tuple[int, int, int]) -> str:
83
+ return f"v{t[0]}.{t[1]}.{t[2]}"
84
+
85
+
86
+ def _ensure_clean(repo: Path) -> None:
87
+ p = _run(["git", "status", "--porcelain"], cwd=repo, check=True)
88
+ if p.stdout.strip():
89
+ raise SystemExit(
90
+ "working tree is not clean; commit or stash before tagging.\n"
91
+ + p.stdout
92
+ )
93
+
94
+
95
+ def main() -> int:
96
+ ap = argparse.ArgumentParser(description=__doc__)
97
+ g = ap.add_mutually_exclusive_group()
98
+ g.add_argument(
99
+ "--patch",
100
+ action="store_const",
101
+ dest="level",
102
+ const="patch",
103
+ help="bump patch (default)",
104
+ )
105
+ g.add_argument(
106
+ "--minor",
107
+ action="store_const",
108
+ dest="level",
109
+ const="minor",
110
+ help="bump minor, reset patch to 0",
111
+ )
112
+ g.add_argument(
113
+ "--major",
114
+ action="store_const",
115
+ dest="level",
116
+ const="major",
117
+ help="bump major, reset minor and patch to 0",
118
+ )
119
+ ap.set_defaults(level="patch")
120
+ ap.add_argument(
121
+ "--dry-run",
122
+ action="store_true",
123
+ help="print the next tag and commands only; no git writes",
124
+ )
125
+ ap.add_argument(
126
+ "--no-pull",
127
+ action="store_true",
128
+ help="do not fetch/checkout main/pull (still creates tag unless --dry-run)",
129
+ )
130
+ ap.add_argument(
131
+ "--no-push",
132
+ action="store_true",
133
+ help="create the annotated tag locally but do not push main or the tag",
134
+ )
135
+ ap.add_argument(
136
+ "--remote",
137
+ default="origin",
138
+ help="git remote for fetch/push (default: origin)",
139
+ )
140
+ ap.add_argument(
141
+ "--branch",
142
+ default="main",
143
+ help="branch to update before tagging (default: main)",
144
+ )
145
+ ns = ap.parse_args()
146
+
147
+ repo = _REPO
148
+ if not (repo / ".git").exists() and not (repo / ".git").is_file():
149
+ print(f"error: no git state at {repo}", file=sys.stderr)
150
+ return 2
151
+
152
+ latest = _latest_version_tag(repo)
153
+ if latest is None:
154
+ ma, mi, pa = 0, 0, 0
155
+ else:
156
+ ma, mi, pa = _parse_semver(latest)
157
+
158
+ new_ver = _bump(ma, mi, pa, level=ns.level)
159
+ new_tag = _format_tag(new_ver)
160
+
161
+ print(f"latest tag: {latest or '(none)'}")
162
+ print(f"next tag: {new_tag} (bump {ns.level})")
163
+
164
+ cmds: list[list[str]] = []
165
+ if not ns.no_pull:
166
+ cmds.extend(
167
+ [
168
+ ["git", "fetch", ns.remote],
169
+ ["git", "checkout", ns.branch],
170
+ ["git", "pull", ns.remote, ns.branch],
171
+ ]
172
+ )
173
+ cmds.append(["git", "tag", "-a", new_tag, "-m", f"easybind {new_tag.removeprefix('v')}"])
174
+ if not ns.no_push:
175
+ cmds.append(["git", "push", ns.remote, ns.branch])
176
+ cmds.append(["git", "push", ns.remote, new_tag])
177
+
178
+ if ns.dry_run:
179
+ print("--- dry-run; commands not executed ---")
180
+ for c in cmds:
181
+ print("+", " ".join(c))
182
+ return 0
183
+
184
+ _ensure_clean(repo)
185
+
186
+ for c in cmds:
187
+ subprocess.run(c, cwd=repo, check=True)
188
+
189
+ if ns.no_push:
190
+ print(f"done: created {new_tag} locally (not pushed)")
191
+ else:
192
+ print(f"done: pushed {new_tag} (remote={ns.remote}, branch={ns.branch})")
193
+ return 0
194
+
195
+
196
+ if __name__ == "__main__":
197
+ raise SystemExit(main())
@@ -0,0 +1,19 @@
1
+ """Developer helpers (PyPI version lookup, bumping ``{dist}~=…`` pins in ``pyproject.toml``)."""
2
+
3
+ from easybind.devtools.pin_pyproject import (
4
+ bump_compatible_pins,
5
+ bump_compatible_pins_in_file,
6
+ bump_easybind_compatible_pins,
7
+ bump_easybind_compatible_pins_in_file,
8
+ fetch_pypi_version,
9
+ installed_distribution_version,
10
+ )
11
+
12
+ __all__ = [
13
+ "bump_compatible_pins",
14
+ "bump_compatible_pins_in_file",
15
+ "bump_easybind_compatible_pins",
16
+ "bump_easybind_compatible_pins_in_file",
17
+ "fetch_pypi_version",
18
+ "installed_distribution_version",
19
+ ]
@@ -0,0 +1,170 @@
1
+ """Bump ``{distribution}~=…`` compatible-release pins in a ``pyproject.toml`` string or file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import re
8
+ import sys
9
+ import urllib.request
10
+ from pathlib import Path
11
+
12
+ # PEP 440 version suitable for ``~=`` RHS in typical pyproject pins (numeric release).
13
+ _VERSION_RE = re.compile(r"^[0-9]+(?:\.[0-9]+)*$")
14
+
15
+ # Reject pathological / mistaken distribution strings (full re.escape covers the rest).
16
+ _DIST_OK = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._-]*$")
17
+
18
+
19
+ def _pin_pattern(distribution: str) -> re.Pattern[str]:
20
+ if not _DIST_OK.match(distribution):
21
+ raise ValueError(
22
+ f"invalid distribution name for pin replacement: {distribution!r} "
23
+ "(expected a PyPI-style name, e.g. easybind, cppdantic, scikit-build-core)"
24
+ )
25
+ return re.compile(rf"({re.escape(distribution)}~=)([0-9]+(?:\.[0-9]+)*)")
26
+
27
+
28
+ def fetch_pypi_version(package: str = "easybind", *, timeout_s: float = 30.0) -> str:
29
+ """Return ``info.version`` from ``https://pypi.org/pypi/{package}/json``."""
30
+ url = f"https://pypi.org/pypi/{package}/json"
31
+ with urllib.request.urlopen(url, timeout=timeout_s) as r:
32
+ data = json.load(r)
33
+ return str(data["info"]["version"])
34
+
35
+
36
+ def installed_distribution_version(package: str = "easybind") -> str:
37
+ """Return ``importlib.metadata.version(package)`` for the active environment."""
38
+ from importlib.metadata import version
39
+
40
+ return version(package)
41
+
42
+
43
+ def bump_compatible_pins(pyproject_toml: str, distribution: str, version: str) -> tuple[str, int]:
44
+ """Replace each ``{distribution}~=X.Y.Z`` with ``{distribution}~={version}``.
45
+
46
+ Returns ``(new_text, replacement_count)``.
47
+ """
48
+ if not _VERSION_RE.match(version):
49
+ raise ValueError(f"invalid PEP 440 version for pin: {version!r}")
50
+
51
+ pat = _pin_pattern(distribution)
52
+
53
+ def repl(m: re.Match[str]) -> str:
54
+ return f"{m.group(1)}{version}"
55
+
56
+ new_text, n = pat.subn(repl, pyproject_toml)
57
+ return new_text, n
58
+
59
+
60
+ def bump_compatible_pins_in_file(
61
+ pyproject: Path | str,
62
+ distribution: str,
63
+ version: str,
64
+ *,
65
+ dry_run: bool = False,
66
+ ) -> int:
67
+ """Read ``pyproject.toml``, apply :func:`bump_compatible_pins`, write back unless ``dry_run``.
68
+
69
+ Returns the number of replacements. Raises ``ValueError`` if no matching pin exists.
70
+ """
71
+ path = Path(pyproject)
72
+ text = path.read_text(encoding="utf-8")
73
+ new_text, n = bump_compatible_pins(text, distribution, version)
74
+ if n == 0:
75
+ raise ValueError(f"no `{distribution}~=...` pin found in {path}")
76
+ if new_text == text:
77
+ return n
78
+ if not dry_run:
79
+ path.write_text(new_text, encoding="utf-8")
80
+ return n
81
+
82
+
83
+ def bump_easybind_compatible_pins(pyproject_toml: str, version: str) -> tuple[str, int]:
84
+ """Same as :func:`bump_compatible_pins` with ``distribution=\"easybind\"``."""
85
+ return bump_compatible_pins(pyproject_toml, "easybind", version)
86
+
87
+
88
+ def bump_easybind_compatible_pins_in_file(
89
+ pyproject: Path | str,
90
+ version: str,
91
+ *,
92
+ dry_run: bool = False,
93
+ ) -> int:
94
+ """Same as :func:`bump_compatible_pins_in_file` with ``distribution=\"easybind\"``."""
95
+ return bump_compatible_pins_in_file(pyproject, "easybind", version, dry_run=dry_run)
96
+
97
+
98
+ def main(argv: list[str] | None = None) -> int:
99
+ """CLI: ``easybind-pin-pyproject`` (see ``--help``)."""
100
+ ap = argparse.ArgumentParser(
101
+ description=(
102
+ "Set every {distribution}~= pin in pyproject.toml "
103
+ "(default distribution: easybind; default version: latest on PyPI)."
104
+ )
105
+ )
106
+ ap.add_argument(
107
+ "--distribution",
108
+ "-d",
109
+ default="easybind",
110
+ metavar="NAME",
111
+ help="PyPI distribution name to match in pins (default: easybind), e.g. cppdantic, easybind",
112
+ )
113
+ ap.add_argument(
114
+ "--pyproject",
115
+ type=Path,
116
+ default=None,
117
+ help="path to pyproject.toml (default: ./pyproject.toml under cwd)",
118
+ )
119
+ ap.add_argument(
120
+ "--version",
121
+ metavar="X.Y.Z",
122
+ default=None,
123
+ help="pin to this version (default: PyPI latest for --distribution, unless --installed)",
124
+ )
125
+ ap.add_argument(
126
+ "--installed",
127
+ action="store_true",
128
+ help="use the version of --distribution installed in the current environment",
129
+ )
130
+ ap.add_argument("--dry-run", action="store_true", help="do not write the file")
131
+ ns = ap.parse_args(argv)
132
+
133
+ if ns.version and ns.installed:
134
+ print("error: use only one of --version or --installed", file=sys.stderr)
135
+ return 2
136
+
137
+ pyproject = ns.pyproject if ns.pyproject is not None else Path.cwd() / "pyproject.toml"
138
+ if not pyproject.is_file():
139
+ print(f"error: {pyproject} not found", file=sys.stderr)
140
+ return 2
141
+
142
+ dist = ns.distribution.strip()
143
+ if not dist:
144
+ print("error: empty --distribution", file=sys.stderr)
145
+ return 2
146
+
147
+ if ns.installed:
148
+ ver = installed_distribution_version(dist)
149
+ elif ns.version:
150
+ ver = ns.version.strip()
151
+ else:
152
+ ver = fetch_pypi_version(dist)
153
+
154
+ if not _VERSION_RE.match(ver):
155
+ print(f"error: bad version string: {ver!r}", file=sys.stderr)
156
+ return 2
157
+
158
+ try:
159
+ n = bump_compatible_pins_in_file(pyproject, dist, ver, dry_run=ns.dry_run)
160
+ except ValueError as e:
161
+ print(f"error: {e}", file=sys.stderr)
162
+ return 1
163
+
164
+ action = "would update" if ns.dry_run else "updated"
165
+ print(f"{action} {n} {dist}~= pin(s) to ~={ver} in {pyproject}")
166
+ return 0
167
+
168
+
169
+ if __name__ == "__main__":
170
+ raise SystemExit(main())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes