python-github-plus 1.1.1__tar.gz → 2.0.0__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 (23) hide show
  1. python_github_plus-2.0.0/PKG-INFO +215 -0
  2. python_github_plus-2.0.0/README.md +192 -0
  3. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/pyproject.toml +7 -3
  4. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/python_github_plus/__init__.py +0 -4
  5. python_github_plus-2.0.0/python_github_plus/consts.py +7 -0
  6. python_github_plus-2.0.0/python_github_plus/github_plus.py +405 -0
  7. python_github_plus-2.0.0/python_github_plus.egg-info/PKG-INFO +215 -0
  8. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/python_github_plus.egg-info/SOURCES.txt +4 -1
  9. python_github_plus-2.0.0/python_github_plus.egg-info/requires.txt +3 -0
  10. python_github_plus-2.0.0/tests/test_github_integration.py +126 -0
  11. python_github_plus-2.0.0/tests/test_github_plus.py +332 -0
  12. python_github_plus-2.0.0/tests/test_github_write_integration.py +195 -0
  13. python_github_plus-1.1.1/PKG-INFO +0 -266
  14. python_github_plus-1.1.1/README.md +0 -239
  15. python_github_plus-1.1.1/python_github_plus/consts.py +0 -1
  16. python_github_plus-1.1.1/python_github_plus/github_plus.py +0 -304
  17. python_github_plus-1.1.1/python_github_plus.egg-info/PKG-INFO +0 -266
  18. python_github_plus-1.1.1/python_github_plus.egg-info/requires.txt +0 -7
  19. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/LICENSE +0 -0
  20. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/MANIFEST.in +0 -0
  21. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/python_github_plus.egg-info/dependency_links.txt +0 -0
  22. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/python_github_plus.egg-info/top_level.txt +0 -0
  23. {python_github_plus-1.1.1 → python_github_plus-2.0.0}/setup.cfg +0 -0
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-github-plus
3
+ Version: 2.0.0
4
+ Summary: Enhanced Python GitHub client.
5
+ Author: Avi Zaguri
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/aviz92/python-github-plus
8
+ Project-URL: Repository, https://github.com/aviz92/python-github-plus
9
+ Project-URL: Issues, https://github.com/aviz92/python-github-plus/issues
10
+ Keywords: development,github,client,api,python
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: custom-python-logger>=4.0.0
20
+ Requires-Dist: pygithub>=2.10.0
21
+ Requires-Dist: python-dotenv>=1.2.1
22
+ Dynamic: license-file
23
+
24
+ ![PyPI version](https://img.shields.io/pypi/v/python-github-plus)
25
+ ![Python](https://img.shields.io/badge/python->=3.12-blue)
26
+ ![Development Status](https://img.shields.io/badge/status-stable-green)
27
+ ![Maintenance](https://img.shields.io/maintenance/yes/2026)
28
+ ![PyPI](https://img.shields.io/pypi/dm/python-github-plus)
29
+ ![License](https://img.shields.io/pypi/l/python-github-plus)
30
+
31
+ ---
32
+
33
+ # Python GitHub Plus
34
+ An enhanced Python client for GitHub built on [PyGithub](https://github.com/PyGithub/PyGithub): one object per repository,
35
+ grouped services, sensible defaults and errors you can act on.
36
+
37
+ ---
38
+
39
+ ## Features
40
+ - ✅ **Service-based architecture**: `project`, `branch`, `pull_request`, `workflow`, `tag`, `file`
41
+ - ✅ **Lazy connection**: creating a client makes no API calls; one connection serves many repositories (`for_repo`)
42
+ - ✅ **GitHub.com and GitHub Enterprise Server** (`base_url`)
43
+ - ✅ **Errors are raised, not logged and swallowed**: `GithubException` from the API, `ValueError` for bad input
44
+ - ✅ **Default branch aware**: branch, tag, PR, workflow and file calls default to the repo's real default branch
45
+ - ✅ **Bounded listing**: every list method takes `limit` and stops paging once it is reached
46
+ - ✅ **Pull requests**: list, create (body, draft), files, assign, comment, approve, merge (merge/squash/rebase), close
47
+ - ✅ **Workflows**: find by name, file name or id, trigger and get the run back, wait for completion
48
+ - ✅ **Branches and tags**: create, delete, list, protect / unprotect
49
+ - ✅ **Files**: read text files (including files over 1 MB) and list directories
50
+
51
+ ---
52
+
53
+ ## Installation
54
+ ```bash
55
+ pip install python-github-plus
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Configuration
61
+ Pass the token directly, or set environment variables. A `.env` file is loaded only when an argument is missing.
62
+
63
+ ```bash
64
+ GITHUB_ACCESS_TOKEN=your_github_access_token
65
+ GITHUB_BASE_URL=https://github.example.com/api/v3 # optional, GitHub Enterprise Server only
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Examples
71
+
72
+ ### Basic Setup and Connection
73
+ ```python
74
+ from python_github_plus import GitHubClient
75
+
76
+ github_client = GitHubClient(
77
+ repo_full_name="your-repo-owner/your-repo-name",
78
+ access_token="your_access_token", # or GITHUB_ACCESS_TOKEN
79
+ # base_url="https://github.example.com/api/v3", # GitHub Enterprise Server
80
+ )
81
+
82
+ # Creating the client makes no API calls; check the token explicitly if you want to fail fast
83
+ github_client.is_connected(raise_if_not_connected=True)
84
+
85
+ # Another repository over the same connection
86
+ other_repo = github_client.for_repo("your-repo-owner/another-repo")
87
+
88
+ # Services
89
+ github_client.project
90
+ github_client.branch
91
+ github_client.pull_request
92
+ github_client.workflow
93
+ github_client.tag
94
+ github_client.file
95
+ ```
96
+
97
+ ### Branch Management
98
+ ```python
99
+ # Create a branch from the default branch (or pass from_branch="develop")
100
+ branch_ref = github_client.branch.create(branch_name="feature/new-feature")
101
+ print(f"Created branch: {branch_ref.ref}")
102
+
103
+ # List the first 50 branches
104
+ for branch in github_client.branch.list(limit=50):
105
+ print(f"Branch: {branch.name}")
106
+
107
+ # Protect a branch: 2 approvals, code owners, required CI check
108
+ github_client.branch.protect(
109
+ "main",
110
+ required_approving_review_count=2,
111
+ require_code_owner_reviews=True,
112
+ require_status_checks=True,
113
+ status_check_contexts=["CI"],
114
+ )
115
+
116
+ # Delete a branch
117
+ github_client.branch.delete("feature/old-feature")
118
+ ```
119
+
120
+ ### Pull Request Management
121
+ ```python
122
+ # Open a draft PR into the default branch
123
+ pr = github_client.pull_request.create(
124
+ title="Add new feature",
125
+ from_branch="feature/new-feature",
126
+ body="What changed and why",
127
+ draft=True,
128
+ )
129
+ print(f"Created PR: #{pr.number}")
130
+
131
+ # List open PRs into main, and the files a PR changes
132
+ for open_pr in github_client.pull_request.list(state="open", base="main", limit=20):
133
+ print(f"#{open_pr.number} {open_pr.title}")
134
+ for changed in github_client.pull_request.files(pr.number):
135
+ print(f"{changed.status}: {changed.filename} (+{changed.additions}/-{changed.deletions})")
136
+
137
+ github_client.pull_request.assign(pr.number, ["username"])
138
+ github_client.pull_request.add_comment(pr.number, "Great work!")
139
+ github_client.pull_request.approve(pr.number)
140
+
141
+ # Merge; raises ValueError if GitHub did not merge it
142
+ merge_sha = github_client.pull_request.merge(pr.number, merge_method="squash")
143
+ ```
144
+
145
+ ### Workflow Operations
146
+ ```python
147
+ # Find a workflow by display name, file name or id, and trigger it on the default branch
148
+ run = github_client.workflow.trigger("ci.yml", inputs={"debug": "true"})
149
+
150
+ # github.com returns the new run; on servers that don't, take the newest run instead
151
+ if run is None:
152
+ run = github_client.workflow.list_runs("ci.yml", limit=1)[0]
153
+
154
+ print(f"Run {run.id}: {github_client.workflow.status(run.id)}")
155
+
156
+ # Poll until the run completes (raises TimeoutError after `timeout` seconds)
157
+ conclusion = github_client.workflow.wait_until_finished(run_id=run.id, check_interval=30, timeout=3600)
158
+ print(f"Workflow run finished: {conclusion}")
159
+ ```
160
+
161
+ ### File Operations
162
+ ```python
163
+ # Read a file from the default branch (or pass ref="v1.0.0")
164
+ content = github_client.file.fetch_content("README.md")
165
+ print(content[:100])
166
+
167
+ # List a directory
168
+ for entry in github_client.file.list_dir("python_github_plus"):
169
+ print(f"{entry.type}: {entry.path}")
170
+ ```
171
+
172
+ ### Tag Management
173
+ ```python
174
+ # Annotated tag on the head of the default branch, or on a specific commit
175
+ tag = github_client.tag.create(tag_name="v1.0.0", message="Release version 1.0.0")
176
+ github_client.tag.create_from_sha(tag_name="v1.0.1", sha="abc123...", message="Hotfix")
177
+
178
+ for repo_tag in github_client.tag.list(limit=10):
179
+ print(f"Tag: {repo_tag.name}")
180
+
181
+ github_client.tag.delete("v0.9.0")
182
+ ```
183
+
184
+ ### Project Management
185
+ ```python
186
+ project_info = github_client.project.get_info()
187
+ print(f"Project: {project_info.full_name} (default branch: {project_info.default_branch})")
188
+
189
+ for member in github_client.project.list_members(limit=100):
190
+ print(f"Member: {member.login}")
191
+
192
+ github_client.project.add_member("newuser", permission="push") # pull, triage, push, maintain or admin
193
+ github_client.project.remove_member("olduser")
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Upgrading from 1.x
199
+ See [CHANGELOG.md](CHANGELOG.md). In short: errors now raise, defaults follow the repo's default branch,
200
+ `workflow.trigger` returns the run, and `pull_request.merge` returns the merge commit sha.
201
+
202
+ ---
203
+
204
+ ## 🤝 Contributing
205
+ If you have a helpful tool, pattern, or improvement to suggest:
206
+ Fork the repo <br>
207
+ Create a new branch <br>
208
+ Submit a pull request <br>
209
+ I welcome additions that promote clean, productive, and maintainable development. <br>
210
+
211
+ ---
212
+
213
+ ## 🙏 Thanks
214
+ Thanks for exploring this repository! <br>
215
+ Happy coding! <br>
@@ -0,0 +1,192 @@
1
+ ![PyPI version](https://img.shields.io/pypi/v/python-github-plus)
2
+ ![Python](https://img.shields.io/badge/python->=3.12-blue)
3
+ ![Development Status](https://img.shields.io/badge/status-stable-green)
4
+ ![Maintenance](https://img.shields.io/maintenance/yes/2026)
5
+ ![PyPI](https://img.shields.io/pypi/dm/python-github-plus)
6
+ ![License](https://img.shields.io/pypi/l/python-github-plus)
7
+
8
+ ---
9
+
10
+ # Python GitHub Plus
11
+ An enhanced Python client for GitHub built on [PyGithub](https://github.com/PyGithub/PyGithub): one object per repository,
12
+ grouped services, sensible defaults and errors you can act on.
13
+
14
+ ---
15
+
16
+ ## Features
17
+ - ✅ **Service-based architecture**: `project`, `branch`, `pull_request`, `workflow`, `tag`, `file`
18
+ - ✅ **Lazy connection**: creating a client makes no API calls; one connection serves many repositories (`for_repo`)
19
+ - ✅ **GitHub.com and GitHub Enterprise Server** (`base_url`)
20
+ - ✅ **Errors are raised, not logged and swallowed**: `GithubException` from the API, `ValueError` for bad input
21
+ - ✅ **Default branch aware**: branch, tag, PR, workflow and file calls default to the repo's real default branch
22
+ - ✅ **Bounded listing**: every list method takes `limit` and stops paging once it is reached
23
+ - ✅ **Pull requests**: list, create (body, draft), files, assign, comment, approve, merge (merge/squash/rebase), close
24
+ - ✅ **Workflows**: find by name, file name or id, trigger and get the run back, wait for completion
25
+ - ✅ **Branches and tags**: create, delete, list, protect / unprotect
26
+ - ✅ **Files**: read text files (including files over 1 MB) and list directories
27
+
28
+ ---
29
+
30
+ ## Installation
31
+ ```bash
32
+ pip install python-github-plus
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Configuration
38
+ Pass the token directly, or set environment variables. A `.env` file is loaded only when an argument is missing.
39
+
40
+ ```bash
41
+ GITHUB_ACCESS_TOKEN=your_github_access_token
42
+ GITHUB_BASE_URL=https://github.example.com/api/v3 # optional, GitHub Enterprise Server only
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Examples
48
+
49
+ ### Basic Setup and Connection
50
+ ```python
51
+ from python_github_plus import GitHubClient
52
+
53
+ github_client = GitHubClient(
54
+ repo_full_name="your-repo-owner/your-repo-name",
55
+ access_token="your_access_token", # or GITHUB_ACCESS_TOKEN
56
+ # base_url="https://github.example.com/api/v3", # GitHub Enterprise Server
57
+ )
58
+
59
+ # Creating the client makes no API calls; check the token explicitly if you want to fail fast
60
+ github_client.is_connected(raise_if_not_connected=True)
61
+
62
+ # Another repository over the same connection
63
+ other_repo = github_client.for_repo("your-repo-owner/another-repo")
64
+
65
+ # Services
66
+ github_client.project
67
+ github_client.branch
68
+ github_client.pull_request
69
+ github_client.workflow
70
+ github_client.tag
71
+ github_client.file
72
+ ```
73
+
74
+ ### Branch Management
75
+ ```python
76
+ # Create a branch from the default branch (or pass from_branch="develop")
77
+ branch_ref = github_client.branch.create(branch_name="feature/new-feature")
78
+ print(f"Created branch: {branch_ref.ref}")
79
+
80
+ # List the first 50 branches
81
+ for branch in github_client.branch.list(limit=50):
82
+ print(f"Branch: {branch.name}")
83
+
84
+ # Protect a branch: 2 approvals, code owners, required CI check
85
+ github_client.branch.protect(
86
+ "main",
87
+ required_approving_review_count=2,
88
+ require_code_owner_reviews=True,
89
+ require_status_checks=True,
90
+ status_check_contexts=["CI"],
91
+ )
92
+
93
+ # Delete a branch
94
+ github_client.branch.delete("feature/old-feature")
95
+ ```
96
+
97
+ ### Pull Request Management
98
+ ```python
99
+ # Open a draft PR into the default branch
100
+ pr = github_client.pull_request.create(
101
+ title="Add new feature",
102
+ from_branch="feature/new-feature",
103
+ body="What changed and why",
104
+ draft=True,
105
+ )
106
+ print(f"Created PR: #{pr.number}")
107
+
108
+ # List open PRs into main, and the files a PR changes
109
+ for open_pr in github_client.pull_request.list(state="open", base="main", limit=20):
110
+ print(f"#{open_pr.number} {open_pr.title}")
111
+ for changed in github_client.pull_request.files(pr.number):
112
+ print(f"{changed.status}: {changed.filename} (+{changed.additions}/-{changed.deletions})")
113
+
114
+ github_client.pull_request.assign(pr.number, ["username"])
115
+ github_client.pull_request.add_comment(pr.number, "Great work!")
116
+ github_client.pull_request.approve(pr.number)
117
+
118
+ # Merge; raises ValueError if GitHub did not merge it
119
+ merge_sha = github_client.pull_request.merge(pr.number, merge_method="squash")
120
+ ```
121
+
122
+ ### Workflow Operations
123
+ ```python
124
+ # Find a workflow by display name, file name or id, and trigger it on the default branch
125
+ run = github_client.workflow.trigger("ci.yml", inputs={"debug": "true"})
126
+
127
+ # github.com returns the new run; on servers that don't, take the newest run instead
128
+ if run is None:
129
+ run = github_client.workflow.list_runs("ci.yml", limit=1)[0]
130
+
131
+ print(f"Run {run.id}: {github_client.workflow.status(run.id)}")
132
+
133
+ # Poll until the run completes (raises TimeoutError after `timeout` seconds)
134
+ conclusion = github_client.workflow.wait_until_finished(run_id=run.id, check_interval=30, timeout=3600)
135
+ print(f"Workflow run finished: {conclusion}")
136
+ ```
137
+
138
+ ### File Operations
139
+ ```python
140
+ # Read a file from the default branch (or pass ref="v1.0.0")
141
+ content = github_client.file.fetch_content("README.md")
142
+ print(content[:100])
143
+
144
+ # List a directory
145
+ for entry in github_client.file.list_dir("python_github_plus"):
146
+ print(f"{entry.type}: {entry.path}")
147
+ ```
148
+
149
+ ### Tag Management
150
+ ```python
151
+ # Annotated tag on the head of the default branch, or on a specific commit
152
+ tag = github_client.tag.create(tag_name="v1.0.0", message="Release version 1.0.0")
153
+ github_client.tag.create_from_sha(tag_name="v1.0.1", sha="abc123...", message="Hotfix")
154
+
155
+ for repo_tag in github_client.tag.list(limit=10):
156
+ print(f"Tag: {repo_tag.name}")
157
+
158
+ github_client.tag.delete("v0.9.0")
159
+ ```
160
+
161
+ ### Project Management
162
+ ```python
163
+ project_info = github_client.project.get_info()
164
+ print(f"Project: {project_info.full_name} (default branch: {project_info.default_branch})")
165
+
166
+ for member in github_client.project.list_members(limit=100):
167
+ print(f"Member: {member.login}")
168
+
169
+ github_client.project.add_member("newuser", permission="push") # pull, triage, push, maintain or admin
170
+ github_client.project.remove_member("olduser")
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Upgrading from 1.x
176
+ See [CHANGELOG.md](CHANGELOG.md). In short: errors now raise, defaults follow the repo's default branch,
177
+ `workflow.trigger` returns the run, and `pull_request.merge` returns the merge commit sha.
178
+
179
+ ---
180
+
181
+ ## 🤝 Contributing
182
+ If you have a helpful tool, pattern, or improvement to suggest:
183
+ Fork the repo <br>
184
+ Create a new branch <br>
185
+ Submit a pull request <br>
186
+ I welcome additions that promote clean, productive, and maintainable development. <br>
187
+
188
+ ---
189
+
190
+ ## 🙏 Thanks
191
+ Thanks for exploring this repository! <br>
192
+ Happy coding! <br>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-github-plus"
7
- version = "1.1.1"
7
+ version = "2.0.0"
8
8
  description = "Enhanced Python GitHub client."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -22,10 +22,14 @@ classifiers = [
22
22
 
23
23
  dependencies = [
24
24
  "custom-python-logger>=4.0.0",
25
+ "pygithub>=2.10.0",
26
+ "python-dotenv>=1.2.1",
27
+ ]
28
+
29
+ [dependency-groups]
30
+ dev = [
25
31
  "pre-commit>=4.5.0",
26
- "pygithub>=2.8.1",
27
32
  "pytest>=9.0.1",
28
- "python-dotenv>=1.2.1",
29
33
  "setuptools>=80.9.0",
30
34
  "wheel>=0.45.1",
31
35
  ]
@@ -1,7 +1,3 @@
1
- from dotenv import load_dotenv
2
-
3
1
  from python_github_plus.github_plus import GitHubClient, GitHubPRStatus, GitHubWorkflowStatus
4
2
 
5
- load_dotenv()
6
-
7
3
  __all__ = ["GitHubPRStatus", "GitHubWorkflowStatus", "GitHubClient"]
@@ -0,0 +1,7 @@
1
+ from typing import Literal
2
+
3
+ LOGGER_NAME = "python_github_plus"
4
+
5
+ DEFAULT_BASE_URL = "https://api.github.com"
6
+ MergeMethod = Literal["merge", "squash", "rebase"]
7
+ PRState = Literal["open", "closed", "all"]