Skip to content

Commit 3ec21f5

Browse files
committed
fix: use plain datetime for model fields (server returns naive datetimes)
1 parent 6eb0a2a commit 3ec21f5

3 files changed

Lines changed: 43 additions & 35 deletions

File tree

justfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ generate:
66
--input-file-type openapi \
77
--output-model-type pydantic_v2.BaseModel \
88
--output stackcoin/stackcoin/models.py \
9-
--target-python-version 3.13
9+
--target-python-version 3.13 \
10+
--output-datetime-class datetime
1011
uvx ruff format stackcoin/
1112

1213
dev:

stackcoin/stackcoin/gateway.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
class Event(BaseModel):
1414
"""A StackCoin event received via the gateway."""
15+
1516
id: int
1617
type: str
1718
data: dict[str, Any]
@@ -20,21 +21,21 @@ class Event(BaseModel):
2021

2122
class Gateway:
2223
"""WebSocket gateway for receiving real-time StackCoin events.
23-
24+
2425
Usage::
25-
26+
2627
gateway = stackcoin.Gateway(
2728
ws_url="ws://localhost:4000/bot/websocket",
2829
token="...",
2930
)
30-
31+
3132
@gateway.on("request.accepted")
3233
async def handle_accepted(event: stackcoin.Event):
3334
print(event.data["request_id"])
34-
35+
3536
await gateway.connect()
3637
"""
37-
38+
3839
def __init__(
3940
self,
4041
ws_url: str,
@@ -58,9 +59,11 @@ def last_event_id(self) -> int:
5859

5960
def on(self, event_type: str) -> Callable[[EventHandler], EventHandler]:
6061
"""Decorator to register an event handler."""
62+
6163
def decorator(func: EventHandler) -> EventHandler:
6264
self.register_handler(event_type, func)
6365
return func
66+
6467
return decorator
6568

6669
def register_handler(self, event_type: str, handler: EventHandler) -> None:
@@ -72,41 +75,43 @@ def register_handler(self, event_type: str, handler: EventHandler) -> None:
7275
async def connect(self) -> None:
7376
"""Connect and listen for events. Reconnects automatically on failure."""
7477
import websockets
75-
78+
7679
self._running = True
77-
80+
7881
while self._running:
7982
try:
8083
url = f"{self._ws_url}?token={self._token}&vsn=2.0.0"
81-
84+
8285
async with websockets.connect(url) as ws:
8386
self._ws = ws
8487
await self._join_channel(ws)
85-
88+
8689
heartbeat_task = asyncio.create_task(self._heartbeat(ws))
8790
try:
8891
async for raw_msg in ws:
8992
msg = json.loads(raw_msg)
9093
await self._handle_message(msg)
9194
finally:
9295
heartbeat_task.cancel()
93-
96+
9497
except Exception:
9598
if self._running:
9699
await asyncio.sleep(5)
97100

98101
async def _join_channel(self, ws: Any) -> None:
99102
"""Join the user:self channel with event replay."""
100103
self._ref_counter += 1
101-
join_msg = json.dumps([
102-
None,
103-
str(self._ref_counter),
104-
"user:self",
105-
"phx_join",
106-
{"last_event_id": self._last_event_id},
107-
])
104+
join_msg = json.dumps(
105+
[
106+
None,
107+
str(self._ref_counter),
108+
"user:self",
109+
"phx_join",
110+
{"last_event_id": self._last_event_id},
111+
]
112+
)
108113
await ws.send(join_msg)
109-
114+
110115
reply = json.loads(await asyncio.wait_for(ws.recv(), timeout=10))
111116
if not (reply[3] == "phx_reply" and reply[4].get("status") == "ok"):
112117
raise ConnectionError(f"Failed to join channel: {reply}")

stackcoin/stackcoin/models.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# generated by datamodel-codegen:
22
# filename: openapi.json
3-
# timestamp: 2026-03-05T03:46:00+00:00
3+
# timestamp: 2026-03-05T04:04:56+00:00
44

55
from __future__ import annotations
66

7-
from pydantic import AwareDatetime, BaseModel, Field
7+
from datetime import datetime
8+
9+
from pydantic import BaseModel, Field
810

911

1012
class CreateRequestParams(BaseModel):
@@ -25,7 +27,7 @@ class Responder(BaseModel):
2527
class CreateRequestResponse(BaseModel):
2628
amount: int = Field(..., description="Requested amount")
2729
request_id: int = Field(..., description="Created request ID")
28-
requested_at: AwareDatetime = Field(..., description="Request timestamp")
30+
requested_at: datetime = Field(..., description="Request timestamp")
2931
requester: Requester
3032
responder: Responder
3133
status: str = Field(..., description="Request status")
@@ -37,7 +39,7 @@ class DiscordGuild(BaseModel):
3739
None, description="Designated channel snowflake ID"
3840
)
3941
id: int = Field(..., description="Guild ID")
40-
last_updated: AwareDatetime = Field(..., description="Last updated timestamp")
42+
last_updated: datetime = Field(..., description="Last updated timestamp")
4143
name: str = Field(..., description="Guild name")
4244
snowflake: str = Field(..., description="Discord guild snowflake ID")
4345

@@ -47,7 +49,7 @@ class DiscordGuildResponse(BaseModel):
4749
None, description="Designated channel snowflake ID"
4850
)
4951
id: int = Field(..., description="Guild ID")
50-
last_updated: AwareDatetime = Field(..., description="Last updated timestamp")
52+
last_updated: datetime = Field(..., description="Last updated timestamp")
5153
name: str = Field(..., description="Guild name")
5254
snowflake: str = Field(..., description="Discord guild snowflake ID")
5355

@@ -72,17 +74,17 @@ class Request(BaseModel):
7274
amount: int = Field(..., description="Requested amount")
7375
id: int = Field(..., description="Request ID")
7476
label: str | None = Field(None, description="Request label")
75-
requested_at: AwareDatetime = Field(..., description="Request timestamp")
77+
requested_at: datetime = Field(..., description="Request timestamp")
7678
requester: Requester
77-
resolved_at: AwareDatetime | None = Field(None, description="Resolution timestamp")
79+
resolved_at: datetime | None = Field(None, description="Resolution timestamp")
7880
responder: Responder
7981
status: str = Field(..., description="Request status")
8082
transaction_id: int | None = Field(None, description="Associated transaction ID")
8183

8284

8385
class RequestActionResponse(BaseModel):
8486
request_id: int = Field(..., description="Request ID")
85-
resolved_at: AwareDatetime = Field(..., description="Resolution timestamp")
87+
resolved_at: datetime = Field(..., description="Resolution timestamp")
8688
status: str = Field(..., description="New request status")
8789
success: bool = Field(..., description="Whether the operation succeeded")
8890
transaction_id: int | None = Field(None, description="Associated transaction ID")
@@ -92,9 +94,9 @@ class RequestResponse(BaseModel):
9294
amount: int = Field(..., description="Requested amount")
9395
id: int = Field(..., description="Request ID")
9496
label: str | None = Field(None, description="Request label")
95-
requested_at: AwareDatetime = Field(..., description="Request timestamp")
97+
requested_at: datetime = Field(..., description="Request timestamp")
9698
requester: Requester
97-
resolved_at: AwareDatetime | None = Field(None, description="Resolution timestamp")
99+
resolved_at: datetime | None = Field(None, description="Resolution timestamp")
98100
responder: Responder
99101
status: str = Field(..., description="Request status")
100102
transaction_id: int | None = Field(None, description="Associated transaction ID")
@@ -133,7 +135,7 @@ class Transaction(BaseModel):
133135
from_: From = Field(..., alias="from")
134136
id: int = Field(..., description="Transaction ID")
135137
label: str | None = Field(None, description="Transaction label")
136-
time: AwareDatetime = Field(..., description="Transaction timestamp")
138+
time: datetime = Field(..., description="Transaction timestamp")
137139
to: To
138140

139141

@@ -142,7 +144,7 @@ class TransactionResponse(BaseModel):
142144
from_: From = Field(..., alias="from")
143145
id: int = Field(..., description="Transaction ID")
144146
label: str | None = Field(None, description="Transaction label")
145-
time: AwareDatetime = Field(..., description="Transaction timestamp")
147+
time: datetime = Field(..., description="Transaction timestamp")
146148
to: To
147149

148150

@@ -156,8 +158,8 @@ class User(BaseModel):
156158
balance: int = Field(..., description="User's STK balance")
157159
banned: bool = Field(..., description="Whether user is banned")
158160
id: int | None = Field(None, description="User ID")
159-
inserted_at: AwareDatetime | None = Field(None, description="Creation timestamp")
160-
updated_at: AwareDatetime | None = Field(None, description="Update timestamp")
161+
inserted_at: datetime | None = Field(None, description="Creation timestamp")
162+
updated_at: datetime | None = Field(None, description="Update timestamp")
161163
username: str = Field(..., description="Username")
162164

163165

@@ -166,8 +168,8 @@ class UserResponse(BaseModel):
166168
balance: int = Field(..., description="User's STK balance")
167169
banned: bool = Field(..., description="Whether user is banned")
168170
id: int | None = Field(None, description="User ID")
169-
inserted_at: AwareDatetime | None = Field(None, description="Creation timestamp")
170-
updated_at: AwareDatetime | None = Field(None, description="Update timestamp")
171+
inserted_at: datetime | None = Field(None, description="Creation timestamp")
172+
updated_at: datetime | None = Field(None, description="Update timestamp")
171173
username: str = Field(..., description="Username")
172174

173175

0 commit comments

Comments
 (0)