easybind 0.2.5__tar.gz → 0.2.7__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 (37) hide show
  1. {easybind-0.2.5 → easybind-0.2.7}/PKG-INFO +5 -2
  2. {easybind-0.2.5 → easybind-0.2.7}/README.md +4 -1
  3. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/devtools/__init__.py +2 -0
  4. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/devtools/pin_pyproject.py +103 -6
  5. {easybind-0.2.5 → easybind-0.2.7}/.clangd +0 -0
  6. {easybind-0.2.5 → easybind-0.2.7}/.github/workflows/publish.yml +0 -0
  7. {easybind-0.2.5 → easybind-0.2.7}/.gitignore +0 -0
  8. {easybind-0.2.5 → easybind-0.2.7}/.vscode/tasks.json +0 -0
  9. {easybind-0.2.5 → easybind-0.2.7}/CMakeLists.txt +0 -0
  10. {easybind-0.2.5 → easybind-0.2.7}/LICENSE +0 -0
  11. {easybind-0.2.5 → easybind-0.2.7}/NOTICE +0 -0
  12. {easybind-0.2.5 → easybind-0.2.7}/RELEASING.md +0 -0
  13. {easybind-0.2.5 → easybind-0.2.7}/cmake/easybind_dependencies.cmake +0 -0
  14. {easybind-0.2.5 → easybind-0.2.7}/cmake/easybind_pip.cmake +0 -0
  15. {easybind-0.2.5 → easybind-0.2.7}/pyproject.toml +0 -0
  16. {easybind-0.2.5 → easybind-0.2.7}/pyrightconfig.json +0 -0
  17. {easybind-0.2.5 → easybind-0.2.7}/scripts/clangd-update.sh +0 -0
  18. {easybind-0.2.5 → easybind-0.2.7}/scripts/release_tag.py +0 -0
  19. {easybind-0.2.5 → easybind-0.2.7}/scripts/wait_pypi_release.py +0 -0
  20. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/__init__.cpp +0 -0
  21. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/bind.hpp +0 -0
  22. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/devtools/release_helpers.py +0 -0
  23. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/devtools/release_tag.py +0 -0
  24. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/devtools/wait_pypi.py +0 -0
  25. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/export.hpp +0 -0
  26. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/module/__init__.cpp +0 -0
  27. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/module/node.cpp +0 -0
  28. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/module/node.hpp +0 -0
  29. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/module/node__init__.cpp +0 -0
  30. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/module/ns_module.hpp +0 -0
  31. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/py.typed +0 -0
  32. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/sample/__init__.cpp +0 -0
  33. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/sample/sample.cpp +0 -0
  34. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/sample/sample.hpp +0 -0
  35. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/sample/test1.py +0 -0
  36. {easybind-0.2.5 → easybind-0.2.7}/src/easybind/sample/test2/__init__.py +0 -0
  37. {easybind-0.2.5 → easybind-0.2.7}/tests/test_sample.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easybind
3
- Version: 0.2.5
3
+ Version: 0.2.7
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>
@@ -90,17 +90,20 @@ Use **`easybind.devtools`** or the **`easybind-pin-pyproject`** CLI to rewrite e
90
90
  - **cppdantic** pinning **easybind** in several tables.
91
91
  - A future project pinning **cppdantic** the same way: pass **`--distribution cppdantic`**.
92
92
 
93
- **CLI** (defaults: **`distribution=easybind`**, version = latest on PyPI for that distribution; run from the tree that contains **`pyproject.toml`**):
93
+ **CLI** (defaults: **`distribution=easybind`**, version = latest on PyPI; run from the tree that contains **`pyproject.toml`**):
94
94
 
95
95
  ```bash
96
96
  easybind-pin-pyproject --dry-run
97
97
  easybind-pin-pyproject
98
+ easybind-pin-pyproject --from-github pymergetic/easybind # latest v* tag via GitHub API (PyPI may lag)
98
99
  easybind-pin-pyproject --installed
99
100
  easybind-pin-pyproject --version 0.2.3
100
101
  easybind-pin-pyproject --distribution cppdantic
101
102
  easybind-pin-pyproject --pyproject /path/to/pyproject.toml
102
103
  ```
103
104
 
105
+ Use **`GITHUB_TOKEN`** for private GitHub repos or higher API rate limits.
106
+
104
107
  **Other devtools CLIs** (after **`pip install`** / **`uv pip install -e .`**, or run **`scripts/…`** shims from a git checkout):
105
108
 
106
109
  ```bash
@@ -61,17 +61,20 @@ Use **`easybind.devtools`** or the **`easybind-pin-pyproject`** CLI to rewrite e
61
61
  - **cppdantic** pinning **easybind** in several tables.
62
62
  - A future project pinning **cppdantic** the same way: pass **`--distribution cppdantic`**.
63
63
 
64
- **CLI** (defaults: **`distribution=easybind`**, version = latest on PyPI for that distribution; run from the tree that contains **`pyproject.toml`**):
64
+ **CLI** (defaults: **`distribution=easybind`**, version = latest on PyPI; run from the tree that contains **`pyproject.toml`**):
65
65
 
66
66
  ```bash
67
67
  easybind-pin-pyproject --dry-run
68
68
  easybind-pin-pyproject
69
+ easybind-pin-pyproject --from-github pymergetic/easybind # latest v* tag via GitHub API (PyPI may lag)
69
70
  easybind-pin-pyproject --installed
70
71
  easybind-pin-pyproject --version 0.2.3
71
72
  easybind-pin-pyproject --distribution cppdantic
72
73
  easybind-pin-pyproject --pyproject /path/to/pyproject.toml
73
74
  ```
74
75
 
76
+ Use **`GITHUB_TOKEN`** for private GitHub repos or higher API rate limits.
77
+
75
78
  **Other devtools CLIs** (after **`pip install`** / **`uv pip install -e .`**, or run **`scripts/…`** shims from a git checkout):
76
79
 
77
80
  ```bash
@@ -8,6 +8,7 @@ from easybind.devtools.pin_pyproject import (
8
8
  compatible_pin_versions,
9
9
  fetch_pypi_version,
10
10
  installed_distribution_version,
11
+ latest_release_version_from_github,
11
12
  pypi_release_exists,
12
13
  single_compatible_pin_version,
13
14
  wait_pypi_for_compatible_pin,
@@ -28,6 +29,7 @@ __all__ = [
28
29
  "ensure_clean_worktree",
29
30
  "fetch_pypi_version",
30
31
  "installed_distribution_version",
32
+ "latest_release_version_from_github",
31
33
  "latest_v_tag",
32
34
  "next_v_tag",
33
35
  "pypi_release_exists",
@@ -4,16 +4,20 @@ from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  import json
7
+ import os
7
8
  import re
8
9
  import sys
9
10
  import time
10
11
  from pathlib import Path
11
12
  from urllib.error import HTTPError
12
- from urllib.request import urlopen
13
+ from urllib.request import Request, urlopen
13
14
 
14
15
  # PEP 440 version suitable for ``~=`` RHS in typical pyproject pins (numeric release).
15
16
  _VERSION_RE = re.compile(r"^[0-9]+(?:\.[0-9]+)*$")
16
17
 
18
+ # Release tags: ``vMAJOR.MINOR.PATCH`` (GitHub / git tag style).
19
+ _V_SEMVER_TAG = re.compile(r"^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$")
20
+
17
21
  # Reject pathological / mistaken distribution strings (full re.escape covers the rest).
18
22
  _DIST_OK = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._-]*$")
19
23
 
@@ -121,6 +125,76 @@ def installed_distribution_version(package: str = "easybind") -> str:
121
125
  return version(package)
122
126
 
123
127
 
128
+ def _github_request_json(url: str, *, token: str | None, timeout_s: float) -> object:
129
+ req = Request(url, headers={"Accept": "application/vnd.github+json"})
130
+ if token:
131
+ req.add_header("Authorization", f"Bearer {token}")
132
+ with urlopen(req, timeout=timeout_s) as r:
133
+ return json.load(r)
134
+
135
+
136
+ def latest_release_version_from_github(
137
+ owner_repo: str,
138
+ *,
139
+ token: str | None = None,
140
+ timeout_s: float = 30.0,
141
+ ) -> str:
142
+ """Return the highest ``vMAJOR.MINOR.PATCH`` tag on GitHub as ``X.Y.Z`` (no local git clone).
143
+
144
+ Uses ``GET /repos/{owner}/{repo}/tags`` (paginated). Works when the tag exists on GitHub but
145
+ PyPI has not published the wheel yet. *owner_repo* is ``OWNER/REPO`` (e.g. ``pymergetic/easybind``).
146
+
147
+ Set ``GITHUB_TOKEN`` or ``GH_TOKEN`` (or pass *token*) for private repos or higher rate limits.
148
+ """
149
+ s = owner_repo.strip().strip("/")
150
+ parts = s.split("/")
151
+ if len(parts) != 2 or not parts[0] or not parts[1]:
152
+ raise ValueError(f"expected OWNER/REPO, got {owner_repo!r}")
153
+
154
+ owner, repo = parts[0], parts[1]
155
+ tok = token if token is not None else os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
156
+
157
+ best: tuple[int, int, int] | None = None
158
+ best_ver: str | None = None
159
+ page = 1
160
+ per_page = 100
161
+ while page <= 100:
162
+ url = f"https://api.github.com/repos/{owner}/{repo}/tags?per_page={per_page}&page={page}"
163
+ try:
164
+ data = _github_request_json(url, token=tok, timeout_s=timeout_s)
165
+ except HTTPError as e:
166
+ detail = ""
167
+ try:
168
+ detail = e.read().decode("utf-8", errors="replace")[:500]
169
+ except Exception:
170
+ pass
171
+ raise ValueError(
172
+ f"GitHub API HTTP {e.code} for {owner}/{repo} (set GITHUB_TOKEN for private repos): {detail}"
173
+ ) from e
174
+ if not isinstance(data, list) or len(data) == 0:
175
+ break
176
+ for item in data:
177
+ if not isinstance(item, dict):
178
+ continue
179
+ name = item.get("name")
180
+ if not isinstance(name, str):
181
+ continue
182
+ m = _V_SEMVER_TAG.fullmatch(name.strip())
183
+ if not m:
184
+ continue
185
+ t = (int(m.group(1)), int(m.group(2)), int(m.group(3)))
186
+ if best is None or t > best:
187
+ best = t
188
+ best_ver = f"{m.group(1)}.{m.group(2)}.{m.group(3)}"
189
+ if len(data) < per_page:
190
+ break
191
+ page += 1
192
+
193
+ if best_ver is None:
194
+ raise ValueError(f"no vMAJOR.MINOR.PATCH tags found for github.com/{owner}/{repo}")
195
+ return best_ver
196
+
197
+
124
198
  def bump_compatible_pins(pyproject_toml: str, distribution: str, version: str) -> tuple[str, int]:
125
199
  """Replace each ``{distribution}~=X.Y.Z`` with ``{distribution}~={version}``.
126
200
 
@@ -181,7 +255,10 @@ def main(argv: list[str] | None = None) -> int:
181
255
  ap = argparse.ArgumentParser(
182
256
  description=(
183
257
  "Set every {distribution}~= pin in pyproject.toml "
184
- "(default distribution: easybind; default version: latest on PyPI)."
258
+ "(default distribution: easybind). "
259
+ "Default version is latest on PyPI (can lag CI). "
260
+ "Use --from-github OWNER/REPO for the latest vX.Y.Z tag on GitHub (no local git); "
261
+ "use --version when you know the number; --installed uses the env's installed wheel."
185
262
  )
186
263
  )
187
264
  ap.add_argument(
@@ -201,18 +278,32 @@ def main(argv: list[str] | None = None) -> int:
201
278
  "--version",
202
279
  metavar="X.Y.Z",
203
280
  default=None,
204
- help="pin to this version (default: PyPI latest for --distribution, unless --installed)",
281
+ help="pin to this exact release (use when PyPI does not list it yet, e.g. CI still publishing)",
205
282
  )
206
283
  ap.add_argument(
207
284
  "--installed",
208
285
  action="store_true",
209
- help="use the version of --distribution installed in the current environment",
286
+ help="use importlib.metadata.version for --distribution in this env",
287
+ )
288
+ ap.add_argument(
289
+ "--from-github",
290
+ metavar="OWNER/REPO",
291
+ default=None,
292
+ help=(
293
+ "pin to highest vMAJOR.MINOR.PATCH tag from GitHub API (e.g. pymergetic/easybind). "
294
+ "Use when the tag exists but PyPI does not yet. Set GITHUB_TOKEN for private repos."
295
+ ),
210
296
  )
211
297
  ap.add_argument("--dry-run", action="store_true", help="do not write the file")
212
298
  ns = ap.parse_args(argv)
213
299
 
214
- if ns.version and ns.installed:
215
- print("error: use only one of --version or --installed", file=sys.stderr)
300
+ nsrc = sum(
301
+ 1
302
+ for x in (ns.version is not None, ns.installed, ns.from_github is not None)
303
+ if x
304
+ )
305
+ if nsrc > 1:
306
+ print("error: use at most one of --version, --installed, or --from-github", file=sys.stderr)
216
307
  return 2
217
308
 
218
309
  pyproject = ns.pyproject if ns.pyproject is not None else Path.cwd() / "pyproject.toml"
@@ -227,6 +318,12 @@ def main(argv: list[str] | None = None) -> int:
227
318
 
228
319
  if ns.installed:
229
320
  ver = installed_distribution_version(dist)
321
+ elif ns.from_github is not None:
322
+ try:
323
+ ver = latest_release_version_from_github(ns.from_github.strip())
324
+ except ValueError as e:
325
+ print(f"error: {e}", file=sys.stderr)
326
+ return 1
230
327
  elif ns.version:
231
328
  ver = ns.version.strip()
232
329
  else:
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
File without changes
File without changes
File without changes