Skip to content

Commit c8890cc

Browse files
committed
fix: add year argument to AoC package
1 parent 66ca46d commit c8890cc

File tree

7 files changed

+56
-42
lines changed

7 files changed

+56
-42
lines changed

aoc/models/authenticator.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
"""
77

88
import json
9-
import os
109
from pathlib import Path
11-
import sys
1210

1311

1412
class Authenticator:
@@ -23,8 +21,9 @@ def get_path() -> Path:
2321
-------
2422
Path to the directory containing the current script or script's parent.
2523
"""
26-
path = os.path.realpath(sys.argv[0])
27-
return Path(path).parent if not Path(path).is_dir() else Path(path)
24+
current_file = Path(__file__).resolve()
25+
aoc_dir = current_file.parent.parent
26+
return aoc_dir.parent
2827

2928
@staticmethod
3029
def get_session() -> str:

aoc/models/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class SolutionBase:
3434

3535
def __init__(
3636
self,
37+
year: int,
3738
day: int = -1,
3839
part_num: int = 1,
3940
*,
@@ -52,16 +53,17 @@ def __init__(
5253
benchmark: Whether to measure solution execution time.
5354
Defaults to `False`.
5455
"""
56+
self.year = year
5557
self.day = day
5658
self.part_num = part_num
5759
self.is_raw = is_raw
5860
self.skip_test = skip_test
5961
self._benchmark = benchmark
6062
self.benchmark_times: list[float] = []
6163
self.data = (
62-
Reader.get_puzzle_input(self.day, raw=self.is_raw)
64+
Reader.get_puzzle_input(self.year, self.day, raw=self.is_raw)
6365
if self.skip_test
64-
else Reader.get_test_input(self.day, self.part_num, raw=self.is_raw)
66+
else Reader.get_test_input(self.year, self.day, self.part_num, raw=self.is_raw)
6567
)
6668

6769
def check_is_raw(self) -> None:

aoc/models/file.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
"""
88

99
from datetime import datetime
10-
from pathlib import Path
1110
from time import sleep
1211

1312
from bs4 import BeautifulSoup
1413
from loguru import logger
1514
import requests
15+
from requests import Request, Session
1616

1717
from aoc.models.authenticator import Authenticator
1818

@@ -26,10 +26,11 @@ class File:
2626
"""
2727

2828
@staticmethod
29-
def download_puzzle_input(day: int) -> str:
29+
def download_puzzle_input(year: int, day: int) -> str:
3030
"""Download the puzzle input for a specific day from Advent of Code.
3131
3232
Args:
33+
year: The year of the puzzle (2024, 2025, etc.)
3334
day: The day number (1-25) of the puzzle.
3435
3536
Returns
@@ -41,28 +42,32 @@ def download_puzzle_input(day: int) -> str:
4142
Year is determined from the project directory name.
4243
"""
4344
session = Authenticator.get_session()
44-
path_obj = Path(Authenticator.get_path())
45-
year = path_obj.parts[-1].split("-")[-1]
46-
4745
headers = Authenticator.get_headers()
46+
4847
headers["Referer"] = f"https://adventofcode.com/{year}/day/{day}"
4948
headers["Cookie"] = f"session={session}"
5049

5150
url = f"https://adventofcode.com/{year}/day/{day}/input"
5251
method = "GET"
53-
request = requests.Request(method, url, headers=headers)
52+
request = Request(method, url, headers=headers)
5453
prepped_request = request.prepare()
5554

56-
with requests.Session() as sess:
55+
logger.debug(f"URL: {url}")
56+
logger.debug(f"Headers: {headers}")
57+
logger.debug(f"Session token length: {len(session)}")
58+
59+
with Session() as sess:
5760
response = sess.send(prepped_request)
61+
logger.debug(response.status_code)
5862
response.raise_for_status()
5963
return response.text
6064

6165
@staticmethod
62-
def download_test_input(day: int, part_num: int) -> str | None:
66+
def download_test_input(year: int, day: int, part_num: int) -> str | None:
6367
"""Download test input from the puzzle description for a specific day and part.
6468
6569
Args:
70+
year: The year of the puzzle (2024, 2025, etc.)
6671
day: The day number (1-25) of the puzzle.
6772
part_num: The puzzle part number (1 or 2).
6873
@@ -75,10 +80,8 @@ def download_test_input(day: int, part_num: int) -> str | None:
7580
Part number determines which code block to use.
7681
"""
7782
session = Authenticator.get_session()
78-
path_obj = Path(Authenticator.get_path())
79-
year = path_obj.parts[-1].split("-")[-1]
80-
8183
headers = Authenticator.get_headers()
84+
8285
headers["Referer"] = f"https://adventofcode.com/{year}/day/{day}"
8386
headers["Cookie"] = f"session={session}"
8487

@@ -97,7 +100,7 @@ def download_test_input(day: int, part_num: int) -> str | None:
97100
return None
98101

99102
@staticmethod
100-
def add_day(day: int) -> None:
103+
def add_day(year: int, day: int) -> None:
101104
"""Set up the file structure for a new puzzle day.
102105
103106
Creates solution file from template and downloads puzzle input when available.
@@ -112,7 +115,7 @@ def add_day(day: int) -> None:
112115
- `data/dayXX/puzzle_input.txt`
113116
"""
114117
path = Authenticator.get_path()
115-
solution_path = path / f"solutions/day{day:02}.py"
118+
solution_path = path / str(year) / f"solutions/day{day:02}.py"
116119

117120
if not solution_path.exists():
118121
sample_file = path / "templates/solutions/sample.py"
@@ -121,7 +124,7 @@ def add_day(day: int) -> None:
121124
solution_path.write_text(content)
122125
logger.info(f"Created file: {solution_path}")
123126

124-
folder_path = path / f"data/day{day:02}"
127+
folder_path = path / str(year) / f"data/day{day:02}"
125128
folder_path.mkdir(parents=True, exist_ok=True)
126129

127130
file_path = folder_path / "puzzle_input.txt"
@@ -146,25 +149,26 @@ def add_day(day: int) -> None:
146149
now = datetime.now()
147150

148151
logger.info("Downloading puzzle input...")
149-
file_path.write_text(File.download_puzzle_input(day))
152+
file_path.write_text(File.download_puzzle_input(year, day))
150153
logger.info(f"Downloaded puzzle input to: {file_path}")
151154

152155
@staticmethod
153-
def add_test_input(day: int, part_num: int) -> None:
156+
def add_test_input(year: int, day: int, part_num: int) -> None:
154157
"""Set up and download test input for a specific puzzle part.
155158
156159
Creates test input file and downloads content when available.
157160
Waits for puzzle unlock time (5:00 UTC) if necessary.
158161
159162
Args:
163+
year: The year of the puzzle (2024, 2025, etc.)
160164
day: The day number (1-25) of the puzzle.
161165
part_num: The puzzle part number (1 or 2).
162166
163167
Note:
164168
Creates file at: `tests/data/dayXX/test_XX_input.txt`
165169
"""
166170
path = Authenticator.get_path()
167-
folder_path = path / f"tests/data/day{day:02}"
171+
folder_path = path / str(year) / f"tests/data/day{day:02}"
168172
folder_path.mkdir(parents=True, exist_ok=True)
169173

170174
file_path = folder_path / f"test_{part_num:02d}_input.txt"
@@ -191,18 +195,19 @@ def add_test_input(day: int, part_num: int) -> None:
191195
logger.info("Downloading test input...")
192196

193197
# Use the method's return value
194-
test_input = File.download_test_input(day, part_num)
198+
test_input = File.download_test_input(year, day, part_num)
195199

196200
# Only write if test input is not None
197201
if test_input is not None:
198202
file_path.write_text(test_input)
199203
logger.info(f"Downloaded test input to: {file_path}")
200204

201205
@staticmethod
202-
def add_test_file(day: int) -> None:
206+
def add_test_file(year: int, day: int) -> None:
203207
"""Create a test file for a specific puzzle day from template.
204208
205209
Args:
210+
year: The year of the puzzle (2024, 2025, etc.)
206211
day: The day number (1-25) to create test file for.
207212
208213
Note:
@@ -215,7 +220,7 @@ def add_test_file(day: int) -> None:
215220
FileNotFoundError: If the template file is not found.
216221
"""
217222
path = Authenticator.get_path()
218-
test_path = path / f"tests/test_{day:02}.py"
223+
test_path = path / str(year) / f"tests/test_{day:02}.py"
219224

220225
logger.info(f"Test file path: {test_path}")
221226

aoc/models/reader.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ class Reader:
2121
PROJECT_ROOT = _aoc_dir.parent
2222

2323
@classmethod
24-
def get_test_input(cls, day: int, part_num: int, *, raw: bool) -> str | list[str]:
24+
def get_test_input(cls, year: int, day: int, part_num: int, *, raw: bool) -> str | list[str]:
2525
"""
2626
Read test input for a specific day and part.
2727
2828
Args:
29+
year: The year of the puzzle (2024, 2025, etc.)
2930
day: The day number (1-25)
3031
raw: If True, preserves newlines. If False, strips whitespace
3132
part_num: The puzzle part number (1 or 2)
@@ -35,7 +36,12 @@ def get_test_input(cls, day: int, part_num: int, *, raw: bool) -> str | list[str
3536
The test input as a string or list of strings
3637
"""
3738
file_path = (
38-
cls.PROJECT_ROOT / "tests" / "data" / f"day{day:02d}" / f"test_{part_num:02d}_input.txt"
39+
cls.PROJECT_ROOT
40+
/ str(year)
41+
/ "tests"
42+
/ "data"
43+
/ f"day{day:02d}"
44+
/ f"test_{part_num:02d}_input.txt"
3945
)
4046

4147
with Path.open(file_path) as f:
@@ -48,19 +54,20 @@ def get_test_input(cls, day: int, part_num: int, *, raw: bool) -> str | list[str
4854
return content.strip().split("\n")
4955

5056
@classmethod
51-
def get_puzzle_input(cls, day: int, *, raw: bool = False) -> str | list[str]:
57+
def get_puzzle_input(cls, year: int, day: int, *, raw: bool = False) -> str | list[str]:
5258
"""
5359
Read actual puzzle input for a specific day.
5460
5561
Args:
62+
year: The year of the puzzle (2024, 2025, etc.)
5663
day: The day number (1-25)
5764
raw: If True, preserves newlines. If False, strips whitespace
5865
5966
Returns
6067
-------
6168
The puzzle input as a string or list of strings
6269
"""
63-
file_path = cls.PROJECT_ROOT / "data" / f"day{day:02d}" / "puzzle_input.txt"
70+
file_path = cls.PROJECT_ROOT / str(year) / "data" / f"day{day:02d}" / "puzzle_input.txt"
6471

6572
with Path.open(file_path) as f:
6673
content = f.read()

aoc/models/submission.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
server's response.
77
"""
88

9-
from pathlib import Path
109
import re
1110
import urllib.parse
1211
import urllib.request
@@ -24,13 +23,14 @@ class Submission:
2423
"""
2524

2625
@staticmethod
27-
def submit(day: int, level: int, answer: int) -> None:
26+
def submit(year: int, day: int, level: int, answer: int) -> None:
2827
"""Submit a puzzle solution to Advent of Code.
2928
3029
Makes a POST request to submit the answer and processes the response,
3130
displaying formatted feedback about whether the answer was correct.
3231
3332
Args:
33+
year: The year of the puzzle (2024, 2025, etc.)
3434
day: The day number (1-25) of the puzzle.
3535
level: The part number (1 or 2) of the puzzle.
3636
answer: The calculated answer to submit.
@@ -42,10 +42,8 @@ def submit(day: int, level: int, answer: int) -> None:
4242
- Preserves important punctuation in separate lines for readability
4343
"""
4444
session = Authenticator.get_session()
45-
path_obj = Path(Authenticator.get_path())
46-
year = path_obj.parts[-1].split("-")[-1]
47-
4845
headers = Authenticator.get_headers()
46+
4947
headers["Referer"] = f"https://adventofcode.com/{year}/day/{day}"
5048
headers["Cookie"] = f"session={session}"
5149

aoc/models/tester.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class TestSolutionUtility:
1919

2020
@staticmethod
2121
def run_test(
22+
year: int,
2223
day: int,
2324
part_num: int,
2425
expected: str | int,
@@ -48,13 +49,13 @@ def run_test(
4849
- Loads test input from `tests/data/dayXX/test_YY_input.txt`
4950
- Expects solution classes to have `part1()` and `part2()` methods
5051
"""
51-
solution: SolutionBase = initialise(day, skip_test=False)
52+
solution: SolutionBase = initialise(year, day, skip_test=False)
5253
part_method = getattr(solution, f"part{part_num}")
53-
test_input = Reader.get_test_input(day, part_num, raw=is_raw)
54+
test_input = Reader.get_test_input(year, day, part_num, raw=is_raw)
5455
result = part_method(data=test_input)
5556

5657
if result != expected:
57-
error_message = (
58+
err_msg = (
5859
f"Test failed for Day {day}, Part {part_num}: Expected {expected}, got {result}."
5960
)
60-
raise ValueError(error_message)
61+
raise ValueError(err_msg)

aoc/utils/initalise.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616

1717
def initialise(
18+
year: int,
1819
day: int,
1920
part: int = 1,
2021
*,
@@ -26,6 +27,7 @@ def initialise(
2627
Dynamically load and instantiate the `Solution` class for a specific puzzle day.
2728
2829
Args:
30+
year (int): The year of the puzzle to initialize.
2931
day (int): The day number (1-25) of the puzzle to initialize.
3032
part (int, optional): Part number (1 or 2) to solve. Defaults to 1.
3133
raw (bool, optional): Whether to use raw input. Defaults to False.
@@ -49,9 +51,9 @@ def initialise(
4951
solution_class: type[SolutionBase] = solution_module.Solution
5052

5153
if not issubclass(solution_class, SolutionBase):
52-
error_message = f"Solution class for day {day} must inherit from SolutionBase"
53-
raise TypeError(error_message)
54+
err_msg = f"Solution class for day {day} must inherit from SolutionBase"
55+
raise TypeError(err_msg)
5456

5557
return solution_class(
56-
day=day, part_num=part, is_raw=raw, skip_test=skip_test, benchmark=benchmark
58+
year=year, day=day, part_num=part, is_raw=raw, skip_test=skip_test, benchmark=benchmark
5759
)

0 commit comments

Comments
 (0)