Skip to content

Commit 21e6a3e

Browse files
authored
Support development without access to EP-internal infrastructure (#222)
* Silence superfluous Discord.py warning * Fix type annotation * Add pretix-mock.py * Update cache file documentation * Don't require uv for pre-commit
1 parent d6410f0 commit 21e6a3e

File tree

6 files changed

+221
-5
lines changed

6 files changed

+221
-5
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ repos:
2424
# run pytest with low verbosity (no header, no tracebacks)
2525
name: pytest
2626
language: system
27-
entry: uv run --dev pytest --no-header --tb=no
27+
entry: pytest --no-header --tb=no
2828
pass_filenames: false
2929
always_run: true

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ A suite of tools for managing the EuroPython Conference Discord server:
55
* [src/europython_discord](./src/europython_discord): Discord bot
66
* [scripts/configure-guild.py](./scripts/configure-guild.py): Configure channels and roles of a Discord server
77
* [scripts/export-members.py](./scripts/export-members.py): Export a list of all server members and their roles
8+
* [scripts/pretix-mock.py](./scripts/pretix-mock.py): Mock Pretix server for development purposes
89

910
The scripts work standalone and only require an Auth token. Please find more documentation in the respective files.
1011

@@ -40,10 +41,10 @@ Included example configuration files:
4041
* [`prod-config.toml`](./prod-config.toml) or [`test-config.toml`](./test-config.toml): Prod/Test configuration
4142
* [`test-livestreams.toml`](./test-livestreams.toml): Test livestream URL configuration
4243

43-
Used cache and log files (will be created if necessary):
44+
Cache files (will be created if necessary):
4445

46+
* `registered_log.txt`: List of registered users
4547
* `pretix_cache.json`: Local cache of Pretix ticket data
46-
* `registered_log.txt`: Log of registered users
4748
* `schedule_cache.json`: Local cache of [programapi](https://github.com/europython/programapi) schedule
4849

4950
## Setup

scripts/pretix-mock.py

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
"""Mock Pretix HTTP Server."""
2+
3+
import argparse
4+
import http.server
5+
import json
6+
import logging
7+
import socketserver
8+
import sys
9+
from typing import Any
10+
from urllib.parse import urlparse
11+
12+
logger = logging.getLogger(__name__)
13+
14+
DESCRIPTION = """\
15+
Mock Pretix HTTP Server with the following orders:
16+
17+
Order 'AAAAA' (paid)
18+
- Business Combined Ticket for 'Jane Doe'
19+
- Business Tutorial Ticket for 'John Doe'
20+
- Childcare
21+
22+
Order 'BBB22' (paid)
23+
- Volunteer Ticket for 'Minta János'
24+
- Speaker Ticket for 'Minta Kata'
25+
- T-Shirt
26+
27+
Order 'CCC33' (paid)
28+
- Personal Remote Ticket for 'Martina Mustermann'
29+
30+
Order 'DDDD44' (paid)
31+
- Sponsor Ticket for Seán Ó Rudaí
32+
- T-Shirt
33+
34+
Order 'EEE55' (payment pending)
35+
- Personal Late Conference Ticket for 'Numerius Negidius'
36+
"""
37+
38+
PRETIX_ITEMS = {
39+
"count": 10,
40+
"next": None,
41+
"results": [
42+
{
43+
"id": 1,
44+
"name": {"en": "Business"},
45+
"variations": [
46+
{"id": 101, "value": {"en": "Conference"}},
47+
{"id": 102, "value": {"en": "Tutorials"}},
48+
{"id": 103, "value": {"en": "Combined (Conference + Tutorials)"}},
49+
{"id": 104, "value": {"en": "Late Conference"}},
50+
{"id": 105, "value": {"en": "Late Combined"}},
51+
],
52+
},
53+
{
54+
"id": 2,
55+
"name": {"en": "Personal"},
56+
"variations": [
57+
{"id": 201, "value": {"en": "Conference"}},
58+
{"id": 202, "value": {"en": "Tutorials"}},
59+
{"id": 203, "value": {"en": "Combined (Conference + Tutorials)"}},
60+
{"id": 204, "value": {"en": "Late Conference"}},
61+
{"id": 205, "value": {"en": "Late Combined"}},
62+
],
63+
},
64+
{
65+
"id": 3,
66+
"name": {"en": "Education"},
67+
"variations": [
68+
{"id": 301, "value": {"en": "Conference"}},
69+
{"id": 302, "value": {"en": "Tutorials"}},
70+
{"id": 303, "value": {"en": "Combined (Conference + Tutorials)"}},
71+
],
72+
},
73+
{
74+
"id": 4,
75+
"name": {"en": "Community Contributors"},
76+
"variations": [
77+
{"id": 401, "value": {"en": "Volunteer"}},
78+
{"id": 402, "value": {"en": "Python Community Organiser"}},
79+
],
80+
},
81+
{
82+
"id": 5,
83+
"name": {"en": "Childcare (Free for children aged 18 months and older)"},
84+
"variations": [],
85+
},
86+
{
87+
"id": 6,
88+
"name": {"en": "Presenter"},
89+
"variations": [
90+
{"id": 601, "value": {"en": "Speaker"}},
91+
{"id": 602, "value": {"en": "Tutorial & Workshop Presenter"}},
92+
{"id": 603, "value": {"en": "Keynote Presenter"}},
93+
],
94+
},
95+
{"id": 7, "name": {"en": "T-shirt (free)"}, "variations": []},
96+
{"id": 8, "name": {"en": "Grant ticket"}, "variations": []},
97+
{"id": 9, "name": {"en": "Sponsor Conference Pass"}, "variations": []},
98+
{
99+
"id": 10,
100+
"name": {"en": "Remote Participation Ticket"},
101+
"variations": [
102+
{"id": 1001, "value": {"en": "Business Remote"}},
103+
{"id": 1002, "value": {"en": "Personal Remote"}},
104+
],
105+
},
106+
],
107+
}
108+
PRETIX_ORDERS = {
109+
"count": 5,
110+
"next": None,
111+
"results": [
112+
{
113+
"code": "AAA11",
114+
"status": "p",
115+
"positions": [
116+
{"order": "AAA11", "item": 1, "variation": 103, "attendee_name": "Jane Doe"},
117+
{"order": "AAA11", "item": 1, "variation": 102, "attendee_name": "John Doe"},
118+
{"order": "AAA11", "item": 5, "variation": None, "attendee_name": None},
119+
],
120+
},
121+
{
122+
"code": "BBB22",
123+
"status": "p",
124+
"positions": [
125+
{"order": "BBB22", "item": 4, "variation": 401, "attendee_name": "Minta János"},
126+
{"order": "BBB22", "item": 6, "variation": 601, "attendee_name": "Minta Kata"},
127+
{"order": "BBB22", "item": 7, "variation": None, "attendee_name": None},
128+
],
129+
},
130+
{
131+
"code": "CCC33",
132+
"status": "p",
133+
"positions": [
134+
{
135+
"order": "CCC33",
136+
"item": 10,
137+
"variation": 1002,
138+
"attendee_name": "Martina Mustermann",
139+
}
140+
],
141+
},
142+
{
143+
"code": "DDD44",
144+
"status": "p",
145+
"positions": [
146+
{"order": "DDD44", "item": 9, "variation": None, "attendee_name": "Seán Ó Rudaí"},
147+
{"order": "DDD44", "item": 7, "variation": None, "attendee_name": None},
148+
],
149+
},
150+
{
151+
"code": "EEE55",
152+
"status": "n",
153+
"positions": [
154+
{
155+
"order": "EEE55",
156+
"item": 2,
157+
"variation": 204,
158+
"attendee_name": "Numerius Negidius",
159+
}
160+
],
161+
},
162+
],
163+
}
164+
165+
166+
class RequestHandler(http.server.BaseHTTPRequestHandler):
167+
def do_GET(self) -> None: # noqa: N802 (function name should be lowercase)
168+
"""Handle GET requests."""
169+
path = urlparse(self.path).path # strip query parameters
170+
171+
path_to_response_body = {
172+
"/items.json": PRETIX_ITEMS,
173+
"/orders.json": PRETIX_ORDERS,
174+
}
175+
response_body = path_to_response_body.get(path)
176+
if response_body is None:
177+
self.send_error(http.HTTPStatus.NOT_FOUND, "Not Found")
178+
logger.warning(f"GET {path} - 404")
179+
else:
180+
self.send_response(http.HTTPStatus.OK, "OK")
181+
self.send_header("Content-type", "application/json; charset=utf-8")
182+
self.end_headers()
183+
self.wfile.write(json.dumps(response_body).encode("utf-8"))
184+
logger.info(f"GET {path} - 200")
185+
186+
def log_message(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 (typing.Any)
187+
pass # disable built-in logging
188+
189+
190+
def main(args: list[str] | None) -> None:
191+
parser = argparse.ArgumentParser(
192+
description=DESCRIPTION,
193+
formatter_class=argparse.RawTextHelpFormatter,
194+
)
195+
parser.add_argument("--port", type=int, default=8080, help="Port to listen on")
196+
197+
args = parser.parse_args(args)
198+
199+
with socketserver.ThreadingTCPServer(("localhost", args.port), RequestHandler) as httpd:
200+
logger.info("Serving at localhost:%d", args.port)
201+
httpd.serve_forever()
202+
203+
204+
if __name__ == "__main__":
205+
logging.basicConfig(
206+
level=logging.INFO,
207+
stream=sys.stdout,
208+
format="%(asctime)s - %(levelname)s - %(message)s",
209+
)
210+
main(sys.argv[1:])

src/europython_discord/bot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
from europython_discord.registration.cog import RegistrationCog
2121
from europython_discord.registration.config import RegistrationConfig
2222

23+
# silence warning about missing discord voice support
24+
# https://github.com/Rapptz/discord.py/issues/1719#issuecomment-437703581
25+
discord.VoiceClient.warn_nacl = False
26+
2327
_logger = logging.getLogger(__name__)
2428

2529

src/europython_discord/program_notifications/program_connector.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ async def fetch_schedule(self) -> None:
8484
self.sessions_by_day = await self.parse_schedule(schedule)
8585
_logger.info("Schedule parsed and loaded.")
8686

87-
async def _get_schedule_from_cache(self) -> dict[date, list[Session]]:
87+
async def _get_schedule_from_cache(self) -> dict[date, list[Session]] | None:
8888
"""Get the schedule data from the cache file."""
8989
try:
9090
_logger.info(f"Getting schedule from cache file {self._cache_file}...")
@@ -95,6 +95,7 @@ async def _get_schedule_from_cache(self) -> dict[date, list[Session]]:
9595

9696
except FileNotFoundError:
9797
_logger.exception("Schedule cache file not found and no schedule is already loaded.")
98+
return None
9899

99100
async def _get_now(self) -> datetime:
100101
"""Get the current time in the conference timezone."""

test-config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ registration_form_channel_name = "registration-form"
55
registration_help_channel_name = "registration-help"
66
registration_log_channel_name = "registration-log"
77

8-
pretix_base_url = "https://pretix.eu/api/v1/organizers/europython/events/ep2025"
8+
pretix_base_url = "http://localhost:8080"
99

1010
registered_cache_file = "registered_log.txt"
1111
pretix_cache_file = "pretix_cache.json"

0 commit comments

Comments
 (0)