fixtureqa 0.1.1__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fixtureqa-0.1.1/fixtureqa.egg-info → fixtureqa-0.1.3}/PKG-INFO +1 -1
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/connection_manager.py +8 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/ws.py +14 -2
- {fixtureqa-0.1.1 → fixtureqa-0.1.3/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/pyproject.toml +1 -1
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/LICENSE +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/README.md +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/__init__.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/__main__.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/app.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/deps.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/auth.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/events.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/models.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/session.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/server.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/static/assets/index-B31-1dt-.css +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/static/assets/index-CTsKxGdI.js +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/static/index.html +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixtureqa.egg-info/SOURCES.txt +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.1.1 → fixtureqa-0.1.3}/setup.cfg +0 -0
|
@@ -5,8 +5,11 @@ fixcore callbacks fire on the asyncio event loop — no thread-crossing needed.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
import logging
|
|
8
9
|
from datetime import datetime, timezone
|
|
9
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
10
13
|
from fastapi import WebSocket
|
|
11
14
|
from starlette.websockets import WebSocketState
|
|
12
15
|
|
|
@@ -105,6 +108,9 @@ class ConnectionManager:
|
|
|
105
108
|
specific = dict(self._clients.get(session_id, {}))
|
|
106
109
|
wildcard = dict(self._clients.get("*", {}))
|
|
107
110
|
|
|
111
|
+
logger.debug("_broadcast: type=%s session=%s owner=%r clients_specific=%d wildcard=%d",
|
|
112
|
+
payload.get("type"), session_id, owner_uid, len(specific), len(wildcard))
|
|
113
|
+
|
|
108
114
|
# Build target set: connections that may receive this event
|
|
109
115
|
# A connection receives the event if:
|
|
110
116
|
# - it is an admin, OR
|
|
@@ -114,6 +120,8 @@ class ConnectionManager:
|
|
|
114
120
|
if info.is_admin or info.uid == owner_uid:
|
|
115
121
|
targets[ws] = info
|
|
116
122
|
|
|
123
|
+
logger.debug("_broadcast: %d target(s) for owner=%r", len(targets), owner_uid)
|
|
124
|
+
|
|
117
125
|
dead: list[tuple[WebSocket, str]] = []
|
|
118
126
|
for ws in targets:
|
|
119
127
|
try:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import logging
|
|
2
3
|
from typing import Annotated, Optional
|
|
3
4
|
|
|
4
5
|
from fastapi import APIRouter, Depends, Query, WebSocket, WebSocketDisconnect
|
|
@@ -10,6 +11,8 @@ from ..deps import get_session_manager, get_conn_manager
|
|
|
10
11
|
from ...core.auth import decode_token
|
|
11
12
|
from ...core.user_store import UserStore
|
|
12
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
13
16
|
router = APIRouter(tags=["websocket"])
|
|
14
17
|
|
|
15
18
|
SM = Annotated[SessionManager, Depends(get_session_manager)]
|
|
@@ -62,6 +65,7 @@ async def ws_all_sessions(
|
|
|
62
65
|
"""Subscribe to events from all sessions."""
|
|
63
66
|
uid, is_admin = _authenticate_ws(websocket, token)
|
|
64
67
|
if not uid:
|
|
68
|
+
logger.warning("WS /ws: auth failed (no token or invalid JWT)")
|
|
65
69
|
await websocket.close(code=4003, reason="Unauthorized")
|
|
66
70
|
return
|
|
67
71
|
|
|
@@ -69,14 +73,16 @@ async def ws_all_sessions(
|
|
|
69
73
|
store: UserStore = websocket.app.state.user_store
|
|
70
74
|
user = store.get_by_uid(uid)
|
|
71
75
|
if user is None or not user.is_active:
|
|
76
|
+
logger.warning("WS /ws: auth failed — user not found or inactive: uid=%s", uid)
|
|
72
77
|
await websocket.close(code=4003, reason="Unauthorized")
|
|
73
78
|
return
|
|
74
79
|
|
|
80
|
+
logger.info("WS /ws: client connected uid=%s is_admin=%s", uid, is_admin)
|
|
75
81
|
await conn_mgr.connect(websocket, session_id="*", uid=uid, is_admin=is_admin)
|
|
76
82
|
try:
|
|
77
83
|
await _send_snapshot(websocket, sm, uid, is_admin)
|
|
78
84
|
while True:
|
|
79
|
-
await asyncio.sleep(
|
|
85
|
+
await asyncio.sleep(5)
|
|
80
86
|
try:
|
|
81
87
|
await websocket.send_json({"type": "ping"})
|
|
82
88
|
except Exception:
|
|
@@ -84,6 +90,7 @@ async def ws_all_sessions(
|
|
|
84
90
|
except WebSocketDisconnect:
|
|
85
91
|
pass
|
|
86
92
|
finally:
|
|
93
|
+
logger.info("WS /ws: client disconnected uid=%s", uid)
|
|
87
94
|
conn_mgr.disconnect(websocket, session_id="*")
|
|
88
95
|
|
|
89
96
|
|
|
@@ -98,12 +105,14 @@ async def ws_single_session(
|
|
|
98
105
|
"""Subscribe to events from a single session."""
|
|
99
106
|
uid, is_admin = _authenticate_ws(websocket, token)
|
|
100
107
|
if not uid:
|
|
108
|
+
logger.warning("WS /ws/%s: auth failed (no token or invalid JWT)", session_id)
|
|
101
109
|
await websocket.close(code=4003, reason="Unauthorized")
|
|
102
110
|
return
|
|
103
111
|
|
|
104
112
|
store: UserStore = websocket.app.state.user_store
|
|
105
113
|
user = store.get_by_uid(uid)
|
|
106
114
|
if user is None or not user.is_active:
|
|
115
|
+
logger.warning("WS /ws/%s: auth failed — user not found or inactive: uid=%s", session_id, uid)
|
|
107
116
|
await websocket.close(code=4003, reason="Unauthorized")
|
|
108
117
|
return
|
|
109
118
|
|
|
@@ -111,14 +120,16 @@ async def ws_single_session(
|
|
|
111
120
|
configs = sm.list_sessions()
|
|
112
121
|
cfg = next((c for c in configs if c.session_id == session_id), None)
|
|
113
122
|
if cfg is None or (not is_admin and cfg.owner_uid != uid):
|
|
123
|
+
logger.warning("WS /ws/%s: access denied for uid=%s", session_id, uid)
|
|
114
124
|
await websocket.close(code=4003, reason="Access denied")
|
|
115
125
|
return
|
|
116
126
|
|
|
127
|
+
logger.info("WS /ws/%s: client connected uid=%s is_admin=%s", session_id, uid, is_admin)
|
|
117
128
|
await conn_mgr.connect(websocket, session_id=session_id, uid=uid, is_admin=is_admin)
|
|
118
129
|
try:
|
|
119
130
|
await _send_snapshot(websocket, sm, uid, is_admin, session_id=session_id)
|
|
120
131
|
while True:
|
|
121
|
-
await asyncio.sleep(
|
|
132
|
+
await asyncio.sleep(5)
|
|
122
133
|
try:
|
|
123
134
|
await websocket.send_json({"type": "ping"})
|
|
124
135
|
except Exception:
|
|
@@ -126,4 +137,5 @@ async def ws_single_session(
|
|
|
126
137
|
except WebSocketDisconnect:
|
|
127
138
|
pass
|
|
128
139
|
finally:
|
|
140
|
+
logger.info("WS /ws/%s: client disconnected uid=%s", session_id, uid)
|
|
129
141
|
conn_mgr.disconnect(websocket, session_id=session_id)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|