mirror of
				https://github.com/RGBCube/GitHubWrapper
				synced 2025-10-31 05:52:45 +00:00 
			
		
		
		
	Adding files to repos
This commit is contained in:
		
							parent
							
								
									72bd647c9b
								
							
						
					
					
						commit
						3d49dd9246
					
				
					 5 changed files with 102 additions and 37 deletions
				
			
		|  | @ -34,7 +34,7 @@ P = ParamSpec('P') | |||
| 
 | ||||
| class GHClient: | ||||
|     """The main client, used to start most use-cases. | ||||
|      | ||||
| 
 | ||||
|     Parameters | ||||
|     ---------- | ||||
|     username: Optional[:class:`str`] | ||||
|  | @ -59,6 +59,7 @@ class GHClient: | |||
|     token: Optional[:class:`str`] | ||||
|         The authenticated Client's token, if applicable. | ||||
|     """ | ||||
| 
 | ||||
|     has_started: bool = False | ||||
| 
 | ||||
|     def __init__( | ||||
|  | @ -109,7 +110,7 @@ class GHClient: | |||
|             raise Exception('HTTP Session doesn\'t exist') from exc | ||||
| 
 | ||||
|     def __repr__(self) -> str: | ||||
|         return f'<{self.__class__.__name__} has_auth={bool(self.__auth)}>' | ||||
|         return f'<Client has_auth={bool(self.__auth)}>' | ||||
| 
 | ||||
|     @overload | ||||
|     def check_limits(self, as_dict: Literal[True] = True) -> Dict[str, Union[str, int]]: | ||||
|  | @ -142,7 +143,7 @@ class GHClient: | |||
| 
 | ||||
|     async def update_auth(self, username: str, token: str) -> None: | ||||
|         """Allows you to input auth information after instantiating the client. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         username: :class:`str` | ||||
|  | @ -161,7 +162,7 @@ class GHClient: | |||
| 
 | ||||
|     async def start(self) -> Self: | ||||
|         """Main entry point to the wrapper, this creates the ClientSession. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         """ | ||||
|  | @ -220,7 +221,7 @@ class GHClient: | |||
| 
 | ||||
|     async def get_user(self, *, user: str) -> User: | ||||
|         """:class:`User`: Fetch a Github user from their username. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         user: :class:`str` | ||||
|  | @ -230,7 +231,7 @@ class GHClient: | |||
| 
 | ||||
|     async def get_repo(self, *, owner: str, repo: str) -> Repository: | ||||
|         """:class:`Repository`: Fetch a Github repository from it's name. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         owner: :class:`str` | ||||
|  | @ -238,11 +239,11 @@ class GHClient: | |||
|         repo: :class:`str` | ||||
|             The name of the repository to fetch. | ||||
|         """ | ||||
|         return Repository(await self.http.get_repo(owner, repo), self.http) #type: ignore | ||||
|         return Repository(await self.http.get_repo(owner, repo), self.http)  # type: ignore | ||||
| 
 | ||||
|     async def get_issue(self, *, owner: str, repo: str, issue: int) -> Issue: | ||||
|         """:class:`Issue`: Fetch a Github Issue from it's name. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         owner: :class:`str` | ||||
|  | @ -252,7 +253,7 @@ class GHClient: | |||
|         issue: :class:`int` | ||||
|             The ID of the issue to fetch. | ||||
|         """ | ||||
|         return Issue(await self.http.get_repo_issue(owner, repo, issue), self.http) #type: ignore #fwiw, this shouldn't error but pyright <3 | ||||
|         return Issue(await self.http.get_repo_issue(owner, repo, issue), self.http)  # type: ignore #fwiw, this shouldn't error but pyright <3 | ||||
| 
 | ||||
|     async def create_repo( | ||||
|         self, | ||||
|  | @ -264,7 +265,7 @@ class GHClient: | |||
|     ) -> Repository: | ||||
|         """Creates a Repository with supplied data. | ||||
|         Requires API authentication. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         name: :class:`str` | ||||
|  | @ -306,7 +307,7 @@ class GHClient: | |||
| 
 | ||||
|     async def get_gist(self, gist: str) -> Gist: | ||||
|         """Fetch a Github gist from it's id. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         gist: :class:`str` | ||||
|  | @ -318,7 +319,9 @@ class GHClient: | |||
|         """ | ||||
|         return Gist(await self.http.get_gist(gist), self.http) | ||||
| 
 | ||||
|     async def create_gist(self, *, files: List[File], description: str, public: bool) -> Gist: | ||||
|     async def create_gist( | ||||
|         self, *, files: List[File], description: str = 'Gist from Github-Api-Wrapper', public: bool = True | ||||
|     ) -> Gist: | ||||
|         """Creates a Gist with the given files, requires authorisation. | ||||
| 
 | ||||
|         Parameters | ||||
|  | @ -342,7 +345,7 @@ class GHClient: | |||
| 
 | ||||
|     async def delete_gist(self, gist: int) -> Optional[str]: | ||||
|         """Delete a Github gist, requires authorisation. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         gist: :class:`int` | ||||
|  | @ -356,12 +359,12 @@ class GHClient: | |||
| 
 | ||||
|     async def get_org(self, org: str) -> Organization: | ||||
|         """Fetch a Github organization from it's name. | ||||
|          | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         org: :class:`str` | ||||
|             The name of the organization to fetch. | ||||
|              | ||||
| 
 | ||||
|         Returns | ||||
|         ------- | ||||
|         :class:`Organization` | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| # == exceptions.py ==# | ||||
| 
 | ||||
| import datetime | ||||
| from typing import Tuple | ||||
| from typing import Optional, Tuple | ||||
| 
 | ||||
| from aiohttp import ClientResponse | ||||
| 
 | ||||
|  | @ -94,7 +94,7 @@ class InvalidAuthCombination(ClientException): | |||
|     """Raised when the username and token are both provided.""" | ||||
| 
 | ||||
|     def __init__(self, msg: str): | ||||
|         #msg = 'The username and token cannot be used together.' | ||||
|         # msg = 'The username and token cannot be used together.' | ||||
|         super().__init__(msg) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -162,3 +162,10 @@ class RepositoryAlreadyExists(ResourceAlreadyExists): | |||
|     def __init__(self): | ||||
|         msg = 'The requested repository already exists.' | ||||
|         super().__init__(msg) | ||||
| 
 | ||||
| 
 | ||||
| class FileAlreadyExists(ResourceAlreadyExists): | ||||
|     def __init__(self, msg: Optional[str] = None): | ||||
|         if msg is None: | ||||
|             msg = 'The requested file already exists.' | ||||
|         super().__init__(msg) | ||||
|  |  | |||
|  | @ -1,20 +1,24 @@ | |||
| # == http.py ==# | ||||
| 
 | ||||
| from __future__ import annotations | ||||
| from asyncio.base_subprocess import ReadSubprocessPipeProto | ||||
| from base64 import b64encode | ||||
| 
 | ||||
| import json | ||||
| import re | ||||
| from datetime import datetime | ||||
| from types import SimpleNamespace | ||||
| from typing import Any, Dict, NamedTuple, Optional, Type, Tuple, Union, List | ||||
| from typing_extensions import TypeAlias | ||||
| from typing import Any, Dict, Literal, NamedTuple, Optional, Type, Tuple, Union, List | ||||
| from typing_extensions import TypeAlias, reveal_type | ||||
| import platform | ||||
| 
 | ||||
| import aiohttp | ||||
| 
 | ||||
| from .exceptions import * | ||||
| from .exceptions import GistNotFound, RepositoryAlreadyExists, MissingPermissions | ||||
| from .objects import User, Gist, Repository, File | ||||
| from .exceptions import FileAlreadyExists | ||||
| from .exceptions import ResourceAlreadyExists | ||||
| from .objects import User, Gist, Repository, File, bytes_to_b64 | ||||
| from .urls import * | ||||
| from . import __version__ | ||||
| 
 | ||||
|  | @ -238,7 +242,7 @@ class http: | |||
|         result = await self.session.delete(REPO_URL.format(owner, repo_name)) | ||||
|         if 204 <= result.status <= 299: | ||||
|             return 'Successfully deleted repository.' | ||||
|         if result.status == 403: #type: ignore | ||||
|         if result.status == 403:  # type: ignore | ||||
|             raise MissingPermissions | ||||
|         raise RepositoryNotFound | ||||
| 
 | ||||
|  | @ -252,7 +256,7 @@ class http: | |||
|         raise GistNotFound | ||||
| 
 | ||||
|     async def get_org(self, org_name: str) -> Dict[str, Union[str, int]]: | ||||
|         """Returns an org's public data in JSON format.""" #type: ignore | ||||
|         """Returns an org's public data in JSON format."""  # type: ignore | ||||
|         result = await self.session.get(ORG_URL.format(org_name)) | ||||
|         if 200 <= result.status <= 299: | ||||
|             return await result.json() | ||||
|  | @ -300,3 +304,23 @@ class http: | |||
|         if result.status == 401: | ||||
|             raise NoAuthProvided | ||||
|         raise RepositoryAlreadyExists | ||||
| 
 | ||||
|     async def add_file(self, owner: str, repo_name: str, filename: str, content: str, message: str, branch: str): | ||||
|         """Adds a file to the given repo.""" | ||||
| 
 | ||||
|         data = { | ||||
|             'content': bytes_to_b64(content=content), | ||||
|             'message': message, | ||||
|             'branch': branch, | ||||
|         } | ||||
| 
 | ||||
|         result = await self.session.put(ADD_FILE_URL.format(owner, repo_name, filename), data=json.dumps(data)) | ||||
|         if 200 <= result.status <= 299: | ||||
|             return await result.json() | ||||
|         if result.status == 401: | ||||
|             raise NoAuthProvided | ||||
|         if result.status == 409: | ||||
|             raise FileAlreadyExists | ||||
|         if result.status == 422: | ||||
|             raise FileAlreadyExists('This file exists, and can only be edited.') | ||||
|         return await result.json(), result.status | ||||
|  |  | |||
|  | @ -1,7 +1,11 @@ | |||
| # == objects.py ==# | ||||
| from __future__ import annotations | ||||
| from base64 import b64encode | ||||
| import json | ||||
| 
 | ||||
| from typing import TYPE_CHECKING, Any, Optional, Tuple, Union, Dict, List | ||||
| from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple, Union, Dict, List | ||||
| 
 | ||||
| import aiohttp | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     from .http import http | ||||
|  | @ -35,8 +39,13 @@ def repr_dt(_datetime: datetime) -> str: | |||
|     return _datetime.strftime(r'%d-%m-%Y, %H:%M:%S') | ||||
| 
 | ||||
| 
 | ||||
| def bytes_to_b64(content) -> str: | ||||
|     return b64encode(content.encode('utf-8')).decode('ascii') | ||||
| 
 | ||||
| 
 | ||||
| class APIObject: | ||||
|     """Top level class for objects created from the API""" | ||||
| 
 | ||||
|     __slots__: Tuple[str, ...] = ('_response', '_http') | ||||
| 
 | ||||
|     def __init__(self, response: Dict[str, Any], _http: http) -> None: | ||||
|  | @ -88,7 +97,7 @@ class _BaseUser(APIObject): | |||
| 
 | ||||
| class User(_BaseUser): | ||||
|     """Representation of a user object on Github. | ||||
|      | ||||
| 
 | ||||
|     Attributes | ||||
|     ---------- | ||||
|     login: :class:`str` | ||||
|  | @ -102,6 +111,7 @@ class User(_BaseUser): | |||
|     created_at: :class:`datetime.datetime` | ||||
|         The time of creation of the user. | ||||
|     """ | ||||
| 
 | ||||
|     __slots__ = ( | ||||
|         'login', | ||||
|         'id', | ||||
|  | @ -130,7 +140,6 @@ class User(_BaseUser): | |||
|         return f'<{self.__class__.__name__} login: {self.login!r}, id: {self.id}, created_at: {self.created_at}>' | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class PartialUser(_BaseUser): | ||||
|     __slots__ = ( | ||||
|         'site_admin', | ||||
|  | @ -158,7 +167,7 @@ class PartialUser(_BaseUser): | |||
| 
 | ||||
| class Repository(APIObject): | ||||
|     """Representation of a repository on Github. | ||||
|      | ||||
| 
 | ||||
|     Attributes | ||||
|     ---------- | ||||
|     id: :class:`int` | ||||
|  | @ -182,6 +191,7 @@ class Repository(APIObject): | |||
|     default_branch: :class:`str` | ||||
|         The name of the default branch of the repository. | ||||
|     """ | ||||
| 
 | ||||
|     if TYPE_CHECKING: | ||||
|         id: int | ||||
|         name: str | ||||
|  | @ -198,7 +208,6 @@ class Repository(APIObject): | |||
|         'disabled', | ||||
|         'updated_at', | ||||
|         'open_issues_count', | ||||
|         'default_branch', | ||||
|         'clone_url', | ||||
|         'stargazers_count', | ||||
|         'watchers_count', | ||||
|  | @ -253,14 +262,30 @@ class Repository(APIObject): | |||
|     def forks(self) -> int: | ||||
|         return self._response.get('forks') | ||||
| 
 | ||||
|     @property | ||||
|     def default_branch(self) -> str: | ||||
|         """:class:`str`: The default branch of the repository.""" | ||||
|         return self._response.get('default_branch') | ||||
| 
 | ||||
|     async def delete(self) -> None: | ||||
|         """Deletes the repository.""" | ||||
|         return await self._http.delete_repo(self.owner.name, self.name,) #type: ignore | ||||
|         return await self._http.delete_repo( | ||||
|             self.owner.name,  # type: ignore this shit is not my fault | ||||
|             self.name, | ||||
|         )  # type: ignore | ||||
| 
 | ||||
|     async def add_file(self, filename: str, message: str, content: str, branch: Optional[str] = None) -> None: | ||||
|         """Adds a file to the repository.""" | ||||
| 
 | ||||
|         if branch is None: | ||||
|             branch = self.default_branch | ||||
| 
 | ||||
|         return await self._http.add_file(owner=self.owner.name, repo_name=self.name, filename=filename, content=content, message=message, branch=branch)  # type: ignore | ||||
| 
 | ||||
| 
 | ||||
| class Issue(APIObject): | ||||
|     """Representation of an issue on Github. | ||||
|      | ||||
| 
 | ||||
|     Attributes | ||||
|     ---------- | ||||
|     id: :class:`int` | ||||
|  | @ -278,6 +303,7 @@ class Issue(APIObject): | |||
|     closed_by: Optional[Union[:class:`PartialUser`, :class:`User`]] | ||||
|         The user the issue was closed by, if applicable. | ||||
|     """ | ||||
| 
 | ||||
|     __slots__ = ( | ||||
|         'id', | ||||
|         'title', | ||||
|  | @ -328,16 +354,17 @@ class Issue(APIObject): | |||
| 
 | ||||
| class File: | ||||
|     """A wrapper around files and in-memory file-like objects. | ||||
|      | ||||
| 
 | ||||
|     Parameters | ||||
|     ---------- | ||||
|     fp: Union[:class:`str`, :class:`io.StringIO`] | ||||
|     fp: Union[:class:`str`, :class:`io.StringIO`, :class:`io.BytesIO`] | ||||
|         The filepath or StringIO representing a file to upload. | ||||
|         If providing a StringIO instance, a filename shuold also be provided to the file. | ||||
|     filename: :class:`str` | ||||
|         An override to the file's name, encouraged to provide this if using a StringIO instance. | ||||
|     """ | ||||
|     def __init__(self, fp: Union[str, io.StringIO], filename: str = 'DefaultFilename.txt') -> None: | ||||
| 
 | ||||
|     def __init__(self, fp: Union[str, io.StringIO, io.BytesIO], filename: str = 'DefaultFilename.txt') -> None: | ||||
|         self.fp = fp | ||||
|         self.filename = filename | ||||
| 
 | ||||
|  | @ -346,12 +373,10 @@ class File: | |||
|             if os.path.exists(self.fp): | ||||
|                 with open(self.fp) as fp: | ||||
|                     data = fp.read() | ||||
| 
 | ||||
|                 return data | ||||
| 
 | ||||
|             return self.fp | ||||
|         elif isinstance(self.fp, io.BytesIO): | ||||
|             return self.fp.read() | ||||
|             return self.fp.read().decode('utf-8') | ||||
|         elif isinstance(self.fp, io.StringIO):  # type: ignore | ||||
|             return self.fp.getvalue() | ||||
| 
 | ||||
|  | @ -360,7 +385,7 @@ class File: | |||
| 
 | ||||
| class Gist(APIObject): | ||||
|     """Representation of a gist on Github. | ||||
|      | ||||
| 
 | ||||
|     Attributes | ||||
|     ---------- | ||||
|     id: :class:`int` | ||||
|  | @ -376,6 +401,7 @@ class Gist(APIObject): | |||
|     created_at: :class:`datetime.datetime` | ||||
|         The time the gist was created at. | ||||
|     """ | ||||
| 
 | ||||
|     __slots__ = ( | ||||
|         'id', | ||||
|         'html_url', | ||||
|  | @ -430,7 +456,7 @@ class Gist(APIObject): | |||
| 
 | ||||
| class Organization(APIObject): | ||||
|     """Representation of an organization in the API. | ||||
|      | ||||
| 
 | ||||
|     Attributes | ||||
|     ---------- | ||||
|     login: :class:`str` | ||||
|  | @ -444,6 +470,7 @@ class Organization(APIObject): | |||
|     avatar_url: :class:`str` | ||||
|         The url of the organization's avatar. | ||||
|     """ | ||||
| 
 | ||||
|     __slots__ = ( | ||||
|         'login', | ||||
|         'id', | ||||
|  |  | |||
|  | @ -28,6 +28,10 @@ REPOS_URL = BASE_URL + '/repos/{0}'  # repos of a user | |||
| 
 | ||||
| REPO_URL = BASE_URL + '/repos/{0}/{1}'  # a specific repo | ||||
| 
 | ||||
| ADD_FILE_URL = BASE_URL + '/repos/{}/{}/contents/{}' | ||||
| 
 | ||||
| ADD_FILE_BRANCH = BASE_URL + '' | ||||
| 
 | ||||
| REPO_ISSUE_URL = REPO_URL + '/issues/{2}'  # a specific issue | ||||
| 
 | ||||
| # == gist urls ==# | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 VarMonke
						VarMonke