Skip to content

Commit 64684eb

Browse files
committed
Increase timeout, add GET to trigger fetch on demand
1 parent eefe7e8 commit 64684eb

2 files changed

Lines changed: 87 additions & 27 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
compose.override.yml
33
__pycache__
44
.venv/
5+
*.swp
6+
*.pyc

main.py

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
import sys
32
from contextlib import asynccontextmanager
43
from datetime import datetime, UTC
54
from typing import Annotated, Any
@@ -39,14 +38,20 @@ def transform_card_number_to_mifare(card_number: str):
3938

4039
class User(BaseModel):
4140
uid: Annotated[str, Field(validation_alias="username")]
42-
mifare_card_ids: Annotated[list[str], Field(validation_alias=AliasPath("attributes", "mifareCardId"))] = []
43-
unique_card_ids: Annotated[list[str], Field(validation_alias=AliasPath("attributes", "uniquecardId"))] = []
41+
mifare_card_ids: Annotated[
42+
list[str], Field(validation_alias=AliasPath("attributes", "mifareCardId"))
43+
] = []
44+
unique_card_ids: Annotated[
45+
list[str], Field(validation_alias=AliasPath("attributes", "uniquecardId"))
46+
] = []
4447
membership_expiration: Annotated[
4548
int,
46-
Field(validation_alias=AliasPath("attributes", "membershipExpirationTimestamp"))
49+
Field(
50+
validation_alias=AliasPath("attributes", "membershipExpirationTimestamp")
51+
),
4752
]
4853

49-
@field_validator("mifare_card_ids", "unique_card_ids", mode='plain')
54+
@field_validator("mifare_card_ids", "unique_card_ids", mode="plain")
5055
def use_default_for_missing_cards(cls, v) -> str:
5156
if v is None:
5257
raise PydanticUseDefault()
@@ -57,7 +62,7 @@ class Settings(BaseSettings):
5762
authentik_token: str | None = None
5863
authentik_token_file: str | None = None
5964

60-
@model_validator(mode='after')
65+
@model_validator(mode="after")
6166
def set_token(self) -> "Settings":
6267
if self.authentik_token:
6368
return self
@@ -96,24 +101,28 @@ def auth():
96101

97102
async def fetch() -> list[User]:
98103
async with httpx.AsyncClient(
99-
headers={"Authorization": f"Bearer {config.authentik_token}"},
100-
timeout=30.0,
101-
) as client:
104+
headers={"Authorization": f"Bearer {config.authentik_token}"},
105+
timeout=60.0,
106+
) as client:
102107
url = (
103108
"https://auth.apps.hskrk.pl/api/v3/core/users/?"
104-
"attributes={\"membershipExpirationTimestamp__gt\": 100}&page_size=50"
109+
'attributes={"membershipExpirationTimestamp__gt": 100}&page_size=50'
105110
)
106111
response = await client.get(url)
107112
parsed_response = response.json()
108113
results = parsed_response["results"]
109-
while parsed_response["pagination"]["next"]:
110-
response = await client.get(f"{url}&page={parsed_response["pagination"]["next"]}")
111-
parsed_response = response.json()
112-
results += parsed_response["results"]
113-
return [
114-
User(**u)
115-
for u in results
116-
]
114+
next_page = parsed_response["pagination"]["next"]
115+
while next_page:
116+
try:
117+
response = await client.get(f"{url}&page={next_page}")
118+
parsed_response = response.json()
119+
results += parsed_response["results"]
120+
next_page = parsed_response["pagination"]["next"]
121+
except httpx.ReadTimeout as t:
122+
logging.warning(
123+
f"[HTTP] Timeout reached on fetching users data, page: {next_page}, {t}"
124+
)
125+
return [User(**u) for u in results]
117126

118127

119128
@repeat_every(seconds=300)
@@ -135,30 +144,46 @@ async def fetch_users():
135144
users_by_card = {
136145
**{
137146
mifare.lower(): user
138-
for user in users for mifare in user.mifare_card_ids
147+
for user in users
148+
for mifare in user.mifare_card_ids
139149
},
140150
**{
141151
transform_card_number_to_unique(mifare).lower(): user
142-
for user in users for mifare in user.mifare_card_ids
152+
for user in users
153+
for mifare in user.mifare_card_ids
143154
},
144155
**{
145156
unique.lower(): user
146-
for user in users for unique in user.unique_card_ids
157+
for user in users
158+
for unique in user.unique_card_ids
147159
},
148160
**{
149161
transform_card_number_to_mifare(unique).lower(): user
150-
for user in users for unique in user.unique_card_ids
162+
for user in users
163+
for unique in user.unique_card_ids
151164
},
152165
}
153-
logging.debug(f"Fetched {len(users)} users ({len(users_by_card)} cards) from Authentik")
166+
logging.debug(
167+
f"Fetched {len(users)} users ({len(users_by_card)} cards) from Authentik"
168+
)
154169

155170

156171
@app.get("/users/-/stats")
157172
async def get_user_stats():
158173
return {
159-
"last_success_run": users_last_success_run.isoformat().replace("+00:00", "Z") if users_last_success_run else None,
160-
"last_failed_run": users_last_failed_run.isoformat().replace("+00:00", "Z") if users_last_failed_run else None,
161-
"last_failed_reason": str(users_last_failed_reason) if users_last_failed_reason else None,
174+
"last_success_run": (
175+
users_last_success_run.isoformat().replace("+00:00", "Z")
176+
if users_last_success_run
177+
else None
178+
),
179+
"last_failed_run": (
180+
users_last_failed_run.isoformat().replace("+00:00", "Z")
181+
if users_last_failed_run
182+
else None
183+
),
184+
"last_failed_reason": (
185+
str(users_last_failed_reason) if users_last_failed_reason else None
186+
),
162187
"users": {
163188
"count": len(users),
164189
},
@@ -178,6 +203,37 @@ async def get_user_by_card(card_id: str):
178203
return user
179204

180205

206+
@app.get("/user/-/sync")
207+
async def sync_on_demand():
208+
global users_last_success_run
209+
current_run = datetime.now(tz=UTC)
210+
diff_seconds = (current_run - users_last_success_run).seconds
211+
if diff_seconds < 30:
212+
logging.debug("Skipping fetching users, data fresh")
213+
return {
214+
"cached": True,
215+
"last_success_run": (
216+
users_last_success_run.isoformat().replace("+00:00", "Z")
217+
if users_last_success_run
218+
else None
219+
),
220+
"users": {
221+
"count": len(users),
222+
},
223+
"last_failed_run": (
224+
users_last_failed_run.isoformat().replace("+00:00", "Z")
225+
if users_last_failed_run
226+
else None
227+
),
228+
}
229+
else:
230+
logging.debug(
231+
"Last successful fetch {diff_seconds}s before. Fetching on demand"
232+
)
233+
fetch_users()
234+
return get_user_stats()
235+
236+
181237
@app.websocket("/ws")
182238
async def websocket_endpoint(websocket: WebSocket):
183239
logging.debug("[WS] New connection")
@@ -199,7 +255,9 @@ async def websocket_endpoint(websocket: WebSocket):
199255
{"status": "error", "error": "user not found"}
200256
)
201257
else:
202-
logging.info("[WS] User %s (%s) found", user.uid, kwargs.get("card_id"))
258+
logging.info(
259+
"[WS] User %s (%s) found", user.uid, kwargs.get("card_id")
260+
)
203261
await websocket.send_json(
204262
{"status": "ok", "object": user.model_dump()}
205263
)

0 commit comments

Comments
 (0)