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.
Files changed (29) hide show
  1. {ghapi-2.1.0/ghapi.egg-info → ghapi-2.1.2}/PKG-INFO +4 -3
  2. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/__init__.py +1 -1
  3. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/_modidx.py +14 -0
  4. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/core.py +58 -16
  5. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/page.py +38 -12
  6. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/skill.py +1 -0
  7. {ghapi-2.1.0 → ghapi-2.1.2/ghapi.egg-info}/PKG-INFO +4 -3
  8. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/requires.txt +3 -2
  9. {ghapi-2.1.0 → ghapi-2.1.2}/pyproject.toml +2 -2
  10. {ghapi-2.1.0 → ghapi-2.1.2}/CONTRIBUTING.md +0 -0
  11. {ghapi-2.1.0 → ghapi-2.1.2}/LICENSE +0 -0
  12. {ghapi-2.1.0 → ghapi-2.1.2}/MANIFEST.in +0 -0
  13. {ghapi-2.1.0 → ghapi-2.1.2}/README.md +0 -0
  14. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/actions.py +0 -0
  15. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/all.py +0 -0
  16. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/auth.py +0 -0
  17. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/build_lib.py +0 -0
  18. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/cli.py +0 -0
  19. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/event.py +0 -0
  20. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/gh_spec.py +0 -0
  21. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/gql_spec.py +0 -0
  22. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/graphql.py +0 -0
  23. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/py.typed +0 -0
  24. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi/templates.py +0 -0
  25. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/SOURCES.txt +0 -0
  26. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/dependency_links.txt +0 -0
  27. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/entry_points.txt +0 -0
  28. {ghapi-2.1.0 → ghapi-2.1.2}/ghapi.egg-info/top_level.txt +0 -0
  29. {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.0
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.6
19
- Requires-Dist: fastspec>=0.2.0
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.0"
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', 'GhTransport', 'GhSyncTransport', 'print_summary', 'gh_patch',
9
- 'GhApi', 'call_gh', 'date2gh', 'gh2date', 'delete_release', 'create_release', 'list_tags', 'list_branches',
10
- 'create_branch_empty', 'delete_tag', 'delete_branch', 'get_branch', 'commit_tree', 'list_files',
11
- 'get_content', 'create_or_update_file', 'create_file', 'delete_file', 'update_contents', 'enable_pages',
12
- 'read_issue', 'read_pr', 'pr_file_diff', 'issue_template', 'issue_body', 'CheckRun', 'CommitStatus',
13
- 'GhRows', 'check_status', 'pr_status', 'failed_step_log', 'dep_key', 'local_dep_graph', 'dep_closure',
14
- 'dep_order', 'dep_dependents', 'dep_graph', 'APIError']
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 fastspec.transport import AsyncTransport, SyncTransport
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(json_data=data)
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
- "Get GitHub notifications from the past `days`, formatted as a summary string"
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(GhApi().activity.list_notifications_for_authenticated_user, all=include_read, since=since, per_page=per_page)]).concat()
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
- lines = []
96
- for n in notifs:
97
- repo, kind = n.repository.full_name, n.subject.type
98
- num = n.subject.url.split('/')[-1] if n.subject.url else '?'
99
- url = f"https://github.com/{repo}/pull/{num}" if kind == 'PullRequest' else f"https://github.com/{repo}/issues/{num}" if kind == 'Issue' else n.subject.url
100
- lines.append(f"- [{kind} #{num}]({url}) {repo} — {n.subject.title} ({n.reason})")
101
- scope = f"past {days} days" if days else "all available"
102
- reason_txt = ", ".join(reasons) if reasons else "all reasons"
103
- return f"**{len(lines)} notifications** ({scope}; {reason_txt})\n\n" + "\n".join(lines)
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.0
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.6
19
- Requires-Dist: fastspec>=0.2.0
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
@@ -1,5 +1,5 @@
1
- fastcore>=2.2.6
2
- fastspec>=0.2.0
1
+ fastcore>=2.2.7
2
+ fastspec>=0.2.1
3
3
 
4
4
  [:python_version < "3.11"]
5
5
  tomli
@@ -12,3 +12,4 @@ nbdev
12
12
  pyyaml
13
13
  pysym2md>=0.0.6
14
14
  llms-txt>=0.0.6
15
+ pyskills
@@ -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.6', 'fastspec>=0.2.0', 'tomli; python_version < "3.11"']
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