Skip to content

Commit 184b71e

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 184b71e

12 files changed

+221
-93
lines changed

aocd/__init__.py

+23-1
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+
"utils",
34+
"types",
35+
]
1936

20-
def __getattr__(name):
37+
if t.TYPE_CHECKING: # pragma: no cover
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

+1-1
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

+3-3
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

+17-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import re
44
import sys
5+
import typing as t
56
from dataclasses import dataclass
67
from datetime import datetime
78
from itertools import zip_longest
@@ -16,7 +17,11 @@
1617
from aocd.utils import get_plugins
1718

1819

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

2126

2227
@dataclass
@@ -34,17 +39,17 @@ class Page:
3439
soup: bs4.BeautifulSoup # The raw_html string parsed into a bs4.BeautifulSoup instance
3540
year: int # AoC puzzle year (2015+) parsed from html title
3641
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
42+
article_a: bs4.Tag # The bs4 tag for the first <article> in the page, i.e. part a
43+
article_b: t.Optional[bs4.Tag] # The bs4 tag for the second <article> in the page, i.e. part b. It will be `None` if part b locked
3944
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
45+
b_raw: t.Optional[str] # The second <article> html as a string. Will be `None` if part b locked
4146

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

4651
@classmethod
47-
def from_raw(cls, html):
52+
def from_raw(cls, html: str) -> "Page":
4853
soup = _get_soup(html)
4954
title_pat = r"^Day (\d{1,2}) - Advent of Code (\d{4})$"
5055
title_text = soup.title.text
@@ -77,7 +82,7 @@ def from_raw(cls, html):
7782
)
7883
return page
7984

80-
def __getattr__(self, name):
85+
def __getattr__(self, name: _AnswerElem) -> t.Sequence[str]:
8186
if not name.startswith(("a_", "b_")):
8287
raise AttributeError(name)
8388
part, sep, tag = name.partition("_")
@@ -118,12 +123,12 @@ class Example(NamedTuple):
118123
"""
119124

120125
input_data: str
121-
answer_a: str = None
122-
answer_b: str = None
123-
extra: str = None
126+
answer_a: t.Optional[str] = None
127+
answer_b: t.Optional[str] = None
128+
extra: t.Optional[str] = None
124129

125130
@property
126-
def answers(self):
131+
def answers(self) -> tuple[t.Optional[str], t.Optional[str]]:
127132
return self.answer_a, self.answer_b
128133

129134

@@ -144,7 +149,7 @@ def _get_unique_real_inputs(year, day):
144149
return list({}.fromkeys(strs))
145150

146151

147-
def main():
152+
def main() -> None:
148153
"""
149154
Summarize an example parser's results with historical puzzles' prose, and
150155
compare the performance against a reference implementation

aocd/get.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import os
33
import re
44
import traceback
5-
from logging import getLogger
5+
import typing as t
6+
from logging import Logger, getLogger
67

78
from ._ipykernel import get_ipynb_path
89
from .exceptions import AocdError
@@ -14,10 +15,15 @@
1415
from .utils import blocker
1516

1617

17-
log = getLogger(__name__)
18+
log: Logger = getLogger(__name__)
1819

1920

20-
def get_data(session=None, day=None, year=None, block=False):
21+
def get_data(
22+
session: t.Optional[str] = None,
23+
day: t.Optional[int] = None,
24+
year: t.Optional[int] = None,
25+
block: bool = False,
26+
) -> str:
2127
"""
2228
Get data for day (1-25) and year (2015+).
2329
User's session cookie (str) is needed - puzzle inputs differ by user.
@@ -45,7 +51,7 @@ def get_data(session=None, day=None, year=None, block=False):
4551
return puzzle.input_data
4652

4753

48-
def most_recent_year():
54+
def most_recent_year() -> int:
4955
"""
5056
This year, if it's December.
5157
The most recent year, otherwise.
@@ -60,7 +66,7 @@ def most_recent_year():
6066
return year
6167

6268

63-
def current_day():
69+
def current_day() -> int:
6470
"""
6571
Most recent day, if it's during the Advent of Code. Happy Holidays!
6672
Day 1 is assumed, otherwise.
@@ -73,7 +79,7 @@ def current_day():
7379
return day
7480

7581

76-
def get_day_and_year():
82+
def get_day_and_year() -> tuple[int, int]:
7783
"""
7884
Returns tuple (day, year).
7985

0 commit comments

Comments
 (0)