Skip to content
110 changes: 106 additions & 4 deletions qbreader/_api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@

import warnings
from enum import Enum, EnumType
from typing import Iterable, Optional, Union
from typing import Iterable, Optional, Union, Tuple

from qbreader.types import (
Category,
Difficulty,
Category,
Subcategory,
AlternateSubcategory,
UnnormalizedCategory,
UnnormalizedDifficulty,
UnnormalizedSubcategory,
UnnormalizedAlternateSubcategory,
)


Expand Down Expand Up @@ -97,9 +99,109 @@ def normalize_cat(unnormalized_cats: UnnormalizedCategory):
return normalize_enumlike(unnormalized_cats, Category)


def normalize_subcat(unnormalized_subcats: UnnormalizedSubcategory):
def normalize_subcat(unnormalized_subcats: UnnormalizedCategory):
"""Normalize a single or list of subcategories to a comma separated string."""
return normalize_enumlike(unnormalized_subcats, Subcategory)
return normalize_enumlike(unnormalized_subcats, Category)


def category_correspondence(
typed_alt_subcat: AlternateSubcategory,
) -> Tuple[Category, Subcategory]:
if typed_alt_subcat in [
AlternateSubcategory.ASTRONOMY,
AlternateSubcategory.COMPUTER_SCIENCE,
AlternateSubcategory.MATH,
AlternateSubcategory.EARTH_SCIENCE,
AlternateSubcategory.ENGINEERING,
AlternateSubcategory.MISC_SCIENCE,
]:
return (None, Subcategory.OTHER_SCIENCE)

if typed_alt_subcat in [
AlternateSubcategory.ARCHITECTURE,
AlternateSubcategory.DANCE,
AlternateSubcategory.FILM,
AlternateSubcategory.JAZZ,
AlternateSubcategory.OPERA,
AlternateSubcategory.PHOTOGRAPHY,
AlternateSubcategory.MISC_ARTS,
]:
return (None, Subcategory.OTHER_FINE_ARTS)

if typed_alt_subcat in [
AlternateSubcategory.ANTHROPOLOGY,
AlternateSubcategory.ECONOMICS,
AlternateSubcategory.LINGUISTICS,
AlternateSubcategory.PSYCHOLOGY,
AlternateSubcategory.SOCIOLOGY,
AlternateSubcategory.OTHER_SOCIAL_SCIENCE,
]:
return (None, Subcategory.SOCIAL_SCIENCE)

if typed_alt_subcat in [
AlternateSubcategory.DRAMA,
AlternateSubcategory.LONG_FICTION,
AlternateSubcategory.POETRY,
AlternateSubcategory.SHORT_FICTION,
AlternateSubcategory.MISC_LITERATURE,
]:
return (Category.LITERATURE, None)


def normalize_cats(
unnormalized_cats: UnnormalizedCategory,
unnormalized_subcats: UnnormalizedSubcategory,
unnormalized_alt_subcats: UnnormalizedAlternateSubcategory,
) -> Tuple[Category, Subcategory, AlternateSubcategory]:
"""
Normalize a single or list of categories, subcategories, and alternate_subcategories
to their corresponding comma-separated strings, taking into account categories and
subcategories that must be added for the alternate_subcategories to work.
"""

typed_alt_subcats: list[AlternateSubcategory] = []

if isinstance(unnormalized_alt_subcats, str):
typed_alt_subcats.append(AlternateSubcategory(unnormalized_alt_subcats))
elif isinstance(unnormalized_alt_subcats, Iterable):
for alt_subcat in unnormalized_alt_subcats:
typed_alt_subcats.append(AlternateSubcategory(alt_subcat))

to_be_pushed_cats: list[Category] = []
to_be_pushed_subcats: list[Subcategory] = []

for alt_subcat in typed_alt_subcats:
cat, subcat = category_correspondence(alt_subcat)
if cat:
to_be_pushed_cats.append(cat)
if subcat:
to_be_pushed_subcats.append(subcat)

final_cats = []
if unnormalized_cats is None:
final_cats = to_be_pushed_cats
elif isinstance(unnormalized_cats, str):
final_cats = [Category(unnormalized_cats), *to_be_pushed_cats]
elif isinstance(unnormalized_cats, Iterable):
for subcat in unnormalized_cats:
final_cats.append(Subcategory(subcat))
final_cats.append(*to_be_pushed_cats)

final_subcats = []
if unnormalized_subcats is None:
final_subcats = to_be_pushed_subcats
elif isinstance(unnormalized_subcats, str):
final_subcats = [Subcategory(unnormalized_subcats), *to_be_pushed_subcats]
elif isinstance(unnormalized_subcats, Iterable):
for subcat in unnormalized_subcats:
final_subcats.append(Subcategory(subcat))
final_subcats.append(*to_be_pushed_subcats)

return (
normalize_enumlike(final_cats, Category),
normalize_enumlike(final_subcats, Subcategory),
normalize_enumlike(typed_alt_subcats, AlternateSubcategory),
)


def prune_none(params: dict) -> dict:
Expand Down
57 changes: 47 additions & 10 deletions qbreader/asynchronous.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
UnnormalizedCategory,
UnnormalizedDifficulty,
UnnormalizedSubcategory,
UnnormalizedAlternateSubcategory,
Year,
)

Expand Down Expand Up @@ -75,6 +76,7 @@ async def query(
difficulties: UnnormalizedDifficulty = None,
categories: UnnormalizedCategory = None,
subcategories: UnnormalizedSubcategory = None,
alternate_subcategories: UnnormalizedAlternateSubcategory = None,
maxReturnLength: Optional[int] = 25,
tossupPagination: Optional[int] = 1,
bonusPagination: Optional[int] = 1,
Expand Down Expand Up @@ -114,6 +116,10 @@ class type.
The subcategories to search for. Can be a single or an array of
`Subcategory` enums or strings. The API does not check for consistency
between categories and subcategories.
alternate_subcategories : qbreader.types.UnnormalizedAlternateSubcategory, optional
The alternate subcategories to search for. Can be a single or an array of
`AlternateSubcategory` enums or strings. The API does not check for
consistency between categories and subcategories
maxReturnLength : int, default = 25
The maximum number of questions to return.
tossupPagination : int, default = 1
Expand Down Expand Up @@ -179,6 +185,12 @@ class type.

url = BASE_URL + "/query"

(
normalized_categories,
normalized_subcategories,
normalized_alternate_subcategories,
) = api_utils.normalize_cats(categories, subcategories, alternate_subcategories)

data = {
"questionType": questionType,
"searchType": searchType,
Expand All @@ -190,8 +202,9 @@ class type.
"randomize": api_utils.normalize_bool(randomize),
"setName": setName,
"difficulties": api_utils.normalize_diff(difficulties),
"categories": api_utils.normalize_cat(categories),
"subcategories": api_utils.normalize_subcat(subcategories),
"categories": normalized_categories,
"subcategories": normalized_subcategories,
"alternateSubcategories": normalized_alternate_subcategories,
"maxReturnLength": maxReturnLength,
"tossupPagination": tossupPagination,
"bonusPagination": bonusPagination,
Expand All @@ -210,6 +223,7 @@ async def random_tossup(
difficulties: UnnormalizedDifficulty = None,
categories: UnnormalizedCategory = None,
subcategories: UnnormalizedSubcategory = None,
alternate_subcategories: UnnormalizedAlternateSubcategory = None,
number: int = 1,
min_year: int = Year.MIN_YEAR,
max_year: int = Year.CURRENT_YEAR,
Expand All @@ -230,6 +244,10 @@ async def random_tossup(
The subcategories to search for. Can be a single or an array of
`Subcategory` enums or strings. The API does not check for consistency
between categories and subcategories.
alternate_subcategories : qbreader.types.UnnormalizedAlternateSubcategory, optional
The alternate subcategories to search for. Can be a single or an array of
`AlternateSubcategory` enums or strings. The API does not check for
consistency between categories and subcategories
number : int, default = 1
The number of tossups to return.
min_year : int, default = Year.MIN_YEAR
Expand Down Expand Up @@ -258,13 +276,20 @@ async def random_tossup(

url = BASE_URL + "/random-tossup"

(
normalized_categories,
normalized_subcategories,
normalized_alternate_subcategories,
) = api_utils.normalize_cats(categories, subcategories, alternate_subcategories)

data = {
"difficulties": api_utils.normalize_diff(difficulties),
"categories": api_utils.normalize_cat(categories),
"subcategories": api_utils.normalize_subcat(subcategories),
"categories": normalized_categories,
"subcategories": normalized_subcategories,
"alternateSubcategories": normalized_alternate_subcategories,
"number": number,
"min_year": min_year,
"max_year": max_year,
"minYear": min_year,
"maxYear": max_year,
}
data = api_utils.prune_none(data)

Expand All @@ -280,6 +305,7 @@ async def random_bonus(
difficulties: UnnormalizedDifficulty = None,
categories: UnnormalizedCategory = None,
subcategories: UnnormalizedSubcategory = None,
alternate_subcategories: UnnormalizedAlternateSubcategory = None,
number: int = 1,
min_year: int = Year.MIN_YEAR,
max_year: int = Year.CURRENT_YEAR,
Expand All @@ -301,6 +327,10 @@ async def random_bonus(
The subcategories to search for. Can be a single or an array of
`Subcategory` enums or strings. The API does not check for consistency
between categories and subcategories.
alternate_subcategories: qbreaader.types.UnnormalizedAlternateSubcategory, optional
The alternates subcategories to search for. Can be a single or an array of
`AlternateSubcategory` enum variants or strings. The API does not check for consistency
between categories, subcategories, and alternate subcategories.
number : int, default = 1
The number of bonuses to return.
min_year : int, default = Year.MIN_YEAR
Expand Down Expand Up @@ -337,13 +367,20 @@ async def random_bonus(

url = BASE_URL + "/random-bonus"

(
normalized_categories,
normalized_subcategories,
normalized_alternate_subcategories,
) = api_utils.normalize_cats(categories, subcategories, alternate_subcategories)

data = {
"difficulties": api_utils.normalize_diff(difficulties),
"categories": api_utils.normalize_cat(categories),
"subcategories": api_utils.normalize_subcat(subcategories),
"categories": normalized_categories,
"subcategories": normalized_subcategories,
"alternateSubcategories": normalized_alternate_subcategories,
"number": number,
"min_year": min_year,
"max_year": max_year,
"minYear": min_year,
"maxYear": max_year,
}
data = api_utils.prune_none(data)

Expand Down
Loading
Loading