1
Fork 0
mirror of https://github.com/RGBCube/GitHubWrapper synced 2025-05-19 07:25:09 +00:00

refactor w/ black

This commit is contained in:
NextChai 2022-04-30 02:27:16 -04:00
parent cc3cde89c8
commit b474492637
8 changed files with 187 additions and 165 deletions

View file

@ -6,9 +6,7 @@ from collections import deque
from collections.abc import MutableMapping from collections.abc import MutableMapping
from typing import Any, Deque, Tuple, TypeVar from typing import Any, Deque, Tuple, TypeVar
__all__: Tuple[str, ...] = ( __all__: Tuple[str, ...] = ('ObjectCache',)
'ObjectCache',
)
K = TypeVar('K') K = TypeVar('K')
@ -50,6 +48,7 @@ class _BaseCache(MutableMapping[K, V]):
class ObjectCache(_BaseCache[K, V]): class ObjectCache(_BaseCache[K, V]):
"""This adjusts the typehints to reflect Github objects.""" """This adjusts the typehints to reflect Github objects."""
def __getitem__(self, __k: K) -> V: def __getitem__(self, __k: K) -> V:
index = self._lru_keys.index(__k) index = self._lru_keys.index(__k)
target = self._lru_keys[index] target = self._lru_keys[index]

View file

@ -26,106 +26,139 @@ __all__ = (
'RepositoryAlreadyExists', 'RepositoryAlreadyExists',
) )
class APIError(Exception): class APIError(Exception):
"""Base level exceptions raised by errors related to any API request or call.""" """Base level exceptions raised by errors related to any API request or call."""
pass pass
class HTTPException(Exception): class HTTPException(Exception):
"""Base level exceptions raised by errors related to HTTP requests.""" """Base level exceptions raised by errors related to HTTP requests."""
pass pass
class ClientException(Exception): class ClientException(Exception):
"""Base level exceptions raised by errors related to the client.""" """Base level exceptions raised by errors related to the client."""
pass pass
class ResourceNotFound(Exception): class ResourceNotFound(Exception):
"""Base level exceptions raised when a resource is not found.""" """Base level exceptions raised when a resource is not found."""
pass pass
class ResourceAlreadyExists(Exception): class ResourceAlreadyExists(Exception):
"""Base level exceptions raised when a resource already exists.""" """Base level exceptions raised when a resource already exists."""
pass pass
class Ratelimited(APIError): class Ratelimited(APIError):
"""Raised when the ratelimit from Github is reached or exceeded.""" """Raised when the ratelimit from Github is reached or exceeded."""
def __init__(self, reset_time: datetime.datetime): def __init__(self, reset_time: datetime.datetime):
formatted = reset_time.strftime(r"%H:%M:%S %A, %d %b") formatted = reset_time.strftime(r"%H:%M:%S %A, %d %b")
msg = "We're being ratelimited, wait until {}.\nAuthentication raises the ratelimit.".format(formatted) msg = "We're being ratelimited, wait until {}.\nAuthentication raises the ratelimit.".format(formatted)
super().__init__(msg) super().__init__(msg)
class WillExceedRatelimit(APIError): class WillExceedRatelimit(APIError):
"""Raised when the library predicts the call will exceed the ratelimit, will abort the call by default.""" """Raised when the library predicts the call will exceed the ratelimit, will abort the call by default."""
def __init__(self, response: ClientRequest, count: int): def __init__(self, response: ClientRequest, count: int):
msg = 'Performing this action will exceed the ratelimit, aborting.\n{} remaining available calls, calls to make: {}.' msg = 'Performing this action will exceed the ratelimit, aborting.\n{} remaining available calls, calls to make: {}.'
msg = msg.format(response.headers['X-RateLimit-Remaining'], count) msg = msg.format(response.headers['X-RateLimit-Remaining'], count)
super().__init__(msg) super().__init__(msg)
class NoAuthProvided(ClientException): class NoAuthProvided(ClientException):
"""Raised when no authentication is provided.""" """Raised when no authentication is provided."""
def __init__(self): def __init__(self):
msg = 'This action required autentication. Pass username and token kwargs to your client instance.' msg = 'This action required autentication. Pass username and token kwargs to your client instance.'
super().__init__(msg) super().__init__(msg)
class InvalidToken(ClientException): class InvalidToken(ClientException):
"""Raised when the token provided is invalid.""" """Raised when the token provided is invalid."""
def __init__(self): def __init__(self):
msg = 'The token provided is invalid.' msg = 'The token provided is invalid.'
super().__init__(msg) super().__init__(msg)
class InvalidAuthCombination(ClientException): class InvalidAuthCombination(ClientException):
"""Raised when the username and token are both provided.""" """Raised when the username and token are both provided."""
def __init__(self): def __init__(self):
msg = 'The username and token cannot be used together.' msg = 'The username and token cannot be used together.'
super().__init__(msg) super().__init__(msg)
class LoginFailure(ClientException): class LoginFailure(ClientException):
"""Raised when the login attempt fails.""" """Raised when the login attempt fails."""
def __init__(self): def __init__(self):
msg = 'The login attempt failed. Provide valid credentials.' msg = 'The login attempt failed. Provide valid credentials.'
super().__init__(msg) super().__init__(msg)
class NotStarted(ClientException): class NotStarted(ClientException):
"""Raised when the client is not started.""" """Raised when the client is not started."""
def __init__(self): def __init__(self):
msg = 'The client is not started. Run Github.GHClient() to start.' msg = 'The client is not started. Run Github.GHClient() to start.'
super().__init__(msg) super().__init__(msg)
class AlreadyStarted(ClientException): class AlreadyStarted(ClientException):
"""Raised when the client is already started.""" """Raised when the client is already started."""
def __init__(self): def __init__(self):
msg = 'The client is already started.' msg = 'The client is already started.'
super().__init__(msg) super().__init__(msg)
class MissingPermissions(APIError): class MissingPermissions(APIError):
def __init__(self): def __init__(self):
msg = 'You do not have permissions to perform this action.' msg = 'You do not have permissions to perform this action.'
super().__init__(msg) super().__init__(msg)
class UserNotFound(ResourceNotFound): class UserNotFound(ResourceNotFound):
def __init__(self): def __init__(self):
msg = 'The requested user was not found.' msg = 'The requested user was not found.'
super().__init__(msg) super().__init__(msg)
class RepositoryNotFound(ResourceNotFound): class RepositoryNotFound(ResourceNotFound):
def __init__(self): def __init__(self):
msg = 'The requested repository is either private or does not exist.' msg = 'The requested repository is either private or does not exist.'
super().__init__(msg) super().__init__(msg)
class IssueNotFound(ResourceNotFound): class IssueNotFound(ResourceNotFound):
def __init__(self): def __init__(self):
msg = 'The requested issue was not found.' msg = 'The requested issue was not found.'
super().__init__(msg) super().__init__(msg)
class OrganizationNotFound(ResourceNotFound): class OrganizationNotFound(ResourceNotFound):
def __init__(self): def __init__(self):
msg = 'The requested organization was not found.' msg = 'The requested organization was not found.'
super().__init__(msg) super().__init__(msg)
class GistNotFound(ResourceNotFound): class GistNotFound(ResourceNotFound):
def __init__(self): def __init__(self):
msg = 'The requested gist was not found.' msg = 'The requested gist was not found.'
super().__init__(msg) super().__init__(msg)
class RepositoryAlreadyExists(ResourceAlreadyExists): class RepositoryAlreadyExists(ResourceAlreadyExists):
def __init__(self): def __init__(self):
msg = 'The requested repository already exists.' msg = 'The requested repository already exists.'

View file

@ -29,19 +29,14 @@ Rates = NamedTuple('Rates', 'remaining', 'used', 'total', 'reset_when', 'last_re
# aiohttp request tracking / checking bits # aiohttp request tracking / checking bits
async def on_req_start( async def on_req_start(
session: aiohttp.ClientSession, session: aiohttp.ClientSession, ctx: SimpleNamespace, params: aiohttp.TraceRequestStartParams
ctx: SimpleNamespace,
params: aiohttp.TraceRequestStartParams
) -> None: ) -> None:
"""Before-request hook to make sure we don't overrun the ratelimit.""" """Before-request hook to make sure we don't overrun the ratelimit."""
# print(repr(session), repr(ctx), repr(params)) # print(repr(session), repr(ctx), repr(params))
pass pass
async def on_req_end(
session: aiohttp.ClientSession, async def on_req_end(session: aiohttp.ClientSession, ctx: SimpleNamespace, params: aiohttp.TraceRequestEndParams) -> None:
ctx: SimpleNamespace,
params: aiohttp.TraceRequestEndParams
) -> None:
"""After-request hook to adjust remaining requests on this time frame.""" """After-request hook to adjust remaining requests on this time frame."""
headers = params.response.headers headers = params.response.headers
@ -53,28 +48,30 @@ async def on_req_end(
session._rates = Rates(remaining, used, total, reset_when, last_req) session._rates = Rates(remaining, used, total, reset_when, last_req)
trace_config = aiohttp.TraceConfig() trace_config = aiohttp.TraceConfig()
trace_config.on_request_start.append(on_req_start) trace_config.on_request_start.append(on_req_start)
trace_config.on_request_end.append(on_req_end) trace_config.on_request_end.append(on_req_end)
APIType: TypeAlias = Union[User, Gist, Repository] APIType: TypeAlias = Union[User, Gist, Repository]
async def make_session(*, headers: Dict[str, str], authorization: Union[aiohttp.BasicAuth, None]) -> aiohttp.ClientSession: async def make_session(*, headers: Dict[str, str], authorization: Union[aiohttp.BasicAuth, None]) -> aiohttp.ClientSession:
"""This makes the ClientSession, attaching the trace config and ensuring a UA header is present.""" """This makes the ClientSession, attaching the trace config and ensuring a UA header is present."""
if not headers.get('User-Agent'): if not headers.get('User-Agent'):
headers['User-Agent'] = f'Github-API-Wrapper (https://github.com/VarMonke/Github-Api-Wrapper) @ {__version__} Python {platform.python_version()} aiohttp {aiohttp.__version__}' headers[
'User-Agent'
] = f'Github-API-Wrapper (https://github.com/VarMonke/Github-Api-Wrapper) @ {__version__} Python {platform.python_version()} aiohttp {aiohttp.__version__}'
session = aiohttp.ClientSession( session = aiohttp.ClientSession(auth=authorization, headers=headers, trace_configs=[trace_config])
auth=authorization,
headers=headers,
trace_configs=[trace_config]
)
session._rates = Rates('', '', '', '', '') session._rates = Rates('', '', '', '', '')
return session return session
# pagination # pagination
class Paginator: class Paginator:
"""This class handles pagination for objects like Repos and Orgs.""" """This class handles pagination for objects like Repos and Orgs."""
def __init__(self, session: aiohttp.ClientSession, response: aiohttp.ClientResponse, target_type: str): def __init__(self, session: aiohttp.ClientSession, response: aiohttp.ClientResponse, target_type: str):
self.session = session self.session = session
self.response = response self.response = response
@ -82,7 +79,7 @@ class Paginator:
types: Dict[str, Type[APIType]] = { # note: the type checker doesnt see subclasses like that types: Dict[str, Type[APIType]] = { # note: the type checker doesnt see subclasses like that
'user': User, 'user': User,
'gist': Gist, 'gist': Gist,
'repo' : Repository 'repo': Repository,
} }
self.target_type: Type[APIType] = types[target_type] self.target_type: Type[APIType] = types[target_type]
self.pages = {} self.pages = {}
@ -121,13 +118,17 @@ class Paginator:
raise WillExceedRatelimit(response, self.max_page) raise WillExceedRatelimit(response, self.max_page)
self.bare_link = groups[0][0][:-1] self.bare_link = groups[0][0][:-1]
# GithubUserData = GithubRepoData = GithubIssueData = GithubOrgData = GithubGistData = Dict[str, Union [str, int]] # GithubUserData = GithubRepoData = GithubIssueData = GithubOrgData = GithubGistData = Dict[str, Union [str, int]]
# Commentnig this out for now, consider using TypeDict's instead in the future <3 # Commentnig this out for now, consider using TypeDict's instead in the future <3
class http: class http:
def __init__(self, headers: Dict[str, Union[str, int]], auth: Union[aiohttp.BasicAuth, None]) -> None: def __init__(self, headers: Dict[str, Union[str, int]], auth: Union[aiohttp.BasicAuth, None]) -> None:
if not headers.get('User-Agent'): if not headers.get('User-Agent'):
headers['User-Agent'] = f'Github-API-Wrapper (https://github.com/VarMonke/Github-Api-Wrapper) @ {__version__} Python/{platform.python_version()} aiohttp/{aiohttp.__version__}' headers[
'User-Agent'
] = f'Github-API-Wrapper (https://github.com/VarMonke/Github-Api-Wrapper) @ {__version__} Python/{platform.python_version()} aiohttp/{aiohttp.__version__}'
self._rates = Rates('', '', '', '', '') self._rates = Rates('', '', '', '', '')
self.headers = headers self.headers = headers
@ -149,6 +150,7 @@ class http:
def update_headers(self, *, flush: bool = False, new_headers: Dict[str, Union[str, int]]): def update_headers(self, *, flush: bool = False, new_headers: Dict[str, Union[str, int]]):
if flush: if flush:
from multidict import CIMultiDict from multidict import CIMultiDict
self.session._default_headers = CIMultiDict(**new_headers) # type: ignore self.session._default_headers = CIMultiDict(**new_headers) # type: ignore
else: else:
self.session._default_headers = {**self.session.headers, **new_headers} # type: ignore self.session._default_headers = {**self.session.headers, **new_headers} # type: ignore
@ -158,11 +160,7 @@ class http:
headers = self.session.headers headers = self.session.headers
config = self.session.trace_configs config = self.session.trace_configs
await self.session.close() await self.session.close()
self.session = aiohttp.ClientSession( self.session = aiohttp.ClientSession(headers=headers, auth=auth, trace_configs=config)
headers=headers,
auth=auth,
trace_configs=config
)
def data(self): def data(self):
# return session headers and auth # return session headers and auth
@ -260,29 +258,26 @@ class http:
raise GistNotFound raise GistNotFound
async def create_gist( async def create_gist(
self, self, *, files: List['File'] = [], description: str = 'Default description', public: bool = False
*,
files: List['File'] = [],
description: str = 'Default description',
public: bool = False
) -> Dict[str, Union[str, int]]: ) -> Dict[str, Union[str, int]]:
data = {} data = {}
data['description'] = description data['description'] = description
data['public'] = public data['public'] = public
data['files'] = {} data['files'] = {}
for file in files: for file in files:
data['files'][file.filename] = { data['files'][file.filename] = {'filename': file.filename, 'content': file.read()} # helps editing the file
'filename' : file.filename, # helps editing the file
'content': file.read()
}
data = json.dumps(data) data = json.dumps(data)
_headers = dict(self.session.headers) _headers = dict(self.session.headers)
result = await self.session.post(CREATE_GIST_URL, data=data, headers=_headers|{'Accept': 'application/vnd.github.v3+json'}) result = await self.session.post(
CREATE_GIST_URL, data=data, headers=_headers | {'Accept': 'application/vnd.github.v3+json'}
)
if 201 == result.status: if 201 == result.status:
return await result.json() return await result.json()
raise InvalidToken raise InvalidToken
async def create_repo(self, name: str, description: str, public: bool, gitignore: Optional[str], license: Optional[str]) -> Dict[str, Union [str, int]]: async def create_repo(
self, name: str, description: str, public: bool, gitignore: Optional[str], license: Optional[str]
) -> Dict[str, Union[str, int]]:
"""Creates a repo for you with given data""" """Creates a repo for you with given data"""
data = { data = {
'name': name, 'name': name,

View file

@ -74,9 +74,7 @@ class GHClient:
return f'<{self.__class__.__name__} has_auth={bool(self._auth)}>' return f'<{self.__class__.__name__} has_auth={bool(self._auth)}>'
def __del__(self): def __del__(self):
asyncio.create_task( asyncio.create_task(self.http.session.close(), name='cleanup-session-github-api-wrapper')
self.http.session.close(), name='cleanup-session-github-api-wrapper'
)
@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]]:
@ -86,9 +84,7 @@ class GHClient:
def check_limits(self, as_dict: Literal[False] = False) -> List[str]: def check_limits(self, as_dict: Literal[False] = False) -> List[str]:
... ...
def check_limits( def check_limits(self, as_dict: bool = False) -> Union[Dict[str, Union[str, int]], List[str]]:
self, as_dict: bool = False
) -> Union[Dict[str, Union[str, int]], List[str]]:
if not self.has_started: if not self.has_started:
raise exceptions.NotStarted raise exceptions.NotStarted
if not as_dict: if not as_dict:
@ -132,13 +128,9 @@ class GHClient:
]: ]:
def wrapper( def wrapper(
func: Callable[Concatenate[Self, P], Awaitable[T]] func: Callable[Concatenate[Self, P], Awaitable[T]]
) -> Callable[ ) -> Callable[Concatenate[Self, P], Awaitable[Optional[Union[T, User, Repository]]]]:
Concatenate[Self, P], Awaitable[Optional[Union[T, User, Repository]]]
]:
@functools.wraps(func) @functools.wraps(func)
async def wrapped( async def wrapped(self: Self, *args: P.args, **kwargs: P.kwargs) -> Optional[Union[T, User, Repository]]:
self: Self, *args: P.args, **kwargs: P.kwargs
) -> Optional[Union[T, User, Repository]]:
if type == 'user': if type == 'user':
if obj := self._user_cache.get(kwargs.get('user')): if obj := self._user_cache.get(kwargs.get('user')):
return obj return obj
@ -176,9 +168,7 @@ class GHClient:
async def get_issue(self, *, owner: str, repo: str, issue: int) -> Issue: async def get_issue(self, *, owner: str, repo: str, issue: int) -> Issue:
"""Fetch a Github Issue from it's name.""" """Fetch a Github Issue from it's name."""
return Issue( return Issue(await self.http.get_repo_issue(owner, repo, issue), self.http)
await self.http.get_repo_issue(owner, repo, issue), self.http
)
async def create_repo( async def create_repo(
self, self,
@ -202,14 +192,10 @@ class GHClient:
"""Fetch a Github gist from it's id.""" """Fetch a Github gist from it's id."""
return Gist(await self.http.get_gist(gist), self.http) return Gist(await self.http.get_gist(gist), self.http)
async def create_gist( async def create_gist(self, *, files: List[File], description: str, public: bool) -> Gist:
self, *, files: List[File], description: str, public: bool
) -> Gist:
"""Creates a Gist with the given files, requires authorisation.""" """Creates a Gist with the given files, requires authorisation."""
return Gist( return Gist(
await self.http.create_gist( await self.http.create_gist(files=files, description=description, public=public),
files=files, description=description, public=public
),
self.http, self.http,
) )

View file

@ -23,21 +23,20 @@ __all__ = (
'Organization', 'Organization',
) )
def dt_formatter(time_str: str) -> Optional[datetime]: def dt_formatter(time_str: str) -> Optional[datetime]:
if time_str is not None: if time_str is not None:
return datetime.strptime(time_str, r"%Y-%m-%dT%H:%M:%SZ") return datetime.strptime(time_str, r"%Y-%m-%dT%H:%M:%SZ")
return None return None
def repr_dt(_datetime: datetime) -> str: 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')
class APIObject: class APIObject:
__slots__: Tuple[str, ...] = ( __slots__: Tuple[str, ...] = ('_response', '_http')
'_response',
'_http'
)
def __init__(self, response: Dict[str, Any], _http: http) -> None: def __init__(self, response: Dict[str, Any], _http: http) -> None:
self._http = _http self._http = _http
@ -49,11 +48,13 @@ class APIObject:
# === User stuff ===# # === User stuff ===#
class _BaseUser(APIObject): class _BaseUser(APIObject):
__slots__ = ( __slots__ = (
'login', 'login',
'id', 'id',
) )
def __init__(self, response: Dict[str, Any], _http: http) -> None: def __init__(self, response: Dict[str, Any], _http: http) -> None:
super().__init__(response, _http) super().__init__(response, _http)
self._http = _http self._http = _http
@ -88,6 +89,7 @@ class User(_BaseUser):
'following', 'following',
'created_at', 'created_at',
) )
def __init__(self, response: Dict[str, Any], _http: http) -> None: def __init__(self, response: Dict[str, Any], _http: http) -> None:
super().__init__(response, _http) super().__init__(response, _http)
tmp = self.__slots__ + _BaseUser.__slots__ tmp = self.__slots__ + _BaseUser.__slots__
@ -128,6 +130,7 @@ class PartialUser(_BaseUser):
# === Repository stuff ===# # === Repository stuff ===#
class Repository(APIObject): class Repository(APIObject):
if TYPE_CHECKING: if TYPE_CHECKING:
id: int id: int
@ -138,8 +141,7 @@ class Repository(APIObject):
'id', 'id',
'name', 'name',
'owner', 'owner',
'size' 'size' 'created_at',
'created_at',
'url', 'url',
'html_url', 'html_url',
'archived', 'archived',
@ -152,6 +154,7 @@ class Repository(APIObject):
'watchers_count', 'watchers_count',
'license', 'license',
) )
def __init__(self, response: Dict[str, Any], _http: http) -> None: def __init__(self, response: Dict[str, Any], _http: http) -> None:
super().__init__(response, _http) super().__init__(response, _http)
tmp = self.__slots__ + APIObject.__slots__ tmp = self.__slots__ + APIObject.__slots__
@ -198,6 +201,7 @@ class Repository(APIObject):
def forks(self) -> int: def forks(self) -> int:
return self._response.get('forks') return self._response.get('forks')
class Issue(APIObject): class Issue(APIObject):
__slots__ = ( __slots__ = (
'id', 'id',
@ -241,8 +245,10 @@ class Issue(APIObject):
def html_url(self) -> str: def html_url(self) -> str:
return self._response.get('html_url') return self._response.get('html_url')
# === Gist stuff ===# # === Gist stuff ===#
class File: class File:
def __init__(self, fp: Union[str, io.StringIO], filename: str = 'DefaultFilename.txt') -> None: def __init__(self, fp: Union[str, io.StringIO], filename: str = 'DefaultFilename.txt') -> None:
self.fp = fp self.fp = fp
@ -264,6 +270,7 @@ class File:
raise TypeError(f'Expected str, io.StringIO, or io.BytesIO, got {type(self.fp)}') raise TypeError(f'Expected str, io.StringIO, or io.BytesIO, got {type(self.fp)}')
class Gist(APIObject): class Gist(APIObject):
__slots__ = ( __slots__ = (
'id', 'id',
@ -275,6 +282,7 @@ class Gist(APIObject):
'created_at', 'created_at',
'truncated', 'truncated',
) )
def __init__(self, response: Dict[str, Any], _http: http) -> None: def __init__(self, response: Dict[str, Any], _http: http) -> None:
super().__init__(response, _http) super().__init__(response, _http)
tmp = self.__slots__ + APIObject.__slots__ tmp = self.__slots__ + APIObject.__slots__
@ -311,6 +319,7 @@ class Gist(APIObject):
# === Organization stuff ===# # === Organization stuff ===#
class Organization(APIObject): class Organization(APIObject):
__slots__ = ( __slots__ = (
'login', 'login',