Skip to content

Commit abe2060

Browse files
committed
Provide type annotations for the public API
The package now includes a PEP 561 `py.typed` flag file to let type checkers know that this package provides type annotations. The annotations provide full coverage for all public functions and classes in the aocd package, with the library reaching 100% coverage as per `pyright --ignoreexternal --verifytypes`.
1 parent 34d73e3 commit abe2060

File tree

12 files changed

+240
-93
lines changed

12 files changed

+240
-93
lines changed

aocd/__init__.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sys
2+
import typing as t
23
from functools import partial
34

45
from . import _ipykernel
@@ -11,13 +12,34 @@
1112
from . import post
1213
from . import runner
1314
from . import utils
15+
from . import types
1416
from .exceptions import AocdError
1517
from .get import get_data
1618
from .get import get_day_and_year
1719
from .post import submit as _impartial_submit
1820

21+
__all__ = [
22+
"_ipykernel",
23+
"cli",
24+
"cookies",
25+
"data",
26+
"examples",
27+
"exceptions",
28+
"get",
29+
"models",
30+
"post",
31+
"runner",
32+
"submit",
33+
"types",
34+
"utils",
35+
]
1936

20-
def __getattr__(name):
37+
if t.TYPE_CHECKING:
38+
data: str
39+
submit = _impartial_submit
40+
41+
42+
def __getattr__(name: str) -> t.Any:
2143
if name == "data":
2244
day, year = get_day_and_year()
2345
return get_data(day=day, year=year)

aocd/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .utils import get_plugins
1414

1515

16-
def main():
16+
def main() -> None:
1717
"""Get your puzzle input data, caching it if necessary, and print it on stdout."""
1818
aoc_now = datetime.datetime.now(tz=AOC_TZ)
1919
days = range(1, 26)

aocd/cookies.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
from .utils import get_owner
1313

1414

15-
log = logging.getLogger(__name__)
15+
log: logging.Logger = logging.getLogger(__name__)
1616

1717

18-
def get_working_tokens():
18+
def get_working_tokens() -> dict[str, str]:
1919
"""Check browser cookie storage for session tokens from .adventofcode.com domain."""
2020
log.debug("checking for installation of browser-cookie3 package")
2121
try:
@@ -66,7 +66,7 @@ def get_working_tokens():
6666
return result
6767

6868

69-
def scrape_session_tokens():
69+
def scrape_session_tokens() -> None:
7070
"""Scrape AoC session tokens from your browser's cookie storage."""
7171
aocd_token_path = AOCD_CONFIG_DIR / "token"
7272
aocd_tokens_path = AOCD_CONFIG_DIR / "tokens.json"

aocd/examples.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from __future__ import annotations
2+
13
import argparse
24
import logging
35
import re
46
import sys
7+
import typing as t
58
from dataclasses import dataclass
69
from datetime import datetime
710
from itertools import zip_longest
@@ -16,7 +19,11 @@
1619
from aocd.utils import get_plugins
1720

1821

19-
log = logging.getLogger(__name__)
22+
log: logging.Logger = logging.getLogger(__name__)
23+
24+
_AnswerElem = t.Literal[
25+
"a_code", "a_li", "a_pre", "a_em", "b_code", "b_li", "b_pre", "b_em"
26+
]
2027

2128

2229
@dataclass
@@ -34,17 +41,17 @@ class Page:
3441
soup: bs4.BeautifulSoup # The raw_html string parsed into a bs4.BeautifulSoup instance
3542
year: int # AoC puzzle year (2015+) parsed from html title
3643
day: int # AoC puzzle day (1-25) parsed from html title
37-
article_a: bs4.element.Tag # The bs4 tag for the first <article> in the page, i.e. part a
38-
article_b: bs4.element.Tag # The bs4 tag for the second <article> in the page, i.e. part b. It will be `None` if part b locked
44+
article_a: bs4.Tag # The bs4 tag for the first <article> in the page, i.e. part a
45+
article_b: bs4.Tag | None # The bs4 tag for the second <article> in the page, i.e. part b. It will be `None` if part b locked
3946
a_raw: str # The first <article> html as a string
40-
b_raw: str # The second <article> html as a string. Will be `None` if part b locked
47+
b_raw: str | None # The second <article> html as a string. Will be `None` if part b locked
4148

42-
def __repr__(self):
49+
def __repr__(self) -> str:
4350
part_a_only = "*" if self.article_b is None else ""
4451
return f"<Page({self.year}, {self.day}){part_a_only} at {hex(id(self))}>"
4552

4653
@classmethod
47-
def from_raw(cls, html):
54+
def from_raw(cls, html: str) -> Page:
4855
soup = _get_soup(html)
4956
title_pat = r"^Day (\d{1,2}) - Advent of Code (\d{4})$"
5057
title_text = soup.title.text
@@ -77,7 +84,7 @@ def from_raw(cls, html):
7784
)
7885
return page
7986

80-
def __getattr__(self, name):
87+
def __getattr__(self, name: _AnswerElem) -> t.Sequence[str]:
8188
if not name.startswith(("a_", "b_")):
8289
raise AttributeError(name)
8390
part, sep, tag = name.partition("_")
@@ -118,12 +125,12 @@ class Example(NamedTuple):
118125
"""
119126

120127
input_data: str
121-
answer_a: str = None
122-
answer_b: str = None
123-
extra: str = None
128+
answer_a: str | None = None
129+
answer_b: str | None = None
130+
extra: str | None = None
124131

125132
@property
126-
def answers(self):
133+
def answers(self) -> tuple[str | None, str | None]:
127134
return self.answer_a, self.answer_b
128135

129136

@@ -144,7 +151,7 @@ def _get_unique_real_inputs(year, day):
144151
return list({}.fromkeys(strs))
145152

146153

147-
def main():
154+
def main() -> None:
148155
"""
149156
Summarize an example parser's results with historical puzzles' prose, and
150157
compare the performance against a reference implementation

aocd/get.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
from __future__ import annotations
2+
13
import datetime
24
import os
35
import re
46
import traceback
5-
from logging import getLogger
7+
import typing as t
8+
from logging import Logger, getLogger
69

710
from ._ipykernel import get_ipynb_path
811
from .exceptions import AocdError
@@ -14,10 +17,15 @@
1417
from .utils import blocker
1518

1619

17-
log = getLogger(__name__)
20+
log: Logger = getLogger(__name__)
1821

1922

20-
def get_data(session=None, day=None, year=None, block=False):
23+
def get_data(
24+
session: str | None = None,
25+
day: int | None = None,
26+
year: int | None = None,
27+
block: bool = False,
28+
) -> str:
2129
"""
2230
Get data for day (1-25) and year (2015+).
2331
User's session cookie (str) is needed - puzzle inputs differ by user.
@@ -45,7 +53,7 @@ def get_data(session=None, day=None, year=None, block=False):
4553
return puzzle.input_data
4654

4755

48-
def most_recent_year():
56+
def most_recent_year() -> int:
4957
"""
5058
This year, if it's December.
5159
The most recent year, otherwise.
@@ -60,7 +68,7 @@ def most_recent_year():
6068
return year
6169

6270

63-
def current_day():
71+
def current_day() -> int:
6472
"""
6573
Most recent day, if it's during the Advent of Code. Happy Holidays!
6674
Day 1 is assumed, otherwise.
@@ -73,7 +81,7 @@ def current_day():
7381
return day
7482

7583

76-
def get_day_and_year():
84+
def get_day_and_year() -> tuple[int, int]:
7785
"""
7886
Returns tuple (day, year).
7987

0 commit comments

Comments
 (0)