From 7e8b612737b49f0699256b292ef79e8bc344fd44 Mon Sep 17 00:00:00 2001
From: PredaaA <46051820+PredaaA@users.noreply.github.com>
Date: Mon, 12 Jun 2023 15:18:25 +0200
Subject: [PATCH] Start working on v2
---
nekosbest/__init__.py | 2 +-
nekosbest/client.py | 62 ++++++++++----
nekosbest/errors.py | 8 ++
nekosbest/http.py | 48 +++++++----
nekosbest/models.py | 182 ++++++++++++++++++++++++++++++++----------
nekosbest/types.py | 6 +-
setup.cfg | 2 +-
7 files changed, 230 insertions(+), 80 deletions(-)
diff --git a/nekosbest/__init__.py b/nekosbest/__init__.py
index ca3f7ed..0e1dabd 100644
--- a/nekosbest/__init__.py
+++ b/nekosbest/__init__.py
@@ -16,7 +16,7 @@
along with this program. If not, see .
"""
-__version__ = "1.1.10"
+__version__ = "2.0.0a"
__author__ = "PredaaA"
__copyright__ = "Copyright 2021-present PredaaA"
diff --git a/nekosbest/client.py b/nekosbest/client.py
index 74a3518..2ba9fe6 100644
--- a/nekosbest/client.py
+++ b/nekosbest/client.py
@@ -16,41 +16,73 @@
along with this program. If not, see .
"""
-from typing import List, Union
+from typing import List, Optional
+from .errors import InvalidAmount, UnknownCategory
from .http import HttpClient
-from .models import CATEGORIES, Result
+from .models import Categories, CategoryEndpoint, Result
+
+# TODO: Add Ruff
class Client:
"""Client to make requests to nekos.best API."""
- def __init__(self):
- self.http = HttpClient()
+ def __init__(self) -> None:
+ self.http: HttpClient = HttpClient()
- async def get_image(self, category: str, amount: int = 1) -> List[Result]:
- """
- |coro|
+ async def close(self) -> None:
+ """Closes the client."""
+ await self.http.session.close()
- Returns an image URL of a specific category.
+ async def fetch(self, category: Optional[Categories] = None, amount: int = 1) -> List[Result]:
+ """Returns one or multiple images URLs of a specific category along with their metadata.
Parameters
----------
- category: str
+ category: Optional[Categories]
The category of image you want to get.
+ If not specified, it will return a random image.
+ Defaults to None which therefore will be a random image.
amount: int
The amount of images. Must be between 1 and 20.
+ Defaults to 1.
Returns
-------
List[Result]
"""
- if not category in CATEGORIES:
- raise ValueError(
- f"This isn't a valid category. It must be one of the following: {', '.join(CATEGORIES)}."
+ if category is None:
+ category = Categories.random()
+
+ if not Categories.is_valid(category):
+ raise UnknownCategory(
+ f"This isn't a valid category. It must be one of the following: {', '.join(Categories.__members__)}."
)
if not 1 <= amount <= 20:
- raise ValueError("Amount parameter must be between 1 and 20.")
+ raise InvalidAmount("Amount parameter must be between 1 and 20.")
+
+ endpoint = CategoryEndpoint(category, amount)
+ response = await self.http.get_results(endpoint)
+ return [Result(result) for result in response["results"]]
- data = await self.http.get(category, amount)
- return Result(data["results"][0]) if amount == 1 else [Result(r) for r in data["results"]]
+ async def fetch_file(
+ self, category: Optional[Categories] = None, amount: int = 1
+ ) -> List[Result]:
+ """Returns one or multiple images bytes of a specific category along with their metadata.
+
+ Parameters
+ ----------
+ category: Optional[Categories]
+ The category of image you want to get.
+ If not specified, it will return a random image.
+ Defaults to None which therefore will be a random image.
+ amount: int
+ The amount of images. Must be between 1 and 20.
+ Defaults to 1.
+
+ Returns
+ -------
+ List[Result]
+ """
+ # TODO
diff --git a/nekosbest/errors.py b/nekosbest/errors.py
index 7dad13d..0233d89 100644
--- a/nekosbest/errors.py
+++ b/nekosbest/errors.py
@@ -21,6 +21,14 @@ class NekosBestBaseError(Exception):
"""Base error of nekosbest client."""
+class UnknownCategory(NekosBestBaseError):
+ """Raised when an unknown category is passed."""
+
+
+class InvalidAmount(NekosBestBaseError):
+ """Raised when an invalid amount is passed."""
+
+
class NotFound(NekosBestBaseError):
"""Raised when API returns a 404."""
diff --git a/nekosbest/http.py b/nekosbest/http.py
index fe41f44..bf84d22 100644
--- a/nekosbest/http.py
+++ b/nekosbest/http.py
@@ -26,29 +26,43 @@
from nekosbest import __version__
from .errors import APIError, ClientError, NotFound
+from .models import CategoryEndpoint, SearchEndpoint
if TYPE_CHECKING:
from .types import ResultType
class HttpClient:
- BASE_URL = "https://nekos.best/api/v2"
- DEFAULT_HEADERS = {
- "User-Agent": f"nekosbest.py v{__version__} (Python/{(platform.python_version())[:3]} aiohttp/{aiohttp.__version__})"
- }
+ def __init__(self) -> None:
+ self.session: aiohttp.ClientSession = aiohttp.ClientSession(
+ headers={
+ "User-Agent": f"nekosbest.py v{__version__} (Python/{(platform.python_version())[:3]} aiohttp/{aiohttp.__version__})"
+ }
+ )
- async def get(self, endpoint: str, amount: int, **kwargs) -> ResultType:
+ async def get_results(self, endpoint: CategoryEndpoint) -> ResultType:
try:
- async with aiohttp.ClientSession() as session:
- async with session.get(
- f"{self.BASE_URL}/{endpoint}",
- params={"amount": amount} if amount > 1 else {},
- headers=self.DEFAULT_HEADERS,
- ) as resp:
- if resp.status == 404:
- raise NotFound()
- if resp.status != 200:
- raise APIError(resp.status)
- return await resp.json(content_type=None)
+ async with self.session.get(endpoint.formatted) as resp:
+ if resp.status == 404:
+ raise NotFound
+ if resp.status != 200:
+ raise APIError(resp.status)
+
+ return await resp.json()
+ except aiohttp.ClientConnectionError:
+ raise ClientError
+
+ async def get_search_results(self, endpoint: SearchEndpoint) -> ResultType:
+ ...
+ # TODO
+
+ async def get_file(self, image_url: str) -> bytes:
+ # Add a idiot proof check here
+ try:
+ async with self.session.get(image_url) as resp:
+ if resp.status != 200:
+ raise APIError(resp.status)
+
+ return await resp.read()
except aiohttp.ClientConnectionError:
- raise ClientError()
+ raise ClientError
diff --git a/nekosbest/models.py b/nekosbest/models.py
index 6a18eea..1b1a2a1 100644
--- a/nekosbest/models.py
+++ b/nekosbest/models.py
@@ -18,54 +18,148 @@
from __future__ import annotations
+import random
+from enum import Enum, IntEnum
from typing import TYPE_CHECKING, Optional
-
if TYPE_CHECKING:
from .types import ResultType
-CATEGORIES = (
- "baka",
- "bite",
- "blush",
- "bored",
- "cry",
- "cuddle",
- "dance",
- "facepalm",
- "feed",
- "happy",
- "highfive",
- "hug",
- "kiss",
- "laugh",
- "neko",
- "pat",
- "poke",
- "pout",
- "shrug",
- "slap",
- "sleep",
- "smile",
- "smug",
- "stare",
- "think",
- "thumbsup",
- "tickle",
- "wave",
- "wink",
- "kitsune",
- "waifu",
- "handhold",
- "kick",
- "punch",
- "shoot",
- "husbando",
- "yeet",
- "nod",
- "nom",
- "nope"
-)
+
+BASE_URL = "https://nekos.best/api/v2"
+
+
+class Categories(Enum):
+ """Represents the categories of images you can get from the API."""
+
+ # Static images
+ neko = "neko"
+ kitsune = "kitsune"
+ waifu = "waifu"
+ husbando = "husbando"
+
+ # Gifs
+ baka = "baka"
+ bite = "bite"
+ blush = "blush"
+ bored = "bored"
+ cry = "cry"
+ cuddle = "cuddle"
+ dance = "dance"
+ facepalm = "facepalm"
+ feed = "feed"
+ happy = "happy"
+ highfive = "highfive"
+ hug = "hug"
+ kiss = "kiss"
+ laugh = "laugh"
+ pat = "pat"
+ poke = "poke"
+ pout = "pout"
+ shrug = "shrug"
+ slap = "slap"
+ sleep = "sleep"
+ smile = "smile"
+ smug = "smug"
+ stare = "stare"
+ think = "think"
+ thumbsup = "thumbsup"
+ tickle = "tickle"
+ wave = "wave"
+ wink = "wink"
+ handhold = "handhold"
+ kick = "kick"
+ punch = "punch"
+ shoot = "shoot"
+ yeet = "yeet"
+ nod = "nod"
+ nom = "nom"
+ nope = "nope"
+
+ @classmethod
+ def is_valid(cls, category: str) -> bool:
+ """Checks if a category is valid.
+
+ Parameters
+ ----------
+ category: str
+ The category to check.
+
+ Returns
+ -------
+ bool
+ Whether the category is valid.
+ """
+
+ return category in cls.__members__
+
+ @classmethod
+ def random(cls) -> Categories:
+ """Gets a random category."""
+ return random.choice(list(cls))
+
+
+class SearchTypes(IntEnum):
+ """Represents the types of search you can do with the API."""
+
+ image = 1
+ gif = 2
+
+
+class CategoryEndpoint:
+ """Represents an category endpoint from the API.
+
+ Attributes
+ ----------
+ category: str
+ The category of the endpoint.
+ amount: int
+ The amount of images to get from the endpoint.
+ """
+
+ __slots__ = ("category", "amount")
+
+ def __init__(self, category: str, amount: int = 1):
+ self.category: str = category
+ self.amount: int = amount
+
+ def __repr__(self) -> str:
+ return f""
+
+ @property
+ def formatted(self) -> str:
+ return f"{BASE_URL}/{self.category}?amount={self.amount}"
+
+
+class SearchEndpoint:
+ """Represents an search endpoint from the API.
+
+ Attributes
+ ----------
+ query: str
+ The query to search for.
+ type: SearchTypes
+ The type of images to return.
+ category: str
+ The category of the images to return.
+ amount: int
+ The amount of images to get from the endpoint.
+ """
+
+ __slots__ = ("query", "type", "category", "amount")
+
+ def __init__(self, query: str, type: SearchTypes, category: str, amount: int = 1):
+ self.query: str = query
+ self.type: SearchTypes = type
+ self.category: str = category
+ self.amount: int = amount
+
+ def __repr__(self) -> str:
+ return f""
+
+ @property
+ def formatted(self) -> str:
+ return f"{BASE_URL}/search?query={self.query}&type={self.type}&category={self.category}&amount={self.amount}"
class Result:
@@ -75,6 +169,8 @@ class Result:
----------
url: Optional[str]
The image / gif URL.
+ data: Optional[bytes]
+ The image / gif bytes.
artist_href: Optional[str]
The artist's page URL.
artist_name: Optional[str]
diff --git a/nekosbest/types.py b/nekosbest/types.py
index 22c3a7b..b8df3a9 100644
--- a/nekosbest/types.py
+++ b/nekosbest/types.py
@@ -19,12 +19,12 @@
from typing import List, TypedDict, Union
-class RandomGifsType(TypedDict):
+class GifsType(TypedDict):
anime_name: str
url: str
-class NekoType(TypedDict):
+class ImagesType(TypedDict):
artist_href: str
artist_name: str
source_url: str
@@ -32,4 +32,4 @@ class NekoType(TypedDict):
class ResultType(TypedDict):
- results: List[Union[NekoType, RandomGifsType]]
+ results: List[Union[ImagesType, GifsType]]
diff --git a/setup.cfg b/setup.cfg
index c4bcf0a..ee79d72 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -32,7 +32,7 @@ include_package_data = False
packages = find_namespace:
python_requires = >=3.8
install_requires =
- aiohttp>=3.6.2,<3.8.1
+ aiohttp>=3.6.2,<3.9.0
[options.packages.find]
include =