easybind 0.2.7__tar.gz → 0.2.9__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.
- {easybind-0.2.7 → easybind-0.2.9}/PKG-INFO +6 -2
- {easybind-0.2.7 → easybind-0.2.9}/README.md +4 -1
- {easybind-0.2.7 → easybind-0.2.9}/pyproject.toml +1 -1
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/devtools/__init__.py +6 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/devtools/pin_pyproject.py +124 -13
- {easybind-0.2.7 → easybind-0.2.9}/.clangd +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/.github/workflows/publish.yml +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/.gitignore +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/.vscode/tasks.json +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/CMakeLists.txt +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/LICENSE +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/NOTICE +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/RELEASING.md +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/cmake/easybind_dependencies.cmake +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/cmake/easybind_pip.cmake +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/pyrightconfig.json +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/scripts/clangd-update.sh +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/scripts/release_tag.py +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/scripts/wait_pypi_release.py +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/__init__.cpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/bind.hpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/devtools/release_helpers.py +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/devtools/release_tag.py +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/devtools/wait_pypi.py +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/export.hpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/module/__init__.cpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/module/node.cpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/module/node.hpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/module/node__init__.cpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/module/ns_module.hpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/py.typed +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/sample/__init__.cpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/sample/sample.cpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/sample/sample.hpp +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/sample/test1.py +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/src/easybind/sample/test2/__init__.py +0 -0
- {easybind-0.2.7 → easybind-0.2.9}/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
|
+
Version: 0.2.9
|
|
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>
|
|
@@ -21,6 +21,7 @@ Project-URL: Repository, https://github.com/pymergetic/easybind
|
|
|
21
21
|
Project-URL: Issues, https://github.com/pymergetic/easybind/issues
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
23
|
Requires-Dist: nanobind~=2.12.0
|
|
24
|
+
Requires-Dist: packaging>=23
|
|
24
25
|
Provides-Extra: dev
|
|
25
26
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
26
27
|
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
@@ -95,7 +96,8 @@ Use **`easybind.devtools`** or the **`easybind-pin-pyproject`** CLI to rewrite e
|
|
|
95
96
|
```bash
|
|
96
97
|
easybind-pin-pyproject --dry-run
|
|
97
98
|
easybind-pin-pyproject
|
|
98
|
-
easybind-pin-pyproject --from-github
|
|
99
|
+
easybind-pin-pyproject --from-github # GitHub OWNER/REPO from PyPI metadata for --distribution
|
|
100
|
+
easybind-pin-pyproject --from-github ORG/REPO # override when PyPI has no GitHub URL
|
|
99
101
|
easybind-pin-pyproject --installed
|
|
100
102
|
easybind-pin-pyproject --version 0.2.3
|
|
101
103
|
easybind-pin-pyproject --distribution cppdantic
|
|
@@ -104,6 +106,8 @@ easybind-pin-pyproject --pyproject /path/to/pyproject.toml
|
|
|
104
106
|
|
|
105
107
|
Use **`GITHUB_TOKEN`** for private GitHub repos or higher API rate limits.
|
|
106
108
|
|
|
109
|
+
**`--from-github`:** GitHub’s tags API is sometimes a few seconds behind right after you push a new `v*` tag. If **`easybind-pin-pyproject --dry-run --from-github`** still shows the previous release, wait briefly and run again (or pin with **`--version`** until the API catches up).
|
|
110
|
+
|
|
107
111
|
**Other devtools CLIs** (after **`pip install`** / **`uv pip install -e .`**, or run **`scripts/…`** shims from a git checkout):
|
|
108
112
|
|
|
109
113
|
```bash
|
|
@@ -66,7 +66,8 @@ Use **`easybind.devtools`** or the **`easybind-pin-pyproject`** CLI to rewrite e
|
|
|
66
66
|
```bash
|
|
67
67
|
easybind-pin-pyproject --dry-run
|
|
68
68
|
easybind-pin-pyproject
|
|
69
|
-
easybind-pin-pyproject --from-github
|
|
69
|
+
easybind-pin-pyproject --from-github # GitHub OWNER/REPO from PyPI metadata for --distribution
|
|
70
|
+
easybind-pin-pyproject --from-github ORG/REPO # override when PyPI has no GitHub URL
|
|
70
71
|
easybind-pin-pyproject --installed
|
|
71
72
|
easybind-pin-pyproject --version 0.2.3
|
|
72
73
|
easybind-pin-pyproject --distribution cppdantic
|
|
@@ -75,6 +76,8 @@ easybind-pin-pyproject --pyproject /path/to/pyproject.toml
|
|
|
75
76
|
|
|
76
77
|
Use **`GITHUB_TOKEN`** for private GitHub repos or higher API rate limits.
|
|
77
78
|
|
|
79
|
+
**`--from-github`:** GitHub’s tags API is sometimes a few seconds behind right after you push a new `v*` tag. If **`easybind-pin-pyproject --dry-run --from-github`** still shows the previous release, wait briefly and run again (or pin with **`--version`** until the API catches up).
|
|
80
|
+
|
|
78
81
|
**Other devtools CLIs** (after **`pip install`** / **`uv pip install -e .`**, or run **`scripts/…`** shims from a git checkout):
|
|
79
82
|
|
|
80
83
|
```bash
|
|
@@ -8,7 +8,7 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "Self-registering nanobind helpers (import as easybind)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
|
-
dependencies = ["nanobind~=2.12.0"]
|
|
11
|
+
dependencies = ["nanobind~=2.12.0", "packaging>=23"]
|
|
12
12
|
license = "Apache-2.0"
|
|
13
13
|
license-files = ["LICENSE", "NOTICE"]
|
|
14
14
|
authors = [
|
|
@@ -6,7 +6,10 @@ from easybind.devtools.pin_pyproject import (
|
|
|
6
6
|
bump_easybind_compatible_pins,
|
|
7
7
|
bump_easybind_compatible_pins_in_file,
|
|
8
8
|
compatible_pin_versions,
|
|
9
|
+
compatible_release_pin_from_installed_version,
|
|
10
|
+
fetch_pypi_project_json,
|
|
9
11
|
fetch_pypi_version,
|
|
12
|
+
github_owner_repo_from_pypi_distribution,
|
|
10
13
|
installed_distribution_version,
|
|
11
14
|
latest_release_version_from_github,
|
|
12
15
|
pypi_release_exists,
|
|
@@ -26,8 +29,11 @@ __all__ = [
|
|
|
26
29
|
"bump_easybind_compatible_pins",
|
|
27
30
|
"bump_easybind_compatible_pins_in_file",
|
|
28
31
|
"compatible_pin_versions",
|
|
32
|
+
"compatible_release_pin_from_installed_version",
|
|
29
33
|
"ensure_clean_worktree",
|
|
34
|
+
"fetch_pypi_project_json",
|
|
30
35
|
"fetch_pypi_version",
|
|
36
|
+
"github_owner_repo_from_pypi_distribution",
|
|
31
37
|
"installed_distribution_version",
|
|
32
38
|
"latest_release_version_from_github",
|
|
33
39
|
"latest_v_tag",
|
|
@@ -18,9 +18,20 @@ _VERSION_RE = re.compile(r"^[0-9]+(?:\.[0-9]+)*$")
|
|
|
18
18
|
# Release tags: ``vMAJOR.MINOR.PATCH`` (GitHub / git tag style).
|
|
19
19
|
_V_SEMVER_TAG = re.compile(r"^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$")
|
|
20
20
|
|
|
21
|
+
|
|
22
|
+
def _semver_tuple(version: str) -> tuple[int, ...]:
|
|
23
|
+
"""Numeric tuple for comparing ``X.Y.Z`` style pins."""
|
|
24
|
+
return tuple(int(p) for p in version.split("."))
|
|
25
|
+
|
|
21
26
|
# Reject pathological / mistaken distribution strings (full re.escape covers the rest).
|
|
22
27
|
_DIST_OK = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._-]*$")
|
|
23
28
|
|
|
29
|
+
# ``https://github.com/OWNER/REPO`` (and ``…/tree/…``, ``.git``, …).
|
|
30
|
+
_GITHUB_OWNER_REPO = re.compile(
|
|
31
|
+
r"https?://github\.com/([^/]+)/([^/#?]+)",
|
|
32
|
+
re.IGNORECASE,
|
|
33
|
+
)
|
|
34
|
+
|
|
24
35
|
|
|
25
36
|
def _pin_pattern(distribution: str) -> re.Pattern[str]:
|
|
26
37
|
if not _DIST_OK.match(distribution):
|
|
@@ -31,14 +42,58 @@ def _pin_pattern(distribution: str) -> re.Pattern[str]:
|
|
|
31
42
|
return re.compile(rf"({re.escape(distribution)}~=)([0-9]+(?:\.[0-9]+)*)")
|
|
32
43
|
|
|
33
44
|
|
|
45
|
+
def fetch_pypi_project_json(distribution: str, *, timeout_s: float = 30.0) -> dict:
|
|
46
|
+
"""Return the JSON object from ``https://pypi.org/pypi/{distribution}/json``."""
|
|
47
|
+
url = f"https://pypi.org/pypi/{distribution}/json"
|
|
48
|
+
try:
|
|
49
|
+
with urlopen(url, timeout=timeout_s) as r:
|
|
50
|
+
return json.load(r)
|
|
51
|
+
except HTTPError as e:
|
|
52
|
+
if e.code == 404:
|
|
53
|
+
raise ValueError(f"no PyPI project named {distribution!r}") from e
|
|
54
|
+
raise
|
|
55
|
+
|
|
56
|
+
|
|
34
57
|
def fetch_pypi_version(package: str = "easybind", *, timeout_s: float = 30.0) -> str:
|
|
35
58
|
"""Return ``info.version`` from ``https://pypi.org/pypi/{package}/json``."""
|
|
36
|
-
|
|
37
|
-
with urlopen(url, timeout=timeout_s) as r:
|
|
38
|
-
data = json.load(r)
|
|
59
|
+
data = fetch_pypi_project_json(package, timeout_s=timeout_s)
|
|
39
60
|
return str(data["info"]["version"])
|
|
40
61
|
|
|
41
62
|
|
|
63
|
+
def github_owner_repo_from_pypi_distribution(distribution: str, *, timeout_s: float = 30.0) -> str:
|
|
64
|
+
"""Return ``OWNER/REPO`` by scanning PyPI ``home_page`` and ``project_urls`` for ``github.com``.
|
|
65
|
+
|
|
66
|
+
Works for any PyPI name whose metadata includes a ``github.com/OWNER/REPO`` link.
|
|
67
|
+
If nothing matches, raises ``ValueError`` — then pass ``--from-github OWNER/REPO`` explicitly.
|
|
68
|
+
"""
|
|
69
|
+
data = fetch_pypi_project_json(distribution, timeout_s=timeout_s)
|
|
70
|
+
info = data.get("info") or {}
|
|
71
|
+
urls: list[str] = []
|
|
72
|
+
pu = info.get("project_urls")
|
|
73
|
+
if isinstance(pu, dict):
|
|
74
|
+
for key in ("Source", "Repository", "Homepage", "Code"):
|
|
75
|
+
v = pu.get(key)
|
|
76
|
+
if isinstance(v, str) and v.strip():
|
|
77
|
+
urls.append(v.strip())
|
|
78
|
+
for v in pu.values():
|
|
79
|
+
if isinstance(v, str) and v.strip() and v.strip() not in urls:
|
|
80
|
+
urls.append(v.strip())
|
|
81
|
+
hp = info.get("home_page")
|
|
82
|
+
if isinstance(hp, str) and hp.strip():
|
|
83
|
+
urls.append(hp.strip())
|
|
84
|
+
|
|
85
|
+
for u in urls:
|
|
86
|
+
m = _GITHUB_OWNER_REPO.search(u)
|
|
87
|
+
if m:
|
|
88
|
+
repo = m.group(2).removesuffix(".git")
|
|
89
|
+
return f"{m.group(1)}/{repo}"
|
|
90
|
+
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"no github.com URL in PyPI metadata for {distribution!r}; "
|
|
93
|
+
"set project URLs on PyPI or pass --from-github OWNER/REPO"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
42
97
|
def pypi_release_exists(package: str, version: str, *, timeout_s: float = 15.0) -> bool:
|
|
43
98
|
"""Return True if ``https://pypi.org/pypi/{package}/{version}/json`` exists (release uploaded)."""
|
|
44
99
|
url = f"https://pypi.org/pypi/{package}/{version}/json"
|
|
@@ -125,6 +180,19 @@ def installed_distribution_version(package: str = "easybind") -> str:
|
|
|
125
180
|
return version(package)
|
|
126
181
|
|
|
127
182
|
|
|
183
|
+
def compatible_release_pin_from_installed_version(pep440: str) -> str:
|
|
184
|
+
"""Map an installed PEP 440 version (e.g. setuptools-scm ``0.2.7.post1.dev0``) to ``MAJOR.MINOR.PATCH`` for ``~=`` pins."""
|
|
185
|
+
from packaging.version import Version
|
|
186
|
+
|
|
187
|
+
v = Version(pep440)
|
|
188
|
+
rel = list(v.release)
|
|
189
|
+
if not rel:
|
|
190
|
+
raise ValueError(f"no release segment in {pep440!r}")
|
|
191
|
+
while len(rel) < 3:
|
|
192
|
+
rel.append(0)
|
|
193
|
+
return f"{rel[0]}.{rel[1]}.{rel[2]}"
|
|
194
|
+
|
|
195
|
+
|
|
128
196
|
def _github_request_json(url: str, *, token: str | None, timeout_s: float) -> object:
|
|
129
197
|
req = Request(url, headers={"Accept": "application/vnd.github+json"})
|
|
130
198
|
if token:
|
|
@@ -142,7 +210,10 @@ def latest_release_version_from_github(
|
|
|
142
210
|
"""Return the highest ``vMAJOR.MINOR.PATCH`` tag on GitHub as ``X.Y.Z`` (no local git clone).
|
|
143
211
|
|
|
144
212
|
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
|
|
213
|
+
PyPI has not published the wheel yet. *owner_repo* is ``OWNER/REPO``.
|
|
214
|
+
|
|
215
|
+
The tags endpoint can lag a few seconds right after you push a new tag (eventual consistency /
|
|
216
|
+
caching). If the version looks stale, wait and call again.
|
|
146
217
|
|
|
147
218
|
Set ``GITHUB_TOKEN`` or ``GH_TOKEN`` (or pass *token*) for private repos or higher rate limits.
|
|
148
219
|
"""
|
|
@@ -256,9 +327,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
256
327
|
description=(
|
|
257
328
|
"Set every {distribution}~= pin in pyproject.toml "
|
|
258
329
|
"(default distribution: easybind). "
|
|
259
|
-
"Default version is latest on PyPI (can lag
|
|
260
|
-
"
|
|
261
|
-
"
|
|
330
|
+
"Default version is latest on PyPI (can lag tags). "
|
|
331
|
+
"--from-github uses the latest vX.Y.Z GitHub tag for that distribution's repo "
|
|
332
|
+
"(OWNER/REPO from PyPI project URLs if you omit it); the GitHub tags API can lag "
|
|
333
|
+
"briefly after a new tag push. "
|
|
334
|
+
"--version / --installed are explicit overrides."
|
|
262
335
|
)
|
|
263
336
|
)
|
|
264
337
|
ap.add_argument(
|
|
@@ -283,15 +356,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
283
356
|
ap.add_argument(
|
|
284
357
|
"--installed",
|
|
285
358
|
action="store_true",
|
|
286
|
-
help="use
|
|
359
|
+
help="use installed version for --distribution, normalized to X.Y.Z (dev/post/local stripped)",
|
|
287
360
|
)
|
|
288
361
|
ap.add_argument(
|
|
289
362
|
"--from-github",
|
|
290
|
-
|
|
363
|
+
nargs="?",
|
|
364
|
+
const="",
|
|
291
365
|
default=None,
|
|
366
|
+
metavar="OWNER/REPO",
|
|
292
367
|
help=(
|
|
293
|
-
"pin to highest vMAJOR.MINOR.PATCH tag
|
|
294
|
-
"
|
|
368
|
+
"pin to highest vMAJOR.MINOR.PATCH tag on GitHub. "
|
|
369
|
+
"If OWNER/REPO is omitted, read github.com/OWNER/REPO from PyPI metadata for --distribution. "
|
|
370
|
+
"The tags list can lag a few seconds after you push a new tag; re-run if the pin looks stale. "
|
|
371
|
+
"Set GITHUB_TOKEN for private repos."
|
|
295
372
|
),
|
|
296
373
|
)
|
|
297
374
|
ap.add_argument("--dry-run", action="store_true", help="do not write the file")
|
|
@@ -316,18 +393,38 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
316
393
|
print("error: empty --distribution", file=sys.stderr)
|
|
317
394
|
return 2
|
|
318
395
|
|
|
396
|
+
ver_source: str
|
|
319
397
|
if ns.installed:
|
|
320
|
-
|
|
398
|
+
raw_installed = installed_distribution_version(dist)
|
|
399
|
+
try:
|
|
400
|
+
ver = compatible_release_pin_from_installed_version(raw_installed)
|
|
401
|
+
except ValueError as e:
|
|
402
|
+
print(f"error: {e}", file=sys.stderr)
|
|
403
|
+
return 1
|
|
404
|
+
if raw_installed != ver:
|
|
405
|
+
print(
|
|
406
|
+
f"note: installed {dist}=={raw_installed!r} -> pin ~={ver} (PEP 440 release triple)",
|
|
407
|
+
file=sys.stderr,
|
|
408
|
+
)
|
|
409
|
+
ver_source = "installed"
|
|
321
410
|
elif ns.from_github is not None:
|
|
411
|
+
raw = ns.from_github.strip()
|
|
322
412
|
try:
|
|
323
|
-
|
|
413
|
+
if not raw:
|
|
414
|
+
owner_repo = github_owner_repo_from_pypi_distribution(dist)
|
|
415
|
+
else:
|
|
416
|
+
owner_repo = raw
|
|
417
|
+
ver = latest_release_version_from_github(owner_repo)
|
|
324
418
|
except ValueError as e:
|
|
325
419
|
print(f"error: {e}", file=sys.stderr)
|
|
326
420
|
return 1
|
|
421
|
+
ver_source = "github"
|
|
327
422
|
elif ns.version:
|
|
328
423
|
ver = ns.version.strip()
|
|
424
|
+
ver_source = "explicit"
|
|
329
425
|
else:
|
|
330
426
|
ver = fetch_pypi_version(dist)
|
|
427
|
+
ver_source = "pypi"
|
|
331
428
|
|
|
332
429
|
if not _VERSION_RE.match(ver):
|
|
333
430
|
print(f"error: bad version string: {ver!r}", file=sys.stderr)
|
|
@@ -341,6 +438,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
341
438
|
|
|
342
439
|
action = "would update" if ns.dry_run else "updated"
|
|
343
440
|
print(f"{action} {n} {dist}~= pin(s) to ~={ver} in {pyproject}")
|
|
441
|
+
|
|
442
|
+
# PyPI / venv often lag a v* tag on GitHub.
|
|
443
|
+
if ns.dry_run and ver_source in ("pypi", "installed"):
|
|
444
|
+
try:
|
|
445
|
+
or_ = github_owner_repo_from_pypi_distribution(dist)
|
|
446
|
+
gh_ver = latest_release_version_from_github(or_)
|
|
447
|
+
if _semver_tuple(gh_ver) > _semver_tuple(ver):
|
|
448
|
+
print(
|
|
449
|
+
f"hint: github.com/{or_} has v{gh_ver} but {ver_source} gave {ver}. "
|
|
450
|
+
"Re-run with: easybind-pin-pyproject --dry-run --from-github",
|
|
451
|
+
)
|
|
452
|
+
except ValueError:
|
|
453
|
+
pass
|
|
454
|
+
|
|
344
455
|
return 0
|
|
345
456
|
|
|
346
457
|
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|