Skip to content

Commit

Permalink
mystbin 4 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
AbstractUmbra committed May 10, 2024
1 parent 038f655 commit f11f583
Show file tree
Hide file tree
Showing 11 changed files with 441 additions and 549 deletions.
6 changes: 6 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
5 changes: 2 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
6.1.0
7.0.0

# mystbin.py Changelog

## Added

## Changes
- Modernise codebase and tooling with Ruff.
- Codebase now aligns with new mystbin.

## Fixes
- KeyError upon accessing late init key in api response.

### Notes
6 changes: 4 additions & 2 deletions mystbin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
DEALINGS IN THE SOFTWARE.
"""

__version__ = "6.1.0"
__version__ = "7.0.0"

from typing import Literal, NamedTuple

Expand All @@ -39,4 +39,6 @@ class VersionInfo(NamedTuple):
serial: int


version_info: VersionInfo = VersionInfo(major=6, minor=1, micro=0, releaselevel="final", serial=0)
version_info: VersionInfo = VersionInfo(major=7, minor=0, micro=0, releaselevel="final", serial=0)

del NamedTuple, Literal, VersionInfo
139 changes: 15 additions & 124 deletions mystbin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

from .http import HTTPClient
from .paste import File, Paste
from .utils import require_authentication

if TYPE_CHECKING:
import datetime
Expand All @@ -41,17 +40,14 @@
class Client:
__slots__ = ("http",)

def __init__(self, *, token: str | None = None, session: ClientSession | None = None) -> None:
self.http: HTTPClient = HTTPClient(token=token, session=session)
def __init__(self, *, session: ClientSession | None = None) -> None:
self.http: HTTPClient = HTTPClient(session=session)

async def __aenter__(self) -> Self:
return self

async def __aexit__(
self,
exc_cls: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None
self, exc_cls: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
) -> None:
await self.close()

Expand All @@ -62,52 +58,10 @@ async def close(self) -> None:
"""
await self.http.close()

@overload
async def create_paste(
self,
*,
filename: str,
content: str,
file: None = ...,
files: None = ...,
password: str | None = ...,
expires: datetime.datetime | None = ...,
) -> Paste:
...

@overload
async def create_paste(
self,
*,
filename: None = ...,
content: None = ...,
file: File,
files: None = ...,
password: str | None = ...,
expires: datetime.datetime | None = ...,
) -> Paste:
...

@overload
async def create_paste(
self,
*,
filename: None = ...,
content: None = ...,
file: None = ...,
files: Sequence[File],
password: str | None = ...,
expires: datetime.datetime | None = ...,
) -> Paste:
...

async def create_paste(
self,
*,
filename: str | None = None,
content: str | None = None,
file: File | None = None,
files: Sequence[File] | None = None,
password: str | None = None,
expires: datetime.datetime | None = None,
) -> Paste:
Expand All @@ -117,84 +71,41 @@ async def create_paste(
Parameters
-----------
filename: Optional[:class:`str`]
The filename to create.
content: Optional[:class:`str`]
The content of the file you are creating.
file: Optional[:class:`~mystbin.File`]
The pre-created file you wish to upload.
files: Optional[List[:class:`~mystbin.File`]]
files: List[:class:`~mystbin.File`]
The pre-creates list of files you wish to upload.
password: Optional[:class:`str`]
The password of the paste, if any.
expires: Optional[:class:`datetime.datetime`]
When the paste expires, if any.
Raises
-------
:exc:`ValueError`
A bad combinarion of singular and plural pastes were passed.
Returns
--------
:class:`mystbin.Paste`
The paste that was created.
..note::
Passing combinations of both singular and plural files is not supports and will raise an exception.
Internally the order of precesence is ``files`` > ``file`` > ``filename and content``.
"""
if (filename and content) and file:
raise ValueError("Cannot provide both `file` and `filename`/`content`")

resolved_files: Sequence[File] = []
if filename and content:
resolved_files = [File(filename=filename, content=content, attachment_url=None)]
elif file:
resolved_files = [file]
data = await self.http.create_paste(files=files, password=password, expires=expires)
return Paste.from_create(data, files=files)

if resolved_files and files:
raise ValueError("Cannot provide both singular and plural files.")

resolved_files = files or resolved_files

data = await self.http.create_paste(files=resolved_files, password=password, expires=expires)
return Paste.from_data(data)

@require_authentication
async def delete_paste(self, paste_id: str, /) -> None:
async def delete_paste(self, security_token: str, /) -> None:
"""|coro|
Delete a paste.
Parameters
-----------
paste_id: :class:`str`
The paste to delete.
security_token: :class:`str`
The security token relating to the paste to delete.
"""
await self.http.delete_pastes(paste_ids=[paste_id])

@require_authentication
async def delete_pastes(self, paste_ids: list[str], /) -> None:
"""|coro|
await self.http.delete_paste(security_token)

Delete multiple pastes.
Parameters
-----------
paste_ids: List[:class:`str`]
The pastes to delete.
"""
await self.http.delete_pastes(paste_ids=paste_ids)
@overload
async def get_paste(self, paste_id: str, *, password: str | None = ..., raw: Literal[False]) -> Paste: ...

@overload
async def get_paste(self, paste_id: str, *, password: str | None = ..., raw: Literal[False]) -> Paste:
...
async def get_paste(self, paste_id: str, *, password: str | None = ..., raw: Literal[True]) -> list[str]: ...

@overload
async def get_paste(self, paste_id: str, *, password: str | None = ..., raw: Literal[True]) -> list[str]:
...
async def get_paste(self, paste_id: str, *, password: str | None = ...) -> Paste: ...

async def get_paste(self, paste_id: str, *, password: str | None = None, raw: bool = False) -> Paste | list[str]:
"""|coro|
Expand All @@ -219,24 +130,4 @@ async def get_paste(self, paste_id: str, *, password: str | None = None, raw: bo
data = await self.http.get_paste(paste_id=paste_id, password=password)
if raw:
return [item["content"] for item in data["files"]]
return Paste.from_data(data)

@require_authentication
async def get_user_pastes(self, *, limit: int = 100) -> list[Paste]:
"""|coro|
Get all pastes belonging to the current authenticated user.
Parameters
-----------
limit: :class:`int`
The amount of pastes to fetch. Defaults to ``100``.
Returns
--------
List[:class:`Paste`]
The pastes that were fetched.
"""
data = await self.http.get_my_pastes(limit=limit)

return [Paste.from_data(x) for x in data]
return Paste.from_get(data)
1 change: 1 addition & 0 deletions mystbin/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""

from aiohttp import ClientResponse

__all__ = (
Expand Down
69 changes: 16 additions & 53 deletions mystbin/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@

from . import __version__
from .errors import APIException
from .utils import MISSING

if TYPE_CHECKING:
from types import TracebackType
Expand All @@ -54,7 +53,8 @@
Response = Coroutine[None, None, T]
MU = TypeVar("MU", bound="MaybeUnlock")
BE = TypeVar("BE", bound=BaseException)
from .types.responses import EditPasteResponse, PasteResponse
from .types.responses import CreatePasteResponse, GetPasteResponse


SupportedHTTPVerb = Literal["GET", "POST", "PUT", "DELETE", "PATCH"]

Expand Down Expand Up @@ -112,7 +112,7 @@ class Route:
"url",
)

API_BASE: ClassVar[str] = "https://api.mystb.in"
API_BASE: ClassVar[str] = "https://mystb.in/api"

def __init__(self, verb: SupportedHTTPVerb, path: str, **params: Any) -> None:
self.verb: SupportedHTTPVerb = verb
Expand All @@ -133,8 +133,7 @@ class HTTPClient:
"user_agent",
)

def __init__(self, *, token: str | None, session: aiohttp.ClientSession | None = None) -> None:
self._token: str | None = token
def __init__(self, *, session: aiohttp.ClientSession | None = None) -> None:
self._session: aiohttp.ClientSession | None = session
self._owns_session: bool = False
self._locks: weakref.WeakValueDictionary[str, asyncio.Lock] = weakref.WeakValueDictionary()
Expand All @@ -161,9 +160,6 @@ async def request(self, route: Route, **kwargs: Any) -> Any:
self._locks[bucket] = lock

headers = kwargs.pop("headers", {})

if self._token is not None:
headers["Authorization"] = f"Bearer {self._token}"
headers["User-Agent"] = self.user_agent

if "json" in kwargs:
Expand Down Expand Up @@ -222,6 +218,7 @@ async def request(self, route: Route, **kwargs: Any) -> Any:
await asyncio.sleep(sleep_)
continue

print(data)
assert isinstance(data, dict)
LOGGER.exception("Unhandled HTTP error occurred: %s -> %s", response.status, data)
raise APIException(
Expand All @@ -244,64 +241,30 @@ async def request(self, route: Route, **kwargs: Any) -> Any:
def create_paste(
self,
*,
file: File | None = None,
files: Sequence[File] | None = None,
files: Sequence[File],
password: str | None,
expires: datetime.datetime | None,
) -> Response[PasteResponse]:
route = Route("PUT", "/paste")
) -> Response[CreatePasteResponse]:
route = Route("POST", "/paste")

json_: dict[str, Any] = {}
if file:
json_["files"] = [file.to_dict()]
elif files:
json_["files"] = [f.to_dict() for f in files]
headers: dict[str, Any] = {}
json_["files"] = [f.to_dict() for f in files]

if password:
json_["password"] = password
headers["password"] = password
if expires:
json_["expires"] = _clean_dt(expires)

return self.request(route=route, json=json_)
return self.request(route=route, json=json_, headers=headers)

def delete_paste(self, *, paste_id: str) -> Response[None]:
return self.delete_pastes(paste_ids=[paste_id])
def delete_paste(self, security_token: str, /) -> Response[bool]:
route = Route("GET", "/security/delete/{security_token}", security_token=security_token)
return self.request(route)

def delete_pastes(self, *, paste_ids: Sequence[str]) -> Response[None]:
route = Route("DELETE", "/paste")
return self.request(route=route, json={"pastes": paste_ids})

def get_paste(self, *, paste_id: str, password: str | None) -> Response[PasteResponse]:
def get_paste(self, *, paste_id: str, password: str | None) -> Response[GetPasteResponse]:
route = Route("GET", "/paste/{paste_id}", paste_id=paste_id)

if password:
return self.request(route=route, params={"password": password})
return self.request(route=route)

def edit_paste(
self,
paste_id: str,
*,
new_content: str = MISSING,
new_filename: str = MISSING,
new_expires: datetime.datetime = MISSING,
) -> Response[EditPasteResponse]:
route = Route("PATCH", "/paste/{paste_id}", paste_id=paste_id)

json_: dict[str, Any] = {}

if new_content is not MISSING:
json_["new_content"] = new_content

if new_filename is not MISSING:
json_["new_filename"] = new_filename

if new_expires is not MISSING:
json_["new_expires"] = _clean_dt(new_expires)

return self.request(route, json=json_)

def get_my_pastes(self, *, limit: int) -> Response[Sequence[PasteResponse]]:
route = Route("GET", "/pastes/@me")

return self.request(route, params={limit: limit})
Loading

0 comments on commit f11f583

Please sign in to comment.