fixtureqa 0.9.2__tar.gz → 0.9.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.
Files changed (107) hide show
  1. {fixtureqa-0.9.2/fixtureqa.egg-info → fixtureqa-0.9.3}/PKG-INFO +2 -2
  2. fixtureqa-0.9.3/fixture/core/fix_builder.py +69 -0
  3. {fixtureqa-0.9.2 → fixtureqa-0.9.3/fixtureqa.egg-info}/PKG-INFO +2 -2
  4. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/requires.txt +1 -1
  5. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/pyproject.toml +2 -2
  6. fixtureqa-0.9.2/fixture/core/fix_builder.py +0 -132
  7. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/LICENSE +0 -0
  8. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/README.md +0 -0
  9. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/__init__.py +0 -0
  10. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/__main__.py +0 -0
  11. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/__init__.py +0 -0
  12. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/app.py +0 -0
  13. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/connection_manager.py +0 -0
  14. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/deps.py +0 -0
  15. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/__init__.py +0 -0
  16. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/admin.py +0 -0
  17. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/auth.py +0 -0
  18. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/branding.py +0 -0
  19. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/custom_tags.py +0 -0
  20. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/fix_spec.py +0 -0
  21. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/messages.py +0 -0
  22. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/perf.py +0 -0
  23. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/scenarios.py +0 -0
  24. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/sessions.py +0 -0
  25. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/setup.py +0 -0
  26. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/spec_overlay.py +0 -0
  27. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/specs.py +0 -0
  28. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/templates.py +0 -0
  29. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/ws.py +0 -0
  30. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/schemas.py +0 -0
  31. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/config/__init__.py +0 -0
  32. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/__init__.py +0 -0
  33. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/atomic_io.py +0 -0
  34. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/auth.py +0 -0
  35. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/config_store.py +0 -0
  36. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/custom_tag_store.py +0 -0
  37. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/db_migrations.py +0 -0
  38. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/events.py +0 -0
  39. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_application.py +0 -0
  40. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_parser.py +0 -0
  41. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_spec_parser.py +0 -0
  42. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_tags.py +0 -0
  43. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_time.py +0 -0
  44. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/housekeeping.py +0 -0
  45. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/inbound.py +0 -0
  46. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/json_store.py +0 -0
  47. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/latency_csv_writer.py +0 -0
  48. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/message_log.py +0 -0
  49. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/message_store.py +0 -0
  50. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/models.py +0 -0
  51. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_charts.py +0 -0
  52. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_engine.py +0 -0
  53. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_models.py +0 -0
  54. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_payload.py +0 -0
  55. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_stats.py +0 -0
  56. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_store.py +0 -0
  57. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_writer.py +0 -0
  58. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/scenario_runner.py +0 -0
  59. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/scenario_store.py +0 -0
  60. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/session.py +0 -0
  61. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/session_manager.py +0 -0
  62. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/spec_overlay_store.py +0 -0
  63. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/spec_store.py +0 -0
  64. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/template_store.py +0 -0
  65. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/user_store.py +0 -0
  66. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/value_gen.py +0 -0
  67. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/venue_responses.py +0 -0
  68. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/server.py +0 -0
  69. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
  70. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/index-B-hpSZWZ.css +0 -0
  71. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/index-CyNOPa0n.js +0 -0
  72. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/index-WJ1w221m.js +0 -0
  73. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
  74. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/favicon.svg +0 -0
  75. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/index.html +0 -0
  76. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/ui/__init__.py +0 -0
  77. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/SOURCES.txt +0 -0
  78. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/dependency_links.txt +0 -0
  79. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/entry_points.txt +0 -0
  80. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/top_level.txt +0 -0
  81. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/setup.cfg +0 -0
  82. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_atomic_io.py +0 -0
  83. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_auth.py +0 -0
  84. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_config_store.py +0 -0
  85. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_connection_manager.py +0 -0
  86. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_db_migrations.py +0 -0
  87. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_fix_builder.py +0 -0
  88. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_health.py +0 -0
  89. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_inbound.py +0 -0
  90. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_inbound_validation.py +0 -0
  91. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_message_store.py +0 -0
  92. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_non_stop_session.py +0 -0
  93. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_api.py +0 -0
  94. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_engine.py +0 -0
  95. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_models.py +0 -0
  96. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_payload.py +0 -0
  97. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_rehydrate.py +0 -0
  98. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_scenarios.py +0 -0
  99. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_session_auto_response.py +0 -0
  100. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_session_lifecycle.py +0 -0
  101. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_session_manager_concurrency.py +0 -0
  102. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_sessions.py +0 -0
  103. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_specs.py +0 -0
  104. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_templates.py +0 -0
  105. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_timezone_data.py +0 -0
  106. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_value_gen.py +0 -0
  107. {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_ws.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixtureqa
3
- Version: 0.9.2
3
+ Version: 0.9.3
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.9.1
7
+ Requires-Dist: fixcore-engine==0.9.2
8
8
  Requires-Dist: fastapi>=0.111.0
9
9
  Requires-Dist: uvicorn[standard]>=0.29.0
10
10
  Requires-Dist: websockets>=12.0
@@ -0,0 +1,69 @@
1
+ """
2
+ Group-aware construction of outbound FIX messages from a raw string.
3
+
4
+ The naive path — parse_raw() into a {tag: value} dict, then set_field() per tag —
5
+ collapses repeating groups: duplicate member tags (e.g. three NoAllocs rows) all
6
+ overwrite down to the last value, while the NoXxx count survives as a literal. So
7
+ the wire ends up claiming N instances but carrying only one.
8
+
9
+ This builder instead keeps fields in order and hands them to fixcore's
10
+ ``Message.from_fields`` (the lenient, fields-based counterpart to ``decode``),
11
+ which splits repeating groups using each group's spec definition when a
12
+ DataDictionary is supplied — the same code path the engine uses to decode
13
+ inbound messages, so the two can't drift. Header / seqnum / checksum tags are
14
+ dropped here; the session stamps them on send.
15
+
16
+ Without a DataDictionary (or for a count tag the spec doesn't define) it falls
17
+ back to flat fields — no worse than before, but standard groups now round-trip.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import Optional
22
+
23
+ from fixcore.message import DataDictionary, Message
24
+
25
+ SOH = "\x01"
26
+
27
+ # Tags the transport owns; never copied from the user's raw into the body.
28
+ CONTROL_TAGS = frozenset({8, 9, 10, 34, 49, 52, 56})
29
+ _MSG_TYPE = 35
30
+ _TRANSACT_TIME = 60
31
+
32
+
33
+ def _ordered_pairs(raw: str) -> list[tuple[int, str]]:
34
+ """Parse raw FIX into an ORDERED list of (tag, value), preserving duplicates."""
35
+ norm = raw if SOH in raw else raw.replace("|", SOH)
36
+ pairs: list[tuple[int, str]] = []
37
+ for part in norm.split(SOH):
38
+ if "=" not in part:
39
+ continue
40
+ tag_str, _, value = part.partition("=")
41
+ try:
42
+ pairs.append((int(tag_str), value))
43
+ except ValueError:
44
+ continue
45
+ return pairs
46
+
47
+
48
+ def build_message_from_raw(
49
+ raw: str,
50
+ data_dictionary: Optional[DataDictionary] = None,
51
+ *,
52
+ transact_time: Optional[str] = None,
53
+ ) -> Message:
54
+ """Build a fixcore Message from raw FIX, preserving repeating-group structure
55
+ when *data_dictionary* is supplied. Injects TransactTime(60) when
56
+ *transact_time* is given and absent. Raises ValueError if MsgType is missing."""
57
+ pairs = _ordered_pairs(raw)
58
+ msg_idx = next((i for i, (t, _) in enumerate(pairs) if t == _MSG_TYPE), None)
59
+ if msg_idx is None:
60
+ raise ValueError("Missing MsgType (tag 35)")
61
+
62
+ # Drop transport-owned tags; MsgType (35) stays — from_fields routes it to
63
+ # the header. The session stamps the control tags on send.
64
+ fields = [(t, v) for t, v in pairs if t not in CONTROL_TAGS]
65
+ if transact_time is not None and not any(t == _TRANSACT_TIME for t, _ in fields):
66
+ idx = next(i for i, (t, _) in enumerate(fields) if t == _MSG_TYPE)
67
+ fields.insert(idx + 1, (_TRANSACT_TIME, transact_time))
68
+
69
+ return Message.from_fields(fields, data_dictionary)
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixtureqa
3
- Version: 0.9.2
3
+ Version: 0.9.3
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.9.1
7
+ Requires-Dist: fixcore-engine==0.9.2
8
8
  Requires-Dist: fastapi>=0.111.0
9
9
  Requires-Dist: uvicorn[standard]>=0.29.0
10
10
  Requires-Dist: websockets>=12.0
@@ -1,4 +1,4 @@
1
- fixcore-engine==0.9.1
1
+ fixcore-engine==0.9.2
2
2
  fastapi>=0.111.0
3
3
  uvicorn[standard]>=0.29.0
4
4
  websockets>=12.0
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fixtureqa"
7
- version = "0.9.2"
7
+ version = "0.9.3"
8
8
  description = "FIXture — FIX Protocol Testing Tool"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
11
  # 0.7.1 owns the tzdata dependency (zoneinfo for the TimeZone schedule key),
12
12
  # so it's no longer declared here — fixcore is the only code using ZoneInfo.
13
- "fixcore-engine==0.9.1",
13
+ "fixcore-engine==0.9.2",
14
14
  "fastapi>=0.111.0",
15
15
  "uvicorn[standard]>=0.29.0",
16
16
  "websockets>=12.0",
@@ -1,132 +0,0 @@
1
- """
2
- Group-aware construction of outbound FIX messages from a raw string.
3
-
4
- The naive path — parse_raw() into a {tag: value} dict, then set_field() per tag —
5
- collapses repeating groups: duplicate member tags (e.g. three NoAllocs rows) all
6
- overwrite down to the last value, while the NoXxx count survives as a literal. So
7
- the wire ends up claiming N instances but carrying only one.
8
-
9
- This builder instead keeps fields in order and, when a DataDictionary is given,
10
- splits repeating groups using each group's spec definition (delimiter + member
11
- tags, nested groups supported) into proper fixcore Group instances. Header /
12
- seqnum / checksum tags are dropped — the session stamps them on send.
13
-
14
- Without a DataDictionary (or for a count tag the spec doesn't define) it falls
15
- back to flat fields — no worse than before, but standard groups now round-trip.
16
- """
17
- from __future__ import annotations
18
-
19
- from typing import Optional
20
-
21
- from fixcore.message import DataDictionary, Group, GroupDef, Message
22
-
23
- SOH = "\x01"
24
-
25
- # Tags the transport owns; never copied from the user's raw into the body.
26
- CONTROL_TAGS = frozenset({8, 9, 10, 34, 49, 52, 56})
27
- _MSG_TYPE = 35
28
- _TRANSACT_TIME = 60
29
-
30
-
31
- def _ordered_pairs(raw: str) -> list[tuple[int, str]]:
32
- """Parse raw FIX into an ORDERED list of (tag, value), preserving duplicates."""
33
- norm = raw if SOH in raw else raw.replace("|", SOH)
34
- pairs: list[tuple[int, str]] = []
35
- for part in norm.split(SOH):
36
- if "=" not in part:
37
- continue
38
- tag_str, _, value = part.partition("=")
39
- try:
40
- pairs.append((int(tag_str), value))
41
- except ValueError:
42
- continue
43
- return pairs
44
-
45
-
46
- def build_message_from_raw(
47
- raw: str,
48
- data_dictionary: Optional[DataDictionary] = None,
49
- *,
50
- transact_time: Optional[str] = None,
51
- ) -> Message:
52
- """Build a fixcore Message from raw FIX, preserving repeating-group structure
53
- when *data_dictionary* is supplied. Injects TransactTime(60) when
54
- *transact_time* is given and absent. Raises ValueError if MsgType is missing."""
55
- pairs = _ordered_pairs(raw)
56
- msg_type = next((v for t, v in pairs if t == _MSG_TYPE), None)
57
- if not msg_type:
58
- raise ValueError("Missing MsgType (tag 35)")
59
-
60
- body = [(t, v) for t, v in pairs if t != _MSG_TYPE and t not in CONTROL_TAGS]
61
- if transact_time is not None and not any(t == _TRANSACT_TIME for t, _ in body):
62
- body.insert(0, (_TRANSACT_TIME, transact_time))
63
-
64
- msg = Message()
65
- msg.header.set(_MSG_TYPE, msg_type)
66
-
67
- group_defs: dict[int, GroupDef] = {}
68
- if data_dictionary is not None:
69
- try:
70
- group_defs = data_dictionary.message_def(msg_type).groups
71
- except Exception:
72
- group_defs = {}
73
-
74
- _populate(msg, body, group_defs)
75
- return msg
76
-
77
-
78
- def _populate(msg: Message, body: list[tuple[int, str]],
79
- group_defs: dict[int, GroupDef]) -> None:
80
- pos, n = 0, len(body)
81
- while pos < n:
82
- tag, value = body[pos]
83
- gd = group_defs.get(tag)
84
- if gd is not None:
85
- count = int(value) if value.lstrip("-").isdigit() else 0
86
- pos += 1
87
- instances, pos = _extract(body, pos, gd, count)
88
- if instances:
89
- for inst in instances:
90
- msg.add_group(tag, inst) # keeps the count field in sync
91
- else:
92
- msg.set_field(tag, str(count)) # nothing matched — keep the count
93
- else:
94
- msg.set_field(tag, value)
95
- pos += 1
96
-
97
-
98
- def _extract(body: list[tuple[int, str]], pos: int, gd: GroupDef,
99
- count: int) -> tuple[list[Group], int]:
100
- """Pull up to *count* group instances starting at *pos*. An instance begins at
101
- the group's delimiter tag and runs until the delimiter repeats or a non-member
102
- tag appears; nested groups recurse."""
103
- members = set(gd.members) | {gd.delimiter}
104
- nested = gd.nested_groups
105
- instances: list[Group] = []
106
- n = len(body)
107
- while pos < n and len(instances) < count:
108
- tag, value = body[pos]
109
- if tag != gd.delimiter:
110
- break # not the start of an instance — group is done
111
- inst = Group()
112
- inst.set_field(tag, value)
113
- pos += 1
114
- while pos < n:
115
- t2, v2 = body[pos]
116
- if t2 == gd.delimiter:
117
- break # next instance
118
- ng = nested.get(t2)
119
- if ng is not None:
120
- c2 = int(v2) if v2.lstrip("-").isdigit() else 0
121
- pos += 1
122
- sub, pos = _extract(body, pos, ng, c2)
123
- for s in sub:
124
- inst.add_group(t2, s)
125
- continue
126
- if t2 in members:
127
- inst.set_field(t2, v2)
128
- pos += 1
129
- else:
130
- break # field outside the group — instance ends
131
- instances.append(inst)
132
- return instances, pos
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