ghapi 2.1.1__tar.gz → 2.1.3__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 (30) hide show
  1. {ghapi-2.1.1/ghapi.egg-info → ghapi-2.1.3}/PKG-INFO +4 -3
  2. ghapi-2.1.3/ghapi/__init__.py +8 -0
  3. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/core.py +25 -22
  4. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/page.py +1 -1
  5. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/skill.py +2 -2
  6. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/templates.py +4 -4
  7. {ghapi-2.1.1 → ghapi-2.1.3/ghapi.egg-info}/PKG-INFO +4 -3
  8. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi.egg-info/requires.txt +3 -2
  9. {ghapi-2.1.1 → ghapi-2.1.3}/pyproject.toml +1 -1
  10. ghapi-2.1.1/ghapi/__init__.py +0 -8
  11. {ghapi-2.1.1 → ghapi-2.1.3}/CONTRIBUTING.md +0 -0
  12. {ghapi-2.1.1 → ghapi-2.1.3}/LICENSE +0 -0
  13. {ghapi-2.1.1 → ghapi-2.1.3}/MANIFEST.in +0 -0
  14. {ghapi-2.1.1 → ghapi-2.1.3}/README.md +0 -0
  15. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/_modidx.py +0 -0
  16. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/actions.py +0 -0
  17. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/all.py +0 -0
  18. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/auth.py +0 -0
  19. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/build_lib.py +0 -0
  20. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/cli.py +0 -0
  21. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/event.py +0 -0
  22. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/gh_spec.py +0 -0
  23. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/gql_spec.py +0 -0
  24. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/graphql.py +0 -0
  25. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi/py.typed +0 -0
  26. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi.egg-info/SOURCES.txt +0 -0
  27. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi.egg-info/dependency_links.txt +0 -0
  28. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi.egg-info/entry_points.txt +0 -0
  29. {ghapi-2.1.1 → ghapi-2.1.3}/ghapi.egg-info/top_level.txt +0 -0
  30. {ghapi-2.1.1 → ghapi-2.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghapi
3
- Version: 2.1.1
3
+ Version: 2.1.3
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,9 @@ 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
+ Requires-Dist: fasttransport>=0.0.2
20
21
  Requires-Dist: tomli; python_version < "3.11"
21
22
  Provides-Extra: dev
22
23
  Requires-Dist: fastgit; extra == "dev"
@@ -0,0 +1,8 @@
1
+ """A python client for the GitHub API
2
+
3
+ Modules:
4
+
5
+ - `ghapi.graphql`: Query GitHub's GraphQL API: schema-aware chained queries, one-request batching, and raw GraphQL
6
+ - `ghapi.skill`: GitHub REST API access via `GhApi`, plus local git operations via `fastgit.Git`. Trigger: ALWAYS read before reading/creating issues and PRs, checking CI status, managing releases/branches/gists, and doing repo-local git operations."""
7
+
8
+ __version__ = "2.1.3"
@@ -5,29 +5,31 @@ 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', 'GhRows', 'delete_release', 'create_release', 'list_tags',
10
- 'list_branches', 'create_branch_empty', 'delete_tag', 'delete_branch', 'get_branch', 'commit_tree',
11
- 'list_files', 'get_content', 'create_or_update_file', 'create_file', 'delete_file', 'update_contents',
12
- 'enable_pages', 'read_issue', 'read_pr', 'pr_file_diff', 'PullRow', 'PullRows', 'list_prs', 'issue_template',
13
- 'issue_body', 'CheckRun', 'CommitStatus', 'check_status', 'pr_status', 'failed_step_log', 'dep_key',
14
- 'local_dep_graph', 'dep_closure', '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
18
19
 
19
20
  from fastcore.all import *
21
+ from fastcore.ansi import strip_ansi
20
22
  from fastspec.spec import SpecParser
21
23
  from fastspec.oapi import OpenAPIClient, OpFunc, SyncOpFunc
22
- from fastspec.transport import AsyncTransport, SyncTransport
24
+ from fasttransport.core import AsyncTransport, SyncTransport
23
25
  from .gh_spec import spec
24
- from fastspec.errors import APIError
26
+ from fasttransport.errors import APIError
25
27
 
26
28
  import mimetypes,base64
27
29
  from collections import Counter
28
30
  from urllib.parse import quote
29
31
  from datetime import datetime, timezone
30
- import os, shutil, tempfile, subprocess, fnmatch, html
32
+ import os, shutil, tempfile, subprocess, fnmatch, html, io, zipfile
31
33
  import asyncio
32
34
 
33
35
  try: import tomllib
@@ -142,7 +144,7 @@ class GhApi(OpenAPIClient):
142
144
  if verb is None: verb = 'POST' if data else 'GET'
143
145
  if route: path = path.format(**{k:quote(str(v), safe='') for k,v in route.items()})
144
146
  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)
147
+ kw = dict(content=data) if isinstance(data, (bytes,str)) else dict(json=data)
146
148
  return self.transport.request(verb, path, headers=headers, params=query, **kw)
147
149
 
148
150
  @property
@@ -487,7 +489,7 @@ async def read_pr(
487
489
  diff = _reduce_ctx(_filter_diff(info.diff, folder=folder))
488
490
  res += f"\n\n## Diff\n```diff\n{diff}\n```"
489
491
  if replies: res += _fmt_replies(info)
490
- return res
492
+ return TruncatedString(res, 8_000)
491
493
 
492
494
  # %% ../nbs/00_core.ipynb #69d12346
493
495
  async def pr_file_diff(
@@ -505,6 +507,8 @@ async def pr_file_diff(
505
507
  return f"File `{filename}` not found in PR #{pr_number}"
506
508
 
507
509
  # %% ../nbs/00_core.ipynb #2c94c2c8
510
+ MAXLEN = 180 # Most characters shown per displayed line
511
+
508
512
  def _age(s):
509
513
  "Compact age of ISO-8601 timestamp `s`"
510
514
  secs = (datetime.now(timezone.utc) - datetime.fromisoformat(s.replace('Z', '+00:00'))).total_seconds()
@@ -520,21 +524,21 @@ def _pr_mark(r):
520
524
 
521
525
  class PullRow(AttrDict):
522
526
  "One pull request as an actionable line"
523
- def line(self, maxlen=120): return f'#{self.number} {_age(self.updated_at)} {_pr_mark(self)}{truncstr(self.title, maxlen)}'
527
+ def line(self, maxlen=MAXLEN): return f'#{self.number} {_age(self.updated_at)} {_pr_mark(self)}{truncstr(self.title, maxlen)}'
524
528
  __repr__ = line
525
529
 
526
530
  class PullRows(GhRows):
527
531
  "Pull request rows, displayed with titles truncated to `maxlen`"
528
- def __init__(self, items, maxlen=120):
532
+ def __init__(self, items, maxlen=MAXLEN):
529
533
  super().__init__(items)
530
534
  self.maxlen = maxlen
531
- def __repr__(self): return '\n'.join(o.line(self.maxlen) for o in self)
535
+ def __repr__(self): return '\n'.join(o.line(self.maxlen) for o in self) or 'no PRs'
532
536
 
533
537
  @gh_patch
534
538
  async def list_prs(
535
539
  self:GhApi,
536
540
  state='open', # 'open', 'closed', or 'all'
537
- maxlen=120, # Title characters shown per row
541
+ maxlen=MAXLEN, # Title characters shown per row
538
542
  per_page=100, # PRs per request (100 max)
539
543
  page=1, # Page of results, for repos with more than `per_page` PRs
540
544
  )->PullRows:
@@ -622,15 +626,14 @@ async def pr_status(self:GhApi, pull_number:int):
622
626
  async def failed_step_log(self:GhApi, job_id:int):
623
627
  "Log sections of `job_id`'s failed steps, each headed by its step name"
624
628
  job = await self.actions.get_job_for_workflow_run(job_id=job_id)
625
- log = await self.actions.download_job_logs_for_workflow_run(job_id=job_id)
629
+ zf = zipfile.ZipFile(io.BytesIO(await self.actions.download_workflow_run_logs(run_id=job.run_id)))
626
630
  res = []
627
631
  for s in job.steps:
628
632
  if s.conclusion!='failure': continue
629
- seg = [l[29:] for l in log.splitlines() if s.started_at[:-1] <= l[:19] <= s.completed_at[:-1]]
630
- errs = [i for i,l in enumerate(seg) if l.startswith(r'##[error]')]
631
- if errs: seg = seg[:errs[-1]+1]
632
- res.append(f'# {s.name}\n' + '\n'.join(seg))
633
- return '\n\n'.join(res)
633
+ name = first(n for n in zf.namelist() if n.startswith(f'{job.name}/{s.number}_'))
634
+ lines = [strip_ansi(l.split(' ', 1)[-1]) for l in zf.read(name).decode().splitlines()]
635
+ res.append(f'# {s.name}\n' + '\n'.join(lines))
636
+ return TruncatedString('\n\n'.join(res), 8_000)
634
637
 
635
638
  # %% ../nbs/00_core.ipynb #751c4e54
636
639
  def dep_key(dep):
@@ -11,7 +11,7 @@ __all__ = ['paged', 'parse_link_hdr', 'pages', 'sync_paged', 'sync_pages', 'Noti
11
11
  from fastcore.all import *
12
12
  from .core import *
13
13
  from .core import _age
14
- from fastspec.errors import APIError
14
+ from fasttransport.errors import APIError
15
15
 
16
16
  import re, asyncio
17
17
  from datetime import datetime, timedelta, timezone
@@ -1,4 +1,4 @@
1
- """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.
1
+ """GitHub REST API access via `GhApi`, plus local git operations via `fastgit.Git`. Trigger: ALWAYS read before reading/creating issues and PRs, checking CI status, managing releases/branches/gists, and doing repo-local git operations.
2
2
 
3
3
  `ghapi` is a full, always-up-to-date wrapper over the entire GitHub REST API, dynamically generated from GitHub's own OpenAPI spec. `fastgit` is a tiny complementary wrapper around the local `git` CLI, for anything that's about the local repo rather than GitHub's servers -- cheaper, and not rate-limited. As of v2, `ghapi` is async by default: `await` every API call (notebooks and modern REPLs support top-level `await`; scripts use `asyncio.run`). For sync code, `GhApi(sync=True)` gives a client whose generated endpoint calls block and return results directly (the convenience methods below stay async-only; drive those with `fastcore.aio.run_sync` on an async client. `paged`/`pages` have sync twins `sync_paged`/`sync_pages`).
4
4
 
@@ -90,7 +90,7 @@ Once the PR exists, the fork branch is the source of truth, and the local clone
90
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.
91
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`.
92
92
  - Rate limits: register `limit_cb` on `GhApi(...)` to get called back whenever the remaining quota changes, or check `api.limit_rem` any time.
93
- - HTTP errors raise `fastspec.errors.APIError`, which carries `.status_code` and the endpoint called.
93
+ - HTTP errors raise `fasttransport.errors.APIError`, which carries `.status_code` and the endpoint called.
94
94
  - `per_page` maxes out at 100; beyond that, use `paged`/`pages` rather than manually looping `page=`.
95
95
  - Per-call headers on named endpoints use `headers_=` (e.g. `await api.pulls.get(n, headers_={'Accept': 'application/vnd.github.v3.diff'})` for the raw diff); plain `headers=` is a kwarg only for direct `api(path, verb, ...)` calls.
96
96
  - `GITHUB_TOKEN` unset means unauthenticated (heavily rate-limited, no write access) -- `GhApi()` warns but doesn't raise.
@@ -14,8 +14,8 @@ $NEEDS
14
14
  matrix: { os: $OPERSYS }
15
15
  runs-on: ${{ matrix.os }}-latest
16
16
  steps:
17
- - uses: actions/checkout@v4
18
- - uses: actions/setup-python@v5
17
+ - uses: actions/checkout@v7
18
+ - uses: actions/setup-python@v7
19
19
  with: {python-version: '3.12'}
20
20
  - name: Run script
21
21
  env:
@@ -30,8 +30,8 @@ pre_tmpl = """prebuild:
30
30
  outputs:
31
31
  out: ${{ toJson(steps) }}
32
32
  steps:
33
- - uses: actions/checkout@v4
34
- - uses: actions/setup-python@v5
33
+ - uses: actions/checkout@v7
34
+ - uses: actions/setup-python@v7
35
35
  with: {python-version: '3.12'}
36
36
  - name: Create release
37
37
  id: step1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghapi
3
- Version: 2.1.1
3
+ Version: 2.1.3
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,9 @@ 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
+ Requires-Dist: fasttransport>=0.0.2
20
21
  Requires-Dist: tomli; python_version < "3.11"
21
22
  Provides-Extra: dev
22
23
  Requires-Dist: fastgit; extra == "dev"
@@ -1,5 +1,6 @@
1
- fastcore>=2.2.6
2
- fastspec>=0.2.0
1
+ fastcore>=2.2.7
2
+ fastspec>=0.2.1
3
+ fasttransport>=0.0.2
3
4
 
4
5
  [:python_version < "3.11"]
5
6
  tomli
@@ -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', 'fasttransport>=0.0.2', 'tomli; python_version < "3.11"']
16
16
 
17
17
  [project.urls]
18
18
  Repository = "https://github.com/fastai/ghapi"
@@ -1,8 +0,0 @@
1
- """A python client for the GitHub API
2
-
3
- Modules:
4
-
5
- - `ghapi.graphql`: Query GitHub's GraphQL API: schema-aware chained queries, one-request batching, and raw GraphQL
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
-
8
- __version__ = "2.1.1"
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