ghapi 2.1.0__tar.gz → 2.1.2__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.
- {ghapi-2.1.0/ghapi.egg-info → ghapi-2.1.2}/PKG-INFO +4 -3
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/__init__.py +1 -1
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/_modidx.py +14 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/core.py +58 -16
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/page.py +38 -12
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/skill.py +1 -0
- {ghapi-2.1.0 → ghapi-2.1.2/ghapi.egg-info}/PKG-INFO +4 -3
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/requires.txt +3 -2
- {ghapi-2.1.0 → ghapi-2.1.2}/pyproject.toml +2 -2
- {ghapi-2.1.0 → ghapi-2.1.2}/CONTRIBUTING.md +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/LICENSE +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/MANIFEST.in +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/README.md +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/actions.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/all.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/auth.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/build_lib.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/cli.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/event.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/gh_spec.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/gql_spec.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/graphql.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/py.typed +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/templates.py +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/SOURCES.txt +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/dependency_links.txt +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/entry_points.txt +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/top_level.txt +0 -0
- {ghapi-2.1.0 → ghapi-2.1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghapi
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: A python client for the GitHub API
|
|
5
5
|
Author-email: Jeremy Howard <info@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: fastcore>=2.2.
|
|
19
|
-
Requires-Dist: fastspec>=0.2.
|
|
18
|
+
Requires-Dist: fastcore>=2.2.7
|
|
19
|
+
Requires-Dist: fastspec>=0.2.1
|
|
20
20
|
Requires-Dist: tomli; python_version < "3.11"
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: fastgit; extra == "dev"
|
|
@@ -26,6 +26,7 @@ Requires-Dist: nbdev; extra == "dev"
|
|
|
26
26
|
Requires-Dist: pyyaml; extra == "dev"
|
|
27
27
|
Requires-Dist: pysym2md>=0.0.6; extra == "dev"
|
|
28
28
|
Requires-Dist: llms-txt>=0.0.6; extra == "dev"
|
|
29
|
+
Requires-Dist: pyskills; extra == "dev"
|
|
29
30
|
Dynamic: license-file
|
|
30
31
|
|
|
31
32
|
# ghapi
|
|
@@ -5,4 +5,4 @@ Modules:
|
|
|
5
5
|
- `ghapi.graphql`: Query GitHub's GraphQL API: schema-aware chained queries, one-request batching, and raw GraphQL
|
|
6
6
|
- `ghapi.skill`: GitHub REST API access via `GhApi`, plus local git operations via `fastgit.Git`. Use this for day-to-day GitHub work: reading/creating issues and PRs, checking CI status, managing releases/branches/gists, and repo-local git operations -- all from Python, no shelling out to `gh`/`git` needed."""
|
|
7
7
|
|
|
8
|
-
__version__ = "2.1.
|
|
8
|
+
__version__ = "2.1.2"
|
|
@@ -74,6 +74,11 @@ d = { 'settings': { 'branch': 'main',
|
|
|
74
74
|
'ghapi.core.GhTransport._post': ('core.html#ghtransport._post', 'ghapi/core.py'),
|
|
75
75
|
'ghapi.core.GhTransport._pre': ('core.html#ghtransport._pre', 'ghapi/core.py'),
|
|
76
76
|
'ghapi.core.GhTransport.request': ('core.html#ghtransport.request', 'ghapi/core.py'),
|
|
77
|
+
'ghapi.core.PullRow': ('core.html#pullrow', 'ghapi/core.py'),
|
|
78
|
+
'ghapi.core.PullRow.line': ('core.html#pullrow.line', 'ghapi/core.py'),
|
|
79
|
+
'ghapi.core.PullRows': ('core.html#pullrows', 'ghapi/core.py'),
|
|
80
|
+
'ghapi.core.PullRows.__init__': ('core.html#pullrows.__init__', 'ghapi/core.py'),
|
|
81
|
+
'ghapi.core.PullRows.__repr__': ('core.html#pullrows.__repr__', 'ghapi/core.py'),
|
|
77
82
|
'ghapi.core._CheckStatus': ('core.html#_checkstatus', 'ghapi/core.py'),
|
|
78
83
|
'ghapi.core._CheckStatus._repr_markdown_': ('core.html#_checkstatus._repr_markdown_', 'ghapi/core.py'),
|
|
79
84
|
'ghapi.core._IssueInfo': ('core.html#_issueinfo', 'ghapi/core.py'),
|
|
@@ -82,6 +87,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
82
87
|
'ghapi.core._LiveDefaults.__contains__': ('core.html#_livedefaults.__contains__', 'ghapi/core.py'),
|
|
83
88
|
'ghapi.core._LiveDefaults.get': ('core.html#_livedefaults.get', 'ghapi/core.py'),
|
|
84
89
|
'ghapi.core._LiveDefaults.items': ('core.html#_livedefaults.items', 'ghapi/core.py'),
|
|
90
|
+
'ghapi.core._age': ('core.html#_age', 'ghapi/core.py'),
|
|
85
91
|
'ghapi.core._dur': ('core.html#_dur', 'ghapi/core.py'),
|
|
86
92
|
'ghapi.core._filter_diff': ('core.html#_filter_diff', 'ghapi/core.py'),
|
|
87
93
|
'ghapi.core._find_matches': ('core.html#_find_matches', 'ghapi/core.py'),
|
|
@@ -89,6 +95,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
89
95
|
'ghapi.core._include': ('core.html#_include', 'ghapi/core.py'),
|
|
90
96
|
'ghapi.core._listed': ('core.html#_listed', 'ghapi/core.py'),
|
|
91
97
|
'ghapi.core._parse_tmpl': ('core.html#_parse_tmpl', 'ghapi/core.py'),
|
|
98
|
+
'ghapi.core._pr_mark': ('core.html#_pr_mark', 'ghapi/core.py'),
|
|
92
99
|
'ghapi.core._reduce_ctx': ('core.html#_reduce_ctx', 'ghapi/core.py'),
|
|
93
100
|
'ghapi.core._run_subp': ('core.html#_run_subp', 'ghapi/core.py'),
|
|
94
101
|
'ghapi.core._transdeps': ('core.html#_transdeps', 'ghapi/core.py'),
|
|
@@ -119,6 +126,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
119
126
|
'ghapi.core.issue_template': ('core.html#issue_template', 'ghapi/core.py'),
|
|
120
127
|
'ghapi.core.list_branches': ('core.html#list_branches', 'ghapi/core.py'),
|
|
121
128
|
'ghapi.core.list_files': ('core.html#list_files', 'ghapi/core.py'),
|
|
129
|
+
'ghapi.core.list_prs': ('core.html#list_prs', 'ghapi/core.py'),
|
|
122
130
|
'ghapi.core.list_tags': ('core.html#list_tags', 'ghapi/core.py'),
|
|
123
131
|
'ghapi.core.local_dep_graph': ('core.html#local_dep_graph', 'ghapi/core.py'),
|
|
124
132
|
'ghapi.core.pr_file_diff': ('core.html#pr_file_diff', 'ghapi/core.py'),
|
|
@@ -150,10 +158,16 @@ d = { 'settings': { 'branch': 'main',
|
|
|
150
158
|
'ghapi.graphql.GhGql.__init__': ('graphql.html#ghgql.__init__', 'ghapi/graphql.py'),
|
|
151
159
|
'ghapi.graphql.GhGql.repo': ('graphql.html#ghgql.repo', 'ghapi/graphql.py')},
|
|
152
160
|
'ghapi.page': { 'ghapi.page.GhApi.last_page': ('page.html#ghapi.last_page', 'ghapi/page.py'),
|
|
161
|
+
'ghapi.page.GhApi.mark_done': ('page.html#ghapi.mark_done', 'ghapi/page.py'),
|
|
162
|
+
'ghapi.page.NotifRow': ('page.html#notifrow', 'ghapi/page.py'),
|
|
163
|
+
'ghapi.page.NotifRow.__repr__': ('page.html#notifrow.__repr__', 'ghapi/page.py'),
|
|
164
|
+
'ghapi.page.NotifRows': ('page.html#notifrows', 'ghapi/page.py'),
|
|
165
|
+
'ghapi.page.NotifRows.__repr__': ('page.html#notifrows.__repr__', 'ghapi/page.py'),
|
|
153
166
|
'ghapi.page._Scanner': ('page.html#_scanner', 'ghapi/page.py'),
|
|
154
167
|
'ghapi.page._Scanner.__getitem__': ('page.html#_scanner.__getitem__', 'ghapi/page.py'),
|
|
155
168
|
'ghapi.page._Scanner.__init__': ('page.html#_scanner.__init__', 'ghapi/page.py'),
|
|
156
169
|
'ghapi.page._Scanner.scan': ('page.html#_scanner.scan', 'ghapi/page.py'),
|
|
170
|
+
'ghapi.page._notif_mark': ('page.html#_notif_mark', 'ghapi/page.py'),
|
|
157
171
|
'ghapi.page._parse_link_hdr': ('page.html#_parse_link_hdr', 'ghapi/page.py'),
|
|
158
172
|
'ghapi.page.gh_notifs': ('page.html#gh_notifs', 'ghapi/page.py'),
|
|
159
173
|
'ghapi.page.paged': ('page.html#paged', 'ghapi/page.py'),
|
|
@@ -5,13 +5,14 @@ Docs: https://ghapi.fast.ai/core.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['GH_HOST', 'pspec', 'img_md_pat', 'EMPTY_TREE_SHA', '
|
|
9
|
-
'GhApi', 'call_gh', 'date2gh', 'gh2date', '
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'dep_order', 'dep_dependents', 'dep_graph',
|
|
8
|
+
__all__ = ['GH_HOST', 'pspec', 'img_md_pat', 'EMPTY_TREE_SHA', 'MAXLEN', 'GhTransport', 'GhSyncTransport', 'print_summary',
|
|
9
|
+
'gh_patch', 'GhApi', 'call_gh', 'date2gh', 'gh2date', 'GhRows', 'delete_release', 'create_release',
|
|
10
|
+
'list_tags', 'list_branches', 'create_branch_empty', 'delete_tag', 'delete_branch', 'get_branch',
|
|
11
|
+
'commit_tree', 'list_files', 'get_content', 'create_or_update_file', 'create_file', 'delete_file',
|
|
12
|
+
'update_contents', 'enable_pages', 'read_issue', 'read_pr', 'pr_file_diff', 'PullRow', 'PullRows',
|
|
13
|
+
'list_prs', 'issue_template', 'issue_body', 'CheckRun', 'CommitStatus', 'check_status', 'pr_status',
|
|
14
|
+
'failed_step_log', 'dep_key', 'local_dep_graph', 'dep_closure', 'dep_order', 'dep_dependents', 'dep_graph',
|
|
15
|
+
'APIError']
|
|
15
16
|
|
|
16
17
|
# %% ../nbs/00_core.ipynb #5b5cba7b
|
|
17
18
|
from contextvars import ContextVar
|
|
@@ -19,14 +20,14 @@ from contextvars import ContextVar
|
|
|
19
20
|
from fastcore.all import *
|
|
20
21
|
from fastspec.spec import SpecParser
|
|
21
22
|
from fastspec.oapi import OpenAPIClient, OpFunc, SyncOpFunc
|
|
22
|
-
from
|
|
23
|
+
from fasttransport.core import AsyncTransport, SyncTransport
|
|
23
24
|
from .gh_spec import spec
|
|
24
25
|
from fastspec.errors import APIError
|
|
25
26
|
|
|
26
27
|
import mimetypes,base64
|
|
27
28
|
from collections import Counter
|
|
28
29
|
from urllib.parse import quote
|
|
29
|
-
from datetime import datetime
|
|
30
|
+
from datetime import datetime, timezone
|
|
30
31
|
import os, shutil, tempfile, subprocess, fnmatch, html
|
|
31
32
|
import asyncio
|
|
32
33
|
|
|
@@ -142,7 +143,7 @@ class GhApi(OpenAPIClient):
|
|
|
142
143
|
if verb is None: verb = 'POST' if data else 'GET'
|
|
143
144
|
if route: path = path.format(**{k:quote(str(v), safe='') for k,v in route.items()})
|
|
144
145
|
if not path.startswith(('http://', 'https://')): path = self.gh_host + path
|
|
145
|
-
kw = dict(content=data) if isinstance(data, (bytes,str)) else dict(
|
|
146
|
+
kw = dict(content=data) if isinstance(data, (bytes,str)) else dict(json=data)
|
|
146
147
|
return self.transport.request(verb, path, headers=headers, params=query, **kw)
|
|
147
148
|
|
|
148
149
|
@property
|
|
@@ -178,6 +179,11 @@ def gh2date(dtstr:str)->datetime:
|
|
|
178
179
|
"Convert date string `dtstr` received from a GitHub API operation to a UTC `datetime`"
|
|
179
180
|
return datetime.fromisoformat(dtstr.replace('Z', ''))
|
|
180
181
|
|
|
182
|
+
# %% ../nbs/00_core.ipynb #7876622c
|
|
183
|
+
class GhRows(L):
|
|
184
|
+
"Result rows whose bare display is one actionable line each"
|
|
185
|
+
def __repr__(self): return '\n'.join(map(repr, self))
|
|
186
|
+
|
|
181
187
|
# %% ../nbs/00_core.ipynb #16068542
|
|
182
188
|
img_md_pat = re.compile(r'!\[(?P<alt>.*?)\]\((?P<url>[^\s]+)\)')
|
|
183
189
|
|
|
@@ -482,7 +488,7 @@ async def read_pr(
|
|
|
482
488
|
diff = _reduce_ctx(_filter_diff(info.diff, folder=folder))
|
|
483
489
|
res += f"\n\n## Diff\n```diff\n{diff}\n```"
|
|
484
490
|
if replies: res += _fmt_replies(info)
|
|
485
|
-
return res
|
|
491
|
+
return TruncatedString(res, 8_000)
|
|
486
492
|
|
|
487
493
|
# %% ../nbs/00_core.ipynb #69d12346
|
|
488
494
|
async def pr_file_diff(
|
|
@@ -499,6 +505,46 @@ async def pr_file_diff(
|
|
|
499
505
|
if f.filename == filename: return f"## {f.filename} (+{f.additions} -{f.deletions})\n\n```diff\n{html.unescape(f.patch)}\n```"
|
|
500
506
|
return f"File `{filename}` not found in PR #{pr_number}"
|
|
501
507
|
|
|
508
|
+
# %% ../nbs/00_core.ipynb #2c94c2c8
|
|
509
|
+
MAXLEN = 180 # Most characters shown per displayed line
|
|
510
|
+
|
|
511
|
+
def _age(s):
|
|
512
|
+
"Compact age of ISO-8601 timestamp `s`"
|
|
513
|
+
secs = (datetime.now(timezone.utc) - datetime.fromisoformat(s.replace('Z', '+00:00'))).total_seconds()
|
|
514
|
+
for n,u in ((31536000,'y'), (2592000,'mo'), (604800,'w'), (86400,'d'), (3600,'h')):
|
|
515
|
+
if secs >= n: return f'{int(secs//n)}{u}'
|
|
516
|
+
return f'{int(secs//60)}m'
|
|
517
|
+
|
|
518
|
+
def _pr_mark(r):
|
|
519
|
+
if r.get('merged_at'): return 'merged '
|
|
520
|
+
if r.state == 'closed': return 'closed '
|
|
521
|
+
if r.get('draft'): return 'draft '
|
|
522
|
+
return ''
|
|
523
|
+
|
|
524
|
+
class PullRow(AttrDict):
|
|
525
|
+
"One pull request as an actionable line"
|
|
526
|
+
def line(self, maxlen=MAXLEN): return f'#{self.number} {_age(self.updated_at)} {_pr_mark(self)}{truncstr(self.title, maxlen)}'
|
|
527
|
+
__repr__ = line
|
|
528
|
+
|
|
529
|
+
class PullRows(GhRows):
|
|
530
|
+
"Pull request rows, displayed with titles truncated to `maxlen`"
|
|
531
|
+
def __init__(self, items, maxlen=MAXLEN):
|
|
532
|
+
super().__init__(items)
|
|
533
|
+
self.maxlen = maxlen
|
|
534
|
+
def __repr__(self): return '\n'.join(o.line(self.maxlen) for o in self) or 'no PRs'
|
|
535
|
+
|
|
536
|
+
@gh_patch
|
|
537
|
+
async def list_prs(
|
|
538
|
+
self:GhApi,
|
|
539
|
+
state='open', # 'open', 'closed', or 'all'
|
|
540
|
+
maxlen=MAXLEN, # Title characters shown per row
|
|
541
|
+
per_page=100, # PRs per request (100 max)
|
|
542
|
+
page=1, # Page of results, for repos with more than `per_page` PRs
|
|
543
|
+
)->PullRows:
|
|
544
|
+
"Pull requests as one-line rows, most recently updated first"
|
|
545
|
+
prs = await self.pulls.list(state=state, sort='updated', direction='desc', per_page=per_page, page=page)
|
|
546
|
+
return PullRows([PullRow(o) for o in prs], maxlen=maxlen)
|
|
547
|
+
|
|
502
548
|
# %% ../nbs/00_core.ipynb #55a976ea
|
|
503
549
|
def _parse_tmpl(name, txt):
|
|
504
550
|
"Parse issue template `txt`: yml forms into section labels for `issue_body`; md templates kept as `raw`"
|
|
@@ -551,10 +597,6 @@ class CheckRun(AttrDict):
|
|
|
551
597
|
class CommitStatus(AttrDict):
|
|
552
598
|
def __repr__(self): return f'{self.context}: {self.state}'
|
|
553
599
|
|
|
554
|
-
class GhRows(L):
|
|
555
|
-
"Result rows whose bare display is one actionable line each"
|
|
556
|
-
def __repr__(self): return '\n'.join(map(repr, self))
|
|
557
|
-
|
|
558
600
|
class _CheckStatus(AttrDict):
|
|
559
601
|
def _repr_markdown_(self):
|
|
560
602
|
runs = self.check_runs
|
|
@@ -591,7 +633,7 @@ async def failed_step_log(self:GhApi, job_id:int):
|
|
|
591
633
|
errs = [i for i,l in enumerate(seg) if l.startswith(r'##[error]')]
|
|
592
634
|
if errs: seg = seg[:errs[-1]+1]
|
|
593
635
|
res.append(f'# {s.name}\n' + '\n'.join(seg))
|
|
594
|
-
return '\n\n'.join(res)
|
|
636
|
+
return TruncatedString('\n\n'.join(res), 8_000)
|
|
595
637
|
|
|
596
638
|
# %% ../nbs/00_core.ipynb #751c4e54
|
|
597
639
|
def dep_key(dep):
|
|
@@ -5,11 +5,13 @@ Docs: https://ghapi.fast.ai/page.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_page.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['paged', 'parse_link_hdr', 'pages', 'sync_paged', 'sync_pages', 'gh_notifs']
|
|
8
|
+
__all__ = ['paged', 'parse_link_hdr', 'pages', 'sync_paged', 'sync_pages', 'NotifRow', 'NotifRows', 'gh_notifs']
|
|
9
9
|
|
|
10
10
|
# %% ../nbs/03_page.ipynb #6a0bf465
|
|
11
11
|
from fastcore.all import *
|
|
12
12
|
from .core import *
|
|
13
|
+
from .core import _age
|
|
14
|
+
from fastspec.errors import APIError
|
|
13
15
|
|
|
14
16
|
import re, asyncio
|
|
15
17
|
from datetime import datetime, timedelta, timezone
|
|
@@ -87,17 +89,41 @@ def sync_pages(oper, n_pages, *args, per_page=100, n_workers=16, **kwargs):
|
|
|
87
89
|
range(n_pages), threadpool=True, n_workers=n_workers))
|
|
88
90
|
|
|
89
91
|
# %% ../nbs/03_page.ipynb #58141083
|
|
92
|
+
class NotifRow(AttrDict):
|
|
93
|
+
"One notification thread as an actionable line"
|
|
94
|
+
def __repr__(self):
|
|
95
|
+
s = self.subject
|
|
96
|
+
num = s.url.rpartition('/')[2] if s.url else ''
|
|
97
|
+
kind = ('PR' if s.type=='PullRequest' else s.type) + (f' #{num}' if num.isdigit() else '')
|
|
98
|
+
return f'{self.id} {_age(self.updated_at)} {self.reason} {self.repository.full_name} {self.get("mark","")}{kind}: {s.title}'
|
|
99
|
+
|
|
100
|
+
class NotifRows(GhRows):
|
|
101
|
+
"Notification rows; displays 'no notifications' when empty"
|
|
102
|
+
def __repr__(self): return super().__repr__() or 'no notifications'
|
|
103
|
+
|
|
104
|
+
def _notif_mark(sub):
|
|
105
|
+
"`[merged] `/`[closed] ` marker for a fetched subject; empty for open or unfetchable subjects"
|
|
106
|
+
if not sub: return ''
|
|
107
|
+
if sub.get('merged_at'): return '[merged] '
|
|
108
|
+
if sub.get('state') == 'closed': return '[closed] '
|
|
109
|
+
return ''
|
|
110
|
+
|
|
90
111
|
async def gh_notifs(days=5, reasons=('mention', 'review_requested', 'author', 'assign'), include_read=False, per_page=100):
|
|
91
|
-
"
|
|
112
|
+
"Notifications from the past `days` (all available if None) as `NotifRows`, marked `[closed]`/`[merged]` when the subject is no longer open; `reasons=None` includes every reason"
|
|
113
|
+
api = GhApi()
|
|
92
114
|
since = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat() if days else None
|
|
93
|
-
notifs = L([o async for o in paged(
|
|
115
|
+
notifs = L([o async for o in paged(api.activity.list_notifications_for_authenticated_user, all=include_read, since=since, per_page=per_page)]).concat()
|
|
94
116
|
if reasons: notifs = notifs.filter(lambda n: n.reason in reasons)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
async def _get(u):
|
|
118
|
+
if not u: return None
|
|
119
|
+
try: return await api(u, 'GET')
|
|
120
|
+
except APIError: return None # deleted or inaccessible subjects lose their mark, not the whole listing
|
|
121
|
+
subs = await asyncio.gather(*(_get(n.subject.url) for n in notifs))
|
|
122
|
+
for n, s in zip(notifs, subs): n['mark'] = _notif_mark(s)
|
|
123
|
+
return NotifRows(NotifRow(o) for o in notifs)
|
|
124
|
+
|
|
125
|
+
# %% ../nbs/03_page.ipynb #abc91f9e
|
|
126
|
+
@patch
|
|
127
|
+
async def mark_done(self:GhApi, *ids):
|
|
128
|
+
"Mark notification threads as done, removing them from the inbox (the API cannot list done threads back)"
|
|
129
|
+
await asyncio.gather(*(self.activity.mark_thread_as_done(i) for i in ids))
|
|
@@ -87,6 +87,7 @@ Once the PR exists, the fork branch is the source of truth, and the local clone
|
|
|
87
87
|
|
|
88
88
|
# Gotchas
|
|
89
89
|
|
|
90
|
+
- Results with tuned reprs (`read_issue`, `check_status`, `list_prs`, `gh_notifs`) display bare. Raw endpoint results don't, so when only confirmation matters (a `create_comment`, a `pulls.update`), assign the result to a variable and show one key (`r.html_url`, `r.state`) instead of displaying the whole payload.
|
|
90
91
|
- A PR *is* an issue for general comments (`issues.list_comments`, not `pulls.*`) -- inline code-review comments are the separate `pulls.list_review_comments`.
|
|
91
92
|
- Rate limits: register `limit_cb` on `GhApi(...)` to get called back whenever the remaining quota changes, or check `api.limit_rem` any time.
|
|
92
93
|
- HTTP errors raise `fastspec.errors.APIError`, which carries `.status_code` and the endpoint called.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghapi
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: A python client for the GitHub API
|
|
5
5
|
Author-email: Jeremy Howard <info@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: fastcore>=2.2.
|
|
19
|
-
Requires-Dist: fastspec>=0.2.
|
|
18
|
+
Requires-Dist: fastcore>=2.2.7
|
|
19
|
+
Requires-Dist: fastspec>=0.2.1
|
|
20
20
|
Requires-Dist: tomli; python_version < "3.11"
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: fastgit; extra == "dev"
|
|
@@ -26,6 +26,7 @@ Requires-Dist: nbdev; extra == "dev"
|
|
|
26
26
|
Requires-Dist: pyyaml; extra == "dev"
|
|
27
27
|
Requires-Dist: pysym2md>=0.0.6; extra == "dev"
|
|
28
28
|
Requires-Dist: llms-txt>=0.0.6; extra == "dev"
|
|
29
|
+
Requires-Dist: pyskills; extra == "dev"
|
|
29
30
|
Dynamic: license-file
|
|
30
31
|
|
|
31
32
|
# ghapi
|
|
@@ -12,7 +12,7 @@ license = {text = "Apache-2.0"}
|
|
|
12
12
|
authors = [{name = "Jeremy Howard", email = "info@fast.ai"}]
|
|
13
13
|
keywords = ['github', 'api']
|
|
14
14
|
classifiers = ["Natural Language :: English", "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only"]
|
|
15
|
-
dependencies = ['fastcore>=2.2.
|
|
15
|
+
dependencies = ['fastcore>=2.2.7', 'fastspec>=0.2.1', 'tomli; python_version < "3.11"']
|
|
16
16
|
|
|
17
17
|
[project.urls]
|
|
18
18
|
Repository = "https://github.com/fastai/ghapi"
|
|
@@ -25,7 +25,7 @@ ghapi = "ghapi._modidx:d"
|
|
|
25
25
|
ghapi = "ghapi.skill"
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
|
-
dev = ['fastgit', 'matplotlib', 'nbclassic', 'nbdev', 'pyyaml', 'pysym2md>=0.0.6', 'llms-txt>=0.0.6']
|
|
28
|
+
dev = ['fastgit', 'matplotlib', 'nbclassic', 'nbdev', 'pyyaml', 'pysym2md>=0.0.6', 'llms-txt>=0.0.6', 'pyskills']
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
[project.scripts]
|
|
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
|