Skip to content

Commit

Permalink
Merge pull request #131 from mjpieters/type_annotations
Browse files Browse the repository at this point in the history
Provide type annotations for the public API
  • Loading branch information
wimglenn committed Dec 23, 2023
2 parents e37d736 + 589325a commit 40d1ef7
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 93 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,29 @@ jobs:
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}

typesafety:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.12'
cache: 'pip'

- run: |
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
# Tell setuptools to *not* create a PEP 660 import hook and to use
# symlinks instead, so that pyright can still find the package. See
# https://microsoft.github.io/pyright/#/import-resolution?id=editable-installs
pip install --editable . --config-settings editable_mode=strict
- run: echo "$PWD/.venv/bin" >> $GITHUB_PATH

- uses: jakebailey/pyright-action@v1
with:
ignore-external: true
verify-types: "aocd"
24 changes: 23 additions & 1 deletion aocd/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
import typing as t
from functools import partial

from . import _ipykernel
Expand All @@ -11,13 +12,34 @@
from . import post
from . import runner
from . import utils
from . import types
from .exceptions import AocdError
from .get import get_data
from .get import get_day_and_year
from .post import submit as _impartial_submit

__all__ = [
"_ipykernel",
"cli",
"cookies",
"data",
"examples",
"exceptions",
"get",
"models",
"post",
"runner",
"submit",
"types",
"utils",
]

def __getattr__(name):
if t.TYPE_CHECKING:
data: str
submit = _impartial_submit


def __getattr__(name: str) -> t.Any:
if name == "data":
day, year = get_day_and_year()
return get_data(day=day, year=year)
Expand Down
2 changes: 1 addition & 1 deletion aocd/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .utils import get_plugins


def main():
def main() -> None:
"""Get your puzzle input data, caching it if necessary, and print it on stdout."""
aoc_now = datetime.datetime.now(tz=AOC_TZ)
days = range(1, 26)
Expand Down
6 changes: 3 additions & 3 deletions aocd/cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
from .utils import get_owner


log = logging.getLogger(__name__)
log: logging.Logger = logging.getLogger(__name__)


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


def scrape_session_tokens():
def scrape_session_tokens() -> None:
"""Scrape AoC session tokens from your browser's cookie storage."""
aocd_token_path = AOCD_CONFIG_DIR / "token"
aocd_tokens_path = AOCD_CONFIG_DIR / "tokens.json"
Expand Down
31 changes: 19 additions & 12 deletions aocd/examples.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import annotations

import argparse
import logging
import re
import sys
import typing as t
from dataclasses import dataclass
from datetime import datetime
from itertools import zip_longest
Expand All @@ -16,7 +19,11 @@
from aocd.utils import get_plugins


log = logging.getLogger(__name__)
log: logging.Logger = logging.getLogger(__name__)

_AnswerElem = t.Literal[
"a_code", "a_li", "a_pre", "a_em", "b_code", "b_li", "b_pre", "b_em"
]


@dataclass
Expand All @@ -34,17 +41,17 @@ class Page:
soup: bs4.BeautifulSoup # The raw_html string parsed into a bs4.BeautifulSoup instance
year: int # AoC puzzle year (2015+) parsed from html title
day: int # AoC puzzle day (1-25) parsed from html title
article_a: bs4.element.Tag # The bs4 tag for the first <article> in the page, i.e. part a
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
article_a: bs4.Tag # The bs4 tag for the first <article> in the page, i.e. part a
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
a_raw: str # The first <article> html as a string
b_raw: str # The second <article> html as a string. Will be `None` if part b locked
b_raw: str | None # The second <article> html as a string. Will be `None` if part b locked

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

@classmethod
def from_raw(cls, html):
def from_raw(cls, html: str) -> Page:
soup = _get_soup(html)
title_pat = r"^Day (\d{1,2}) - Advent of Code (\d{4})$"
title_text = soup.title.text
Expand Down Expand Up @@ -77,7 +84,7 @@ def from_raw(cls, html):
)
return page

def __getattr__(self, name):
def __getattr__(self, name: _AnswerElem) -> t.Sequence[str]:
if not name.startswith(("a_", "b_")):
raise AttributeError(name)
part, sep, tag = name.partition("_")
Expand Down Expand Up @@ -118,12 +125,12 @@ class Example(NamedTuple):
"""

input_data: str
answer_a: str = None
answer_b: str = None
extra: str = None
answer_a: str | None = None
answer_b: str | None = None
extra: str | None = None

@property
def answers(self):
def answers(self) -> tuple[str | None, str | None]:
return self.answer_a, self.answer_b


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


def main():
def main() -> None:
"""
Summarize an example parser's results with historical puzzles' prose, and
compare the performance against a reference implementation
Expand Down
20 changes: 14 additions & 6 deletions aocd/get.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from __future__ import annotations

import datetime
import os
import re
import traceback
from logging import getLogger
import typing as t
from logging import Logger, getLogger

from ._ipykernel import get_ipynb_path
from .exceptions import AocdError
Expand All @@ -14,10 +17,15 @@
from .utils import blocker


log = getLogger(__name__)
log: Logger = getLogger(__name__)


def get_data(session=None, day=None, year=None, block=False):
def get_data(
session: str | None = None,
day: int | None = None,
year: int | None = None,
block: bool = False,
) -> str:
"""
Get data for day (1-25) and year (2015+).
User's session cookie (str) is needed - puzzle inputs differ by user.
Expand Down Expand Up @@ -45,7 +53,7 @@ def get_data(session=None, day=None, year=None, block=False):
return puzzle.input_data


def most_recent_year():
def most_recent_year() -> int:
"""
This year, if it's December.
The most recent year, otherwise.
Expand All @@ -60,7 +68,7 @@ def most_recent_year():
return year


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


def get_day_and_year():
def get_day_and_year() -> tuple[int, int | None]:
"""
Returns tuple (day, year).
Expand Down
Loading

0 comments on commit 40d1ef7

Please sign in to comment.