fixtureqa 0.7.0__tar.gz → 0.8.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.
- {fixtureqa-0.7.0/fixtureqa.egg-info → fixtureqa-0.8.1}/PKG-INFO +3 -2
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/sessions.py +18 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/schemas.py +54 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/models.py +59 -1
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/session.py +47 -6
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/session_manager.py +10 -0
- fixtureqa-0.8.1/fixture/static/assets/index-Cu3H3T9B.js +102 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/static/index.html +1 -1
- {fixtureqa-0.7.0 → fixtureqa-0.8.1/fixtureqa.egg-info}/PKG-INFO +3 -2
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixtureqa.egg-info/SOURCES.txt +3 -1
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixtureqa.egg-info/requires.txt +2 -1
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/pyproject.toml +7 -2
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_config_store.py +20 -0
- fixtureqa-0.8.1/tests/test_non_stop_session.py +177 -0
- fixtureqa-0.8.1/tests/test_timezone_data.py +39 -0
- fixtureqa-0.7.0/fixture/static/assets/index-BJKwfdd8.js +0 -102
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/LICENSE +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/README.md +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/__init__.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/__main__.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/app.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/deps.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/auth.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/events.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/fix_builder.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/latency_csv_writer.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/perf_charts.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/perf_engine.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/perf_models.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/perf_payload.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/perf_stats.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/value_gen.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/server.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/static/assets/index-B-hpSZWZ.css +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/setup.cfg +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_auth.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_health.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_inbound.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_message_store.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_perf_engine.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_session_auto_response.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_sessions.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_templates.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_value_gen.py +0 -0
- {fixtureqa-0.7.0 → fixtureqa-0.8.1}/tests/test_ws.py +0 -0
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fixtureqa
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
+
Requires-Dist: fixcore-engine==0.7.0
|
|
8
8
|
Requires-Dist: fastapi>=0.111.0
|
|
9
9
|
Requires-Dist: uvicorn[standard]>=0.29.0
|
|
10
10
|
Requires-Dist: websockets>=12.0
|
|
11
11
|
Requires-Dist: python-jose[cryptography]>=3.3.0
|
|
12
12
|
Requires-Dist: passlib[bcrypt]>=1.7.4
|
|
13
13
|
Requires-Dist: matplotlib>=3.7
|
|
14
|
+
Requires-Dist: tzdata>=2024.1
|
|
14
15
|
Provides-Extra: dev
|
|
15
16
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
16
17
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -54,19 +54,37 @@ def _session_response(sm: SessionManager, session_id: str, us: Optional[UserStor
|
|
|
54
54
|
begin_string=cfg.begin_string,
|
|
55
55
|
sender_comp_id=cfg.sender_comp_id,
|
|
56
56
|
target_comp_id=cfg.target_comp_id,
|
|
57
|
+
session_qualifier=cfg.session_qualifier,
|
|
57
58
|
connection_type=cfg.connection_type.value,
|
|
58
59
|
host=cfg.host,
|
|
59
60
|
port=cfg.port,
|
|
61
|
+
socket_accept_host=cfg.socket_accept_host,
|
|
60
62
|
heartbeat_interval=cfg.heartbeat_interval,
|
|
61
63
|
reconnect_interval=cfg.reconnect_interval,
|
|
64
|
+
logon_timeout=cfg.logon_timeout,
|
|
65
|
+
logout_timeout=cfg.logout_timeout,
|
|
66
|
+
check_latency=cfg.check_latency,
|
|
67
|
+
max_latency=cfg.max_latency,
|
|
68
|
+
requires_orig_sending_time=cfg.requires_orig_sending_time,
|
|
69
|
+
resend_request_chunk_size=cfg.resend_request_chunk_size,
|
|
70
|
+
send_redundant_resend_requests=cfg.send_redundant_resend_requests,
|
|
62
71
|
use_file_log=cfg.use_file_log,
|
|
63
72
|
start_time=cfg.start_time,
|
|
64
73
|
end_time=cfg.end_time,
|
|
74
|
+
start_day=cfg.start_day,
|
|
75
|
+
end_day=cfg.end_day,
|
|
76
|
+
weekdays=cfg.weekdays,
|
|
77
|
+
time_zone=cfg.time_zone,
|
|
78
|
+
non_stop_session=cfg.non_stop_session,
|
|
65
79
|
reset_on_logon=cfg.reset_on_logon,
|
|
66
80
|
reset_on_logout=cfg.reset_on_logout,
|
|
81
|
+
reset_on_disconnect=cfg.reset_on_disconnect,
|
|
67
82
|
durable_seqnums=cfg.durable_seqnums,
|
|
68
83
|
log_messages=cfg.log_messages,
|
|
69
84
|
validate_inbound=cfg.validate_inbound,
|
|
85
|
+
validate_fields_have_values=cfg.validate_fields_have_values,
|
|
86
|
+
validate_user_defined_fields=cfg.validate_user_defined_fields,
|
|
87
|
+
validate_fields_out_of_range=cfg.validate_fields_out_of_range,
|
|
70
88
|
session_role=cfg.session_role.value,
|
|
71
89
|
auto_ack=cfg.auto_ack,
|
|
72
90
|
auto_ack_delay_ms=cfg.auto_ack_delay_ms,
|
|
@@ -7,20 +7,38 @@ class SessionConfigRequest(BaseModel):
|
|
|
7
7
|
begin_string: str = "FIX.4.2"
|
|
8
8
|
sender_comp_id: str
|
|
9
9
|
target_comp_id: str
|
|
10
|
+
session_qualifier: str = "" # SessionQualifier (disambiguate same triple)
|
|
10
11
|
connection_type: str = "initiator" # "initiator" or "acceptor"
|
|
11
12
|
host: str = "localhost"
|
|
12
13
|
port: int = Field(default=9876, ge=1, le=65535)
|
|
14
|
+
socket_accept_host: str = "" # SocketAcceptHost bind addr ("" = 0.0.0.0)
|
|
13
15
|
heartbeat_interval: int = Field(default=30, ge=1)
|
|
14
16
|
reconnect_interval: int = Field(default=10, ge=1)
|
|
17
|
+
logon_timeout: int = Field(default=10, ge=1)
|
|
18
|
+
logout_timeout: int = Field(default=10, ge=1)
|
|
19
|
+
check_latency: bool = False # CheckLatency: reject stale SendingTime
|
|
20
|
+
max_latency: int = Field(default=120, ge=1) # MaxLatency (s) when check_latency
|
|
21
|
+
requires_orig_sending_time: bool = True # RequiresOrigSendingTime
|
|
22
|
+
resend_request_chunk_size: int = Field(default=0, ge=0) # 0 = unlimited
|
|
23
|
+
send_redundant_resend_requests: bool = False
|
|
15
24
|
data_dictionary_path: Optional[str] = None
|
|
16
25
|
use_file_log: bool = True
|
|
17
26
|
start_time: str = "00:00:00"
|
|
18
27
|
end_time: str = "00:00:00"
|
|
28
|
+
start_day: str = "" # StartDay/EndDay together = weekly window
|
|
29
|
+
end_day: str = ""
|
|
30
|
+
weekdays: str = "" # e.g. "Mon,Tue,Wed,Thu,Fri" (excl. start/end day)
|
|
31
|
+
time_zone: str = "" # IANA name; "" = UTC
|
|
32
|
+
non_stop_session: bool = False # True = NonStopSession=Y (no daily seqnum reset)
|
|
19
33
|
reset_on_logon: bool = False
|
|
20
34
|
reset_on_logout: bool = False
|
|
35
|
+
reset_on_disconnect: bool = False
|
|
21
36
|
durable_seqnums: bool = True # False = in-memory store (no per-msg fsync; high-rate)
|
|
22
37
|
log_messages: bool = True # False = in-memory log only (no per-msg SQLite; high-rate)
|
|
23
38
|
validate_inbound: bool = False # True = reject non-conforming inbound (conformant-venue mode)
|
|
39
|
+
validate_fields_have_values: bool = True # reject "55=" (empty value)
|
|
40
|
+
validate_user_defined_fields: bool = False # reject unknown tag>=5000
|
|
41
|
+
validate_fields_out_of_range: bool = False # reject bad enums (e.g. Side=Z)
|
|
24
42
|
session_role: str = "generic" # "client", "venue", or "generic"
|
|
25
43
|
auto_ack: bool = False
|
|
26
44
|
auto_ack_delay_ms: int = Field(default=100, ge=0)
|
|
@@ -35,19 +53,37 @@ class SessionResponse(BaseModel):
|
|
|
35
53
|
begin_string: str
|
|
36
54
|
sender_comp_id: str
|
|
37
55
|
target_comp_id: str
|
|
56
|
+
session_qualifier: str
|
|
38
57
|
connection_type: str
|
|
39
58
|
host: str
|
|
40
59
|
port: int
|
|
60
|
+
socket_accept_host: str
|
|
41
61
|
heartbeat_interval: int
|
|
42
62
|
reconnect_interval: int
|
|
63
|
+
logon_timeout: int
|
|
64
|
+
logout_timeout: int
|
|
65
|
+
check_latency: bool
|
|
66
|
+
max_latency: int
|
|
67
|
+
requires_orig_sending_time: bool
|
|
68
|
+
resend_request_chunk_size: int
|
|
69
|
+
send_redundant_resend_requests: bool
|
|
43
70
|
use_file_log: bool
|
|
44
71
|
start_time: str
|
|
45
72
|
end_time: str
|
|
73
|
+
start_day: str
|
|
74
|
+
end_day: str
|
|
75
|
+
weekdays: str
|
|
76
|
+
time_zone: str
|
|
77
|
+
non_stop_session: bool
|
|
46
78
|
reset_on_logon: bool
|
|
47
79
|
reset_on_logout: bool
|
|
80
|
+
reset_on_disconnect: bool
|
|
48
81
|
durable_seqnums: bool
|
|
49
82
|
log_messages: bool
|
|
50
83
|
validate_inbound: bool
|
|
84
|
+
validate_fields_have_values: bool
|
|
85
|
+
validate_user_defined_fields: bool
|
|
86
|
+
validate_fields_out_of_range: bool
|
|
51
87
|
session_role: str
|
|
52
88
|
auto_ack: bool
|
|
53
89
|
auto_ack_delay_ms: int
|
|
@@ -68,15 +104,29 @@ class UpdateSessionRequest(BaseModel):
|
|
|
68
104
|
begin_string: Optional[str] = None
|
|
69
105
|
sender_comp_id: Optional[str] = None
|
|
70
106
|
target_comp_id: Optional[str] = None
|
|
107
|
+
session_qualifier: Optional[str] = None
|
|
71
108
|
connection_type: Optional[str] = None
|
|
72
109
|
host: Optional[str] = None
|
|
73
110
|
port: Optional[int] = Field(default=None, ge=1, le=65535)
|
|
111
|
+
socket_accept_host: Optional[str] = None
|
|
74
112
|
heartbeat_interval: Optional[int] = Field(default=None, ge=1)
|
|
75
113
|
reconnect_interval: Optional[int] = Field(default=None, ge=1)
|
|
114
|
+
logon_timeout: Optional[int] = Field(default=None, ge=1)
|
|
115
|
+
logout_timeout: Optional[int] = Field(default=None, ge=1)
|
|
116
|
+
check_latency: Optional[bool] = None
|
|
117
|
+
max_latency: Optional[int] = Field(default=None, ge=1)
|
|
118
|
+
requires_orig_sending_time: Optional[bool] = None
|
|
119
|
+
resend_request_chunk_size: Optional[int] = Field(default=None, ge=0)
|
|
120
|
+
send_redundant_resend_requests: Optional[bool] = None
|
|
76
121
|
data_dictionary_path: Optional[str] = None
|
|
77
122
|
use_file_log: Optional[bool] = None
|
|
78
123
|
start_time: Optional[str] = None
|
|
79
124
|
end_time: Optional[str] = None
|
|
125
|
+
start_day: Optional[str] = None
|
|
126
|
+
end_day: Optional[str] = None
|
|
127
|
+
weekdays: Optional[str] = None
|
|
128
|
+
time_zone: Optional[str] = None
|
|
129
|
+
non_stop_session: Optional[bool] = None
|
|
80
130
|
session_role: Optional[str] = None
|
|
81
131
|
auto_ack: Optional[bool] = None
|
|
82
132
|
auto_ack_delay_ms: Optional[int] = Field(default=None, ge=0)
|
|
@@ -84,9 +134,13 @@ class UpdateSessionRequest(BaseModel):
|
|
|
84
134
|
auto_fill_delay_ms: Optional[int] = Field(default=None, ge=0)
|
|
85
135
|
reset_on_logon: Optional[bool] = None
|
|
86
136
|
reset_on_logout: Optional[bool] = None
|
|
137
|
+
reset_on_disconnect: Optional[bool] = None
|
|
87
138
|
durable_seqnums: Optional[bool] = None
|
|
88
139
|
log_messages: Optional[bool] = None
|
|
89
140
|
validate_inbound: Optional[bool] = None
|
|
141
|
+
validate_fields_have_values: Optional[bool] = None
|
|
142
|
+
validate_user_defined_fields: Optional[bool] = None
|
|
143
|
+
validate_fields_out_of_range: Optional[bool] = None
|
|
90
144
|
|
|
91
145
|
|
|
92
146
|
class MessageEntryResponse(BaseModel):
|
|
@@ -39,12 +39,28 @@ class SessionConfig:
|
|
|
39
39
|
sender_comp_id: str = ""
|
|
40
40
|
target_comp_id: str = ""
|
|
41
41
|
|
|
42
|
+
# Disambiguates two sessions sharing the same BeginString/Sender/Target
|
|
43
|
+
# triple (fixcore SessionQualifier). "" = none.
|
|
44
|
+
session_qualifier: str = ""
|
|
45
|
+
|
|
42
46
|
# Connection
|
|
43
47
|
connection_type: ConnectionType = ConnectionType.INITIATOR
|
|
44
48
|
host: str = "localhost" # ignored for acceptor
|
|
45
49
|
port: int = 9876
|
|
46
50
|
heartbeat_interval: int = 30
|
|
47
51
|
reconnect_interval: int = 10 # initiator only
|
|
52
|
+
# Acceptor bind address (fixcore SocketAcceptHost). "" = 0.0.0.0 (all
|
|
53
|
+
# interfaces). Ignored for an initiator.
|
|
54
|
+
socket_accept_host: str = ""
|
|
55
|
+
|
|
56
|
+
# Liveness timers
|
|
57
|
+
logon_timeout: int = 10 # LogonTimeout (s): wait for Logon to complete
|
|
58
|
+
logout_timeout: int = 10 # LogoutTimeout (s): wait for Logout confirmation
|
|
59
|
+
# Reject inbound messages whose SendingTime(52) skews more than max_latency
|
|
60
|
+
# seconds from the local clock (fixcore CheckLatency). Off by default
|
|
61
|
+
# (fixcore's default; QuickFIX defaults this on). PossDup resends are exempt.
|
|
62
|
+
check_latency: bool = False
|
|
63
|
+
max_latency: int = 120 # MaxLatency (s): allowed SendingTime skew when check_latency
|
|
48
64
|
|
|
49
65
|
# Data dictionary (None = use bundled spec)
|
|
50
66
|
data_dictionary_path: Optional[str] = None
|
|
@@ -54,13 +70,43 @@ class SessionConfig:
|
|
|
54
70
|
file_log_path: str = "./log"
|
|
55
71
|
use_file_log: bool = True
|
|
56
72
|
|
|
57
|
-
# Session schedule ("00:00:00" for both = always active
|
|
73
|
+
# Session schedule ("00:00:00" for both = always active that day, with a
|
|
74
|
+
# daily sequence-number reset at midnight — fixcore's default daily window).
|
|
58
75
|
start_time: str = "00:00:00"
|
|
59
76
|
end_time: str = "00:00:00"
|
|
60
77
|
|
|
78
|
+
# Disable the daily session window entirely: when True, the engine runs as a
|
|
79
|
+
# single never-ending session (fixcore NonStopSession=Y) and never resets
|
|
80
|
+
# sequence numbers on a day boundary. False (default) keeps the daily window
|
|
81
|
+
# above, which performs the standard midnight seqnum reset. NonStopSession=Y
|
|
82
|
+
# is mutually exclusive with StartTime/EndTime, so those keys are omitted
|
|
83
|
+
# when this is set (see Session._build_settings).
|
|
84
|
+
non_stop_session: bool = False
|
|
85
|
+
|
|
86
|
+
# Extended schedule (fixcore SessionSchedule). All optional; combine with
|
|
87
|
+
# start_time/end_time. start_day+end_day => a *weekly* window (both required
|
|
88
|
+
# together). weekdays (e.g. "Mon,Tue,Wed,Thu,Fri") restricts the daily
|
|
89
|
+
# window to those days and is mutually exclusive with start_day/end_day.
|
|
90
|
+
# time_zone is an IANA name ("" = UTC). All ignored when non_stop_session.
|
|
91
|
+
start_day: str = ""
|
|
92
|
+
end_day: str = ""
|
|
93
|
+
weekdays: str = ""
|
|
94
|
+
time_zone: str = ""
|
|
95
|
+
|
|
61
96
|
# Sequence number reset behaviour
|
|
62
97
|
reset_on_logon: bool = False
|
|
63
98
|
reset_on_logout: bool = False
|
|
99
|
+
reset_on_disconnect: bool = False # ResetOnDisconnect: reset to 1 on every drop
|
|
100
|
+
|
|
101
|
+
# Resend / duplicate handling (fixcore)
|
|
102
|
+
# Reject a resent app message (PossDupFlag=Y) missing OrigSendingTime(122).
|
|
103
|
+
requires_orig_sending_time: bool = True
|
|
104
|
+
# Cap messages requested per outbound ResendRequest (0 = unlimited / one
|
|
105
|
+
# open-ended request); a positive value chunks recovery of a large gap.
|
|
106
|
+
resend_request_chunk_size: int = 0
|
|
107
|
+
# Re-send a ResendRequest for every out-of-order message while one is already
|
|
108
|
+
# outstanding (default N = re-request only when the gap advances).
|
|
109
|
+
send_redundant_resend_requests: bool = False
|
|
64
110
|
|
|
65
111
|
# Seqnum/message store durability. True (default) = fixcore FileStore, which
|
|
66
112
|
# fsyncs per message (durable resend/recovery, but caps throughput at disk
|
|
@@ -85,6 +131,18 @@ class SessionConfig:
|
|
|
85
131
|
# on regardless of this flag.
|
|
86
132
|
validate_inbound: bool = False
|
|
87
133
|
|
|
134
|
+
# Finer-grained validation, effective only while validate_inbound is on
|
|
135
|
+
# (mapped to the matching fixcore keys):
|
|
136
|
+
# validate_fields_have_values -> ValidateFieldsHaveValues (reject "55=")
|
|
137
|
+
# validate_user_defined_fields -> ValidateUserDefinedFields (reject unknown tag>=5000)
|
|
138
|
+
# validate_fields_out_of_range -> ValidateFieldsOutOfRange (reject bad enums,
|
|
139
|
+
# e.g. Side=Z). This is the fixcore-native key; it subsumes QuickFIX/n's
|
|
140
|
+
# inverse AllowUnknownEnumValues (=N), so that alias is not exposed
|
|
141
|
+
# separately — leaving this off is equivalent to AllowUnknownEnumValues=Y.
|
|
142
|
+
validate_fields_have_values: bool = True
|
|
143
|
+
validate_user_defined_fields: bool = False
|
|
144
|
+
validate_fields_out_of_range: bool = False
|
|
145
|
+
|
|
88
146
|
# Role / intent
|
|
89
147
|
session_role: SessionRole = SessionRole.GENERIC
|
|
90
148
|
|
|
@@ -340,15 +340,49 @@ class Session:
|
|
|
340
340
|
os.path.join(self.config.file_log_path, self.config.session_id)
|
|
341
341
|
)
|
|
342
342
|
|
|
343
|
+
yn = lambda b: "Y" if b else "N" # noqa: E731 — local boolean->FIX cfg
|
|
344
|
+
|
|
345
|
+
# Session schedule. NonStopSession=Y is mutually exclusive with *any*
|
|
346
|
+
# schedule key in fixcore (it raises on the combination), so emit either
|
|
347
|
+
# NonStopSession or the StartTime/EndTime window (+ optional refinements):
|
|
348
|
+
# - TimeZone scopes the times/days;
|
|
349
|
+
# - Weekdays restricts the daily window to listed days and is itself
|
|
350
|
+
# exclusive with StartDay/EndDay, so prefer it when both are set;
|
|
351
|
+
# - StartDay+EndDay (only together) make it a weekly window.
|
|
352
|
+
if self.config.non_stop_session:
|
|
353
|
+
schedule_lines = ["NonStopSession=Y"]
|
|
354
|
+
else:
|
|
355
|
+
schedule_lines = [
|
|
356
|
+
f"StartTime={self.config.start_time}",
|
|
357
|
+
f"EndTime={self.config.end_time}",
|
|
358
|
+
]
|
|
359
|
+
if self.config.time_zone:
|
|
360
|
+
schedule_lines.append(f"TimeZone={self.config.time_zone}")
|
|
361
|
+
if self.config.weekdays:
|
|
362
|
+
schedule_lines.append(f"Weekdays={self.config.weekdays}")
|
|
363
|
+
elif self.config.start_day and self.config.end_day:
|
|
364
|
+
schedule_lines += [
|
|
365
|
+
f"StartDay={self.config.start_day}",
|
|
366
|
+
f"EndDay={self.config.end_day}",
|
|
367
|
+
]
|
|
368
|
+
|
|
343
369
|
lines = [
|
|
344
370
|
"[DEFAULT]",
|
|
345
371
|
f"ConnectionType={ct}",
|
|
346
372
|
f"BeginString={self.config.begin_string}",
|
|
347
373
|
f"SenderCompID={self.config.sender_comp_id}",
|
|
348
374
|
f"TargetCompID={self.config.target_comp_id}",
|
|
375
|
+
*([f"SessionQualifier={self.config.session_qualifier}"]
|
|
376
|
+
if self.config.session_qualifier else []),
|
|
349
377
|
f"HeartBtInt={self.config.heartbeat_interval}",
|
|
350
|
-
f"
|
|
351
|
-
f"
|
|
378
|
+
f"LogonTimeout={self.config.logon_timeout}",
|
|
379
|
+
f"LogoutTimeout={self.config.logout_timeout}",
|
|
380
|
+
f"CheckLatency={yn(self.config.check_latency)}",
|
|
381
|
+
f"MaxLatency={self.config.max_latency}",
|
|
382
|
+
f"RequiresOrigSendingTime={yn(self.config.requires_orig_sending_time)}",
|
|
383
|
+
f"ResendRequestChunkSize={self.config.resend_request_chunk_size}",
|
|
384
|
+
f"SendRedundantResendRequests={yn(self.config.send_redundant_resend_requests)}",
|
|
385
|
+
*schedule_lines,
|
|
352
386
|
f"DataDictionary={dd_path}",
|
|
353
387
|
# Always load the dictionary so inbound repeating groups decode into
|
|
354
388
|
# proper instances (otherwise duplicate group-member tags collapse to
|
|
@@ -357,12 +391,17 @@ class Session:
|
|
|
357
391
|
"UseDataDictionary=Y",
|
|
358
392
|
# Permissive by default: decode group-aware but don't auto-reject
|
|
359
393
|
# non-conforming inbound. Flip on per session to behave like a
|
|
360
|
-
# conformant counterparty. Requires fixcore >= 0.5.0.
|
|
361
|
-
|
|
394
|
+
# conformant counterparty. Requires fixcore >= 0.5.0. The finer
|
|
395
|
+
# toggles below only bite while ValidateIncomingMessage=Y.
|
|
396
|
+
f"ValidateIncomingMessage={yn(self.config.validate_inbound)}",
|
|
397
|
+
f"ValidateFieldsHaveValues={yn(self.config.validate_fields_have_values)}",
|
|
398
|
+
f"ValidateUserDefinedFields={yn(self.config.validate_user_defined_fields)}",
|
|
399
|
+
f"ValidateFieldsOutOfRange={yn(self.config.validate_fields_out_of_range)}",
|
|
362
400
|
f"FileStorePath={store_path}",
|
|
363
401
|
f"FileLogPath={log_path}",
|
|
364
|
-
f"ResetOnLogon={
|
|
365
|
-
f"ResetOnLogout={
|
|
402
|
+
f"ResetOnLogon={yn(self.config.reset_on_logon)}",
|
|
403
|
+
f"ResetOnLogout={yn(self.config.reset_on_logout)}",
|
|
404
|
+
f"ResetOnDisconnect={yn(self.config.reset_on_disconnect)}",
|
|
366
405
|
"",
|
|
367
406
|
"[SESSION]",
|
|
368
407
|
]
|
|
@@ -375,6 +414,8 @@ class Session:
|
|
|
375
414
|
]
|
|
376
415
|
else:
|
|
377
416
|
lines.append(f"SocketAcceptPort={self.config.port}")
|
|
417
|
+
if self.config.socket_accept_host:
|
|
418
|
+
lines.append(f"SocketAcceptHost={self.config.socket_accept_host}")
|
|
378
419
|
|
|
379
420
|
return SessionSettings.from_string("\n".join(lines))
|
|
380
421
|
|
|
@@ -264,6 +264,16 @@ class SessionManager:
|
|
|
264
264
|
raise ValueError("target_comp_id is required")
|
|
265
265
|
if config.begin_string not in ("FIX.4.2", "FIX.4.4"):
|
|
266
266
|
raise ValueError(f"Unsupported FIX version: {config.begin_string!r}")
|
|
267
|
+
# Schedule-combo rules (mirror fixcore so a bad combo fails on save with a
|
|
268
|
+
# clear message instead of at engine start). Only meaningful when a
|
|
269
|
+
# windowed schedule is in play, i.e. not non-stop.
|
|
270
|
+
if not config.non_stop_session:
|
|
271
|
+
if config.weekdays and (config.start_day or config.end_day):
|
|
272
|
+
raise ValueError("Weekdays cannot be combined with StartDay/EndDay")
|
|
273
|
+
if bool(config.start_day) != bool(config.end_day):
|
|
274
|
+
raise ValueError("StartDay and EndDay must both be set (weekly window)")
|
|
275
|
+
if config.check_latency and config.max_latency < 1:
|
|
276
|
+
raise ValueError("max_latency must be >= 1 when check_latency is on")
|
|
267
277
|
from .models import ConnectionType
|
|
268
278
|
if config.connection_type == ConnectionType.ACCEPTOR:
|
|
269
279
|
for sid, s in self._sessions.items():
|