From 87d887f738d450b6e85df9091137809c349ab6d7 Mon Sep 17 00:00:00 2001 From: Dylan Bowman Date: Sat, 8 Jun 2024 18:22:41 -0400 Subject: [PATCH 1/7] fixes types to fit new api --- qbreader/types.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qbreader/types.py b/qbreader/types.py index 27c4a22..58daf77 100644 --- a/qbreader/types.py +++ b/qbreader/types.py @@ -252,10 +252,10 @@ def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: answer=json["answer"], category=Category(json["category"]), subcategory=Subcategory(json["subcategory"]), - set=json["setName"], - year=json["setYear"], - packet_number=json["packetNumber"], - question_number=json["questionNumber"], + set=json["set"]["name"], + year=json["set"]["year"], + packet_number=json["packet"]["name"], + question_number=json["packet"]["number"], difficulty=Difficulty(str(json["difficulty"])), ) @@ -338,10 +338,10 @@ def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: answers=json["answers"], category=Category(json["category"]), subcategory=Subcategory(json["subcategory"]), - set=json["setName"], - year=json["setYear"], - packet_number=json["packetNumber"], - question_number=json["questionNumber"], + set=json["set"]["name"], + year=json["set"]["year"], + packet_number=json["packet"]["name"], + question_number=json["packet"]["number"], difficulty=Difficulty(str(json["difficulty"])), ) From 0158c5e5da223a9c18ba887680181dff6a29d6cc Mon Sep 17 00:00:00 2001 From: Dylan Bowman Date: Sat, 8 Jun 2024 18:25:37 -0400 Subject: [PATCH 2/7] fixes demo in readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a6b3dfa..03e4084 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ both asynchronous and synchronous interfaces to the API along with functionality ```py >>> from qbreader import Sync as qbr # synchronous interface ->>> tossup = qbr.random_tossup()[0] +>>> sync_client = qbr() +>>> tossup = sync_client.random_tossup()[0] >>> tossup.question 'Tim Peters wrote 19 “guiding principles” of this programming language, which include the maxim “Complex is better than complicated.” The “pandas” library was written for this language. Unicode string values had to be defined with a “u” in version 2 of this language. Libraries in this language include Tkinter, Tensorflow, (*) NumPy (“numb pie”) and SciPy (“sigh pie”). The framework Django was written in this language. This language uses “duck typing.” Variables in this language are often named “spam” and “eggs.” Guido van Rossum invented, for 10 points, what programming language named for a British comedy troupe?' >>> tossup.answer From 05424660ccb0305a327d7cf903bcc51c1a84e615 Mon Sep 17 00:00:00 2001 From: Dylan Bowman Date: Mon, 10 Jun 2024 23:44:30 -0400 Subject: [PATCH 3/7] adds Year as enum --- qbreader/asynchronous.py | 17 +++++++++-------- qbreader/synchronous.py | 19 +++++++++++-------- qbreader/types.py | 7 +++++++ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/qbreader/asynchronous.py b/qbreader/asynchronous.py index 812d62d..55cc767 100644 --- a/qbreader/asynchronous.py +++ b/qbreader/asynchronous.py @@ -19,6 +19,7 @@ UnnormalizedCategory, UnnormalizedDifficulty, UnnormalizedSubcategory, + Year, ) @@ -210,8 +211,8 @@ async def random_tossup( categories: UnnormalizedCategory = None, subcategories: UnnormalizedSubcategory = None, number: int = 1, - min_year: int = 2010, - max_year: int = 2023, + min_year: int = Year.MIN_YEAR, + max_year: int = Year.CURRENT_YEAR, ) -> tuple[Tossup, ...]: """Get random tossups from the database. @@ -231,9 +232,9 @@ async def random_tossup( between categories and subcategories. number : int, default = 1 The number of tossups to return. - min_year : int, default = 2010 + min_year : int, default = Year.MIN_YEAR The oldest year to search for. - max_year : int, default = 2023 + max_year : int, default = Year.CURRENT_YEAR The most recent year to search for. Returns @@ -280,8 +281,8 @@ async def random_bonus( categories: UnnormalizedCategory = None, subcategories: UnnormalizedSubcategory = None, number: int = 1, - min_year: int = 2010, - max_year: int = 2023, + min_year: int = Year.MIN_YEAR, + max_year: int = Year.CURRENT_YEAR, three_part_bonuses: bool = False, ) -> tuple[Bonus, ...]: """Get random bonuses from the database. @@ -302,9 +303,9 @@ async def random_bonus( between categories and subcategories. number : int, default = 1 The number of bonuses to return. - min_year : int, default = 2010 + min_year : int, default = Year.MIN_YEAR The oldest year to search for. - max_year : int, default = 2023 + max_year : int, default = Year.CURRENT_YEAR The most recent year to search for. three_part_bonuses : bool, default = False Whether to only return bonuses with 3 parts. diff --git a/qbreader/synchronous.py b/qbreader/synchronous.py index 54c985b..4b932c8 100644 --- a/qbreader/synchronous.py +++ b/qbreader/synchronous.py @@ -19,6 +19,7 @@ UnnormalizedCategory, UnnormalizedDifficulty, UnnormalizedSubcategory, + Year, ) @@ -175,8 +176,8 @@ def random_tossup( categories: UnnormalizedCategory = None, subcategories: UnnormalizedSubcategory = None, number: int = 1, - min_year: int = 2010, - max_year: int = 2023, + min_year: int = Year.MIN_YEAR, + max_year: int = Year.CURRENT_YEAR, ) -> tuple[Tossup, ...]: """Get random tossups from the database. @@ -196,9 +197,9 @@ def random_tossup( between categories and subcategories. number : int, default = 1 The number of tossups to return. - min_year : int, default = 2010 + min_year : int, default = Year.MIN_YEAR The oldest year to search for. - max_year : int, default = 2023 + max_year : int, default = Year.CURRENT_YEAR The most recent year to search for. Returns @@ -234,6 +235,8 @@ def random_tossup( response: requests.Response = requests.get(url, params=data) + print(response.json()) + if response.status_code != 200: raise Exception(str(response.status_code) + " bad request") @@ -245,8 +248,8 @@ def random_bonus( categories: UnnormalizedCategory = None, subcategories: UnnormalizedSubcategory = None, number: int = 1, - min_year: int = 2010, - max_year: int = 2023, + min_year: int = Year.MIN_YEAR, + max_year: int = Year.CURRENT_YEAR, three_part_bonuses: bool = False, ) -> tuple[Bonus, ...]: """Get random bonuses from the database. @@ -267,9 +270,9 @@ def random_bonus( between categories and subcategories. number : int, default = 1 The number of bonuses to return. - min_year : int, default = 2010 + min_year : int, default = Year.MIN_YEAR The oldest year to search for. - max_year : int, default = 2023 + max_year : int, default = Year.CURRENT_YEAR The most recent year to search for. three_part_bonuses : bool, default = False Whether to only return bonuses with 3 parts. diff --git a/qbreader/types.py b/qbreader/types.py index 58daf77..5032b1a 100644 --- a/qbreader/types.py +++ b/qbreader/types.py @@ -92,6 +92,13 @@ class Directive(enum.StrEnum): PROMPT = "prompt" +class Year(enum.IntEnum): + """Min/max year enum""" + + MIN_YEAR = 2010 + CURRENT_YEAR = 2024 + + class AnswerJudgement: """A judgement given by `api/check-answer`.""" From d6d08e72dacb05aca3058b18a983918a79cb774a Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Sat, 30 Nov 2024 15:14:50 -0600 Subject: [PATCH 4/7] use python3.11 only --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9e8f2bc..74e5d67 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiohttp" @@ -1458,5 +1458,5 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" -python-versions = "^3.11" -content-hash = "789e828d581093fa2d0a6bb185d3ee61e3776e362d089bb691b1a04b09bbe279" +python-versions = "~3.11" +content-hash = "469cc03b8ed58ce11896446c961cdbaf3ba0967bf3a784acb0a675ec5857228e" diff --git a/pyproject.toml b/pyproject.toml index ac70a42..ce215fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ ] [tool.poetry.dependencies] -python = "^3.11" +python = "~3.11" requests = "^2.31.0" aiohttp = "^3.8.4" From 6d578aa49a2924c308a50e4a3dce9659154b18ab Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Sat, 30 Nov 2024 16:11:47 -0600 Subject: [PATCH 5/7] conform types to new schema --- README.md | 32 ++++++- qbreader/synchronous.py | 2 - qbreader/types.py | 202 ++++++++++++++++++++++++++++------------ tests/__init__.py | 2 + tests/test_types.py | 132 +++++++++++++++++--------- 5 files changed, 265 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 03e4084..5d04b24 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,19 @@ `qbreader` is a Python wrapper to the qbreader API as well as a general quizbowl library. It provides both asynchronous and synchronous interfaces to the API along with functionality for representing questions. +## Tossup Example + ```py >>> from qbreader import Sync as qbr # synchronous interface >>> sync_client = qbr() >>> tossup = sync_client.random_tossup()[0] >>> tossup.question +'Tim Peters wrote 19 “guiding principles” of this programming language, which include the maxim “Complex is better than complicated.” The “pandas” library was written for this language. Unicode string values had to be defined with a “u” in version 2 of this language. Libraries in this language include Tkinter, Tensorflow, (*) NumPy (“numb pie”) and SciPy (“sigh pie”). The framework Django was written in this language. This language uses “duck typing.” Variables in this language are often named “spam” and “eggs.” Guido van Rossum invented, for 10 points, what programming language named for a British comedy troupe?' +>>> tossup.question_sanitized 'Tim Peters wrote 19 “guiding principles” of this programming language, which include the maxim “Complex is better than complicated.” The “pandas” library was written for this language. Unicode string values had to be defined with a “u” in version 2 of this language. Libraries in this language include Tkinter, Tensorflow, (*) NumPy (“numb pie”) and SciPy (“sigh pie”). The framework Django was written in this language. This language uses “duck typing.” Variables in this language are often named “spam” and “eggs.” Guido van Rossum invented, for 10 points, what programming language named for a British comedy troupe?' >>> tossup.answer +'Python' +>>> tossup.answer_sanitized 'Python' >>> tossup.category @@ -27,8 +33,30 @@ both asynchronous and synchronous interfaces to the API along with functionality >>> tossup.difficulty ->>> tossup.set +>>> tossup.set.name '2022 Prison Bowl' ->>> (tossup.packet_number, tossup.question_number) +>>> (tossup.packet.number, tossup.number) (4, 20) ``` + +## Bonus Example + +```py +>>> bonus = sync_client.random_bonus()[0] +>>> bonus.leadin +'The Curry–Howard isomorphism states that computer programs are directly equivalent to these mathematical constructs, which can be automated using the languages Lean or Rocq (“rock”). For 10 points each:' +>>> bonus.leadin_sanitized +'The Curry-Howard isomorphism states that computer programs are directly equivalent to these mathematical constructs, which can be automated using the languages Lean or Rocq ("rock"). For 10 points each:' +>>> bonus.parts +('Name these mathematical constructs that are used to formally demonstrate the truth of a mathematical statement.', 'According to the Curry–Howard isomorphism, these programming concepts correspond to individual propositions of a proof. One method of “inferring” these things in programming languages like Python is named for the duck test.', 'Haskell Curry also lends his name to “currying,” a common tool in functional programming languages that transforms a function into a sequence of functions each with a smaller value for this property. A description is acceptable.') +>>> bonus.parts_sanitized +('Name these mathematical constructs that are used to formally demonstrate the truth of a mathematical statement.', 'According to the Curry-Howard isomorphism, these programming concepts correspond to individual propositions of a proof. One method of "inferring" these things in programming languages like Python is named for the duck test.', 'Haskell Curry also lends his name to "currying," a common tool in functional programming languages that transforms a function into a sequence of functions each with a smaller value for this property. A description is acceptable.') +>>> bonus.answers +('mathematical proofs [or formal proofs or proofs of correctness; accept proof assistant or theorem prover or Rocq prover]', 'data types [accept type inference or duck typing]', 'arity [accept descriptions of the number of arguments or the number of parameters or the number of inputs of a function]') +>>> bonus.answers_sanitized +('mathematical proofs [or formal proofs or proofs of correctness; accept proof assistant or theorem prover or Rocq prover]', 'data types [accept type inference or duck typing]', 'arity [accept descriptions of the number of arguments or the number of parameters or the number of inputs of a function]') +>>> bonus.difficultyModifiers +('e', 'm', 'h') +>>> bonus.values +(10, 10, 10) +``` diff --git a/qbreader/synchronous.py b/qbreader/synchronous.py index 4b932c8..ae5b426 100644 --- a/qbreader/synchronous.py +++ b/qbreader/synchronous.py @@ -235,8 +235,6 @@ def random_tossup( response: requests.Response = requests.get(url, params=data) - print(response.json()) - if response.status_code != 200: raise Exception(str(response.status_code) + " bad request") diff --git a/qbreader/types.py b/qbreader/types.py index 5032b1a..a73d185 100644 --- a/qbreader/types.py +++ b/qbreader/types.py @@ -84,6 +84,14 @@ class Difficulty(enum.StrEnum): OPEN = "10" +class DifficultyModifier(enum.StrEnum): + """Difficulty modifier enum.""" + + EASY = "e" + MEDIUM = "m" + HARD = "h" + + class Directive(enum.StrEnum): """Directives given by `api/check-answer`.""" @@ -226,26 +234,26 @@ class Tossup: def __init__( self: Self, question: str, - formatted_answer: Optional[str], + question_sanitized: str, answer: str, + answer_sanitized: str, + difficulty: Difficulty, category: Category, subcategory: Subcategory, - set: str, - year: int, - packet_number: int, - question_number: int, - difficulty: Difficulty, + packet: PacketMetadata, + set: SetMetadata, + number: int, ): self.question: str = question - self.formatted_answer: str = formatted_answer if formatted_answer else answer + self.question_sanitized: str = question_sanitized self.answer: str = answer + self.answer_sanitized: str = answer_sanitized + self.difficulty: Difficulty = difficulty self.category: Category = category self.subcategory: Subcategory = subcategory - self.set: str = set - self.year: int = year - self.packet_number: int = packet_number - self.question_number: int = question_number - self.difficulty: Difficulty = difficulty + self.packet: PacketMetadata = packet + self.set: SetMetadata = set + self.number: int = number @classmethod def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: @@ -255,27 +263,27 @@ def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: """ return cls( question=json["question"], - formatted_answer=json.get("formatted_answer", json["answer"]), + question_sanitized=json["question_sanitized"], answer=json["answer"], + answer_sanitized=json["answer_sanitized"], + difficulty=Difficulty(str(json["difficulty"])), category=Category(json["category"]), subcategory=Subcategory(json["subcategory"]), - set=json["set"]["name"], - year=json["set"]["year"], - packet_number=json["packet"]["name"], - question_number=json["packet"]["number"], - difficulty=Difficulty(str(json["difficulty"])), + packet=PacketMetadata.from_json(json["packet"]), + set=SetMetadata.from_json(json["set"]), + number=json["number"], ) def check_answer_sync(self, givenAnswer: str) -> AnswerJudgement: """Check whether an answer is correct.""" - return AnswerJudgement.check_answer_sync(self.formatted_answer, givenAnswer) + return AnswerJudgement.check_answer_sync(self.answer, givenAnswer) async def check_answer_async( self, givenAnswer: str, session: aiohttp.ClientSession | None = None ) -> AnswerJudgement: """Asynchronously check whether an answer is correct.""" return await AnswerJudgement.check_answer_async( - self.formatted_answer, givenAnswer, session + self.answer, givenAnswer, session ) def __eq__(self, other: object) -> bool: @@ -285,15 +293,15 @@ def __eq__(self, other: object) -> bool: return ( self.question == other.question - and self.formatted_answer == other.formatted_answer + and self.question_sanitized == other.question_sanitized and self.answer == other.answer + and self.answer_sanitized == other.answer_sanitized + and self.difficulty == other.difficulty and self.category == other.category and self.subcategory == other.subcategory + and self.packet == other.packet and self.set == other.set - and self.year == other.year - and self.packet_number == other.packet_number - and self.question_number == other.question_number - and self.difficulty == other.difficulty + and self.number == other.number ) def __str__(self) -> str: @@ -307,30 +315,36 @@ class Bonus: def __init__( self: Self, leadin: str, + leadin_sanitized: str, parts: Sequence[str], - formatted_answers: Optional[Sequence[str]], + parts_sanitized: Sequence[str], answers: Sequence[str], + answers_sanitized: Sequence[str], + difficulty: Difficulty, category: Category, subcategory: Subcategory, - set: str, - year: int, - packet_number: int, - question_number: int, - difficulty: Difficulty, + set: SetMetadata, + packet: PacketMetadata, + number: int, + values: Optional[Sequence[int]] = None, + difficultyModifiers: Optional[Sequence[DifficultyModifier]] = None, ): self.leadin: str = leadin + self.leadin_sanitized: str = leadin_sanitized self.parts: tuple[str, ...] = tuple(parts) - self.formatted_answers: tuple[str, ...] = tuple( - formatted_answers if formatted_answers else answers - ) + self.parts_sanitized: tuple[str, ...] = tuple(parts_sanitized) self.answers: tuple[str, ...] = tuple(answers) + self.answers_sanitized: tuple[str, ...] = tuple(answers_sanitized) + self.difficulty: Difficulty = difficulty self.category: Category = category self.subcategory: Subcategory = subcategory - self.set: str = set - self.year: int = year - self.packet_number: int = packet_number - self.question_number: int = question_number - self.difficulty: Difficulty = difficulty + self.set: SetMetadata = set + self.packet: PacketMetadata = packet + self.number: int = number + self.values: Optional[tuple[int, ...]] = tuple(values) if values else None + self.difficultyModifiers: Optional[tuple[DifficultyModifier, ...]] = ( + tuple(difficultyModifiers) if difficultyModifiers else None + ) @classmethod def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: @@ -340,30 +354,31 @@ def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: """ return cls( leadin=json["leadin"], + leadin_sanitized=json["leadin_sanitized"], parts=json["parts"], - formatted_answers=json.get("formatted_answers", json["answers"]), + parts_sanitized=json["parts_sanitized"], answers=json["answers"], + answers_sanitized=json["answers_sanitized"], + difficulty=Difficulty(str(json["difficulty"])), category=Category(json["category"]), subcategory=Subcategory(json["subcategory"]), - set=json["set"]["name"], - year=json["set"]["year"], - packet_number=json["packet"]["name"], - question_number=json["packet"]["number"], - difficulty=Difficulty(str(json["difficulty"])), + set=SetMetadata.from_json(json["set"]), + packet=PacketMetadata.from_json(json["packet"]), + number=json["number"], + values=json.get("values", None), + difficultyModifiers=json.get("difficultyModifiers", None), ) def check_answer_sync(self, part: int, givenAnswer: str) -> AnswerJudgement: """Check whether an answer is correct.""" - return AnswerJudgement.check_answer_sync( - self.formatted_answers[part], givenAnswer - ) + return AnswerJudgement.check_answer_sync(self.answers[part], givenAnswer) async def check_answer_async( self, part: int, givenAnswer: str, session: aiohttp.ClientSession ) -> AnswerJudgement: """Asynchronously check whether an answer is correct.""" return await AnswerJudgement.check_answer_async( - self.formatted_answers[part], givenAnswer, session + self.answers[part], givenAnswer, session ) def __eq__(self, other: object) -> bool: @@ -373,16 +388,19 @@ def __eq__(self, other: object) -> bool: return ( self.leadin == other.leadin + and self.leadin_sanitized == other.leadin_sanitized and self.parts == other.parts - and self.formatted_answers == other.formatted_answers + and self.parts_sanitized == other.parts_sanitized and self.answers == other.answers + and self.answers_sanitized == other.answers_sanitized + and self.difficulty == other.difficulty and self.category == other.category and self.subcategory == other.subcategory and self.set == other.set - and self.year == other.year - and self.packet_number == other.packet_number - and self.question_number == other.question_number - and self.difficulty == other.difficulty + and self.packet == other.packet + and self.number == other.number + and self.values == other.values + and self.difficultyModifiers == other.difficultyModifiers ) def __str__(self) -> str: @@ -447,9 +465,9 @@ def __init__( ): self.tossups: tuple[Tossup, ...] = tuple(tossups) self.bonuses: tuple[Bonus, ...] = tuple(bonuses) - self.number: Optional[int] = number - self.name: Optional[str] = name if name else self.tossups[0].set - self.year: Optional[int] = year if year else self.tossups[0].year + self.number: Optional[int] = number if number else self.tossups[0].packet.number + self.name: Optional[str] = name if name else self.tossups[0].set.name + self.year: Optional[int] = year if year else self.tossups[0].set.year @classmethod def from_json( @@ -495,6 +513,76 @@ def __str__(self) -> str: ) +class PacketMetadata: + def __init__( + self: Self, + _id: str, + name: str, + number: int, + ): + self._id: str = _id + self.name: str = name + self.number: int = number + + @classmethod + def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: + return cls( + _id=json["_id"], + name=json["name"], + number=json["number"], + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PacketMetadata): + return NotImplemented + + return ( + self._id == other._id + and self.name == other.name + and self.number == other.number + ) + + def __str__(self) -> str: + return self.name + + +class SetMetadata: + def __init__( + self: Self, + _id: str, + name: str, + year: int, + standard: bool, + ): + self._id: str = _id + self.name: str = name + self.year: int = year + self.standard: bool = standard + + @classmethod + def from_json(cls: Type[Self], json: dict[str, Any]) -> Self: + return cls( + _id=json["_id"], + name=json["name"], + year=json["year"], + standard=json["standard"], + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SetMetadata): + return NotImplemented + + return ( + self._id == other._id + and self.name == other.name + and self.year == other.year + and self.standard == other.standard + ) + + def __str__(self) -> str: + return self.name + + QuestionType: TypeAlias = Union[ Literal["tossup", "bonus", "all"], Type[Tossup], Type[Bonus] ] diff --git a/tests/__init__.py b/tests/__init__.py index a06ba42..d22fa50 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -10,6 +10,8 @@ def check_internet_connection(): urlopen("https://www.qbreader.org") return True except Exception: + # you may want to check if you've installed SSL certificates + # https://stackoverflow.com/questions/44649449/brew-installation-of-python-3-6-1-ssl-certificate-verify-failed-certificate return diff --git a/tests/test_types.py b/tests/test_types.py index 703af05..7f0b661 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,31 +1,31 @@ """Test the types, classes, and structures used by the qbreader library.""" - import qbreader as qb -from qbreader.types import Bonus, Tossup +from qbreader.types import Bonus, Tossup, PacketMetadata, SetMetadata class TestTossup: """Test the Tossup class.""" tu_json = { - "_id": "64a0ccb31634f2d5eb7df02a", - "question": "This general class of devices could move between hypothetical castles via up and down escalators in a cycler. These devices can transfer their angular momentum to two “yo-yo” masses attached with cords in order to decrease their rotation rate. The Oberth effect describes how these devices gain kinetic energy more efficiently while at (*) periapsis. Hohmann transfers and bi-elliptic transfers are used to adjust the altitude of these devices. The trajectory-dependent delta-v budget of one of these devices can be reduced by using a slingshot maneuver. Station-keeping can help these objects remain geosynchronous. For 10 points, name this general class of devices used to collect astronomical data, such as Voyager 1.", # noqa: E501 - "formatted_answer": "spacecraft [or spaceships; accept space probes, satellites, or space stations, or space vehicles; prompt with rockets or thrusters with, “To what devices are rockets attached?”] (The first clue describes the Aldrin cycle, proposed by Buzz Aldrin.)", # noqa: E501 - "answer": "spacecraft [or spaceships; accept space probes, satellites, or space stations, or space vehicles; prompt with rockets or thrusters with, “To what devices are rockets attached?”] (The first clue describes the Aldrin cycle, proposed by Buzz Aldrin.)", # noqa: E501 + "_id": "64046cc6de59b8af97422da5", + "question": "Radiative power is inversely proportional to this quantity cubed, times 6-pi-epsilon, according to the Larmor formula. This quantity is in the numerator in the formula for the index of refraction. When a charged particle exceeds this quantity while in a medium, it produces Cherenkov radiation. This (*) quantity is equal to one divided by the square root of the product of the vacuum permittivity and permeability. This quantity is constant in all inertial reference frames. For 10 points, name this value symbolized c, that is about 30 million meters per second.", + "answer": "Speed of Light", "category": "Science", - "subcategory": "Other Science", - "packet": "64a0ccb31634f2d5eb7df01e", - "set": "64a0ccb31634f2d5eb7deed5", - "setName": "2023 MRNA", - "setYear": 2023, - "type": "tossup", - "packetNumber": 9, - "packetName": "09", - "questionNumber": 12, - "createdAt": "2023-07-02T01:02:43.628Z", - "updatedAt": "2023-07-02T01:03:31.712Z", - "difficulty": 7, + "subcategory": "Physics", + "packet": {"_id": "64046cc6de59b8af97422da2", "name": "03", "number": 3}, + "set": { + "_id": "64046cc6de59b8af97422d4f", + "name": "2017 WHAQ", + "year": 2017, + "standard": True, + }, + "createdAt": "2023-03-05T10:19:50.469Z", + "updatedAt": "2024-11-24T22:47:40.013Z", + "difficulty": 3, + "number": 3, + "answer_sanitized": "Speed of Light", + "question_sanitized": "Radiative power is inversely proportional to this quantity cubed, times 6-pi-epsilon, according to the Larmor formula. This quantity is in the numerator in the formula for the index of refraction. When a charged particle exceeds this quantity while in a medium, it produces Cherenkov radiation. This (*) quantity is equal to one divided by the square root of the product of the vacuum permittivity and permeability. This quantity is constant in all inertial reference frames. For 10 points, name this value symbolized c, that is about 30 million meters per second.", } def test_from_json(self): @@ -49,37 +49,49 @@ class TestBonus: """Test the Bonus class.""" b_json = { - "_id": "644932c99f0045ff841d6792", - "leadin": "Using this metal as a charge carrier avoids both cross-contamination and electrodeposition because it has four stable oxidation states: plus-two, plus-three, plus-four, and plus-five. For 10 points each:", # noqa: E501 + "_id": "673ec00f90236da031c2cedb", + "leadin": "With George Jean Nathan, H. L. Mencken co-founded a newspaper called The [this adjective] Mercury, which eventually fell under far-right leadership. For 10 points each:", + "leadin_sanitized": "With George Jean Nathan, H. L. Mencken co-founded a newspaper called The [this adjective] Mercury, which eventually fell under far-right leadership. For 10 points each:", "parts": [ - "Name this transition metal used as the charge carrier in the most common redox flow battery for electric grids.", # noqa: E501 - "To balance charge between the pipes, redox flow batteries rely on one of these semipermeable barriers made of Nafion. They also separate the compartments of fuel cells.", # noqa: E501 - "The electrolyte of a vanadium redox flow battery is a solution of this compound. This “acid” in a lead-acid battery is prepared using a vanadium catalyst in the contact process.", # noqa: E501 + "Name this adjective in the title of a Mencken book that pays homage to Noah Webster. That book claims that the sentence “who are you talking to” is “doubly” this adjective since it forgoes “whom” and puts a preposition at the end of a sentence.", + " The Baltimore Sun sent Mencken to cover one of these events in Dayton, Tennessee, where he gave it a famous nickname. That event of this type was fictionalized in the play Inherit the Wind.", + "At the end of Inherit the Wind, Henry Drummond picks up a book by Darwin in one hand and this book with the other. Mencken claimed to have coined the term for a “Belt” in the Southern United States named for this text.", + ], + "parts_sanitized": [ + 'Name this adjective in the title of a Mencken book that pays homage to Noah Webster. That book claims that the sentence "who are you talking to" is "doubly" this adjective since it forgoes "whom" and puts a preposition at the end of a sentence.', + "The Baltimore Sun sent Mencken to cover one of these events in Dayton, Tennessee, where he gave it a famous nickname. That event of this type was fictionalized in the play Inherit the Wind.", + 'At the end of Inherit the Wind, Henry Drummond picks up a book by Darwin in one hand and this book with the other. Mencken claimed to have coined the term for a "Belt" in the Southern United States named for this text.', ], - "values": [], "answers": [ - "vanadium [or V]", - "proton-exchange membranes [or PEMs; or polymer-electrolyte membranes; prompt on membranes orion-exchange membranes]", # noqa: E501 - "sulfuric acid [or H2SO4]", + "American [accept The American Mercury or The American Language]", + "trial [accept Scopes trial or Scopes Monkey trial]", + "the Bible ", ], - "formatted_answers": [ - "vanadium [or V]", - "proton-exchange membranes [or PEMs; or polymer-electrolyte membranes; prompt on membranes orion-exchange membranes]", # noqa: E501 - "sulfuric acid [or H2SO4]", + "answers_sanitized": [ + "American [accept The American Mercury or The American Language]", + "trial [accept Scopes trial or Scopes Monkey trial]", + "the Bible", ], - "category": "Science", - "subcategory": "Chemistry", - "packet": "644932c99f0045ff841d6777", - "set": "644932c99f0045ff841d66fb", - "setName": "2023 ACF Nationals", - "setYear": 2023, - "type": "bonus", - "packetNumber": 4, - "packetName": "Finals 2. Editors 12", - "questionNumber": 7, - "createdAt": "2023-04-26T14:18:49.683Z", - "updatedAt": "2023-04-26T14:19:23.999Z", - "difficulty": 9, + "updatedAt": "2024-11-21T05:07:27.318Z", + "category": "Literature", + "subcategory": "American Literature", + "alternate_subcategory": "Misc Literature", + "values": [10, 10, 10], + "difficultyModifiers": ["h", "m", "e"], + "number": 1, + "createdAt": "2024-11-21T05:07:27.318Z", + "difficulty": 7, + "packet": { + "_id": "673ec00f90236da031c2cec6", + "name": "A - Claremont A, Edinburgh A, Haverford A, Georgia Tech B, Illinois C, Michigan B", + "number": 1, + }, + "set": { + "_id": "673ec00f90236da031c2cec5", + "name": "2024 ACF Winter", + "year": 2024, + "standard": True, + }, } def test_from_json(self): @@ -121,6 +133,38 @@ def test_iter(self): assert b == self.packet.bonuses[i] +class TestPacketMetadata: + """Test the PacketMetadata class.""" + + packetMetadata = PacketMetadata.from_json(TestTossup.tu_json["packet"]) + + def test_eq(self): + """Test the __eq__ method.""" + p1 = p2 = self.packetMetadata + assert p1 == p2 + assert p1 != "not packet metadata" + + def test_str(self): + """Test the __str__ method.""" + assert str(self.packetMetadata) + + +class TestSetMetadata: + """Test the SetMetadata class.""" + + setMetadata = SetMetadata.from_json(TestTossup.tu_json["set"]) + + def test_eq(self): + """Test the __eq__ method.""" + s1 = s2 = self.setMetadata + assert s1 == s2 + assert s1 != "not set metadata" + + def test_str(self): + """Test the __str__ method.""" + assert str(self.setMetadata) + + class TestQueryResponse: """Test the QueryResponse class.""" From 398c4082fe75fe36b37fac685a40cbe4e34cf108 Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Sat, 30 Nov 2024 16:12:51 -0600 Subject: [PATCH 6/7] update version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ce215fd..6fd0996 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "qbreader" -version = "1.0.0-rc.2" +version = "1.0.0-rc.3" description = "Quizbowl library and Python wrapper for the qbreader API" authors = [ "Sky \"g3ner1c\" Hong ", From 5734d80add2b9eb8b774e571d84fdd8c3acb2fc8 Mon Sep 17 00:00:00 2001 From: Geoffrey Wu Date: Sat, 30 Nov 2024 16:26:14 -0600 Subject: [PATCH 7/7] fix noqa errors --- tests/test_types.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/test_types.py b/tests/test_types.py index 7f0b661..a2b8fac 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -9,7 +9,7 @@ class TestTossup: tu_json = { "_id": "64046cc6de59b8af97422da5", - "question": "Radiative power is inversely proportional to this quantity cubed, times 6-pi-epsilon, according to the Larmor formula. This quantity is in the numerator in the formula for the index of refraction. When a charged particle exceeds this quantity while in a medium, it produces Cherenkov radiation. This (*) quantity is equal to one divided by the square root of the product of the vacuum permittivity and permeability. This quantity is constant in all inertial reference frames. For 10 points, name this value symbolized c, that is about 30 million meters per second.", + "question": "Radiative power is inversely proportional to this quantity cubed, times 6-pi-epsilon, according to the Larmor formula. This quantity is in the numerator in the formula for the index of refraction. When a charged particle exceeds this quantity while in a medium, it produces Cherenkov radiation. This (*) quantity is equal to one divided by the square root of the product of the vacuum permittivity and permeability. This quantity is constant in all inertial reference frames. For 10 points, name this value symbolized c, that is about 30 million meters per second.", # noqa: E501 "answer": "Speed of Light", "category": "Science", "subcategory": "Physics", @@ -25,7 +25,7 @@ class TestTossup: "difficulty": 3, "number": 3, "answer_sanitized": "Speed of Light", - "question_sanitized": "Radiative power is inversely proportional to this quantity cubed, times 6-pi-epsilon, according to the Larmor formula. This quantity is in the numerator in the formula for the index of refraction. When a charged particle exceeds this quantity while in a medium, it produces Cherenkov radiation. This (*) quantity is equal to one divided by the square root of the product of the vacuum permittivity and permeability. This quantity is constant in all inertial reference frames. For 10 points, name this value symbolized c, that is about 30 million meters per second.", + "question_sanitized": "Radiative power is inversely proportional to this quantity cubed, times 6-pi-epsilon, according to the Larmor formula. This quantity is in the numerator in the formula for the index of refraction. When a charged particle exceeds this quantity while in a medium, it produces Cherenkov radiation. This (*) quantity is equal to one divided by the square root of the product of the vacuum permittivity and permeability. This quantity is constant in all inertial reference frames. For 10 points, name this value symbolized c, that is about 30 million meters per second.", # noqa: E501 } def test_from_json(self): @@ -50,21 +50,21 @@ class TestBonus: b_json = { "_id": "673ec00f90236da031c2cedb", - "leadin": "With George Jean Nathan, H. L. Mencken co-founded a newspaper called The [this adjective] Mercury, which eventually fell under far-right leadership. For 10 points each:", - "leadin_sanitized": "With George Jean Nathan, H. L. Mencken co-founded a newspaper called The [this adjective] Mercury, which eventually fell under far-right leadership. For 10 points each:", + "leadin": "With George Jean Nathan, H. L. Mencken co-founded a newspaper called The [this adjective] Mercury, which eventually fell under far-right leadership. For 10 points each:", # noqa: E501 + "leadin_sanitized": "With George Jean Nathan, H. L. Mencken co-founded a newspaper called The [this adjective] Mercury, which eventually fell under far-right leadership. For 10 points each:", # noqa: E501 "parts": [ - "Name this adjective in the title of a Mencken book that pays homage to Noah Webster. That book claims that the sentence “who are you talking to” is “doubly” this adjective since it forgoes “whom” and puts a preposition at the end of a sentence.", - " The Baltimore Sun sent Mencken to cover one of these events in Dayton, Tennessee, where he gave it a famous nickname. That event of this type was fictionalized in the play Inherit the Wind.", - "At the end of Inherit the Wind, Henry Drummond picks up a book by Darwin in one hand and this book with the other. Mencken claimed to have coined the term for a “Belt” in the Southern United States named for this text.", + "Name this adjective in the title of a Mencken book that pays homage to Noah Webster. That book claims that the sentence “who are you talking to” is “doubly” this adjective since it forgoes “whom” and puts a preposition at the end of a sentence.", # noqa: E501 + " The Baltimore Sun sent Mencken to cover one of these events in Dayton, Tennessee, where he gave it a famous nickname. That event of this type was fictionalized in the play Inherit the Wind.", # noqa: E501 + "At the end of Inherit the Wind, Henry Drummond picks up a book by Darwin in one hand and this book with the other. Mencken claimed to have coined the term for a “Belt” in the Southern United States named for this text.", # noqa: E501 ], "parts_sanitized": [ - 'Name this adjective in the title of a Mencken book that pays homage to Noah Webster. That book claims that the sentence "who are you talking to" is "doubly" this adjective since it forgoes "whom" and puts a preposition at the end of a sentence.', - "The Baltimore Sun sent Mencken to cover one of these events in Dayton, Tennessee, where he gave it a famous nickname. That event of this type was fictionalized in the play Inherit the Wind.", - 'At the end of Inherit the Wind, Henry Drummond picks up a book by Darwin in one hand and this book with the other. Mencken claimed to have coined the term for a "Belt" in the Southern United States named for this text.', + 'Name this adjective in the title of a Mencken book that pays homage to Noah Webster. That book claims that the sentence "who are you talking to" is "doubly" this adjective since it forgoes "whom" and puts a preposition at the end of a sentence.', # noqa: E501 + "The Baltimore Sun sent Mencken to cover one of these events in Dayton, Tennessee, where he gave it a famous nickname. That event of this type was fictionalized in the play Inherit the Wind.", # noqa: E501 + 'At the end of Inherit the Wind, Henry Drummond picks up a book by Darwin in one hand and this book with the other. Mencken claimed to have coined the term for a "Belt" in the Southern United States named for this text.', # noqa: E501 ], "answers": [ - "American [accept The American Mercury or The American Language]", - "trial [accept Scopes trial or Scopes Monkey trial]", + "American [accept The American Mercury or The American Language]", # noqa: E501 + "trial [accept Scopes trial or Scopes Monkey trial]", # noqa: E501 "the Bible ", ], "answers_sanitized": [ @@ -83,7 +83,7 @@ class TestBonus: "difficulty": 7, "packet": { "_id": "673ec00f90236da031c2cec6", - "name": "A - Claremont A, Edinburgh A, Haverford A, Georgia Tech B, Illinois C, Michigan B", + "name": "A - Claremont A, Edinburgh A, Haverford A, Georgia Tech B, Illinois C, Michigan B", # noqa: E501 "number": 1, }, "set": {