Skip to content

Commit f8ccbfe

Browse files
authored
Add more tests and GH action to run tests (#5)
* Add GH action for tests * Move the assertion into main function * recreate lock file to fix pytest deprecation warning * migrate to pydantic v2 * add speakers example test * remove redundant conversion
1 parent b27401e commit f8ccbfe

File tree

7 files changed

+92
-69
lines changed

7 files changed

+92
-69
lines changed

.github/workflows/tests.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Tests
2+
3+
on: [push, pull_request, workflow_dispatch]
4+
5+
jobs:
6+
tests:
7+
name: Run tests
8+
runs-on: ubuntu-latest
9+
timeout-minutes: 10
10+
11+
steps:
12+
- name: Check out repository
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python 3
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: '3.x'
19+
cache: "pip"
20+
21+
- name: Install dependencies
22+
run: make deps/pre install
23+
24+
- name: Run pytest
25+
run: make test

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ test:
2525
pre-commit:
2626
pre-commit install
2727
pre-commit run --all-files
28+
29+
clean:
30+
git clean -xdf

data/examples/output/speakers.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"biography": null,
66
"avatar": "https://pretalx.com/media/avatars/picture.jpg",
77
"slug": "a-speaker",
8-
"submissions": [],
8+
"submissions": ["A8CD3F"],
99
"affiliation": "A Company",
1010
"homepage": null,
1111
"twitter": null,

requirements.in

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
pdbpp
2-
black
3-
isort
42
pytest
53
pre-commit
64

requirements.txt

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@
44
#
55
# pip-compile
66
#
7-
attrs==21.4.0
8-
# via pytest
9-
black==22.3.0
10-
# via -r requirements.in
11-
certifi==2021.10.8
7+
annotated-types==0.6.0
8+
# via pydantic
9+
attrs==23.2.0
10+
# via wmctrl
11+
certifi==2024.2.2
1212
# via requests
1313
cfgv==3.4.0
1414
# via pre-commit
15-
charset-normalizer==2.0.12
15+
charset-normalizer==3.3.2
1616
# via requests
17-
click==8.1.3
18-
# via black
1917
distlib==0.3.8
2018
# via virtualenv
2119
fancycompleter==0.9.1
@@ -24,61 +22,51 @@ filelock==3.14.0
2422
# via virtualenv
2523
identify==2.5.36
2624
# via pre-commit
27-
idna==3.3
25+
idna==3.7
2826
# via requests
29-
iniconfig==1.1.1
27+
iniconfig==2.0.0
3028
# via pytest
31-
isort==5.10.1
32-
# via -r requirements.in
33-
mypy-extensions==0.4.3
34-
# via black
3529
nodeenv==1.8.0
3630
# via pre-commit
37-
packaging==21.3
31+
packaging==24.0
3832
# via pytest
39-
pathspec==0.9.0
40-
# via black
4133
pdbpp==0.10.3
4234
# via -r requirements.in
43-
platformdirs==2.5.2
44-
# via
45-
# black
46-
# virtualenv
47-
pluggy==1.0.0
35+
platformdirs==4.2.2
36+
# via virtualenv
37+
pluggy==1.5.0
4838
# via pytest
4939
pre-commit==3.7.1
5040
# via -r requirements.in
51-
py==1.11.0
52-
# via pytest
53-
pydantic==1.9.0
41+
pydantic==2.7.1
5442
# via -r requirements.in
55-
pygments==2.12.0
43+
pydantic-core==2.18.2
44+
# via pydantic
45+
pygments==2.18.0
5646
# via pdbpp
57-
pyparsing==3.0.9
58-
# via packaging
5947
pyrepl==0.9.0
6048
# via fancycompleter
61-
pytest==7.1.2
49+
pytest==8.2.1
6250
# via -r requirements.in
63-
python-slugify==6.1.2
51+
python-slugify==8.0.4
6452
# via -r requirements.in
6553
pyyaml==6.0.1
6654
# via pre-commit
67-
requests==2.27.1
55+
requests==2.31.0
6856
# via -r requirements.in
6957
text-unidecode==1.3
7058
# via python-slugify
71-
tomli==2.0.1
72-
# via pytest
7359
tqdm==4.66.4
7460
# via -r requirements.in
75-
typing-extensions==4.2.0
76-
# via pydantic
77-
urllib3==1.26.9
61+
typing-extensions==4.11.0
62+
# via
63+
# pydantic
64+
# pydantic-core
65+
urllib3==2.2.1
7866
# via requests
79-
virtualenv==20.21.1
67+
virtualenv==20.26.2
8068
# via pre-commit
81-
wmctrl==0.4
69+
wmctrl==0.5
8270
# via pdbpp
8371

8472
# The following packages are considered to be unsafe in a requirements file:

src/transform.py

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import json
22
from datetime import datetime
33

4-
from pydantic import BaseModel, Field
5-
from pydantic.class_validators import root_validator
4+
from pydantic import BaseModel, Field, model_validator
65
from slugify import slugify
76

87
from src.config import Config
@@ -35,7 +34,8 @@ class PretalxAnswer(BaseModel):
3534
submission_id: str | None
3635
speaker_id: str | None
3736

38-
@root_validator(pre=True)
37+
@model_validator(mode="before")
38+
@classmethod
3939
def extract(cls, values):
4040
values["question_text"] = values["question"]["question"]["en"]
4141
values["answer_text"] = values["answer"]
@@ -60,11 +60,12 @@ class PretalxSpeaker(BaseModel):
6060
twitter: str | None = None
6161
mastodon: str | None = None
6262

63-
@root_validator(pre=True)
63+
@model_validator(mode="before")
64+
@classmethod
6465
def extract(cls, values):
6566
values["slug"] = slugify(values["name"])
6667

67-
answers = [PretalxAnswer.parse_obj(ans) for ans in values["answers"]]
68+
answers = [PretalxAnswer.model_validate(ans) for ans in values["answers"]]
6869

6970
for answer in answers:
7071
if answer.question_text == SpeakerQuestion.affiliation:
@@ -107,16 +108,16 @@ class PretalxSubmission(BaseModel):
107108
start: datetime | None = None
108109
end: datetime | None = None
109110

110-
# TODO: once we have schedule data then we can prefill those in the code
111-
# here
111+
# TODO: once we have schedule data then we can prefill those in the code here
112112
talks_in_parallel: list[str] | None = None
113113
talks_after: list[str] | None = None
114114
next_talk_code: str | None = None
115115
prev_talk_code: str | None = None
116116

117117
website_url: str | None = None
118118

119-
@root_validator(pre=True)
119+
@model_validator(mode="before")
120+
@classmethod
120121
def extract(cls, values):
121122
# # SubmissionType and Track have localised names. For this project we
122123
# # only care about their english versions, so we can extract them here
@@ -132,7 +133,7 @@ def extract(cls, values):
132133

133134
values["speakers"] = sorted([s["code"] for s in values["speakers"]])
134135

135-
answers = [PretalxAnswer.parse_obj(ans) for ans in values["answers"]]
136+
answers = [PretalxAnswer.model_validate(ans) for ans in values["answers"]]
136137

137138
for answer in answers:
138139
# TODO if we need any other questions
@@ -148,6 +149,10 @@ def extract(cls, values):
148149
if answer.question_text == SubmissionQuestion.level:
149150
values["level"] = answer.answer_text.lower()
150151

152+
# Convert duration to string for model validation
153+
if isinstance(values["duration"], int):
154+
values["duration"] = str(values["duration"])
155+
151156
slug = slugify(values["title"])
152157
values["slug"] = slug
153158
values["website_url"] = f"https://ep2024.europython.eu/session/{slug}"
@@ -173,11 +178,7 @@ def parse_submissions() -> list[PretalxSubmission]:
173178
"""
174179
with open(Config.raw_path / "submissions_latest.json") as fd:
175180
js = json.load(fd)
176-
subs = []
177-
for item in js:
178-
sub = PretalxSubmission.parse_obj(item)
179-
subs.append(sub)
180-
181+
subs = [PretalxSubmission.model_validate(item) for item in js]
181182
return subs
182183

183184

@@ -187,11 +188,7 @@ def parse_speakers() -> list[PretalxSpeaker]:
187188
"""
188189
with open(Config.raw_path / "speakers_latest.json") as fd:
189190
js = json.load(fd)
190-
speakers = []
191-
for item in js:
192-
speaker = PretalxSpeaker.parse_obj(item)
193-
speakers.append(speaker)
194-
191+
speakers = [PretalxSpeaker.model_validate(item) for item in js]
195192
return speakers
196193

197194

@@ -217,7 +214,7 @@ def save_publishable_sessions():
217214

218215
publishable = publishable_submissions()
219216

220-
data = {k: v.dict() for k, v in publishable.items()}
217+
data = {k: v.model_dump() for k, v in publishable.items()}
221218
with open(path, "w") as fd:
222219
json.dump(data, fd, indent=2)
223220

@@ -228,16 +225,16 @@ def save_publishable_speakers():
228225
publishable = publishable_submissions()
229226
speakers = publishable_speakers(publishable.keys())
230227

231-
data = {k: v.dict() for k, v in speakers.items()}
228+
data = {k: v.model_dump() for k, v in speakers.items()}
232229
with open(path, "w") as fd:
233230
json.dump(data, fd, indent=2)
234231

235232

236-
assert len(set(s.slug for s in publishable_submissions().values())) == len(
237-
publishable_submissions()
238-
)
239-
240233
if __name__ == "__main__":
234+
print("Checking for duplicate slugs...")
235+
assert len(set(s.slug for s in publishable_submissions().values())) == len(
236+
publishable_submissions()
237+
)
241238
print("Saving publishable data...")
242239
save_publishable_sessions()
243240
save_publishable_speakers()

tests/test_examples_are_up_to_date.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22

3-
from src.transform import PretalxSubmission
3+
from src.transform import PretalxSpeaker, PretalxSubmission
44

55
with open("./data/examples/pretalx/submissions.json") as fd:
66
pretalx_submissions = json.load(fd)
@@ -13,9 +13,21 @@ def test_sessions_example():
1313
assert pretalx_submissions[0]["code"] == "A8CD3F"
1414
pretalx = pretalx_submissions[0]
1515

16-
transformed = PretalxSubmission.parse_obj(pretalx)
16+
transformed = PretalxSubmission.model_validate(pretalx)
1717

1818
with open("./data/examples/output/sessions.json") as fd:
1919
sessions = json.load(fd)
2020

21-
assert transformed.dict() == sessions["A8CD3F"]
21+
assert transformed.model_dump() == sessions["A8CD3F"]
22+
23+
24+
def test_speakers_example():
25+
assert pretalx_speakers[0]["code"] == "F3DC8A"
26+
pretalx = pretalx_speakers[0]
27+
28+
transformed = PretalxSpeaker.model_validate(pretalx)
29+
30+
with open("./data/examples/output/speakers.json") as fd:
31+
speakers = json.load(fd)
32+
33+
assert transformed.model_dump() == speakers["F3DC8A"]

0 commit comments

Comments
 (0)