From 1789b2ce453f8d4308a904930ba114ef8294e1c6 Mon Sep 17 00:00:00 2001 From: ducklet Date: Sat, 18 May 2024 23:38:33 +0200 Subject: [PATCH] fix: encode query params for GQL request --- unwind/imdb.py | 25 ++++++++++++++++--------- unwind/models.py | 6 ++---- unwind/utils.py | 4 ++++ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/unwind/imdb.py b/unwind/imdb.py index cff1d68..0f46c24 100644 --- a/unwind/imdb.py +++ b/unwind/imdb.py @@ -12,6 +12,7 @@ import bs4 from . import db from .models import Movie, Rating, User from .request import adownload, asession, asoup_from_url, cache_path +from .utils import json_dump log = logging.getLogger(__name__) @@ -355,9 +356,12 @@ async def _load_ratings_page_legacy(url: str, soup: bs4.BeautifulSoup) -> _Ratin return page -async def load_and_store_ratings( - user_id: UserId, -) -> AsyncIterable[tuple[Rating, bool]]: +async def load_and_store_ratings(user_id: UserId) -> AsyncIterable[tuple[Rating, bool]]: + """Load user ratings from imdb.com and store them in our database. + + All loaded ratings are yielded together with the information whether each rating + was already present in our database. + """ async with db.new_connection() as conn: user = await db.get(conn, User, imdb_id=user_id) or User( imdb_id=user_id, name="", secret="" @@ -385,6 +389,7 @@ async def load_and_store_ratings( async def load_ratings(user_id: UserId) -> AsyncIterable[Rating]: + """Return all ratings for the given user from imdb.com.""" next_url = user_ratings_url(user_id) while next_url: @@ -443,13 +448,15 @@ async def load_top_250() -> list[MovieId]: qgl_api_url = "https://caching.graphql.imdb.com/" query = { "operationName": "Top250MoviesPagination", - "variables": {"first": 250, "locale": "en-US"}, - "extensions": { - "persistedQuery": { - "sha256Hash": "26114ee01d97e04f65d6c8c7212ae8b7888fa57ceed105450d1fce09df749b2d", - "version": 1, + "variables": json_dump({"first": 250, "locale": "en-US"}), + "extensions": json_dump( + { + "persistedQuery": { + "sha256Hash": "26114ee01d97e04f65d6c8c7212ae8b7888fa57ceed105450d1fce09df749b2d", + "version": 1, + } } - }, + ), } headers = { "accept": "application/graphql+json, application/json", diff --git a/unwind/models.py b/unwind/models.py index 4867599..a2ffffb 100644 --- a/unwind/models.py +++ b/unwind/models.py @@ -2,7 +2,6 @@ import json from dataclasses import dataclass, field from dataclasses import fields as _fields from datetime import datetime, timezone -from functools import partial from types import UnionType from typing import ( Annotated, @@ -26,6 +25,7 @@ from sqlalchemy import Column, ForeignKey, Index, Integer, String, Table from sqlalchemy.orm import registry from .types import ULID +from .utils import json_dump JSONScalar: TypeAlias = int | float | str | None type JSON = JSONScalar | list["JSON"] | dict[str, "JSON"] @@ -110,10 +110,8 @@ def optional_fields(o): yield f -json_dump = partial(json.dumps, separators=(",", ":")) - - def _id(x: T) -> T: + """Return the given argument, aka. the identity function.""" return x diff --git a/unwind/utils.py b/unwind/utils.py index 6ad4d32..d1733a8 100644 --- a/unwind/utils.py +++ b/unwind/utils.py @@ -1,8 +1,12 @@ import base64 import hashlib +import json import secrets +from functools import partial from typing import Any, TypedDict +json_dump = partial(json.dumps, separators=(",", ":")) + def b64encode(b: bytes) -> str: return base64.b64encode(b).decode().rstrip("=")