Skip to content

Commit f54851b

Browse files
committed
Add 2022
1 parent 383c2f1 commit f54851b

File tree

7 files changed

+30
-193
lines changed

7 files changed

+30
-193
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Variables for the project
22
# =========================
3-
CONFERENCE ?= ep2023
3+
CONFERENCE ?= ep2022
44
DATA_DIR ?= ./data/public/$(CONFERENCE)/
55

66
# Variables for remote host

README.md

Lines changed: 6 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,9 @@
11
# 🎤 programapi
22

3-
This project powers the **EuroPython 2025** website and Discord bot by downloading, transforming, and serving clean, structured JSON files for sessions, speakers, and the schedule, all pulled from Pretalx.
3+
> [!TIP]
4+
> This Program API as we know it was not implemented until 2024. The data might be incomplete or not fully accurate.
45
5-
Built for transparency. Designed for reuse. Optimized for EuroPython.
6-
7-
---
8-
9-
## 🚀 What This Project Does
10-
11-
1. **Downloads** submission and speaker data from Pretalx.
12-
2. **Transforms** raw data:
13-
- Removes private/irrelevant fields
14-
- Normalizes formats
15-
- Adds computed fields (e.g. URLs, delivery mode)
16-
3. **Serves** the transformed JSON files via a static API.
17-
18-
---
19-
20-
## ⚙️ Installation
21-
22-
1. **Clone the repo**
23-
```bash
24-
git clone https://github.com/EuroPython/programapi.git
25-
cd programapi
26-
```
27-
28-
2. **Install [uv](https://docs.astral.sh/uv/getting-started/installation/)** (fast Python package manager)
29-
30-
3. **Create a Python 3.13 virtual environment**
31-
```bash
32-
uv venv -p 3.13
33-
```
34-
35-
4. **Install dev dependencies**
36-
```bash
37-
make dev
38-
```
39-
40-
5. **Enable pre-commit hooks**
41-
```bash
42-
make pre-commit
43-
```
44-
45-
---
46-
47-
## 🛠️ Configuration
48-
49-
You can update the event year or shortname in [`src/config.py`](src/config.py).
50-
51-
Also, create a `.env` file in the project root and set:
52-
53-
```env
54-
PRETALX_TOKEN=your_api_token_here
55-
```
56-
57-
(Yes, Pretalx has rate limits. Please be nice. 🤪)
58-
59-
---
60-
61-
## 📦 Usage
62-
63-
- Run the **entire pipeline**:
64-
```bash
65-
make all
66-
```
67-
68-
- Run only the **download step**:
69-
```bash
70-
make download
71-
```
72-
73-
- Run only the **transformation step**:
74-
```bash
75-
make transform
76-
```
77-
78-
- (Optional) **Exclude components**:
79-
```bash
80-
make all EXCLUDE="schedule youtube"
81-
```
82-
83-
---
84-
85-
## 🌐 API Endpoints
86-
87-
Hosted at:
88-
89-
```
90-
https://static.europython.eu/programme/ep2025/releases/current
91-
```
92-
93-
| Endpoint | Description |
94-
|---------------------|--------------------------------------------|
95-
| `/speakers.json` | List of confirmed speakers |
96-
| `/sessions.json` | List of confirmed sessions |
97-
| `/schedule.json` | Finalized conference schedule *(TBA)* |
98-
99-
---
100-
101-
## 📖 Schema Documentation
102-
103-
Looking for field definitions and examples?
104-
Check out the 👉 [`data/examples/README.md`](data/examples/README.md) for a full schema reference with example payloads and explanations.
105-
106-
---
107-
108-
## 💬 Questions? Feedback?
109-
110-
Feel free to open an issue or reach us at [[email protected]](mailto:[email protected]). We love contributors 💜
111-
112-
---
113-
114-
📅 Last updated for: **EuroPython 2025**
6+
## Accessing the Program API
7+
- https://static.europython.eu/programme/ep2022/releases/current/speakers.json
8+
- https://static.europython.eu/programme/ep2022/releases/current/sessions.json
9+
- https://static.europython.eu/programme/ep2022/releases/current/schedule.json

src/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66

77
class Config:
8-
event = "europython-2023"
9-
event_dir_name = "ep2023"
8+
event = "europython-2022"
9+
event_dir_name = "ep2022"
1010
project_root = Path(__file__).resolve().parents[1]
1111
raw_path = Path(f"{project_root}/data/raw/{event_dir_name}")
1212
public_path = Path(f"{project_root}/data/public/{event_dir_name}")

src/download.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
parser.add_argument(
1212
"-e",
1313
"--exclude",
14-
choices=["schedule", "youtube"],
14+
choices=["schedule"],
1515
action="append",
1616
help="Exclude certain resources from download.",
1717
)
@@ -32,9 +32,6 @@
3232
"speakers?questions=all",
3333
]
3434

35-
if "youtube" not in exclude:
36-
resources.append("p/youtube")
37-
3835
Config.raw_path.mkdir(parents=True, exist_ok=True)
3936

4037
for resource in resources:

src/misc.py

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
class SpeakerQuestion:
55
affiliation = "Company / Institute"
66
homepage = "Homepage"
7-
twitter_mastodon = "Twitter / Mastodon handle(s)"
7+
twitter = "Twitter handle"
88
linkedin = "LinkedIn"
99
gitx = "Github/Gitlab"
1010

1111

1212
class SubmissionQuestion:
13-
tweet = "Abstract as a tweet / toot"
13+
tweet = "Abstract as a tweet"
1414
delivery = "My presentation can be delivered"
1515
level = "Expected audience expertise"
1616

@@ -29,26 +29,17 @@ class Room(Enum):
2929
Rooms at the conference venue, this can change year to year
3030
"""
3131

32-
# Tutorial/workshop rooms
33-
club_a = "Club A"
34-
club_b = "Club B"
35-
club_c = "Club C"
36-
club_d = "Club D"
37-
club_e = "Club E"
38-
club_h = "Club H"
39-
40-
# Conference rooms
41-
forum_hall = "PyCharm (Forum Hall)"
42-
terrace_2a = "Terrace 2A"
43-
terrace_2b = "Terrace 2B"
44-
north_hall = "North Hall"
45-
south_hall_2a = "South Hall 2A"
46-
south_hall_2b = "South Hall 2B"
47-
exhibit_hall = "Exhibit Hall"
48-
49-
# Other rooms
50-
open_space = "Open Space"
51-
32+
liffey_a = "Liffey A"
33+
liffey_b = "Liffey B"
34+
liffey_hall_1 = "Liffey Hall 1"
35+
liffey_hall_2 = "Liffey Hall 2"
36+
wicklow_hall_1 = "Wicklow Hall 1"
37+
wicklow_hall_2 = "Wicklow Hall 2"
38+
wicklow_hall_2a = "Wicklow Hall 2A"
39+
wicklow_hall_2b = "Wicklow Hall 2B"
40+
the_auditorium = "The Auditorium"
41+
liffey_meeting_room_2 = "Liffey Meeting Room 2"
42+
forum = "Forum"
5243

5344
class EventType(Enum):
5445
SESSION = "session"

src/models/europython.py

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,8 @@ def extract_answers(cls, values) -> dict:
5151
if answer.question_text == SpeakerQuestion.homepage:
5252
values["homepage"] = answer.answer_text
5353

54-
if answer.question_text == SpeakerQuestion.twitter_mastodon:
55-
if extracted := cls.extract_twitter_url(answer.answer_text):
56-
values["twitter_url"] = extracted
57-
elif extracted := cls.extract_mastodon_url(answer.answer_text):
58-
values["mastodon_url"] = extracted
54+
if answer.question_text == SpeakerQuestion.twitter:
55+
values["twitter_url"] = cls.extract_twitter_url(answer.answer_text)
5956

6057
if answer.question_text == SpeakerQuestion.linkedin:
6158
values["linkedin_url"] = cls.extract_linkedin_url(answer.answer_text)
@@ -93,43 +90,6 @@ def extract_twitter_url(text: str) -> str | None:
9390
print(f"Invalid Twitter URL: {cleaned}")
9491
return None
9592

96-
@staticmethod
97-
def extract_mastodon_url(text: str) -> str | None:
98-
"""
99-
Extracts a Mastodon profile URL from the given text.
100-
Supports formats like:
101-
- @username@instance
102-
- username@instance
103-
- instance/@username
104-
- instance/@username@instance (with redirect)
105-
Returns: https://<instance>/@<username>
106-
"""
107-
cleaned = EuroPythonSpeaker._clean_social_input(text)
108-
if not cleaned:
109-
print(f"Invalid Mastodon URL: {text}")
110-
return None
111-
112-
# instance/@username
113-
match = re.match(r"^([\w\.-]+)/@([\w\.-]+)$", cleaned)
114-
if match:
115-
instance, username = match.groups()
116-
return f"https://{instance}/@{username}"
117-
118-
parts = cleaned.split("@")
119-
if len(parts) == 3: # instance@username@instance
120-
_, username, instance = parts
121-
elif len(parts) == 2: # username@instance
122-
username, instance = parts
123-
else:
124-
print(f"Invalid Mastodon URL: {cleaned}")
125-
return None
126-
127-
if username and instance:
128-
return f"https://{instance}/@{username}"
129-
130-
print(f"Invalid Mastodon URL: {cleaned}")
131-
return None
132-
13393
@staticmethod
13494
def extract_linkedin_url(text: str) -> str | None:
13595
"""
@@ -291,8 +251,10 @@ def extract_answers(cls, values) -> dict:
291251
values["tweet"] = answer.answer_text
292252

293253
if answer.question_text == SubmissionQuestion.delivery:
294-
if "in-person" in answer.answer_text:
254+
if answer.answer_text == "in-person at the conference":
295255
values["delivery"] = "in-person"
256+
elif answer.answer_text == "in-person or remote":
257+
values["delivery"] = "in-person or remote"
296258
else:
297259
values["delivery"] = "remote"
298260

src/transform.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
parser.add_argument(
2020
"-e",
2121
"--exclude",
22-
choices=["schedule", "youtube"],
22+
choices=["schedule"],
2323
action="append",
2424
help="Exclude certain data from transformation.",
2525
)
@@ -40,15 +40,7 @@
4040
)
4141
print(" done.")
4242

43-
if "youtube" not in exclude:
44-
print(
45-
f"Parsing YouTube data from {Config.raw_path}/youtube_latest.json...",
46-
end="",
47-
)
48-
youtube_data = Parse.youtube(Config.raw_path / "youtube_latest.json")
49-
print(" done.")
50-
else:
51-
youtube_data = {}
43+
youtube_data = {}
5244

5345
print("\nComputing timing relationships...", end="")
5446
TimingRelationships.compute(pretalx_submissions.values())

0 commit comments

Comments
 (0)