Skip to content

Commit 84ccbb5

Browse files
authored
refactor: Continuing merge (#14)
* WIP * Don't combine if-branches * If passed in a date * refactoring a little bit * refactoring a little bit * Should all be my timezone * Correcting * Just keep fixing
1 parent 66edff0 commit 84ccbb5

File tree

7 files changed

+121
-70
lines changed

7 files changed

+121
-70
lines changed

scripts/_common_tasks_stuff.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ def _email_text(hour_start: datetime | time, hour_end: datetime | time, course_n
1616
def _next_weekday() -> date | None:
1717
now = datetime.now()
1818

19-
if (now.hour in [12, 20] and now.isoweekday() == 3) or (now.hour in [17, 20] and now.isoweekday() != 3): # Wednesday
19+
if now.hour in [12, 20] and now.isoweekday() == 3: # Wednesday # noqa: SIM114
20+
... # Ok
21+
elif now.hour in [17, 20] and now.isoweekday() != 3: # !Wednesday
2022
... # Ok
2123
else:
2224
return None

src/smartschool/agenda.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import time
44
from abc import ABC
55
from dataclasses import dataclass
6-
from datetime import datetime
76
from typing import TYPE_CHECKING, ClassVar
87

98
from . import objects
109
from ._xml_interface import SmartschoolXML_WeeklyCache
10+
from .common import convert_to_datetime
1111
from .objects import AgendaHour, AgendaMomentInfo
1212
from .session import SessionMixin
1313

@@ -83,7 +83,7 @@ def _action(self) -> str:
8383

8484
@property
8585
def _params(self) -> dict:
86-
now = (self.timestamp_to_use or datetime.now()).timestamp()
86+
now = convert_to_datetime(self.timestamp_to_use).timestamp()
8787
in_5_days = now + 5 * 24 * 3600
8888

8989
return {

src/smartschool/common.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import smtplib
99
import warnings
1010
import xml.etree.ElementTree as ET
11+
from datetime import date, datetime
1112
from email.mime.multipart import MIMEMultipart
1213
from email.mime.text import MIMEText
1314
from enum import Enum, auto
14-
from typing import Any, Callable, Literal
15+
from typing import TYPE_CHECKING, Any, Callable, Literal
1516

1617
from bs4 import BeautifulSoup, FeatureNotFound, GuessedAtParserWarning
1718
from logprise import logger
@@ -29,6 +30,12 @@
2930
"send_email",
3031
"xml_to_dict",
3132
]
33+
34+
from smartschool.exceptions import SmartSchoolParsingError
35+
36+
if TYPE_CHECKING:
37+
from smartschool.objects import String
38+
3239
_used_bs4_option = None
3340

3441

@@ -245,3 +252,45 @@ def xml_to_dict(element, *, depth: int = 0):
245252
result[tag] = child_data
246253

247254
return result
255+
256+
257+
def convert_to_datetime(x: str | String | date | datetime | None) -> datetime:
258+
if x is None:
259+
return datetime.now().astimezone()
260+
261+
if isinstance(x, datetime):
262+
if x.tzinfo is None:
263+
raise ValueError("No timezone information found in this date")
264+
return x
265+
266+
if isinstance(x, date):
267+
return datetime.combine(x, datetime.min.time()).astimezone()
268+
269+
possible_formats = [
270+
"%Y-%m-%dT%H:%M:%S%z",
271+
"%Y-%m-%d %H:%M",
272+
"%Y-%m-%d",
273+
]
274+
275+
for fmt in possible_formats:
276+
with contextlib.suppress(ValueError):
277+
x = datetime.strptime(x, fmt)
278+
if x.tzinfo is None:
279+
return x.astimezone()
280+
return x
281+
282+
raise SmartSchoolParsingError(f"Cannot convert '{x}' to `datetime`")
283+
284+
285+
def convert_to_date(x: str | String | date | datetime | None) -> date:
286+
if x is None:
287+
return date.today()
288+
if isinstance(x, datetime):
289+
return x.date()
290+
if isinstance(x, date):
291+
return x
292+
293+
with contextlib.suppress(ValueError):
294+
return datetime.strptime(x, "%Y-%m-%d").date()
295+
296+
raise SmartSchoolParsingError(f"Cannot convert '{x}' to `date`")

src/smartschool/exceptions.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
class SmartSchoolException(Exception): ...
2-
3-
4-
class SmartSchoolDownloadError(SmartSchoolException): ...
1+
class SmartSchoolException(Exception):
2+
"""Base exception class for smartschool API errors."""
53

64

75
class SmartSchoolAuthenticationError(SmartSchoolException):
86
"""Indicates an error during the authentication process."""
7+
8+
9+
class SmartSchoolParsingError(SmartSchoolException):
10+
"""Indicates an error occurred while parsing data from Smartschool."""
11+
12+
13+
class SmartSchoolDownloadError(SmartSchoolException):
14+
"""Indicates an error occurred during a file download operation."""

src/smartschool/objects.py

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,11 @@
1010
from pydantic import AliasChoices, BeforeValidator, constr
1111
from pydantic.dataclasses import Field, dataclass
1212

13-
from .common import as_float
13+
from .common import as_float, convert_to_date, convert_to_datetime
1414

1515
String = constr(strip_whitespace=True)
1616
UUID = constr(pattern=re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", flags=re.IGNORECASE))
1717

18-
19-
def convert_to_datetime(x: str | String | datetime | None) -> datetime:
20-
if x is None:
21-
return datetime.now().astimezone()
22-
23-
if isinstance(x, datetime):
24-
if x.tzinfo is None:
25-
raise ValueError("No timezone information found in this date")
26-
return x
27-
28-
try:
29-
return datetime.strptime(x, "%Y-%m-%dT%H:%M:%S%z")
30-
except ValueError: # 2023-11-16 08:24
31-
return datetime.strptime(x, "%Y-%m-%d %H:%M")
32-
33-
34-
def convert_to_date(x: str | String | date | datetime | None) -> date:
35-
if x is None:
36-
return date.today()
37-
if isinstance(x, datetime):
38-
return x.date()
39-
if isinstance(x, date):
40-
return x
41-
42-
return datetime.strptime(x, "%Y-%m-%d").date()
43-
44-
4518
Url = String
4619
Date = Annotated[date, BeforeValidator(convert_to_date)]
4720
DateTime = Annotated[datetime, BeforeValidator(convert_to_datetime)]

tests/common_tests.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
11
import warnings
22
from copy import deepcopy
3+
from datetime import date, datetime, time, timedelta, timezone
34
from pathlib import Path
45

6+
import pytest
57
import pytest_mock
8+
import time_machine
69
from bs4 import BeautifulSoup, FeatureNotFound, GuessedAtParserWarning
710
from logprise import logger
811
from requests import Response
912

1013
from smartschool import Smartschool
11-
from smartschool.common import IsSaved, as_float, bs4_html, fill_form, get_all_values_from_form, make_filesystem_safe, save, send_email, xml_to_dict
14+
from smartschool.common import (
15+
IsSaved,
16+
as_float,
17+
bs4_html,
18+
convert_to_date,
19+
convert_to_datetime,
20+
fill_form,
21+
get_all_values_from_form,
22+
make_filesystem_safe,
23+
save,
24+
send_email,
25+
xml_to_dict,
26+
)
27+
from smartschool.exceptions import SmartSchoolParsingError
1228
from smartschool.objects import Student
1329

1430

@@ -160,12 +176,15 @@ def test_fill_form(mocker: pytest_mock.MockerFixture):
160176

161177

162178
def test_missing_name():
163-
"""Test missing name attribute."""
164-
html = BeautifulSoup("""
179+
"""Test for missing name attribute."""
180+
html = BeautifulSoup(
181+
"""
165182
<form>
166183
<input value="test" />
167184
<input value="test2" name="test2"/>
168-
""")
185+
""",
186+
features="html.parser",
187+
)
169188
result = get_all_values_from_form(html, "form")
170189
assert result == [{"name": "test2", "value": "test2"}]
171190

@@ -391,3 +410,35 @@ def test_select_single_multiple_selected_last_wins():
391410
select_element = result[0]
392411

393412
assert select_element["value"] == "high" # Last selected wins
413+
414+
415+
@time_machine.travel("2023-09-01 10:02:03+02:00", tick=False)
416+
def test_convert_to_datetime() -> None:
417+
expected = datetime(2023, 9, 1, 10, 2, 3, tzinfo=timezone(timedelta(hours=2)))
418+
expected_no_time = datetime(2023, 9, 1, 0, 0, 0)
419+
420+
assert convert_to_datetime("2023-09-01T10:02:03+02:00") == expected.astimezone()
421+
assert convert_to_datetime(expected) == expected.astimezone()
422+
assert convert_to_datetime(None) == expected.astimezone()
423+
assert convert_to_datetime("2023-09-01") == expected_no_time.astimezone()
424+
assert convert_to_datetime(expected.date()) == expected_no_time.astimezone()
425+
assert convert_to_datetime("2023-09-01 10:02") == expected.replace(second=0, tzinfo=None).astimezone()
426+
427+
with pytest.raises(ValueError, match="No timezone information found in this date"):
428+
convert_to_datetime(expected.replace(tzinfo=None))
429+
430+
with pytest.raises(SmartSchoolParsingError, match="Cannot convert 'boom' to `datetime`"):
431+
convert_to_datetime("boom")
432+
433+
434+
@time_machine.travel("2023-09-01 10:02:03+02:00", tick=False)
435+
def test_convert_to_date() -> None:
436+
expected = date(2023, 9, 1)
437+
438+
assert convert_to_date("2023-09-01") == expected
439+
assert convert_to_date(None) == expected
440+
assert convert_to_date(expected) == expected
441+
assert convert_to_date(datetime.combine(expected, time.min)) == expected
442+
443+
with pytest.raises(SmartSchoolParsingError, match="Cannot convert 'boom' to `date`"):
444+
convert_to_date("boom")

tests/objects_tests.py

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +0,0 @@
1-
from datetime import date, datetime, time, timedelta, timezone
2-
3-
import pytest
4-
import time_machine
5-
6-
from smartschool.objects import convert_to_date, convert_to_datetime
7-
8-
9-
@time_machine.travel("2023-09-01 10:02:03+02:00", tick=False)
10-
def test_convert_to_datetime() -> None:
11-
expected = datetime(2023, 9, 1, 10, 2, 3, tzinfo=timezone(timedelta(hours=2)))
12-
13-
assert convert_to_datetime("2023-09-01T10:02:03+02:00") == expected
14-
assert convert_to_datetime(expected) == expected
15-
assert convert_to_datetime(None) == expected
16-
17-
with pytest.raises(ValueError, match="No timezone information found in this date"):
18-
convert_to_datetime(expected.replace(tzinfo=None))
19-
20-
assert convert_to_datetime("2023-09-01 10:02") == expected.replace(second=0, tzinfo=None)
21-
22-
23-
@time_machine.travel("2023-09-01 10:02:03+02:00", tick=False)
24-
def test_convert_to_date() -> None:
25-
expected = date(2023, 9, 1)
26-
27-
assert convert_to_date("2023-09-01") == expected
28-
assert convert_to_date(None) == expected
29-
assert convert_to_date(expected) == expected
30-
assert convert_to_date(datetime.combine(expected, time.min)) == expected

0 commit comments

Comments
 (0)