flwr 1.25.0__py3-none-any.whl → 1.26.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.
Files changed (140) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/app/__init__.py +4 -1
  3. flwr/app/message_type.py +29 -0
  4. flwr/app/metadata.py +5 -2
  5. flwr/app/user_config.py +19 -0
  6. flwr/cli/app.py +37 -19
  7. flwr/cli/app_cmd/publish.py +25 -75
  8. flwr/cli/app_cmd/review.py +18 -69
  9. flwr/cli/auth_plugin/auth_plugin.py +5 -10
  10. flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
  11. flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
  12. flwr/cli/build.py +15 -28
  13. flwr/cli/config/__init__.py +21 -0
  14. flwr/cli/config/ls.py +71 -0
  15. flwr/cli/config_migration.py +297 -0
  16. flwr/cli/config_utils.py +63 -156
  17. flwr/cli/constant.py +71 -0
  18. flwr/cli/federation/__init__.py +0 -2
  19. flwr/cli/federation/ls.py +256 -64
  20. flwr/cli/flower_config.py +429 -0
  21. flwr/cli/install.py +23 -62
  22. flwr/cli/log.py +23 -37
  23. flwr/cli/login/login.py +29 -63
  24. flwr/cli/ls.py +28 -58
  25. flwr/cli/new/new.py +9 -29
  26. flwr/cli/pull.py +19 -37
  27. flwr/cli/run/run.py +85 -93
  28. flwr/cli/run_utils.py +1 -1
  29. flwr/cli/stop.py +32 -73
  30. flwr/cli/supernode/ls.py +25 -57
  31. flwr/cli/supernode/register.py +31 -80
  32. flwr/cli/supernode/unregister.py +24 -70
  33. flwr/cli/typing.py +200 -0
  34. flwr/cli/utils.py +160 -275
  35. flwr/client/grpc_rere_client/connection.py +3 -3
  36. flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
  37. flwr/client/message_handler/message_handler.py +2 -1
  38. flwr/client/mod/centraldp_mods.py +1 -1
  39. flwr/client/mod/localdp_mod.py +1 -1
  40. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  41. flwr/client/run_info_store.py +2 -1
  42. flwr/clientapp/client_app.py +2 -1
  43. flwr/common/__init__.py +3 -2
  44. flwr/common/args.py +5 -5
  45. flwr/common/config.py +12 -17
  46. flwr/common/constant.py +3 -16
  47. flwr/common/context.py +2 -1
  48. flwr/common/exit/exit.py +4 -4
  49. flwr/common/exit/exit_code.py +6 -0
  50. flwr/common/grpc.py +2 -1
  51. flwr/common/logger.py +1 -1
  52. flwr/common/message.py +1 -1
  53. flwr/common/retry_invoker.py +13 -5
  54. flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
  55. flwr/common/serde.py +7 -5
  56. flwr/common/telemetry.py +1 -1
  57. flwr/common/typing.py +4 -3
  58. flwr/compat/client/app.py +6 -9
  59. flwr/compat/client/grpc_client/connection.py +2 -1
  60. flwr/compat/common/constant.py +29 -0
  61. flwr/compat/server/app.py +1 -1
  62. flwr/proto/clientappio_pb2.py +2 -2
  63. flwr/proto/clientappio_pb2_grpc.py +104 -88
  64. flwr/proto/clientappio_pb2_grpc.pyi +140 -80
  65. flwr/proto/federation_pb2.py +5 -3
  66. flwr/proto/federation_pb2.pyi +32 -2
  67. flwr/proto/run_pb2.py +5 -13
  68. flwr/proto/run_pb2.pyi +0 -57
  69. flwr/proto/serverappio_pb2.py +2 -2
  70. flwr/proto/serverappio_pb2_grpc.py +138 -207
  71. flwr/proto/serverappio_pb2_grpc.pyi +189 -155
  72. flwr/proto/simulationio_pb2.py +2 -2
  73. flwr/proto/simulationio_pb2_grpc.py +62 -90
  74. flwr/proto/simulationio_pb2_grpc.pyi +95 -55
  75. flwr/server/app.py +6 -13
  76. flwr/server/compat/grid_client_proxy.py +2 -1
  77. flwr/server/grid/grpc_grid.py +5 -5
  78. flwr/server/serverapp/app.py +11 -4
  79. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
  80. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
  81. flwr/server/superlink/fleet/message_handler/message_handler.py +6 -5
  82. flwr/server/superlink/linkstate/__init__.py +2 -2
  83. flwr/server/superlink/linkstate/in_memory_linkstate.py +2 -10
  84. flwr/server/superlink/linkstate/linkstate.py +2 -21
  85. flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
  86. flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +432 -534
  87. flwr/server/superlink/linkstate/utils.py +49 -2
  88. flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
  89. flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
  90. flwr/server/utils/validator.py +1 -1
  91. flwr/server/workflow/default_workflows.py +2 -1
  92. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  93. flwr/serverapp/strategy/bulyan.py +7 -1
  94. flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
  95. flwr/serverapp/strategy/fedavg.py +1 -1
  96. flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
  97. flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
  98. flwr/simulation/run_simulation.py +3 -12
  99. flwr/simulation/simulationio_connection.py +3 -3
  100. flwr/{common → supercore}/address.py +7 -33
  101. flwr/supercore/app_utils.py +2 -1
  102. flwr/supercore/constant.py +24 -2
  103. flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
  104. flwr/supercore/credential_store/__init__.py +33 -0
  105. flwr/supercore/credential_store/credential_store.py +34 -0
  106. flwr/supercore/credential_store/file_credential_store.py +76 -0
  107. flwr/{common → supercore}/date.py +0 -11
  108. flwr/supercore/ffs/disk_ffs.py +1 -1
  109. flwr/supercore/object_store/object_store_factory.py +14 -6
  110. flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
  111. flwr/supercore/sql_mixin.py +315 -0
  112. flwr/supercore/state/__init__.py +15 -0
  113. flwr/supercore/state/alembic/__init__.py +15 -0
  114. flwr/supercore/state/alembic/env.py +103 -0
  115. flwr/supercore/state/alembic/script.py.mako +43 -0
  116. flwr/supercore/state/alembic/utils.py +239 -0
  117. flwr/supercore/state/alembic/versions/__init__.py +15 -0
  118. flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
  119. flwr/supercore/state/schema/README.md +121 -0
  120. flwr/supercore/state/schema/__init__.py +15 -0
  121. flwr/supercore/state/schema/corestate_tables.py +36 -0
  122. flwr/supercore/state/schema/linkstate_tables.py +152 -0
  123. flwr/supercore/state/schema/objectstore_tables.py +90 -0
  124. flwr/supercore/superexec/run_superexec.py +2 -2
  125. flwr/supercore/utils.py +36 -1
  126. flwr/superlink/federation/federation_manager.py +2 -2
  127. flwr/superlink/federation/noop_federation_manager.py +8 -6
  128. flwr/superlink/servicer/control/control_servicer.py +19 -17
  129. flwr/supernode/cli/flower_supernode.py +2 -1
  130. flwr/supernode/runtime/run_clientapp.py +14 -14
  131. flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -8
  132. flwr/supernode/start_client_internal.py +10 -6
  133. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/METADATA +7 -5
  134. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/RECORD +137 -116
  135. flwr/cli/federation/show.py +0 -318
  136. flwr/common/pyproject.py +0 -42
  137. flwr/supercore/sqlite_mixin.py +0 -159
  138. /flwr/{common → supercore}/version.py +0 -0
  139. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
  140. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
@@ -1,318 +0,0 @@
1
- # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- # ==============================================================================
15
- """Flower command line interface `federation show` command."""
16
-
17
-
18
- import io
19
- from pathlib import Path
20
- from typing import Annotated, Any
21
-
22
- import typer
23
- from rich.console import Console
24
- from rich.table import Table
25
- from rich.text import Text
26
-
27
- from flwr.cli.config_utils import (
28
- exit_if_no_address,
29
- load_and_validate,
30
- process_loaded_project_config,
31
- validate_federation_in_project_config,
32
- )
33
- from flwr.cli.ls import _get_status_style
34
- from flwr.common.constant import FAB_CONFIG_FILE, NOOP_ACCOUNT_NAME, CliOutputFormat
35
- from flwr.common.logger import print_json_error, redirect_output, restore_output
36
- from flwr.common.serde import run_from_proto
37
- from flwr.proto.control_pb2 import ( # pylint: disable=E0611
38
- ShowFederationRequest,
39
- ShowFederationResponse,
40
- )
41
- from flwr.proto.control_pb2_grpc import ControlStub
42
- from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
43
- from flwr.supercore.constant import NOOP_FEDERATION
44
- from flwr.supercore.utils import humanize_duration
45
-
46
- from ..run_utils import RunRow, format_runs
47
- from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
48
-
49
-
50
- def show( # pylint: disable=R0914, R0913, R0917
51
- app: Annotated[
52
- Path,
53
- typer.Argument(help="Path of the Flower project"),
54
- ] = Path("."),
55
- federation: Annotated[
56
- str | None,
57
- typer.Argument(help="Name of the federation"),
58
- ] = None,
59
- output_format: Annotated[
60
- str,
61
- typer.Option(
62
- "--format",
63
- case_sensitive=False,
64
- help="Format output using 'default' view or 'json'",
65
- ),
66
- ] = CliOutputFormat.DEFAULT,
67
- ) -> None:
68
- """Show details of a federation.
69
-
70
- Display comprehensive information about a federation including its members,
71
- registered SuperNodes, and runs.
72
- """
73
- suppress_output = output_format == CliOutputFormat.JSON
74
- captured_output = io.StringIO()
75
- try:
76
- if suppress_output:
77
- redirect_output(captured_output)
78
- typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
79
-
80
- pyproject_path = app / FAB_CONFIG_FILE if app else None
81
- config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
82
- config = process_loaded_project_config(config, errors, warnings)
83
- federation, federation_config = validate_federation_in_project_config(
84
- federation, config
85
- )
86
- exit_if_no_address(federation_config, "federation show")
87
- real_federation: str = federation_config.get("federation", NOOP_FEDERATION)
88
- channel = None
89
- try:
90
- auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
91
- channel = init_channel(app, federation_config, auth_plugin)
92
- stub = ControlStub(channel)
93
- typer.echo(f"📄 Showing '{real_federation}' federation ...")
94
- members, nodes, runs = _show_federation(stub, real_federation)
95
- restore_output()
96
- if output_format == CliOutputFormat.JSON:
97
- Console().print_json(data=_to_json(members, nodes, runs))
98
- else:
99
- Console().print(_to_members_table(members))
100
- Console().print(_to_nodes_table(nodes))
101
- Console().print(_to_runs_table(runs))
102
- finally:
103
- if channel:
104
- channel.close()
105
- except (typer.Exit, Exception) as err: # pylint: disable=broad-except
106
- if suppress_output:
107
- restore_output()
108
- e_message = captured_output.getvalue()
109
- print_json_error(e_message, err)
110
- else:
111
- typer.secho(
112
- f"{err}",
113
- fg=typer.colors.RED,
114
- bold=True,
115
- err=True,
116
- )
117
- finally:
118
- if suppress_output:
119
- restore_output()
120
- captured_output.close()
121
-
122
-
123
- def _show_federation(
124
- stub: ControlStub, federation: str
125
- ) -> tuple[list[str], list[NodeInfo], list[RunRow]]:
126
- """Show federation details.
127
-
128
- Parameters
129
- ----------
130
- stub : ControlStub
131
- The gRPC stub for Control API communication.
132
- federation : str
133
- Name of the federation to show.
134
-
135
- Returns
136
- -------
137
- tuple[list[str], list[NodeInfo], list[RunRow]]
138
- A tuple containing (member_account_ids, nodes, runs).
139
- """
140
- with flwr_cli_grpc_exc_handler():
141
- res: ShowFederationResponse = stub.ShowFederation(
142
- ShowFederationRequest(federation_name=federation)
143
- )
144
-
145
- fed_proto = res.federation
146
- runs = [run_from_proto(run_proto) for run_proto in fed_proto.runs]
147
- formatted_runs = format_runs(runs, res.now)
148
-
149
- return list(fed_proto.member_aids), list(fed_proto.nodes), formatted_runs
150
-
151
-
152
- def _to_members_table(member_aids: list[str]) -> Table:
153
- """Format the provided list of federation members as a rich Table.
154
-
155
- Parameters
156
- ----------
157
- member_aids : list[str]
158
- List of member account identifiers.
159
-
160
- Returns
161
- -------
162
- Table
163
- Rich Table object with formatted member information.
164
- """
165
- table = Table(title="Federation Members", header_style="bold cyan", show_lines=True)
166
-
167
- table.add_column(
168
- Text("Account ID", justify="center"), style="bright_black", no_wrap=True
169
- )
170
- table.add_column(Text("Role", justify="center"), style="bright_black", no_wrap=True)
171
-
172
- for member_aid in member_aids:
173
- table.add_row(member_aid, "Member")
174
-
175
- return table
176
-
177
-
178
- def _to_nodes_table(nodes: list[NodeInfo]) -> Table:
179
- """Format the provided list of federation nodes as a rich Table.
180
-
181
- Parameters
182
- ----------
183
- nodes : list[NodeInfo]
184
- List of NodeInfo objects containing node details.
185
-
186
- Returns
187
- -------
188
- Table
189
- Rich Table object with formatted node information.
190
-
191
- Raises
192
- ------
193
- ValueError
194
- If an unexpected node status is encountered.
195
- """
196
- table = Table(
197
- title="SuperNodes in the Federation", header_style="bold cyan", show_lines=True
198
- )
199
-
200
- # Add columns
201
- table.add_column(
202
- Text("Node ID", justify="center"), style="bright_black", no_wrap=True
203
- )
204
- table.add_column(Text("Owner", justify="center"))
205
- table.add_column(Text("Status", justify="center"))
206
-
207
- for row in nodes:
208
- owner_name = row.owner_name
209
- status = row.status
210
-
211
- if status == "online":
212
- status_style = "green"
213
- elif status == "offline":
214
- status_style = "bright_yellow"
215
- elif status == "unregistered":
216
- continue
217
- elif status == "registered":
218
- status_style = "blue"
219
- else:
220
- raise ValueError(f"Unexpected node status '{status}'")
221
-
222
- formatted_row = (
223
- f"[bold]{row.node_id}[/bold]",
224
- (
225
- f"{owner_name}"
226
- if owner_name != NOOP_ACCOUNT_NAME
227
- else f"[dim]{owner_name}[/dim]"
228
- ),
229
- f"[{status_style}]{status}",
230
- )
231
- table.add_row(*formatted_row)
232
-
233
- return table
234
-
235
-
236
- def _to_runs_table(run_list: list[RunRow]) -> Table:
237
- """Format the provided list of federation runs as a rich Table.
238
-
239
- Parameters
240
- ----------
241
- run_list : list[RunRow]
242
- List of RunRow objects containing run details.
243
-
244
- Returns
245
- -------
246
- Table
247
- Rich Table object with formatted run information.
248
- """
249
- table = Table(
250
- title="Runs in the Federation", header_style="bold cyan", show_lines=True
251
- )
252
-
253
- # Add columns
254
- table.add_column(Text("Run ID", justify="center"), no_wrap=True)
255
- table.add_column(Text("App", justify="center"))
256
- table.add_column(Text("Status", justify="center"))
257
- table.add_column(Text("Elapsed", justify="center"), style="blue")
258
-
259
- for row in run_list:
260
- status_style = _get_status_style(row.status_text)
261
-
262
- formatted_row = (
263
- f"[bold]{row.run_id}[/bold]",
264
- f"@{row.fab_id}=={row.fab_version}",
265
- f"[{status_style}]{row.status_text}[/{status_style}]",
266
- f"{humanize_duration(row.elapsed)}",
267
- )
268
- table.add_row(*formatted_row)
269
-
270
- return table
271
-
272
-
273
- def _to_json(
274
- members: list[str], nodes: list[NodeInfo], runs: list[RunRow]
275
- ) -> list[list[dict[str, str]]]:
276
- """Format the provided federation information to JSON serializable format.
277
-
278
- Parameters
279
- ----------
280
- members : list[str]
281
- List of member account identifiers.
282
- nodes : list[NodeInfo]
283
- List of NodeInfo objects.
284
- runs : list[RunRow]
285
- List of RunRow objects.
286
-
287
- Returns
288
- -------
289
- list[list[dict[str, str]]]
290
- Nested list containing dictionaries for members, nodes, and runs.
291
- """
292
- members_list: list[dict[str, Any]] = []
293
- nodes_list: list[dict[str, Any]] = []
294
- runs_list: list[dict[str, Any]] = []
295
-
296
- for member in members:
297
- members_list.append({"member_id": member, "role": "Member"})
298
-
299
- for node in nodes:
300
- nodes_list.append(
301
- {
302
- "node_id": f"{node.node_id}",
303
- "owner": node.owner_name,
304
- "status": node.status,
305
- }
306
- )
307
-
308
- for run in runs:
309
- runs_list.append(
310
- {
311
- "run_id": f"{run.run_id}",
312
- "app": f"@{run.fab_id}=={run.fab_version}",
313
- "status": run.status_text,
314
- "elapsed": run.elapsed,
315
- }
316
- )
317
-
318
- return [members_list, nodes_list, runs_list]
flwr/common/pyproject.py DELETED
@@ -1,42 +0,0 @@
1
- # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- # ==============================================================================
15
- """Validates the project's name property."""
16
-
17
-
18
- import re
19
-
20
-
21
- def validate_project_name(name: str) -> bool:
22
- """Validate the project name against PEP 621 and PEP 503 specifications.
23
-
24
- Conventions at a glance:
25
- - Must be lowercase
26
- - Must not contain special characters
27
- - Must use hyphens(recommended) or underscores. No spaces.
28
- - Recommended to be no more than 40 characters long (But it can be)
29
-
30
- Parameters
31
- ----------
32
- name : str
33
- The project name to validate.
34
-
35
- Returns
36
- -------
37
- bool
38
- True if the name is valid, False otherwise.
39
- """
40
- if not name or len(name) > 40 or not re.match(r"^[a-z0-9-_]+$", name):
41
- return False
42
- return True
@@ -1,159 +0,0 @@
1
- # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- # ==============================================================================
15
- """Mixin providing common SQLite connection and initialization logic."""
16
-
17
-
18
- import re
19
- import sqlite3
20
- from abc import ABC
21
- from collections.abc import Sequence
22
- from logging import DEBUG, ERROR
23
- from typing import Any
24
-
25
- from flwr.common.logger import log
26
-
27
- DictOrTuple = tuple[Any, ...] | dict[str, Any]
28
-
29
-
30
- class SqliteMixin(ABC):
31
- """Mixin providing common SQLite connection and initialization logic."""
32
-
33
- def __init__(self, database_path: str) -> None:
34
- self.database_path = database_path
35
- self._conn: sqlite3.Connection | None = None
36
-
37
- @property
38
- def conn(self) -> sqlite3.Connection:
39
- """Get the SQLite connection."""
40
- if self._conn is None:
41
- raise AttributeError("Database not initialized. Call initialize() first.")
42
- return self._conn
43
-
44
- def get_sql_statements(self) -> tuple[str, ...]:
45
- """Return SQL statements for this class.
46
-
47
- Subclasses can override this to provide their SQL CREATE statements.
48
- The base implementation returns an empty tuple.
49
-
50
- Returns
51
- -------
52
- tuple[str, ...]
53
- SQL CREATE TABLE/INDEX statements for this class.
54
- """
55
- return ()
56
-
57
- def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
58
- """Connect to the DB, enable FK support, and create tables if needed.
59
-
60
- This method executes SQL statements returned by `get_sql_statements()`.
61
-
62
- Parameters
63
- ----------
64
- log_queries : bool
65
- Log each query which is executed.
66
-
67
- Returns
68
- -------
69
- list[tuple[str]]
70
- The list of all tables in the DB.
71
-
72
- Examples
73
- --------
74
- Override `get_sql_statements()` in your subclass:
75
-
76
- .. code:: python
77
-
78
- def get_sql_statements(self) -> tuple[str, ...]:
79
- return (
80
- SQL_CREATE_TABLE_FOO,
81
- SQL_CREATE_TABLE_BAR,
82
- )
83
-
84
- To include parent SQL statements, call super():
85
-
86
- .. code:: python
87
-
88
- def get_sql_statements(self) -> tuple[str, ...]:
89
- return super().get_sql_statements() + (
90
- SQL_CREATE_TABLE_FOO,
91
- SQL_CREATE_TABLE_BAR,
92
- )
93
- """
94
- self._conn = sqlite3.connect(self.database_path)
95
- # Enable Write-Ahead Logging (WAL) for better concurrency
96
- self._conn.execute("PRAGMA journal_mode = WAL;")
97
- self._conn.execute("PRAGMA synchronous = NORMAL;")
98
- self._conn.execute("PRAGMA foreign_keys = ON;")
99
- self._conn.execute("PRAGMA cache_size = -64000;") # 64MB cache
100
- self._conn.execute("PRAGMA temp_store = MEMORY;") # In-memory temp tables
101
- self._conn.execute("PRAGMA mmap_size = 268435456;") # 256MB memory-mapped I/O
102
- self._conn.row_factory = dict_factory
103
-
104
- if log_queries:
105
- self._conn.set_trace_callback(lambda q: log(DEBUG, q))
106
-
107
- # Create tables and indexes
108
- cur = self._conn.cursor()
109
- for sql in self.get_sql_statements():
110
- cur.execute(sql)
111
- res = cur.execute("SELECT name FROM sqlite_schema;")
112
- return res.fetchall()
113
-
114
- def query(
115
- self,
116
- query: str,
117
- data: Sequence[DictOrTuple] | DictOrTuple | None = None,
118
- ) -> list[dict[str, Any]]:
119
- """Execute a SQL query and return the results as list of dicts."""
120
- if self._conn is None:
121
- raise AttributeError("LinkState is not initialized.")
122
-
123
- if data is None:
124
- data = []
125
-
126
- # Clean up whitespace to make the logs nicer
127
- query = re.sub(r"\s+", " ", query)
128
-
129
- try:
130
- with self._conn:
131
- if (
132
- len(data) > 0
133
- and isinstance(data, (tuple | list))
134
- and isinstance(data[0], (tuple | dict))
135
- ):
136
- rows = self._conn.executemany(query, data)
137
- else:
138
- rows = self._conn.execute(query, data)
139
-
140
- # Extract results before committing to support
141
- # INSERT/UPDATE ... RETURNING
142
- # style queries
143
- result = rows.fetchall()
144
- except KeyError as exc:
145
- log(ERROR, {"query": query, "data": data, "exception": exc})
146
-
147
- return result
148
-
149
-
150
- def dict_factory(
151
- cursor: sqlite3.Cursor,
152
- row: sqlite3.Row,
153
- ) -> dict[str, Any]:
154
- """Turn SQLite results into dicts.
155
-
156
- Less efficent for retrival of large amounts of data but easier to use.
157
- """
158
- fields = [column[0] for column in cursor.description]
159
- return dict(zip(fields, row, strict=True))
File without changes
File without changes