Skip to content

Commit

Permalink
Start adding types for API responses. Work on adding a seperate Async…
Browse files Browse the repository at this point in the history
…HTTP client with proper typehints
  • Loading branch information
scrazzz committed Jan 30, 2024
1 parent 0c8f42a commit 245e230
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 23 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
7 changes: 4 additions & 3 deletions redgifs/aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

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

import aiohttp
Expand Down Expand Up @@ -61,18 +62,18 @@ async def get_tags(self) -> List[Dict[str, Union[str, int]]]:
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 Down
123 changes: 108 additions & 15 deletions redgifs/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
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
import yarl

from redgifs.types.response import GifResponse, SearchResponse

from . import __version__
from .errors import HTTPException
from .enums import Order, Type
Expand All @@ -44,6 +46,10 @@

_log = logging.getLogger(__name__)

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

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

Expand Down Expand Up @@ -110,11 +116,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 +251,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 +276,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 +290,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):
return self.request(Route('GET', '/v2/home/feeds'))

def get_tags(self, **params: Any):
return self.request(Route('GET', '/v1/tags'), **params)

def get_gif(self, id: str, **params: Any) -> Response[GifResponse]:
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[SearchResponse]:
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):
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):
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):
r = Route('GET', '/v2/explore/trending-images')
return self.request(r)

# Tag methods

def get_trending_tags(self):
r = Route('GET', '/v2/search/trending')
return self.request(r)

def get_tag_suggestions(self, query: str):
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 @@ -324,9 +418,8 @@ async def dl(url: str) -> int:
if 'watch' in yarl_url.path:
id = yarl_url.path.strip('/watch/')
hd_url = (await self.get_gif(id))['gif']['urls']['hd']
# gif = await (await self.get_gif('id'))
# hd_url = gif['gif']['urls']['hd']
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()
8 changes: 4 additions & 4 deletions redgifs/models.py
Original file line number Diff line number Diff line change
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 @@ -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
4 changes: 3 additions & 1 deletion redgifs/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from datetime import datetime
from typing import Any, Dict

from redgifs.types.response import SearchResponse

from .enums import Type
from .utils import _gifs_iter, _images_iter, _users_iter, build_file_url, to_web_url
from .models import GIF, URL, CreatorResult, Feeds, Image, User, SearchResult, CreatorsResult
Expand Down Expand Up @@ -58,7 +60,7 @@ def parse_feeds(json: Dict[str, Any]) -> Feeds:
)

# For GIFs
def parse_search(searched_for: str, json: Dict[str, Any]) -> SearchResult:
def parse_search(searched_for: str, json: SearchResponse) -> SearchResult:
_log.debug('Using `parse_search` for: {searched_for}')
json_gifs = json['gifs']
users = json['users']
Expand Down
14 changes: 14 additions & 0 deletions redgifs/types/feeds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import List, TypedDict


class Feeds(TypedDict):
horizontalGifs: List[...]

Check failure on line 5 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

"..." is not allowed in this context
hotCreators: List[...]

Check failure on line 6 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

"..." is not allowed in this context
hotGifs: List[]

Check failure on line 7 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

Expected index or slice expression
hotImages: List[]

Check failure on line 8 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

Expected index or slice expression
longGifs: List[]

Check failure on line 9 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

Expected index or slice expression
newCreators: List[]

Check failure on line 10 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

Expected index or slice expression
soundGifs: List[]

Check failure on line 11 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

Expected index or slice expression
verifiedGifs: List[]

Check failure on line 12 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

Expected index or slice expression
verifiedImages: List[]

Check failure on line 13 in redgifs/types/feeds.py

View workflow job for this annotation

GitHub Actions / check 3.8

Expected index or slice expression
verticalGifs: List[]
50 changes: 50 additions & 0 deletions redgifs/types/gif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from typing import List, Optional, TypedDict

class UserInfo(TypedDict):
creationtime: int
description: str
followers: int
following: int
gifs: int
name: str
profileImageUrl: str
profileUrl: str
publishedCollections: Optional[int]
publishedGifs: int
status: Optional[str]
subscription: int
url: str
username: str
verified: bool
views: int
poster: Optional[str]
preview: Optional[str]
thumbnail: Optional[str]
likes: Optional[int]

class MediaInfo(TypedDict):
sd: str
hd: str
gif: str
poster: str
thumbnail: str
vthumbnail: str

class GifInfo(TypedDict):
id: str
client_id: Optional[str]
createDate: int
hasAudio: bool
width: int
height: int
likes: int
tags: List[str]
verified: bool
views: Optional[int]
duration: float
published: bool
type: int # Literal[1,2]
urls: MediaInfo
userName: str
avgColor: str
gallry: str
13 changes: 13 additions & 0 deletions redgifs/types/response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import List, TypedDict
from redgifs.types.gif import GifInfo, UserInfo

class GifResponse(TypedDict):
gif: GifInfo

class SearchResponse(TypedDict):
page: int
pages: int
total: int
gifs: List[GifInfo]
users: List[UserInfo]
tags: List[str]

0 comments on commit 245e230

Please sign in to comment.