Skip to content

Commit

Permalink
Add proper API types and typehints (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
scrazzz authored Jan 31, 2024
1 parent 0c8f42a commit d558361
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ docs/_build/
*.egg-info
build/*
dist/*
*.json
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.pyright]
include = [
"redgifs",
"redgifs/types"
]
exclude = [
"**/__pycache__",
"build",
"dist",
"docs",
]
reportUnnecessaryTypeIgnoreComment = "warning"
reportUnusedImport = "error"
pythonVersion = "3.8"
typeCheckingMode = "basic"
8 changes: 4 additions & 4 deletions redgifs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@

from typing import Literal, NamedTuple

from .api import API
from .http import ProxyAuth
from .enums import Order, Type
from .tags import Tags
from .api import *
from .http import *
from .enums import *
from .tags import *
from .errors import *
from .utils import *

Expand Down
18 changes: 11 additions & 7 deletions redgifs/aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

import io
import os
from typing import Any, Dict, List, Optional, Union
from datetime import datetime
from typing import TYPE_CHECKING, Any, List, Optional, Union

import aiohttp

Expand All @@ -37,6 +38,9 @@
from .parser import parse_feeds, parse_search, parse_creator, parse_creators, parse_search_image
from .models import GIF, URL, CreatorResult, Feeds, Image, SearchResult, CreatorsResult

if TYPE_CHECKING:
from redgifs.types.tags import TagInfo

class API:
def __init__(
self,
Expand All @@ -56,23 +60,23 @@ async def get_feeds(self) -> Feeds:
feeds = await self.http.get_feeds()
return parse_feeds(feeds)

async def get_tags(self) -> List[Dict[str, Union[str, int]]]:
async def get_tags(self) -> List[TagInfo]:
resp = await self.http.get_tags()
return resp['tags']

async def get_gif(self, id: str) -> GIF:
json: Dict[str, Any] = (await self.http.get_gif(id))['gif']
json = (await self.http.get_gif(id))['gif']
urls = json['urls']
return GIF(
id=json['id'],
create_date=json['createDate'],
create_date=datetime.utcfromtimestamp(json['createDate']),
has_audio=json['hasAudio'],
width=json['width'],
height=json['height'],
likes=json['likes'],
tags=json['tags'],
verified=json['verified'],
views=json['views'],
views=json.get('views'),
duration=json['duration'],
published=json['published'],
urls=URL(
Expand All @@ -89,7 +93,7 @@ async def get_gif(self, id: str) -> GIF:
avg_color=json['avgColor'],
)

async def get_trending_tags(self) -> List[Dict[str, Union[str, int]]]:
async def get_trending_tags(self) -> List[TagInfo]:
result = (await self.http.get_trending_tags())['tags']
return result

Expand All @@ -103,7 +107,7 @@ async def get_trending_images(self) -> List[Image]:

async def fetch_tag_suggestions(self, query: str) -> List[str]:
result = await self.http.get_tag_suggestions(query)
return [d['text'] for d in result]
return [d['text'] for d in result] # type: ignore - `get_tag_suggestions` isn't properly TypedDict'd so ignore the warning

async def search(
self,
Expand Down
2 changes: 2 additions & 0 deletions redgifs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
from .parser import parse_creator, parse_feeds, parse_search, parse_creators, parse_search_image
from .models import URL, GIF, CreatorResult, Feeds, Image, SearchResult, CreatorsResult

__all__ = ('API',)

class API:
"""The API Instance to get information from the RedGifs API.
Expand Down
2 changes: 2 additions & 0 deletions redgifs/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

from enum import Enum

__all__ = ('Order', 'Type')

class Order(Enum):
"""An enum representing the Order."""

Expand Down
2 changes: 2 additions & 0 deletions redgifs/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
DEALINGS IN THE SOFTWARE.
"""

from __future__ import annotations

import requests
import aiohttp

Expand Down
126 changes: 111 additions & 15 deletions redgifs/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import sys
import logging
from urllib.parse import quote
from typing import Any, ClassVar, Coroutine, Dict, List, NamedTuple, Optional, Union
from typing import TYPE_CHECKING, Any, ClassVar, Coroutine, Dict, List, NamedTuple, Optional, TypeVar, Union

import requests
import aiohttp
Expand All @@ -42,8 +42,19 @@
from .const import REDGIFS_THUMBS_RE
from .utils import strip_ip

__all__ = ('ProxyAuth',)

_log = logging.getLogger(__name__)

if TYPE_CHECKING:
from redgifs.types.gif import GetGifResponse, GifResponse, TrendingGifsResponse
from redgifs.types.image import ImageResponse, TrendingImagesResponse
from redgifs.types.feeds import FeedsResponse
from redgifs.types.tags import TagsResponse

T = TypeVar('T')
Response = Coroutine[Any, Any, T]

class Route:
BASE: ClassVar[str] = "https://api.redgifs.com"

Expand Down Expand Up @@ -110,11 +121,11 @@ def request(self, route: Route, **kwargs: Any) -> Any:
else:
raise HTTPException(r, js)

def close(self) -> Union[Coroutine[Any, Any, None], None]:
def close(self) -> None:
self.__session.close()

# TODO: Implement OAuth login support
def login(self, username: Optional[str] = None, password: Optional[str] = None) -> Union[Coroutine[Any, Any, None], None]:
def login(self, username: Optional[str] = None, password: Optional[str] = None) -> None:
if (username and password) is None:
temp_token = self.get_temp_token()['token']
self.headers['authorization'] = f'Bearer {temp_token}'
Expand Down Expand Up @@ -245,7 +256,7 @@ def dl(url: str) -> int:
raise TypeError(f'"{strip_ip(str_url)}" is not a valid RedGifs URL')


class AsyncHttp(HTTP):
class AsyncHttp:
def __init__(
self,
session: Optional[aiohttp.ClientSession] = None,
Expand All @@ -270,14 +281,6 @@ def __init__(
else:
self._proxy_auth = None

# TODO: Implement OAuth login support
async def login(self, username: Optional[str] = None, password: Optional[str] = None) -> None:
if (username and password) is None:
temp_token = await self.get_temp_token()
self.headers['authorization'] = f'Bearer {temp_token["token"]}'
else:
raise NotImplementedError

async def request(self, route: Route, **kwargs: Any) -> Any:
url: str = route.url
method: str = route.method
Expand All @@ -292,9 +295,105 @@ async def request(self, route: Route, **kwargs: Any) -> Any:
else:
raise HTTPException(resp, js)

async def close(self) -> None:
await self.__session.close()

async def get_temp_token(self):
return (await self.request(Route('GET', '/v2/auth/temporary')))

# TODO: Implement OAuth login support
async def login(self, username: Optional[str] = None, password: Optional[str] = None) -> None:
if (username and password) is None:
temp_token = await self.get_temp_token()
self.headers['authorization'] = f'Bearer {temp_token["token"]}'
else:
raise NotImplementedError

# GIF methods

def get_feeds(self) -> Response[FeedsResponse]:
return self.request(Route('GET', '/v2/home/feeds'))

def get_tags(self, **params: Any) -> Response[TagsResponse]:
return self.request(Route('GET', '/v1/tags'), **params)

def get_gif(self, id: str, **params: Any) -> Response[GetGifResponse]:
return self.request(Route('GET', '/v2/gifs/{id}', id=id), **params)

def search(self, search_text: str, order: Order, count: int, page: int, **params: Any) -> Response[GifResponse]:
r = Route(
'GET', '/v2/gifs/search?search_text={search_text}&order={order}&count={count}&page={page}',
search_text=search_text, order=order.value, count=count, page=page
)
return self.request(r, **params)

# User/Creator methods

def search_creators(
self,
page: int,
order: Order,
verified: bool,
tags: Optional[List[str]],
**params: Any
):
url = '/v1/creators/search?page={page}&order={order}'
if verified:
url += '&verified={verified}'
if tags:
url += '&tags={tags}'
r = Route(
'GET', url,
page=page, order=order.value, verified='y' if verified else 'n',
tags=','.join(t for t in tags)
)
return self.request(r, **params)
else:
r = Route(
'GET', url,
page=page, order=order.value, verified='y' if verified else 'n'
)
return self.request(r, **params)

def search_creator(self, username: str, page: int, count: int, order: Order, type: Type, **params):
r = Route(
'GET', '/v2/users/{username}/search?page={page}&count={count}&order={order}&type={type}',
username=username, page=page, count=count, order=order.value, type=type.value
)
return self.request(r, **params)

def get_trending_gifs(self) -> Response[TrendingGifsResponse]:
r = Route('GET', '/v2/explore/trending-gifs')
return self.request(r)

# Pic methods

def search_image(self, search_text: str, order: Order, count: int, page: int, **params: Any) -> Response[ImageResponse]:
r = Route(
'GET', '/v2/gifs/search?search_text={search_text}&order={order}&count={count}&page={page}&type=i',
search_text=search_text, order=order.value, count=count, page=page
)
return self.request(r, **params)

def get_trending_images(self) -> Response[TrendingImagesResponse]:
r = Route('GET', '/v2/explore/trending-images')
return self.request(r)

# Tag methods

def get_trending_tags(self) -> Response[TagsResponse]:
r = Route('GET', '/v2/search/trending')
return self.request(r)

def get_tag_suggestions(self, query: str) -> Response[List[Dict[str, Union[str, int]]]]:
r = Route(
'GET', '/v2/search/suggest?query={query}',
query=query
)
return self.request(r)

# download

async def download(self, url: str, fp: Union[str, bytes, os.PathLike[Any], io.BufferedIOBase]) -> int:
yarl_url = yarl.URL(url)
str_url = str(yarl_url)
Expand Down Expand Up @@ -327,6 +426,3 @@ async def dl(url: str) -> int:
return (await dl(hd_url))

raise TypeError(f'"{strip_ip(str_url)}" is not a valid RedGifs URL')

async def close(self) -> None:
await self.__session.close()
24 changes: 12 additions & 12 deletions redgifs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class URL:
The poster URL of the GIF.
thumbnail: :class:`str`
The thumbnail URL of the GIF.
vthumbnail: :class:`str`
vthumbnail: Optional[:class:`str`]
The vthumbnail URL of the GIF.
web_url: :class:`str`
The website URL of the GIF.
Expand All @@ -59,7 +59,7 @@ class URL:
hd: str
poster: str
thumbnail: str
vthumbnail: str
vthumbnail: Optional[str]
web_url: str
file_url: Optional[str]

Expand All @@ -86,9 +86,9 @@ class GIF:
A list of tags for the GIF.
verified: :class:`bool`
Wheather the publisher of the GIF is a verified creator.
views: :class:`int`
views: Optional[:class:`int`]
The amount of views for the GIF.
duration: :class:`int`
duration: :class:`float`
The GIF's duration in seconds.
published: :class:`bool`
Wheather the GIF is published.
Expand Down Expand Up @@ -116,8 +116,8 @@ class GIF:
likes: int
tags: List[str]
verified: bool
views: int
duration: int
views: Optional[int]
duration: float
published: bool
urls: URL
username: str
Expand Down Expand Up @@ -146,7 +146,7 @@ class Image:
A list of tags for the GIF.
verified: :class:`bool`
Wheather the publisher of the image is a verified creator.
views: :class:`int`
views: Optional[:class:`int`]
The amount of views the image has.
published: :class:`bool`
Wheather the image is published.
Expand All @@ -173,7 +173,7 @@ class Image:
likes: int
tags: List[str]
verified: bool
views: int
views: Optional[int]
published: bool
urls: URL
username: str
Expand Down Expand Up @@ -204,11 +204,11 @@ class User:
profile_url: :class:`str`
The user's "profile URL".
This is NOT the user's URL on ``redgifs.com``, see :py:attr:`url <redgifs.models.User.url>` for that.
published_collections: :class:`int`
published_collections: Optional[:class:`int`]
The user's amount of published collections.
published_gifs: :class:`int`
The user's amount of published GIFs.
status: :class:`str`
status: Optional[:class:`str`]
The user's status.
subscription: :class:`int`
Expand Down Expand Up @@ -245,9 +245,9 @@ class User:
name: str
profile_image_url: str
profile_url: str
published_collections: int
published_collections: Optional[int]
published_gifs: int
status: str
status: Optional[str]
subscription: int
url: str
username: str
Expand Down
Loading

0 comments on commit d558361

Please sign in to comment.