mirror of
https://github.com/RGBCube/GitHubWrapper
synced 2025-05-18 06:55:09 +00:00
Adding files to repos
This commit is contained in:
parent
72bd647c9b
commit
3d49dd9246
5 changed files with 102 additions and 37 deletions
|
@ -59,6 +59,7 @@ class GHClient:
|
||||||
token: Optional[:class:`str`]
|
token: Optional[:class:`str`]
|
||||||
The authenticated Client's token, if applicable.
|
The authenticated Client's token, if applicable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
has_started: bool = False
|
has_started: bool = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -109,7 +110,7 @@ class GHClient:
|
||||||
raise Exception('HTTP Session doesn\'t exist') from exc
|
raise Exception('HTTP Session doesn\'t exist') from exc
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<{self.__class__.__name__} has_auth={bool(self.__auth)}>'
|
return f'<Client has_auth={bool(self.__auth)}>'
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def check_limits(self, as_dict: Literal[True] = True) -> Dict[str, Union[str, int]]:
|
def check_limits(self, as_dict: Literal[True] = True) -> Dict[str, Union[str, int]]:
|
||||||
|
@ -238,7 +239,7 @@ class GHClient:
|
||||||
repo: :class:`str`
|
repo: :class:`str`
|
||||||
The name of the repository to fetch.
|
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:
|
async def get_issue(self, *, owner: str, repo: str, issue: int) -> Issue:
|
||||||
""":class:`Issue`: Fetch a Github Issue from it's name.
|
""":class:`Issue`: Fetch a Github Issue from it's name.
|
||||||
|
@ -252,7 +253,7 @@ class GHClient:
|
||||||
issue: :class:`int`
|
issue: :class:`int`
|
||||||
The ID of the issue to fetch.
|
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(
|
async def create_repo(
|
||||||
self,
|
self,
|
||||||
|
@ -318,7 +319,9 @@ class GHClient:
|
||||||
"""
|
"""
|
||||||
return Gist(await self.http.get_gist(gist), self.http)
|
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.
|
"""Creates a Gist with the given files, requires authorisation.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# == exceptions.py ==#
|
# == exceptions.py ==#
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from aiohttp import ClientResponse
|
from aiohttp import ClientResponse
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class InvalidAuthCombination(ClientException):
|
||||||
"""Raised when the username and token are both provided."""
|
"""Raised when the username and token are both provided."""
|
||||||
|
|
||||||
def __init__(self, msg: str):
|
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)
|
super().__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,3 +162,10 @@ class RepositoryAlreadyExists(ResourceAlreadyExists):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
msg = 'The requested repository already exists.'
|
msg = 'The requested repository already exists.'
|
||||||
super().__init__(msg)
|
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 ==#
|
# == http.py ==#
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from asyncio.base_subprocess import ReadSubprocessPipeProto
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from typing import Any, Dict, NamedTuple, Optional, Type, Tuple, Union, List
|
from typing import Any, Dict, Literal, NamedTuple, Optional, Type, Tuple, Union, List
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias, reveal_type
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .exceptions import GistNotFound, RepositoryAlreadyExists, MissingPermissions
|
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 .urls import *
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
|
@ -238,7 +242,7 @@ class http:
|
||||||
result = await self.session.delete(REPO_URL.format(owner, repo_name))
|
result = await self.session.delete(REPO_URL.format(owner, repo_name))
|
||||||
if 204 <= result.status <= 299:
|
if 204 <= result.status <= 299:
|
||||||
return 'Successfully deleted repository.'
|
return 'Successfully deleted repository.'
|
||||||
if result.status == 403: #type: ignore
|
if result.status == 403: # type: ignore
|
||||||
raise MissingPermissions
|
raise MissingPermissions
|
||||||
raise RepositoryNotFound
|
raise RepositoryNotFound
|
||||||
|
|
||||||
|
@ -252,7 +256,7 @@ class http:
|
||||||
raise GistNotFound
|
raise GistNotFound
|
||||||
|
|
||||||
async def get_org(self, org_name: str) -> Dict[str, Union[str, int]]:
|
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))
|
result = await self.session.get(ORG_URL.format(org_name))
|
||||||
if 200 <= result.status <= 299:
|
if 200 <= result.status <= 299:
|
||||||
return await result.json()
|
return await result.json()
|
||||||
|
@ -300,3 +304,23 @@ class http:
|
||||||
if result.status == 401:
|
if result.status == 401:
|
||||||
raise NoAuthProvided
|
raise NoAuthProvided
|
||||||
raise RepositoryAlreadyExists
|
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 ==#
|
# == objects.py ==#
|
||||||
from __future__ import annotations
|
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:
|
if TYPE_CHECKING:
|
||||||
from .http import http
|
from .http import http
|
||||||
|
@ -35,8 +39,13 @@ def repr_dt(_datetime: datetime) -> str:
|
||||||
return _datetime.strftime(r'%d-%m-%Y, %H:%M:%S')
|
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:
|
class APIObject:
|
||||||
"""Top level class for objects created from the API"""
|
"""Top level class for objects created from the API"""
|
||||||
|
|
||||||
__slots__: Tuple[str, ...] = ('_response', '_http')
|
__slots__: Tuple[str, ...] = ('_response', '_http')
|
||||||
|
|
||||||
def __init__(self, response: Dict[str, Any], _http: http) -> None:
|
def __init__(self, response: Dict[str, Any], _http: http) -> None:
|
||||||
|
@ -102,6 +111,7 @@ class User(_BaseUser):
|
||||||
created_at: :class:`datetime.datetime`
|
created_at: :class:`datetime.datetime`
|
||||||
The time of creation of the user.
|
The time of creation of the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'login',
|
'login',
|
||||||
'id',
|
'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}>'
|
return f'<{self.__class__.__name__} login: {self.login!r}, id: {self.id}, created_at: {self.created_at}>'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PartialUser(_BaseUser):
|
class PartialUser(_BaseUser):
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'site_admin',
|
'site_admin',
|
||||||
|
@ -182,6 +191,7 @@ class Repository(APIObject):
|
||||||
default_branch: :class:`str`
|
default_branch: :class:`str`
|
||||||
The name of the default branch of the repository.
|
The name of the default branch of the repository.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
|
@ -198,7 +208,6 @@ class Repository(APIObject):
|
||||||
'disabled',
|
'disabled',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
'open_issues_count',
|
'open_issues_count',
|
||||||
'default_branch',
|
|
||||||
'clone_url',
|
'clone_url',
|
||||||
'stargazers_count',
|
'stargazers_count',
|
||||||
'watchers_count',
|
'watchers_count',
|
||||||
|
@ -253,9 +262,25 @@ class Repository(APIObject):
|
||||||
def forks(self) -> int:
|
def forks(self) -> int:
|
||||||
return self._response.get('forks')
|
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:
|
async def delete(self) -> None:
|
||||||
"""Deletes the repository."""
|
"""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):
|
class Issue(APIObject):
|
||||||
|
@ -278,6 +303,7 @@ class Issue(APIObject):
|
||||||
closed_by: Optional[Union[:class:`PartialUser`, :class:`User`]]
|
closed_by: Optional[Union[:class:`PartialUser`, :class:`User`]]
|
||||||
The user the issue was closed by, if applicable.
|
The user the issue was closed by, if applicable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'id',
|
'id',
|
||||||
'title',
|
'title',
|
||||||
|
@ -331,13 +357,14 @@ class File:
|
||||||
|
|
||||||
Parameters
|
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.
|
The filepath or StringIO representing a file to upload.
|
||||||
If providing a StringIO instance, a filename shuold also be provided to the file.
|
If providing a StringIO instance, a filename shuold also be provided to the file.
|
||||||
filename: :class:`str`
|
filename: :class:`str`
|
||||||
An override to the file's name, encouraged to provide this if using a StringIO instance.
|
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.fp = fp
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
|
|
||||||
|
@ -346,12 +373,10 @@ class File:
|
||||||
if os.path.exists(self.fp):
|
if os.path.exists(self.fp):
|
||||||
with open(self.fp) as fp:
|
with open(self.fp) as fp:
|
||||||
data = fp.read()
|
data = fp.read()
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return self.fp
|
return self.fp
|
||||||
elif isinstance(self.fp, io.BytesIO):
|
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
|
elif isinstance(self.fp, io.StringIO): # type: ignore
|
||||||
return self.fp.getvalue()
|
return self.fp.getvalue()
|
||||||
|
|
||||||
|
@ -376,6 +401,7 @@ class Gist(APIObject):
|
||||||
created_at: :class:`datetime.datetime`
|
created_at: :class:`datetime.datetime`
|
||||||
The time the gist was created at.
|
The time the gist was created at.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'id',
|
'id',
|
||||||
'html_url',
|
'html_url',
|
||||||
|
@ -444,6 +470,7 @@ class Organization(APIObject):
|
||||||
avatar_url: :class:`str`
|
avatar_url: :class:`str`
|
||||||
The url of the organization's avatar.
|
The url of the organization's avatar.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'login',
|
'login',
|
||||||
'id',
|
'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
|
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
|
REPO_ISSUE_URL = REPO_URL + '/issues/{2}' # a specific issue
|
||||||
|
|
||||||
# == gist urls ==#
|
# == gist urls ==#
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue