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.
- {fixtureqa-0.9.2/fixtureqa.egg-info → fixtureqa-0.9.3}/PKG-INFO +2 -2
- fixtureqa-0.9.3/fixture/core/fix_builder.py +69 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3/fixtureqa.egg-info}/PKG-INFO +2 -2
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/requires.txt +1 -1
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/pyproject.toml +2 -2
- fixtureqa-0.9.2/fixture/core/fix_builder.py +0 -132
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/LICENSE +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/README.md +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/__init__.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/__main__.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/app.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/deps.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/specs.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/auth.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/events.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/latency_csv_writer.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/models.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_charts.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_engine.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_models.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_payload.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_stats.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/session.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/spec_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/value_gen.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/server.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/index-B-hpSZWZ.css +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/index-WJ1w221m.js +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/static/index.html +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/SOURCES.txt +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/setup.cfg +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_auth.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_config_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_health.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_inbound.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_message_store.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_non_stop_session.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_engine.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_session_auto_response.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_sessions.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_specs.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_templates.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_timezone_data.py +0 -0
- {fixtureqa-0.9.2 → fixtureqa-0.9.3}/tests/test_value_gen.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fixtureqa"
|
|
7
|
-
version = "0.9.
|
|
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.
|
|
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
|
|
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
|
|
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
|