bugpipe 3.0.1__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,207 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ import time
6
+ import typing as t
7
+ from importlib.metadata import version
8
+ from pathlib import Path
9
+ from tempfile import gettempdir
10
+
11
+ import httpx
12
+ from rich.status import Status
13
+
14
+ __pkg__ = "bugpipe"
15
+ __version__ = version(__pkg__)
16
+
17
+ CACHE_FILE = Path(gettempdir()) / "bugpipe_update_check.json"
18
+ CACHE_TTL = 3600
19
+
20
+
21
+ def query_pypi(package: str, include_prereleases: bool) -> dict:
22
+ """
23
+ Query PyPI for the latest version of a package.
24
+
25
+ :param package: The package name to look up on PyPI.
26
+ :param include_prereleases: Whether to consider pre-release versions.
27
+ :return: A dict with ``success`` key, and ``data`` containing
28
+ ``version`` and ``upload_time`` on success.
29
+ """
30
+
31
+ try:
32
+ response = httpx.get(f"https://pypi.org/pypi/{package}/json", timeout=3)
33
+ except httpx.HTTPError:
34
+ return {"success": False}
35
+ if response.status_code != 200:
36
+ return {"success": False}
37
+ data = response.json()
38
+ versions = list(data["releases"].keys())
39
+ versions.sort(key=parse_version, reverse=True)
40
+
41
+ version = versions[0]
42
+ for tmp_version in versions:
43
+ if include_prereleases or standard_release(tmp_version):
44
+ version = tmp_version
45
+ break
46
+
47
+ upload_time = None
48
+ for file_info in data["releases"][version]:
49
+ if file_info["upload_time"]:
50
+ upload_time = file_info["upload_time"]
51
+ break
52
+
53
+ return {"success": True, "data": {"upload_time": upload_time, "version": version}}
54
+
55
+
56
+ def cached_query_pypi(package: str, include_prereleases: bool) -> dict:
57
+ """
58
+ Return the PyPI lookup result, reusing a cached one under an hour old.
59
+
60
+ :param package: The package name to look up on PyPI.
61
+ :param include_prereleases: Whether to consider pre-release versions.
62
+ :return: The same dict as :func:`query_pypi`.
63
+ """
64
+
65
+ key = {"package": package, "include_prereleases": include_prereleases}
66
+ try:
67
+ cached = json.loads(CACHE_FILE.read_text())
68
+ if cached["key"] == key and time.time() - cached["time"] < CACHE_TTL:
69
+ return cached["data"]
70
+ except (OSError, ValueError, KeyError, TypeError):
71
+ pass # missing or unreadable cache, query PyPI
72
+
73
+ data = query_pypi(package=package, include_prereleases=include_prereleases)
74
+ if data.get("success"):
75
+ try:
76
+ CACHE_FILE.write_text(
77
+ json.dumps({"key": key, "time": time.time(), "data": data})
78
+ )
79
+ except OSError:
80
+ pass # cache is best effort
81
+ return data
82
+
83
+
84
+ def standard_release(version: str) -> bool:
85
+ """
86
+ Check whether a version string represents a standard (non-pre-release) release.
87
+
88
+ :param version: The version string to check.
89
+ :return: ``True`` if the version contains only digits and dots.
90
+ """
91
+
92
+ return version.replace(".", "").isdigit()
93
+
94
+
95
+ def check(
96
+ package_name: str = "buganize",
97
+ package_version: str = __version__,
98
+ status: Status | None = None,
99
+ ):
100
+ """Print a notice when PyPI has a newer version.
101
+
102
+ :param package_name: Package to check.
103
+ :param package_version: Running version.
104
+ """
105
+
106
+ if isinstance(status, Status):
107
+ status.update("[dim]Checking for updates…[/dim]")
108
+
109
+ data = cached_query_pypi(
110
+ package=package_name,
111
+ include_prereleases=not standard_release(version=package_version),
112
+ )
113
+ if not data.get("success") or (
114
+ parse_version(string=package_version)
115
+ >= parse_version(string=data["data"]["version"])
116
+ ):
117
+ return
118
+
119
+ available = data["data"]["version"]
120
+ message = (
121
+ f"Version {package_version} of {package_name} is outdated. "
122
+ f"Version {available} "
123
+ )
124
+ release_date = data["data"]["upload_time"]
125
+ message += (
126
+ f"was released on {release_date[:10]}." if release_date else "is available."
127
+ )
128
+
129
+ from .term import console
130
+
131
+ console.log(f"[bold blue]⬆[/bold blue] {message}")
132
+
133
+
134
+ # The following section of code is taken from setuptools pkg_resources.py (PSF
135
+ # license). Unfortunately importing pkg_resources to directly use the
136
+ # parse_version function results in some undesired side effects.
137
+
138
+ component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
139
+ replace = {"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@"}.get
140
+
141
+
142
+ def _parse_version_parts(version_string: str) -> t.Generator[str]:
143
+ """
144
+ Yield normalized version parts from a version string.
145
+
146
+ :param version_string: A version string to split into comparable parts.
147
+ """
148
+
149
+ for part in component_re.split(version_string):
150
+ part = replace(part, part)
151
+ if not part or part == ".":
152
+ continue
153
+ if part[:1] in "0123456789":
154
+ yield part.zfill(8) # pad for numeric comparison
155
+ else:
156
+ yield "*" + part
157
+
158
+ yield "*final" # ensure that alpha/beta/candidate are before final
159
+
160
+
161
+ def parse_version(string: str) -> tuple[str, ...]:
162
+ """
163
+ Convert a version string to a chronologically-sortable key.
164
+
165
+ This is a rough cross between distutils' StrictVersion and LooseVersion;
166
+ if you give it versions that would work with StrictVersion, then it behaves
167
+ the same; otherwise it acts like a slightly-smarter LooseVersion. It is
168
+ *possible* to create pathological version coding schemes that will fool
169
+ this parser, but they should be very rare in practice.
170
+
171
+ The returned value will be a tuple of strings. Numeric portions of the
172
+ version are padded to 8 digits so they will compare numerically, but
173
+ without relying on how numbers compare relative to strings. Dots are
174
+ dropped, but dashes are retained. Trailing zeros between alpha segments
175
+ or dashes are suppressed, so that e.g. "2.4.0" is considered the same as
176
+ "2.4". Alphanumeric parts are lower-cased.
177
+
178
+ The algorithm assumes that strings like "-" and any alpha string that
179
+ alphabetically follows "final" represents a "patch level". So, "2.4-1"
180
+ is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is
181
+ considered newer than "2.4-1", which in turn is newer than "2.4".
182
+
183
+ Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that
184
+ come before "final" alphabetically) are assumed to be pre-release versions,
185
+ so that the version "2.4" is considered newer than "2.4a1".
186
+
187
+ Finally, to handle miscellaneous cases, the strings "pre", "preview", and
188
+ "rc" are treated as if they were "c", i.e. as though they were release
189
+ candidates, and therefore are not as new as a version string that does not
190
+ contain them, and "dev" is replaced with an '@' so that it sorts lower than
191
+ any other pre-release tag.
192
+
193
+ :param string: The version string to parse.
194
+ :return: A tuple of strings suitable for comparison.
195
+ """
196
+
197
+ parts = []
198
+ for part in _parse_version_parts(string.lower()):
199
+ if part.startswith("*"):
200
+ if part < "*final": # remove '-' before a prerelease tag
201
+ while parts and parts[-1] == "*final-":
202
+ parts.pop()
203
+ # remove trailing zeros from each series of numeric parts
204
+ while parts and parts[-1] == "00000000":
205
+ parts.pop()
206
+ parts.append(part)
207
+ return tuple(parts)
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: bugpipe
3
+ Version: 3.0.1
4
+ Summary: Unofficial Python client for Buganizer; the Google Issue Tracking system
5
+ Keywords: buganizer,google-issue-tracker,issue-tracker,bug-tracker,bug-bounty,vulnerability,security,chromium,cve,vrp,google
6
+ Author: Ritchie Mwewa
7
+ Author-email: Ritchie Mwewa <rly0nheart@duck.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: Software Development :: Bug Tracking
18
+ Classifier: Framework :: AsyncIO
19
+ Requires-Dist: httpx>=0.28.1
20
+ Requires-Dist: rich>=14.3.3
21
+ Requires-Python: >=3.13
22
+ Project-URL: Changelog, https://github.com/rly0nheart/bugpipe/blob/master/CHANGELOG.md
23
+ Project-URL: Documentation, https://bugpipe.readthedocs.io
24
+ Project-URL: Repository, https://github.com/rly0nheart/bugpipe
25
+ Description-Content-Type: text/markdown
26
+
27
+ <p align="center">
28
+ <img alt="logo" src="https://www.gstatic.com/buganizer/img/v0/logo.svg" width="150" height="150">
29
+ <br>
30
+ <br>
31
+ <strong><i>Unofficial Python client for Buganizer; the Google Issue Tracking system.</i></strong>
32
+ </p>
33
+
34
+ ## Quick Start
35
+
36
+ ```bash
37
+ bugpipe search "status:open"
38
+ ```
39
+
40
+ ```python
41
+ from bugpipe import Bugpipe
42
+
43
+
44
+ with Bugpipe() as client:
45
+ result = client.search(query="status:open priority:p1", page_size=25)
46
+ for issue in result.issues:
47
+ print(f"#{issue.id} [{issue.status.name}] {issue.title}")
48
+ ```
49
+
50
+ ## Documentation
51
+
52
+ Refer to [the docs](https://bugpipe.readthedocs.io) for installation, usage and api reference.
@@ -0,0 +1,15 @@
1
+ bugpipe/__init__.py,sha256=QO0tHq-83svpy4MoCN6nPHNcwt3-jpz2gUDKYaOsbfI,732
2
+ bugpipe/api/AUDIT.md,sha256=9g2HYEOtges3nPY7xQBwTwge129KP6ho46fga58LOhM,10668
3
+ bugpipe/api/README.md,sha256=V41sjZ9upbNxZ8iNw4yFoOsqDr_wiwKBpSzMshN_XNM,26191
4
+ bugpipe/api/__init__.py,sha256=uty5L3YBFqcW79GG97ldKgWtHY5I-TaYh6Wje7VfNzc,79
5
+ bugpipe/api/client.py,sha256=RZ3fmQV9yXuz2lc1cpd-vtHJTM7kUHJ-dUCBTo03ryQ,10539
6
+ bugpipe/api/models.py,sha256=cPx30gx1yZmchb6XkN0wv2n8VdtC23raaE5sfqCLokU,23157
7
+ bugpipe/api/parser.py,sha256=xShNVE_ok7rWKzGRsA7Omct2AEOZLWe0zi6IllKDST0,24239
8
+ bugpipe/cli/__init__.py,sha256=-vcD4_pFoFmFfx-Z7Bz-W4RLn5c-XHVDiN-PEnCmBQ4,2167
9
+ bugpipe/cli/cmd.py,sha256=0BSoXTiF6N5vw9g-9NKDRtPvgJ054uxsOJwkzkeWs18,9533
10
+ bugpipe/cli/term.py,sha256=SqjUkuVQCngPE8sxhPZOsqCXhw2VojxCMjOEbH1UXdA,7095
11
+ bugpipe/cli/update_checker.py,sha256=HxOvPo-EgaLlseY-X1DfAP18hSmMBp8QxSsvRn8SAfE,7266
12
+ bugpipe-3.0.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
13
+ bugpipe-3.0.1.dist-info/entry_points.txt,sha256=wND_xpvRjJOmnH18Cp1s68Bb0CGmLgYVioPtnEnblGU,47
14
+ bugpipe-3.0.1.dist-info/METADATA,sha256=rHQdo9G5FI9x2_EbL-F8YktM23g-KK-E8FOHaNyvuV4,1846
15
+ bugpipe-3.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ bugpipe = bugpipe.cli:start
3
+