bridgemate-dataconnector-client 1.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of bridgemate-dataconnector-client might be problematic. Click here for more details.

Files changed (66) hide show
  1. bridgemate_dataconnector/__init__.py +25 -0
  2. bridgemate_dataconnector/client.py +431 -0
  3. bridgemate_dataconnector/dto/__init__.py +114 -0
  4. bridgemate_dataconnector/dto/_support.py +17 -0
  5. bridgemate_dataconnector/dto/bm2_contract_representation_option.py +17 -0
  6. bridgemate_dataconnector/dto/bm2_enter_handrecord_when_option.py +19 -0
  7. bridgemate_dataconnector/dto/bm2_name_source.py +21 -0
  8. bridgemate_dataconnector/dto/bm2_number_validation_option.py +23 -0
  9. bridgemate_dataconnector/dto/bm2_pair_number_entry_option.py +19 -0
  10. bridgemate_dataconnector/dto/bm2_point_score_perspective_option.py +17 -0
  11. bridgemate_dataconnector/dto/bm2_result_entry_method.py +19 -0
  12. bridgemate_dataconnector/dto/bm2_results_overview_option.py +25 -0
  13. bridgemate_dataconnector/dto/bm2_show_player_names_option.py +19 -0
  14. bridgemate_dataconnector/dto/bm2_show_ranking_option.py +19 -0
  15. bridgemate_dataconnector/dto/bm2_summary_point_option.py +19 -0
  16. bridgemate_dataconnector/dto/bm3_confirm_np_option.py +17 -0
  17. bridgemate_dataconnector/dto/bm3_connection_mode_option.py +22 -0
  18. bridgemate_dataconnector/dto/bm3_enter_handrecord_option.py +19 -0
  19. bridgemate_dataconnector/dto/bm3_language_option.py +35 -0
  20. bridgemate_dataconnector/dto/bm3_lead_card_entry_option.py +23 -0
  21. bridgemate_dataconnector/dto/bm3_next_round_seating_option.py +19 -0
  22. bridgemate_dataconnector/dto/bm3_player_number_mandatory_option.py +17 -0
  23. bridgemate_dataconnector/dto/bm3_player_registration_option.py +19 -0
  24. bridgemate_dataconnector/dto/bm3_player_registration_option_during_play.py +18 -0
  25. bridgemate_dataconnector/dto/bm3_player_view_option.py +17 -0
  26. bridgemate_dataconnector/dto/bm3_ranking_display_option.py +21 -0
  27. bridgemate_dataconnector/dto/bm3_result_entry_method.py +19 -0
  28. bridgemate_dataconnector/dto/bm3_result_reentry_option.py +17 -0
  29. bridgemate_dataconnector/dto/bm3_score_column_option.py +17 -0
  30. bridgemate_dataconnector/dto/bm3_score_recap_option.py +19 -0
  31. bridgemate_dataconnector/dto/bm3_session_summary_option.py +17 -0
  32. bridgemate_dataconnector/dto/bm3_starting_positions_option.py +17 -0
  33. bridgemate_dataconnector/dto/bm3_td_call_option.py +17 -0
  34. bridgemate_dataconnector/dto/bm3_view_handrecord_option.py +17 -0
  35. bridgemate_dataconnector/dto/bm3_view_percentages_option.py +17 -0
  36. bridgemate_dataconnector/dto/bm3_view_results_option.py +21 -0
  37. bridgemate_dataconnector/dto/bridgemate2_settings_dto.py +296 -0
  38. bridgemate_dataconnector/dto/bridgemate3_settings_dto.py +196 -0
  39. bridgemate_dataconnector/dto/bridgemate_settings_dto.py +17 -0
  40. bridgemate_dataconnector/dto/continue_dto.py +46 -0
  41. bridgemate_dataconnector/dto/data_connector_response_data.py +65 -0
  42. bridgemate_dataconnector/dto/error_type.py +44 -0
  43. bridgemate_dataconnector/dto/handrecord_dto.py +246 -0
  44. bridgemate_dataconnector/dto/init_dto.py +120 -0
  45. bridgemate_dataconnector/dto/participation_dto.py +89 -0
  46. bridgemate_dataconnector/dto/player_data_dto.py +58 -0
  47. bridgemate_dataconnector/dto/result_dto.py +217 -0
  48. bridgemate_dataconnector/dto/round_dto.py +102 -0
  49. bridgemate_dataconnector/dto/scoring_group_dto.py +118 -0
  50. bridgemate_dataconnector/dto/scoring_program_data_connector_commands.py +105 -0
  51. bridgemate_dataconnector/dto/scoring_program_request.py +50 -0
  52. bridgemate_dataconnector/dto/scoring_program_response.py +57 -0
  53. bridgemate_dataconnector/dto/section_dto.py +116 -0
  54. bridgemate_dataconnector/dto/section_update_dto.py +137 -0
  55. bridgemate_dataconnector/dto/session_dto.py +89 -0
  56. bridgemate_dataconnector/dto/table_direction.py +23 -0
  57. bridgemate_dataconnector/dto/table_dto.py +66 -0
  58. bridgemate_dataconnector/dto/td_call_dto.py +71 -0
  59. bridgemate_dataconnector/http_transport.py +25 -0
  60. bridgemate_dataconnector/py.typed +0 -0
  61. bridgemate_dataconnector/transport_exception.py +13 -0
  62. bridgemate_dataconnector/urllib_transport.py +49 -0
  63. bridgemate_dataconnector_client-1.0.0.dist-info/METADATA +166 -0
  64. bridgemate_dataconnector_client-1.0.0.dist-info/RECORD +66 -0
  65. bridgemate_dataconnector_client-1.0.0.dist-info/WHEEL +4 -0
  66. bridgemate_dataconnector_client-1.0.0.dist-info/licenses/LICENSE +165 -0
@@ -0,0 +1,25 @@
1
+ """Scoring program client for the Bridgemate Data Connector over http.
2
+
3
+ The hand-written runtime lives in this package; the generated wire-format types live in the
4
+ dto subpackage (import them with: from bridgemate_dataconnector.dto import InitDTO).
5
+ """
6
+
7
+ from .client import DataConnectorClient
8
+ from .http_transport import HttpTransport
9
+ from .transport_exception import TransportException
10
+ from .urllib_transport import UrllibTransport
11
+
12
+ try:
13
+ from importlib.metadata import version
14
+
15
+ __version__ = version("bridgemate-dataconnector-client")
16
+ except Exception: # pragma: no cover - only hit when running from a plain checkout.
17
+ __version__ = "0.0.0"
18
+
19
+ __all__ = [
20
+ "DataConnectorClient",
21
+ "HttpTransport",
22
+ "TransportException",
23
+ "UrllibTransport",
24
+ "__version__",
25
+ ]
@@ -0,0 +1,431 @@
1
+ """The scoring program client. See DataConnectorClient."""
2
+
3
+ import json
4
+ import re
5
+ import time
6
+
7
+ from .dto import (
8
+ Bridgemate2SettingsDTO,
9
+ Bridgemate3SettingsDTO,
10
+ ContinueDTO,
11
+ DataConnectorResponseData,
12
+ ErrorType,
13
+ HandrecordDTO,
14
+ InitDTO,
15
+ ParticipationDTO,
16
+ PlayerDataDTO,
17
+ ResultDTO,
18
+ ScoringGroupDTO,
19
+ ScoringProgramDataConnectorCommands,
20
+ ScoringProgramRequest,
21
+ ScoringProgramResponse,
22
+ SectionUpdateDTO,
23
+ TdCallDTO,
24
+ )
25
+ from .http_transport import HttpTransport
26
+ from .transport_exception import TransportException
27
+ from .urllib_transport import UrllibTransport
28
+
29
+
30
+ def _encode(data) -> str:
31
+ """Serializes a DTO or a list of DTOs to compact JSON, like System.Text.Json does."""
32
+ if isinstance(data, list):
33
+ return json.dumps([dto.to_dict() for dto in data], separators=(",", ":"))
34
+ return json.dumps(data.to_dict(), separators=(",", ":"))
35
+
36
+
37
+ class DataConnectorClient:
38
+ """Scoring program client for the Bridgemate Data Connector over http.
39
+
40
+ The wire behaviour mirrors the .NET ScoringProgramDataConnectorHttpClient: a JSON serialized
41
+ ScoringProgramRequest is POSTed to the dc-scoringprogram endpoint and answered with a JSON
42
+ serialized ScoringProgramResponse. Since http is stateless there is no persistent connection:
43
+ connect() is a ping and there is no disconnect.
44
+
45
+ Methods never raise on communication problems; they return a ScoringProgramResponse with
46
+ data_type Error (poll methods return an empty list), exactly like the .NET client.
47
+ """
48
+
49
+ # The default http port of the Data Connector.
50
+ DEFAULT_PORT = 5079
51
+
52
+ # The endpoint that handles scoring program requests.
53
+ SCORING_PROGRAM_ENDPOINT = "dc-scoringprogram"
54
+
55
+ # Part of the expected response body when pinging the Data Connector with a GET request.
56
+ API_PING_RESPONSE = "Bridgemate dataconnector service version"
57
+
58
+ def __init__(
59
+ self,
60
+ club_id: str,
61
+ licence_key: str,
62
+ base_address: str | None = None,
63
+ transport: HttpTransport | None = None,
64
+ ):
65
+ """Creates the client.
66
+
67
+ club_id: the id of the club that is using the client. Required for http communication.
68
+ licence_key: the licence key for the club using the client. Required for http communication.
69
+ base_address: the base address of the Data Connector host, e.g. "http://192.168.1.50:5079".
70
+ When None the client targets the Data Connector on the local computer, discovering its
71
+ port through the Windows registry (default 5079). On non-Windows hosts pass the address
72
+ explicitly.
73
+ transport: override the http implementation; used by tests and by the wire trace.
74
+ """
75
+ self._club_id = club_id
76
+ self._licence_key = licence_key
77
+ if base_address is not None:
78
+ trimmed = base_address.rstrip("/")
79
+ if not re.match("^https?://", trimmed, re.IGNORECASE):
80
+ raise ValueError(f"'{base_address}' is not an absolute http or https url.")
81
+ self._base_address = trimmed
82
+ else:
83
+ self._base_address = self.discover_local_base_address()
84
+ self._transport = transport if transport is not None else UrllibTransport()
85
+ # The id of the last downloaded queue item per data type, cached by the poll methods and
86
+ # used by accept_queue_data. Keyed by the int value of DataConnectorResponseData.
87
+ self._last_queue_item_ids: dict[int, int] = {}
88
+
89
+ @property
90
+ def base_address(self) -> str:
91
+ """The base address in use, e.g. "http://localhost:5079"."""
92
+ return self._base_address
93
+
94
+ @staticmethod
95
+ def discover_local_base_address() -> str:
96
+ """The url of the Data Connector on the local computer.
97
+
98
+ On Windows the Data Connector service publishes the port it listens on in the registry
99
+ (HKEY_CURRENT_USER\\Software\\Bridge Systems BV\\BridgemateDataConnector, value HttpPort);
100
+ when nothing is published the default port 5079 is assumed.
101
+ """
102
+ try:
103
+ import winreg # Only available on Windows; guarded by the except below.
104
+
105
+ with winreg.OpenKey(
106
+ winreg.HKEY_CURRENT_USER, r"Software\Bridge Systems BV\BridgemateDataConnector"
107
+ ) as key:
108
+ value, value_type = winreg.QueryValueEx(key, "HttpPort")
109
+ if value_type == winreg.REG_DWORD and 0 < value <= 65535:
110
+ return f"http://localhost:{value}"
111
+ except (ImportError, OSError):
112
+ # Non-Windows host, or the key/value is absent: fall through to the default.
113
+ pass
114
+ return f"http://localhost:{DataConnectorClient.DEFAULT_PORT}"
115
+
116
+ def connect(self) -> ScoringProgramResponse:
117
+ """Checks if the Data Connector can be reached by sending a GET request to its base address."""
118
+ try:
119
+ body = self._transport.get(self._base_address + "/")
120
+ success = self.API_PING_RESPONSE in body
121
+ except TransportException as exception:
122
+ body = str(exception)
123
+ success = False
124
+ response = ScoringProgramResponse()
125
+ response.request_command = ScoringProgramDataConnectorCommands.Connect
126
+ response.data_type = (
127
+ DataConnectorResponseData.OK if success else DataConnectorResponseData.Error
128
+ )
129
+ response.error_type = ErrorType.None_ if success else ErrorType.NoConnection
130
+ response.serialized_data = json.dumps(body)
131
+ return response
132
+
133
+ def ping(self) -> ScoringProgramResponse:
134
+ """Checks that the Data Connector is responsive by sending it a piece of data that it
135
+ must echo.
136
+ """
137
+ # The .NET client sends the current time in ticks; any unique string would do.
138
+ request_ticks = str(int(time.time() * 10_000_000))
139
+ response = self._send("", ScoringProgramDataConnectorCommands.Ping, json.dumps(request_ticks))
140
+ if response.request_command != ScoringProgramDataConnectorCommands.Ping:
141
+ return self._error_response(
142
+ ScoringProgramDataConnectorCommands.Ping,
143
+ ErrorType.Unknown,
144
+ f"Invalid command in response to Ping: '{response.request_command.name}'",
145
+ )
146
+ if response.data_type != DataConnectorResponseData.OK:
147
+ return response
148
+ response_ticks = json.loads(response.serialized_data or "null")
149
+ error = response_ticks != request_ticks
150
+ result = ScoringProgramResponse()
151
+ result.request_command = ScoringProgramDataConnectorCommands.Ping
152
+ result.data_type = (
153
+ DataConnectorResponseData.Error if error else DataConnectorResponseData.OK
154
+ )
155
+ result.error_type = ErrorType.Validation if error else ErrorType.None_
156
+ result.serialized_data = response.serialized_data
157
+ return result
158
+
159
+ def initialize(self, init_dto: InitDTO) -> ScoringProgramResponse:
160
+ """Instructs BCS to create a new event with the provided sessions, scoring groups,
161
+ sections, tables and rounds. Player data, participations and handrecords can be included.
162
+ """
163
+ return self._send("", ScoringProgramDataConnectorCommands.InitializeEvent, _encode(init_dto))
164
+
165
+ def continue_event(self, continue_dto: ContinueDTO) -> ScoringProgramResponse:
166
+ """Instructs BCS to continue working with a previously created event."""
167
+ return self._send("", ScoringProgramDataConnectorCommands.ContinueEvent, _encode(continue_dto))
168
+
169
+ def update_movement(self, updated_section: SectionUpdateDTO) -> ScoringProgramResponse:
170
+ """Updates the movement for a section, or deletes the section."""
171
+ return self._send(
172
+ updated_section.session_guid or "",
173
+ ScoringProgramDataConnectorCommands.UpdateMovement,
174
+ _encode(updated_section),
175
+ )
176
+
177
+ def update_scoring_groups(self, scoring_groups: list[ScoringGroupDTO]) -> ScoringProgramResponse:
178
+ """Updates the scoring method of the scoring groups and/or rearranges the assignment of
179
+ the sections to them. Sends one request per session, like the .NET client.
180
+ """
181
+ groups: dict[str, list[ScoringGroupDTO]] = {}
182
+ for scoring_group in scoring_groups:
183
+ groups.setdefault(scoring_group.session_guid or "", []).append(scoring_group)
184
+ for session_guid, groups_for_session in groups.items():
185
+ response = self._send(
186
+ session_guid,
187
+ ScoringProgramDataConnectorCommands.UpdateScoringGroups,
188
+ _encode(groups_for_session),
189
+ )
190
+ if response.data_type != DataConnectorResponseData.OK:
191
+ return response
192
+ response = ScoringProgramResponse()
193
+ response.request_command = ScoringProgramDataConnectorCommands.UpdateScoringGroups
194
+ response.data_type = DataConnectorResponseData.OK
195
+ response.serialized_data = json.dumps("Scoring groups updated.")
196
+ return response
197
+
198
+ def send_results(self, session_guid: str, results: list[ResultDTO]) -> ScoringProgramResponse:
199
+ """Sends board results to the BCS queue. Only DTOs whose session_guid matches are sent."""
200
+ return self._send_for_session(session_guid, ScoringProgramDataConnectorCommands.PutResults, results)
201
+
202
+ def send_player_data(self, session_guid: str, player_data: list[PlayerDataDTO]) -> ScoringProgramResponse:
203
+ """Sends player data to the BCS queue. Only DTOs whose session_guid matches are sent."""
204
+ return self._send_for_session(session_guid, ScoringProgramDataConnectorCommands.PutPlayerData, player_data)
205
+
206
+ def send_participations(self, session_guid: str, participations: list[ParticipationDTO]) -> ScoringProgramResponse:
207
+ """Sends participations to the BCS queue. Only DTOs whose session_guid matches are sent;
208
+ when none match a NoData error is returned without calling the Data Connector.
209
+ """
210
+ for_session = self._filter_by_session(session_guid, participations)
211
+ if not for_session:
212
+ return self._error_response(
213
+ ScoringProgramDataConnectorCommands.PutParticipations, ErrorType.NoData, "Empty data"
214
+ )
215
+ return self._send(
216
+ session_guid, ScoringProgramDataConnectorCommands.PutParticipations, _encode(for_session)
217
+ )
218
+
219
+ def send_handrecords(self, session_guid: str, handrecords: list[HandrecordDTO]) -> ScoringProgramResponse:
220
+ """Sends handrecords to the BCS queue. Only DTOs whose session_guid matches are sent."""
221
+ return self._send_for_session(session_guid, ScoringProgramDataConnectorCommands.PutHandrecords, handrecords)
222
+
223
+ def send_td_calls(self, session_guid: str, td_calls: list[TdCallDTO]) -> ScoringProgramResponse:
224
+ """Sends TD calls to the BCS queue. Only DTOs whose session_guid matches are sent."""
225
+ return self._send_for_session(session_guid, ScoringProgramDataConnectorCommands.PutTdCalls, td_calls)
226
+
227
+ def send_bridgemate2_settings(
228
+ self, session_guid: str, settings: list[Bridgemate2SettingsDTO]
229
+ ) -> ScoringProgramResponse:
230
+ """Adds or updates the Bridgemate 2 settings for the given sections. One DTO per section."""
231
+ return self._send_for_session(
232
+ session_guid, ScoringProgramDataConnectorCommands.PutBridgemate2Settings, settings
233
+ )
234
+
235
+ def send_bridgemate3_settings(
236
+ self, session_guid: str, settings: list[Bridgemate3SettingsDTO]
237
+ ) -> ScoringProgramResponse:
238
+ """Adds or updates the Bridgemate 3 settings for the given sections. One DTO per section."""
239
+ return self._send_for_session(
240
+ session_guid, ScoringProgramDataConnectorCommands.PutBridgemate3Settings, settings
241
+ )
242
+
243
+ def poll_for_results(self, session_guid: str, all: bool = False) -> list[ResultDTO]:
244
+ """Polls the queue for board results.
245
+
246
+ all: also return results that were polled before.
247
+ """
248
+ return self._poll(
249
+ session_guid,
250
+ ScoringProgramDataConnectorCommands.PollQueueForAllResults
251
+ if all
252
+ else ScoringProgramDataConnectorCommands.PollQueueForNewResults,
253
+ DataConnectorResponseData.Results,
254
+ ResultDTO,
255
+ )
256
+
257
+ def poll_for_player_data(self, session_guid: str, all: bool = False) -> list[PlayerDataDTO]:
258
+ """Polls the queue for player data.
259
+
260
+ all: also return player data that was polled before.
261
+ """
262
+ return self._poll(
263
+ session_guid,
264
+ ScoringProgramDataConnectorCommands.PollQueueForAllPlayerData
265
+ if all
266
+ else ScoringProgramDataConnectorCommands.PollQueueForNewPlayerData,
267
+ DataConnectorResponseData.PlayerData,
268
+ PlayerDataDTO,
269
+ )
270
+
271
+ def poll_for_participations(self, session_guid: str, all: bool = False) -> list[ParticipationDTO]:
272
+ """Polls the queue for participations.
273
+
274
+ all: also return participations that were polled before.
275
+ """
276
+ return self._poll(
277
+ session_guid,
278
+ ScoringProgramDataConnectorCommands.PollQueueForAllParticipations
279
+ if all
280
+ else ScoringProgramDataConnectorCommands.PollQueueForNewParticipations,
281
+ DataConnectorResponseData.Participations,
282
+ ParticipationDTO,
283
+ )
284
+
285
+ def poll_for_handrecords(self, session_guid: str, all: bool = False) -> list[HandrecordDTO]:
286
+ """Polls the queue for handrecords.
287
+
288
+ all: also return handrecords that were polled before.
289
+ """
290
+ return self._poll(
291
+ session_guid,
292
+ ScoringProgramDataConnectorCommands.PollQueueForAllHandrecords
293
+ if all
294
+ else ScoringProgramDataConnectorCommands.PollQueueForNewHandrecords,
295
+ DataConnectorResponseData.Handrecords,
296
+ HandrecordDTO,
297
+ )
298
+
299
+ def poll_for_td_calls(self, session_guid: str, all: bool = False) -> list[TdCallDTO]:
300
+ """Polls the queue for TD calls.
301
+
302
+ all: also return TD calls that were polled before.
303
+ """
304
+ return self._poll(
305
+ session_guid,
306
+ ScoringProgramDataConnectorCommands.PollQueueForAllTdCalls
307
+ if all
308
+ else ScoringProgramDataConnectorCommands.PollQueueForNewTdCalls,
309
+ DataConnectorResponseData.TdCalls,
310
+ TdCallDTO,
311
+ )
312
+
313
+ def accept_queue_data(
314
+ self, session_guid: str, data_type: DataConnectorResponseData
315
+ ) -> ScoringProgramResponse:
316
+ """Signals to the Data Connector that queue data of the given type, up to and including
317
+ the last polled item, does not need to be sent again.
318
+
319
+ data_type: Results, PlayerData, Participations, Handrecords or TdCalls.
320
+ """
321
+ commands = {
322
+ DataConnectorResponseData.Results: ScoringProgramDataConnectorCommands.AcceptResultQueueItems,
323
+ DataConnectorResponseData.PlayerData: ScoringProgramDataConnectorCommands.AcceptPlayerDataQueueItems,
324
+ DataConnectorResponseData.Participations: ScoringProgramDataConnectorCommands.AcceptParticipantQueueItems,
325
+ DataConnectorResponseData.Handrecords: ScoringProgramDataConnectorCommands.AcceptHandrecordQueueItems,
326
+ DataConnectorResponseData.TdCalls: ScoringProgramDataConnectorCommands.AcceptTdCallQueueItems,
327
+ }
328
+ command = commands.get(data_type)
329
+ if command is None:
330
+ return self._error_response(
331
+ ScoringProgramDataConnectorCommands.None_,
332
+ ErrorType.Validation,
333
+ f"Invalid datatype '{data_type.name}'",
334
+ )
335
+ last_queue_item_id = self._last_queue_item_ids.get(int(data_type), 0)
336
+ return self._send(session_guid, command, json.dumps(last_queue_item_id))
337
+
338
+ def get_last_queue_item_id(self, data_type: DataConnectorResponseData) -> int:
339
+ """The id of the last queue item downloaded for the given data type, as cached by the
340
+ poll methods. Zero when nothing has been polled yet.
341
+ """
342
+ return self._last_queue_item_ids.get(int(data_type), 0)
343
+
344
+ def send_request(self, request: ScoringProgramRequest) -> ScoringProgramResponse:
345
+ """Sends a raw request. The club id and licence key are set on the request by this method.
346
+
347
+ All higher level methods funnel through here; use it directly for commands this client
348
+ does not wrap yet. A breakpoint on this method shows every envelope that goes over
349
+ the wire.
350
+ """
351
+ request.club_id = self._club_id
352
+ request.licence_key = self._licence_key
353
+ body = json.dumps(request.to_dict(), separators=(",", ":"))
354
+ url = f"{self._base_address}/{self.SCORING_PROGRAM_ENDPOINT}"
355
+
356
+ last_error_message = "No connection"
357
+ for attempt in range(5):
358
+ try:
359
+ response_body = self._transport.post(url, body)
360
+ decoded = json.loads(response_body)
361
+ if not isinstance(decoded, dict):
362
+ return self._error_response(
363
+ request.command, ErrorType.EmptyResponse, "Empty response"
364
+ )
365
+ return ScoringProgramResponse.from_dict(decoded)
366
+ except (TransportException, json.JSONDecodeError) as exception:
367
+ last_error_message = str(exception)
368
+ # The .NET client backs off 200/400/600/800 ms between its five attempts.
369
+ if attempt < 4:
370
+ time.sleep((200 + attempt * 200) / 1000)
371
+ return self._error_response(request.command, ErrorType.NoConnection, last_error_message)
372
+
373
+ def _send(
374
+ self,
375
+ session_guid: str,
376
+ command: ScoringProgramDataConnectorCommands,
377
+ serialized_data: str,
378
+ ) -> ScoringProgramResponse:
379
+ request = ScoringProgramRequest()
380
+ request.command = command
381
+ request.session_guid = session_guid
382
+ request.serialized_data = serialized_data
383
+ return self.send_request(request)
384
+
385
+ def _send_for_session(
386
+ self,
387
+ session_guid: str,
388
+ command: ScoringProgramDataConnectorCommands,
389
+ dtos: list,
390
+ ) -> ScoringProgramResponse:
391
+ return self._send(session_guid, command, _encode(self._filter_by_session(session_guid, dtos)))
392
+
393
+ @staticmethod
394
+ def _filter_by_session(session_guid: str, dtos: list) -> list:
395
+ return [dto for dto in dtos if dto.session_guid == session_guid]
396
+
397
+ def _poll(
398
+ self,
399
+ session_guid: str,
400
+ command: ScoringProgramDataConnectorCommands,
401
+ expected_data_type: DataConnectorResponseData,
402
+ dto_class,
403
+ ) -> list:
404
+ response = self._send(session_guid, command, "")
405
+ if response.data_type != expected_data_type:
406
+ return []
407
+ if response.serialized_data is None or response.serialized_data.strip() == "":
408
+ return []
409
+ try:
410
+ items = json.loads(response.serialized_data)
411
+ except json.JSONDecodeError:
412
+ return []
413
+ if not isinstance(items, list):
414
+ return []
415
+ dtos = [dto_class.from_dict(item) for item in items]
416
+ if dtos:
417
+ self._last_queue_item_ids[int(expected_data_type)] = response.last_queue_item_id
418
+ return dtos
419
+
420
+ def _error_response(
421
+ self,
422
+ command: ScoringProgramDataConnectorCommands,
423
+ error_type: ErrorType,
424
+ message: str,
425
+ ) -> ScoringProgramResponse:
426
+ response = ScoringProgramResponse()
427
+ response.request_command = command
428
+ response.data_type = DataConnectorResponseData.Error
429
+ response.error_type = error_type
430
+ response.serialized_data = json.dumps(message)
431
+ return response
@@ -0,0 +1,114 @@
1
+ # Generated by tools/DtoGenerator from BridgeSystems.Bridgemate.DataConnector.ScoringProgramClient 5.0.1036.0. Do not edit by hand.
2
+ """Generated DTO layer: the wire-format types shared with the .NET reference client."""
3
+
4
+ from .bridgemate_settings_dto import BridgemateSettingsDTO
5
+ from .continue_dto import ContinueDTO
6
+ from .handrecord_dto import HandrecordDTO
7
+ from .init_dto import InitDTO
8
+ from .participation_dto import ParticipationDTO
9
+ from .player_data_dto import PlayerDataDTO
10
+ from .result_dto import ResultDTO
11
+ from .round_dto import RoundDTO
12
+ from .scoring_group_dto import ScoringGroupDTO
13
+ from .scoring_program_request import ScoringProgramRequest
14
+ from .scoring_program_response import ScoringProgramResponse
15
+ from .section_dto import SectionDTO
16
+ from .section_update_dto import SectionUpdateDTO
17
+ from .session_dto import SessionDTO
18
+ from .table_dto import TableDTO
19
+ from .td_call_dto import TdCallDTO
20
+ from .bridgemate2_settings_dto import Bridgemate2SettingsDTO
21
+ from .bridgemate3_settings_dto import Bridgemate3SettingsDTO
22
+ from .bm2_result_entry_method import BM2ResultEntryMethod
23
+ from .bm3_player_view_option import BM3PlayerViewOption
24
+ from .bm3_starting_positions_option import BM3StartingPositionsOption
25
+ from .bm2_contract_representation_option import Bm2ContractRepresentationOption
26
+ from .bm2_enter_handrecord_when_option import Bm2EnterHandrecordWhenOption
27
+ from .bm2_name_source import Bm2NameSource
28
+ from .bm2_number_validation_option import Bm2NumberValidationOption
29
+ from .bm2_pair_number_entry_option import Bm2PairNumberEntryOption
30
+ from .bm2_point_score_perspective_option import Bm2PointScorePerspectiveOption
31
+ from .bm2_results_overview_option import Bm2ResultsOverviewOption
32
+ from .bm2_show_player_names_option import Bm2ShowPlayerNamesOption
33
+ from .bm2_show_ranking_option import Bm2ShowRankingOption
34
+ from .bm2_summary_point_option import Bm2SummaryPointOption
35
+ from .bm3_confirm_np_option import Bm3ConfirmNPOption
36
+ from .bm3_connection_mode_option import Bm3ConnectionModeOption
37
+ from .bm3_enter_handrecord_option import Bm3EnterHandrecordOption
38
+ from .bm3_language_option import Bm3LanguageOption
39
+ from .bm3_lead_card_entry_option import Bm3LeadCardEntryOption
40
+ from .bm3_next_round_seating_option import Bm3NextRoundSeatingOption
41
+ from .bm3_player_number_mandatory_option import Bm3PlayerNumberMandatoryOption
42
+ from .bm3_player_registration_option import Bm3PlayerRegistrationOption
43
+ from .bm3_player_registration_option_during_play import Bm3PlayerRegistrationOptionDuringPlay
44
+ from .bm3_ranking_display_option import Bm3RankingDisplayOption
45
+ from .bm3_result_entry_method import Bm3ResultEntryMethod
46
+ from .bm3_result_reentry_option import Bm3ResultReentryOption
47
+ from .bm3_score_column_option import Bm3ScoreColumnOption
48
+ from .bm3_score_recap_option import Bm3ScoreRecapOption
49
+ from .bm3_session_summary_option import Bm3SessionSummaryOption
50
+ from .bm3_td_call_option import Bm3TdCallOption
51
+ from .bm3_view_handrecord_option import Bm3ViewHandrecordOption
52
+ from .bm3_view_percentages_option import Bm3ViewPercentagesOption
53
+ from .bm3_view_results_option import Bm3ViewResultsOption
54
+ from .data_connector_response_data import DataConnectorResponseData
55
+ from .error_type import ErrorType
56
+ from .scoring_program_data_connector_commands import ScoringProgramDataConnectorCommands
57
+ from .table_direction import TableDirection
58
+
59
+ __all__ = [
60
+ "BridgemateSettingsDTO",
61
+ "ContinueDTO",
62
+ "HandrecordDTO",
63
+ "InitDTO",
64
+ "ParticipationDTO",
65
+ "PlayerDataDTO",
66
+ "ResultDTO",
67
+ "RoundDTO",
68
+ "ScoringGroupDTO",
69
+ "ScoringProgramRequest",
70
+ "ScoringProgramResponse",
71
+ "SectionDTO",
72
+ "SectionUpdateDTO",
73
+ "SessionDTO",
74
+ "TableDTO",
75
+ "TdCallDTO",
76
+ "Bridgemate2SettingsDTO",
77
+ "Bridgemate3SettingsDTO",
78
+ "BM2ResultEntryMethod",
79
+ "BM3PlayerViewOption",
80
+ "BM3StartingPositionsOption",
81
+ "Bm2ContractRepresentationOption",
82
+ "Bm2EnterHandrecordWhenOption",
83
+ "Bm2NameSource",
84
+ "Bm2NumberValidationOption",
85
+ "Bm2PairNumberEntryOption",
86
+ "Bm2PointScorePerspectiveOption",
87
+ "Bm2ResultsOverviewOption",
88
+ "Bm2ShowPlayerNamesOption",
89
+ "Bm2ShowRankingOption",
90
+ "Bm2SummaryPointOption",
91
+ "Bm3ConfirmNPOption",
92
+ "Bm3ConnectionModeOption",
93
+ "Bm3EnterHandrecordOption",
94
+ "Bm3LanguageOption",
95
+ "Bm3LeadCardEntryOption",
96
+ "Bm3NextRoundSeatingOption",
97
+ "Bm3PlayerNumberMandatoryOption",
98
+ "Bm3PlayerRegistrationOption",
99
+ "Bm3PlayerRegistrationOptionDuringPlay",
100
+ "Bm3RankingDisplayOption",
101
+ "Bm3ResultEntryMethod",
102
+ "Bm3ResultReentryOption",
103
+ "Bm3ScoreColumnOption",
104
+ "Bm3ScoreRecapOption",
105
+ "Bm3SessionSummaryOption",
106
+ "Bm3TdCallOption",
107
+ "Bm3ViewHandrecordOption",
108
+ "Bm3ViewPercentagesOption",
109
+ "Bm3ViewResultsOption",
110
+ "DataConnectorResponseData",
111
+ "ErrorType",
112
+ "ScoringProgramDataConnectorCommands",
113
+ "TableDirection",
114
+ ]
@@ -0,0 +1,17 @@
1
+ # Generated by tools/DtoGenerator from BridgeSystems.Bridgemate.DataConnector.ScoringProgramClient 5.0.1036.0. Do not edit by hand.
2
+ """Internal helper shared by the generated DTO modules."""
3
+
4
+
5
+ def enum_from_wire(enum_cls, value, default=None):
6
+ """Maps a wire value to the enum.
7
+
8
+ None (absent or JSON null) falls back to the default. Unknown values from a newer
9
+ Data Connector either map through the enum's _missing_ hook (enums with a zero
10
+ case) or fall back to the default here, instead of failing the whole response.
11
+ """
12
+ if value is None:
13
+ return default
14
+ try:
15
+ return enum_cls(value)
16
+ except ValueError:
17
+ return default
@@ -0,0 +1,17 @@
1
+ # Generated by tools/DtoGenerator from BridgeSystems.Bridgemate.DataConnector.ScoringProgramClient 5.0.1036.0. Do not edit by hand.
2
+ from enum import IntEnum
3
+
4
+
5
+ class Bm2ContractRepresentationOption(IntEnum):
6
+ """How to represent the contract denomination"""
7
+
8
+ # Use the symbols for clubs, diamonds, hearts and spades
9
+ UseSymbols = 0
10
+ # Use letters for the denominations
11
+ UseLetter = 1
12
+
13
+ @classmethod
14
+ def _missing_(cls, value):
15
+ # Unknown wire values (from a newer Data Connector) map to the zero case
16
+ # instead of failing the whole response.
17
+ return cls.UseSymbols
@@ -0,0 +1,19 @@
1
+ # Generated by tools/DtoGenerator from BridgeSystems.Bridgemate.DataConnector.ScoringProgramClient 5.0.1036.0. Do not edit by hand.
2
+ from enum import IntEnum
3
+
4
+
5
+ class Bm2EnterHandrecordWhenOption(IntEnum):
6
+ """Specifies whether and when the players can enter the handrecord for a board."""
7
+
8
+ # The handrecord can be entered at the end of each round.
9
+ AtEndOfRound = 0
10
+ # The handrecord can be entered after the play of each board.
11
+ AtEndOfBoard = 1
12
+ # The handrecord cannot be entered.
13
+ DoNotEnter = 2
14
+
15
+ @classmethod
16
+ def _missing_(cls, value):
17
+ # Unknown wire values (from a newer Data Connector) map to the zero case
18
+ # instead of failing the whole response.
19
+ return cls.AtEndOfRound
@@ -0,0 +1,21 @@
1
+ # Generated by tools/DtoGenerator from BridgeSystems.Bridgemate.DataConnector.ScoringProgramClient 5.0.1036.0. Do not edit by hand.
2
+ from enum import IntEnum
3
+
4
+
5
+ class Bm2NameSource(IntEnum):
6
+ """Specifies which source data should be consulted to look up player names for player numbers."""
7
+
8
+ # The PlayerNames table of the .bws scoring file.
9
+ PlayerNamesTable = 0
10
+ # A separate MS-Access table.
11
+ BMPlayerDB = 1
12
+ # No External source.
13
+ NoExternalSource = 2
14
+ # First check the PlayerNames table then the external MS-Access file.
15
+ PlayerNamesTableThenBMPlayerDB = 3
16
+
17
+ @classmethod
18
+ def _missing_(cls, value):
19
+ # Unknown wire values (from a newer Data Connector) map to the zero case
20
+ # instead of failing the whole response.
21
+ return cls.PlayerNamesTable
@@ -0,0 +1,23 @@
1
+ # Generated by tools/DtoGenerator from BridgeSystems.Bridgemate.DataConnector.ScoringProgramClient 5.0.1036.0. Do not edit by hand.
2
+ from enum import IntEnum
3
+
4
+
5
+ class Bm2NumberValidationOption(IntEnum):
6
+ """Specifies for which organization an entered playernumber should be validated."""
7
+
8
+ # No validation.
9
+ NoValidation = 0
10
+ # Validate for the ACBL (USA).
11
+ ACBL = 1
12
+ # Validate for the FFB (France).
13
+ FFB = 2
14
+ # Validate for the ABF (Australia).
15
+ ABF = 3
16
+ # Validate for the JCBL (Japanese).
17
+ JCBL = 4
18
+
19
+ @classmethod
20
+ def _missing_(cls, value):
21
+ # Unknown wire values (from a newer Data Connector) map to the zero case
22
+ # instead of failing the whole response.
23
+ return cls.NoValidation