fixtureqa 0.8.3__tar.gz → 0.9.1__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.
Files changed (105) hide show
  1. {fixtureqa-0.8.3/fixtureqa.egg-info → fixtureqa-0.9.1}/PKG-INFO +2 -2
  2. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/perf.py +31 -0
  3. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/sessions.py +1 -0
  4. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/schemas.py +3 -0
  5. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/models.py +6 -0
  6. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/session.py +1 -0
  7. fixtureqa-0.8.3/fixture/static/assets/index-Cu3H3T9B.js → fixtureqa-0.9.1/fixture/static/assets/index-CbJP_HMn.js +17 -17
  8. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/static/index.html +1 -1
  9. {fixtureqa-0.8.3 → fixtureqa-0.9.1/fixtureqa.egg-info}/PKG-INFO +2 -2
  10. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixtureqa.egg-info/SOURCES.txt +1 -1
  11. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixtureqa.egg-info/requires.txt +1 -1
  12. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/pyproject.toml +2 -2
  13. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_config_store.py +1 -0
  14. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_non_stop_session.py +14 -0
  15. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_perf_api.py +21 -0
  16. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/LICENSE +0 -0
  17. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/README.md +0 -0
  18. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/__init__.py +0 -0
  19. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/__main__.py +0 -0
  20. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/__init__.py +0 -0
  21. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/app.py +0 -0
  22. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/connection_manager.py +0 -0
  23. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/deps.py +0 -0
  24. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/__init__.py +0 -0
  25. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/admin.py +0 -0
  26. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/auth.py +0 -0
  27. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/branding.py +0 -0
  28. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/custom_tags.py +0 -0
  29. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/fix_spec.py +0 -0
  30. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/messages.py +0 -0
  31. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/scenarios.py +0 -0
  32. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/setup.py +0 -0
  33. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/spec_overlay.py +0 -0
  34. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/templates.py +0 -0
  35. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/api/routers/ws.py +0 -0
  36. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/config/__init__.py +0 -0
  37. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/__init__.py +0 -0
  38. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/atomic_io.py +0 -0
  39. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/auth.py +0 -0
  40. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/config_store.py +0 -0
  41. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/custom_tag_store.py +0 -0
  42. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/db_migrations.py +0 -0
  43. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/events.py +0 -0
  44. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/fix_application.py +0 -0
  45. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/fix_builder.py +0 -0
  46. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/fix_parser.py +0 -0
  47. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/fix_spec_parser.py +0 -0
  48. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/fix_tags.py +0 -0
  49. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/fix_time.py +0 -0
  50. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/housekeeping.py +0 -0
  51. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/inbound.py +0 -0
  52. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/json_store.py +0 -0
  53. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/latency_csv_writer.py +0 -0
  54. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/message_log.py +0 -0
  55. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/message_store.py +0 -0
  56. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/perf_charts.py +0 -0
  57. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/perf_engine.py +0 -0
  58. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/perf_models.py +0 -0
  59. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/perf_payload.py +0 -0
  60. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/perf_stats.py +0 -0
  61. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/perf_store.py +0 -0
  62. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/perf_writer.py +0 -0
  63. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/scenario_runner.py +0 -0
  64. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/scenario_store.py +0 -0
  65. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/session_manager.py +0 -0
  66. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/spec_overlay_store.py +0 -0
  67. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/template_store.py +0 -0
  68. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/user_store.py +0 -0
  69. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/value_gen.py +0 -0
  70. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/core/venue_responses.py +0 -0
  71. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/fix_specs/FIX42.xml +0 -0
  72. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/fix_specs/FIX44.xml +0 -0
  73. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/server.py +0 -0
  74. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
  75. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/static/assets/index-B-hpSZWZ.css +0 -0
  76. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/static/assets/index-CyNOPa0n.js +0 -0
  77. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
  78. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/static/favicon.svg +0 -0
  79. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixture/ui/__init__.py +0 -0
  80. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixtureqa.egg-info/dependency_links.txt +0 -0
  81. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixtureqa.egg-info/entry_points.txt +0 -0
  82. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/fixtureqa.egg-info/top_level.txt +0 -0
  83. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/setup.cfg +0 -0
  84. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_atomic_io.py +0 -0
  85. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_auth.py +0 -0
  86. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_connection_manager.py +0 -0
  87. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_db_migrations.py +0 -0
  88. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_fix_builder.py +0 -0
  89. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_health.py +0 -0
  90. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_inbound.py +0 -0
  91. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_inbound_validation.py +0 -0
  92. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_message_store.py +0 -0
  93. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_perf_engine.py +0 -0
  94. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_perf_models.py +0 -0
  95. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_perf_payload.py +0 -0
  96. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_perf_rehydrate.py +0 -0
  97. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_scenarios.py +0 -0
  98. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_session_auto_response.py +0 -0
  99. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_session_lifecycle.py +0 -0
  100. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_session_manager_concurrency.py +0 -0
  101. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_sessions.py +0 -0
  102. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_templates.py +0 -0
  103. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_timezone_data.py +0 -0
  104. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_value_gen.py +0 -0
  105. {fixtureqa-0.8.3 → fixtureqa-0.9.1}/tests/test_ws.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixtureqa
3
- Version: 0.8.3
3
+ Version: 0.9.1
4
4
  Summary: FIXture — FIX Protocol Testing Tool
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
7
- Requires-Dist: fixcore-engine==0.7.1
7
+ Requires-Dist: fixcore-engine==0.8.1
8
8
  Requires-Dist: fastapi>=0.111.0
9
9
  Requires-Dist: uvicorn[standard]>=0.29.0
10
10
  Requires-Dist: websockets>=12.0
@@ -53,6 +53,18 @@ _TERMINAL = ("completed", "stopped", "error")
53
53
  # up front (before the run starts) rather than wedging the server.
54
54
  DURABLE_SAFE_MSG_RATE = 30 # messages/second a logged/durable session can sustain
55
55
 
56
+ # Session configs that add work to the per-message hot path on the event loop —
57
+ # they distort (or, for check_latency, can outright reject messages and corrupt)
58
+ # the very latency a perf run measures. Unlike durable persistence there is no
59
+ # rate at which they're safe, so a run that drives a session with any of them on
60
+ # is refused up front. (label, why) — the Performance-session toggle in the
61
+ # session modal clears all of them.
62
+ _PERF_HOSTILE_KEYS: tuple[tuple[str, str], ...] = (
63
+ ("validate_inbound", "validates every inbound message against the spec on the loop"),
64
+ ("check_latency", "parses SendingTime(52) and may reject messages on the loop"),
65
+ ("enable_last_msg_seq_num_processed", "stamps LastMsgSeqNumProcessed(369) on every outbound message"),
66
+ )
67
+
56
68
 
57
69
  def _order_rate_per_s(body: RunConfig) -> float:
58
70
  r = body.rate
@@ -144,6 +156,25 @@ async def create_run(body: RunConfig, sm: SM, reg: REG, ts: TS, admin: Admin):
144
156
  f"~{safe_order_rate:.0f} orders/s (~{DURABLE_SAFE_MSG_RATE} msg/s) — at "
145
157
  f"higher rates the event loop stalls on per-message fsync/DB writes")
146
158
 
159
+ # Per-message latency guard — BEFORE reg.create(). Refuse outright if any
160
+ # driven session has a perf-hostile key on: these add work to the per-message
161
+ # hot path on the event loop (or reject messages outright) and distort the
162
+ # latency the run exists to measure. No safe rate, so unlike the durability
163
+ # guard this is a hard refusal rather than a throttle.
164
+ offenders = [
165
+ (label, attr, why)
166
+ for label, cfg in participating
167
+ for attr, why in _PERF_HOSTILE_KEYS
168
+ if getattr(cfg, attr, False)
169
+ ]
170
+ if offenders:
171
+ detail = "; ".join(f"{label} session has {attr} on ({why})"
172
+ for label, attr, why in offenders)
173
+ raise HTTPException(status_code=409, detail=(
174
+ f"Refusing to start: {detail}. These add per-message work on the event "
175
+ f"loop and distort latency measurement — turn them off (or tick "
176
+ f"'Performance session' in the session modal) before running a perf test."))
177
+
147
178
  # CompIDs from the driving (client, else venue) session — for template tokens.
148
179
  drive_sid = body.client_session_id if need_client else body.venue_session_id
149
180
  dcfg = sm.get_state(drive_sid).config
@@ -68,6 +68,7 @@ def _session_response(sm: SessionManager, session_id: str, us: Optional[UserStor
68
68
  requires_orig_sending_time=cfg.requires_orig_sending_time,
69
69
  resend_request_chunk_size=cfg.resend_request_chunk_size,
70
70
  send_redundant_resend_requests=cfg.send_redundant_resend_requests,
71
+ enable_last_msg_seq_num_processed=cfg.enable_last_msg_seq_num_processed,
71
72
  use_file_log=cfg.use_file_log,
72
73
  start_time=cfg.start_time,
73
74
  end_time=cfg.end_time,
@@ -21,6 +21,7 @@ class SessionConfigRequest(BaseModel):
21
21
  requires_orig_sending_time: bool = True # RequiresOrigSendingTime
22
22
  resend_request_chunk_size: int = Field(default=0, ge=0) # 0 = unlimited
23
23
  send_redundant_resend_requests: bool = False
24
+ enable_last_msg_seq_num_processed: bool = False # stamp LastMsgSeqNumProcessed(369)
24
25
  data_dictionary_path: Optional[str] = None
25
26
  use_file_log: bool = True
26
27
  start_time: str = "00:00:00"
@@ -67,6 +68,7 @@ class SessionResponse(BaseModel):
67
68
  requires_orig_sending_time: bool
68
69
  resend_request_chunk_size: int
69
70
  send_redundant_resend_requests: bool
71
+ enable_last_msg_seq_num_processed: bool
70
72
  use_file_log: bool
71
73
  start_time: str
72
74
  end_time: str
@@ -118,6 +120,7 @@ class UpdateSessionRequest(BaseModel):
118
120
  requires_orig_sending_time: Optional[bool] = None
119
121
  resend_request_chunk_size: Optional[int] = Field(default=None, ge=0)
120
122
  send_redundant_resend_requests: Optional[bool] = None
123
+ enable_last_msg_seq_num_processed: Optional[bool] = None
121
124
  data_dictionary_path: Optional[str] = None
122
125
  use_file_log: Optional[bool] = None
123
126
  start_time: Optional[str] = None
@@ -108,6 +108,12 @@ class SessionConfig:
108
108
  # outstanding (default N = re-request only when the gap advances).
109
109
  send_redundant_resend_requests: bool = False
110
110
 
111
+ # Message header (fixcore)
112
+ # Stamp LastMsgSeqNumProcessed(369) on every outbound message header, set to
113
+ # the seqnum of the last inbound message processed, so the peer can gauge how
114
+ # far behind we are (fixcore EnableLastMsgSeqNumProcessed). Off by default.
115
+ enable_last_msg_seq_num_processed: bool = False
116
+
111
117
  # Seqnum/message store durability. True (default) = fixcore FileStore, which
112
118
  # fsyncs per message (durable resend/recovery, but caps throughput at disk
113
119
  # speed). False = in-memory store: no per-message fsync, far higher
@@ -382,6 +382,7 @@ class Session:
382
382
  f"RequiresOrigSendingTime={yn(self.config.requires_orig_sending_time)}",
383
383
  f"ResendRequestChunkSize={self.config.resend_request_chunk_size}",
384
384
  f"SendRedundantResendRequests={yn(self.config.send_redundant_resend_requests)}",
385
+ f"EnableLastMsgSeqNumProcessed={yn(self.config.enable_last_msg_seq_num_processed)}",
385
386
  *schedule_lines,
386
387
  f"DataDictionary={dd_path}",
387
388
  # Always load the dictionary so inbound repeating groups decode into