fixtureqa 0.10.3__tar.gz → 0.11.0__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 (107) hide show
  1. {fixtureqa-0.10.3/fixtureqa.egg-info → fixtureqa-0.11.0}/PKG-INFO +1 -1
  2. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/perf_engine.py +1 -1
  3. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/perf_models.py +23 -4
  4. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/perf_payload.py +36 -11
  5. fixtureqa-0.11.0/fixture/static/assets/index-CfkKS026.js +102 -0
  6. fixtureqa-0.10.3/fixture/static/assets/index-CwSn-gHP.css → fixtureqa-0.11.0/fixture/static/assets/index-uoEGYkf_.css +1 -1
  7. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/static/index.html +2 -2
  8. {fixtureqa-0.10.3 → fixtureqa-0.11.0/fixtureqa.egg-info}/PKG-INFO +1 -1
  9. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixtureqa.egg-info/SOURCES.txt +2 -2
  10. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/pyproject.toml +1 -1
  11. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_perf_payload.py +43 -0
  12. fixtureqa-0.10.3/fixture/static/assets/index-pH3MpqD6.js +0 -102
  13. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/LICENSE +0 -0
  14. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/README.md +0 -0
  15. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/__init__.py +0 -0
  16. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/__main__.py +0 -0
  17. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/__init__.py +0 -0
  18. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/app.py +0 -0
  19. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/connection_manager.py +0 -0
  20. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/deps.py +0 -0
  21. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/__init__.py +0 -0
  22. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/admin.py +0 -0
  23. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/auth.py +0 -0
  24. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/branding.py +0 -0
  25. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/custom_tags.py +0 -0
  26. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/fix_spec.py +0 -0
  27. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/messages.py +0 -0
  28. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/perf.py +0 -0
  29. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/scenarios.py +0 -0
  30. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/sessions.py +0 -0
  31. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/setup.py +0 -0
  32. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/spec_overlay.py +0 -0
  33. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/specs.py +0 -0
  34. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/templates.py +0 -0
  35. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/routers/ws.py +0 -0
  36. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/api/schemas.py +0 -0
  37. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/config/__init__.py +0 -0
  38. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/__init__.py +0 -0
  39. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/atomic_io.py +0 -0
  40. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/auth.py +0 -0
  41. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/config_store.py +0 -0
  42. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/custom_tag_store.py +0 -0
  43. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/db_migrations.py +0 -0
  44. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/events.py +0 -0
  45. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/fix_application.py +0 -0
  46. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/fix_builder.py +0 -0
  47. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/fix_parser.py +0 -0
  48. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/fix_spec_parser.py +0 -0
  49. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/fix_tags.py +0 -0
  50. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/fix_time.py +0 -0
  51. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/housekeeping.py +0 -0
  52. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/inbound.py +0 -0
  53. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/json_store.py +0 -0
  54. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/latency_csv_writer.py +0 -0
  55. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/message_log.py +0 -0
  56. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/message_store.py +0 -0
  57. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/models.py +0 -0
  58. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/perf_charts.py +0 -0
  59. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/perf_stats.py +0 -0
  60. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/perf_store.py +0 -0
  61. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/perf_writer.py +0 -0
  62. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/scenario_runner.py +0 -0
  63. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/scenario_store.py +0 -0
  64. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/session.py +0 -0
  65. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/session_manager.py +0 -0
  66. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/spec_overlay_store.py +0 -0
  67. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/spec_store.py +0 -0
  68. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/template_store.py +0 -0
  69. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/user_store.py +0 -0
  70. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/value_gen.py +0 -0
  71. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/core/venue_responses.py +0 -0
  72. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/server.py +0 -0
  73. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
  74. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/static/assets/index-CyNOPa0n.js +0 -0
  75. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
  76. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/static/favicon.svg +0 -0
  77. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixture/ui/__init__.py +0 -0
  78. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixtureqa.egg-info/dependency_links.txt +0 -0
  79. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixtureqa.egg-info/entry_points.txt +0 -0
  80. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixtureqa.egg-info/requires.txt +0 -0
  81. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/fixtureqa.egg-info/top_level.txt +0 -0
  82. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/setup.cfg +0 -0
  83. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_atomic_io.py +0 -0
  84. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_auth.py +0 -0
  85. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_config_store.py +0 -0
  86. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_connection_manager.py +0 -0
  87. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_db_migrations.py +0 -0
  88. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_fix_builder.py +0 -0
  89. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_health.py +0 -0
  90. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_inbound.py +0 -0
  91. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_inbound_validation.py +0 -0
  92. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_message_store.py +0 -0
  93. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_non_stop_session.py +0 -0
  94. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_perf_api.py +0 -0
  95. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_perf_engine.py +0 -0
  96. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_perf_models.py +0 -0
  97. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_perf_rehydrate.py +0 -0
  98. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_scenarios.py +0 -0
  99. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_session_auto_response.py +0 -0
  100. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_session_lifecycle.py +0 -0
  101. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_session_manager_concurrency.py +0 -0
  102. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_sessions.py +0 -0
  103. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_specs.py +0 -0
  104. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_templates.py +0 -0
  105. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_timezone_data.py +0 -0
  106. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_value_gen.py +0 -0
  107. {fixtureqa-0.10.3 → fixtureqa-0.11.0}/tests/test_ws.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixtureqa
3
- Version: 0.10.3
3
+ Version: 0.11.0
4
4
  Summary: FIXture — FIX Protocol Testing Tool
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
@@ -868,7 +868,7 @@ class PerfRun:
868
868
  def _exec(self, of: dict, **kw) -> Optional[Message]:
869
869
  """Build one ExecReport via the exec template when configured, else the
870
870
  built-in builder (Phase B). Both enforce the standard correlation tags."""
871
- if self.config.payload.exec_template_id:
871
+ if self._factory.has_exec_template:
872
872
  return self._factory.build_exec_message(of, **kw)
873
873
  return self._build_exec(of, **kw)
874
874
 
@@ -175,10 +175,14 @@ class PayloadConfig(BaseModel):
175
175
  fill: FillPayloadConfig = Field(default_factory=FillPayloadConfig)
176
176
  scenarios: list[ScenarioConfig] = Field(default_factory=list)
177
177
  # Phase B — optional TemplateStore template ids (resolved for the run's owner).
178
- # order_template_id shapes single-order injection (ignored when scenarios are set);
179
- # exec_template_id shapes the venue's ack/fill ExecReports. Omitted built-in builders.
180
- order_template_id: Optional[str] = None
181
- exec_template_id: Optional[str] = None
178
+ # The *_ids lists shape single-order injection / the venue's ack-fill ExecReports;
179
+ # when more than one is given the engine round-robins them per order / per report.
180
+ # Empty built-in builders. The singular *_id fields are the legacy one-template
181
+ # form kept for old saved configs; _fold_template_ids merges them into the lists.
182
+ order_template_ids: list[str] = Field(default_factory=list)
183
+ exec_template_ids: list[str] = Field(default_factory=list)
184
+ order_template_id: Optional[str] = None # deprecated — folded into order_template_ids
185
+ exec_template_id: Optional[str] = None # deprecated — folded into exec_template_ids
182
186
  # Send-options parity with the standard send UI (client-injected messages only):
183
187
  # auto_expiry rewrites ExpireTime(126)/ExpireDate(432) to a future instant when
184
188
  # the message already carries them (never inserts); gen_rules set tag → generator
@@ -194,6 +198,21 @@ class PayloadConfig(BaseModel):
194
198
  raise ValueError("scenario weights must be > 0")
195
199
  return self
196
200
 
201
+ @model_validator(mode="after")
202
+ def _fold_template_ids(self) -> "PayloadConfig":
203
+ # Old saved configs / API clients send the singular order_template_id /
204
+ # exec_template_id; fold those into the lists so the engine only reads
205
+ # the lists. Drop blanks and de-dup while preserving order.
206
+ if not self.order_template_ids and self.order_template_id:
207
+ self.order_template_ids = [self.order_template_id]
208
+ if not self.exec_template_ids and self.exec_template_id:
209
+ self.exec_template_ids = [self.exec_template_id]
210
+ self.order_template_ids = [t for t in dict.fromkeys(self.order_template_ids) if t]
211
+ self.exec_template_ids = [t for t in dict.fromkeys(self.exec_template_ids) if t]
212
+ self.order_template_id = self.order_template_ids[0] if self.order_template_ids else None
213
+ self.exec_template_id = self.exec_template_ids[0] if self.exec_template_ids else None
214
+ return self
215
+
197
216
 
198
217
  class RelayConfig(BaseModel):
199
218
  """middle_manager relay behaviour. Verbatim forward by default; an optional
@@ -127,8 +127,12 @@ class PayloadFactory:
127
127
  self._sender = sender_comp_id
128
128
  self._target = target_comp_id
129
129
  self._templates: dict[str, dict[int, str]] = {}
130
- self._order_tpl: Optional[dict[int, str]] = None # Phase B single-order template
131
- self._exec_tpl: Optional[dict[int, str]] = None # Phase B ExecReport template
130
+ # Phase B standalone templates one or more order / ExecReport specs,
131
+ # round-robined per order / per report when more than one is configured.
132
+ self._order_tpls: list[dict[int, str]] = []
133
+ self._exec_tpls: list[dict[int, str]] = []
134
+ self._order_tpl_i = 0
135
+ self._exec_tpl_i = 0
132
136
  self._gen_rules: dict[int, str] = {} # tag → expression, every client msg
133
137
  self._counters: dict[int, int] = {} # seq() state, per tag per run
134
138
  self._symbol_i = 0
@@ -157,9 +161,10 @@ class PayloadFactory:
157
161
 
158
162
  # Phase B standalone templates (no scenario sequence → no {ref:}).
159
163
  p = self.config.payload
160
- self._order_tpl = self._load_standalone(p.order_template_id, _SIMPLE, "order",
161
- merge_rules=True)
162
- self._exec_tpl = self._load_standalone(p.exec_template_id, _SIMPLE | _EXEC_TOKENS, "exec")
164
+ self._order_tpls = [self._load_standalone(tid, _SIMPLE, "order", merge_rules=True)
165
+ for tid in p.order_template_ids]
166
+ self._exec_tpls = [self._load_standalone(tid, _SIMPLE | _EXEC_TOKENS, "exec")
167
+ for tid in p.exec_template_ids]
163
168
 
164
169
  def _parse_gen_rules(self, vars_: dict) -> dict[int, str]:
165
170
  rules: dict[int, str] = {}
@@ -238,6 +243,26 @@ class PayloadFactory:
238
243
  return
239
244
  raise PerfConfigError(f"unknown token {{{tok}}} in template {tpl_id!r}")
240
245
 
246
+ # -- standalone template selection (round-robin) -------------------
247
+
248
+ @property
249
+ def has_order_template(self) -> bool:
250
+ return bool(self._order_tpls)
251
+
252
+ @property
253
+ def has_exec_template(self) -> bool:
254
+ return bool(self._exec_tpls)
255
+
256
+ def _next_order_tpl(self) -> dict[int, str]:
257
+ tpl = self._order_tpls[self._order_tpl_i % len(self._order_tpls)]
258
+ self._order_tpl_i += 1
259
+ return tpl
260
+
261
+ def _next_exec_tpl(self) -> dict[int, str]:
262
+ tpl = self._exec_tpls[self._exec_tpl_i % len(self._exec_tpls)]
263
+ self._exec_tpl_i += 1
264
+ return tpl
265
+
241
266
  # -- order context (per scenario instance / per single order) ------
242
267
 
243
268
  def new_context(self) -> dict:
@@ -262,8 +287,8 @@ class PayloadFactory:
262
287
  def build_single_order(self) -> tuple[Message, dict]:
263
288
  ctx = self.new_context()
264
289
  corr, clordid = _uuid(), self._clordid()
265
- if self._order_tpl is not None:
266
- return self._build_order_from_template(ctx, corr, clordid)
290
+ if self._order_tpls:
291
+ return self._build_order_from_template(self._next_order_tpl(), ctx, corr, clordid)
267
292
  p = self.config.payload.order
268
293
  is_market = p.ord_type == "market"
269
294
  msg = Message()
@@ -437,11 +462,11 @@ class PayloadFactory:
437
462
 
438
463
  # -- Phase B: templated single order ------------------------------------
439
464
 
440
- def _build_order_from_template(self, ctx: dict, corr: str, clordid: str) -> tuple[Message, dict]:
441
- """Render the order template, then ensure the standard tags the venue and
465
+ def _build_order_from_template(self, fields: dict[int, str], ctx: dict,
466
+ corr: str, clordid: str) -> tuple[Message, dict]:
467
+ """Render one order template, then ensure the standard tags the venue and
442
468
  correlator need are present (injected from ctx when the template omits them),
443
469
  so an authored order spec can never break filling/correlation."""
444
- fields = self._order_tpl
445
470
  mtype = fields.get(35, "D")
446
471
  msg = Message()
447
472
  msg.header.set(35, mtype)
@@ -501,7 +526,7 @@ class PayloadFactory:
501
526
  }
502
527
  msg = Message()
503
528
  msg.header.set(35, "8")
504
- for tag, raw in self._exec_tpl.items():
529
+ for tag, raw in self._next_exec_tpl().items():
505
530
  if tag == 35:
506
531
  continue
507
532
  msg.set_field(tag, self._resolve_exec(raw, of, exec_ctx, tag))