flwr-nightly 1.26.0.dev20260116__py3-none-any.whl → 1.26.0.dev20260119__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.
@@ -208,7 +208,7 @@ def migrate(
208
208
  def migrate_if_legacy_usage(
209
209
  superlink: str,
210
210
  args: list[str],
211
- ) -> None:
211
+ ) -> bool:
212
212
  """Migrate legacy TOML configuration to Flower config if legacy usage is
213
213
  detected."""
214
214
  # Trigger the same typer error when detecting unexpected extra args
@@ -217,9 +217,10 @@ def migrate_if_legacy_usage(
217
217
 
218
218
  # Skip migration if no legacy usage is detected
219
219
  if not _is_legacy_usage(superlink, args):
220
- return
220
+ return False
221
221
 
222
222
  migrate(
223
223
  app=Path(superlink),
224
224
  toml_federation=args[0] if len(args) == 1 else None,
225
225
  )
226
+ return True
flwr/cli/config_utils.py CHANGED
@@ -307,16 +307,9 @@ def load_certificate_in_connection(
307
307
  typer.Exit
308
308
  If the configuration is invalid or the certificate file cannot be read.
309
309
  """
310
- if connection.insecure is None:
311
- raise ValueError(
312
- f"SuperLink connection '{connection.name}' is missing insecure setting."
313
- )
314
-
315
- insecure = connection.insecure
316
-
317
310
  # Process root certificates
318
311
  if root_certificates := connection.root_certificates:
319
- if insecure:
312
+ if connection.insecure:
320
313
  typer.secho(
321
314
  "❌ `root-certificates` were provided but the `insecure` parameter "
322
315
  "is set to `True`.",
flwr/cli/flower_config.py CHANGED
@@ -215,18 +215,19 @@ def serialize_superlink_connection(connection: SuperLinkConnection) -> dict[str,
215
215
  dict[str, Any]
216
216
  Dictionary representation suitable for TOML serialization.
217
217
  """
218
+ # pylint: disable=protected-access
218
219
  conn_dict: dict[str, Any] = {
219
- SuperLinkConnectionTomlKey.ADDRESS: connection.address,
220
- SuperLinkConnectionTomlKey.ROOT_CERTIFICATES: connection.root_certificates,
221
- SuperLinkConnectionTomlKey.INSECURE: connection.insecure,
222
- SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH: connection.enable_account_auth,
223
- SuperLinkConnectionTomlKey.FEDERATION: connection.federation,
220
+ SuperLinkConnectionTomlKey.ADDRESS: connection._address,
221
+ SuperLinkConnectionTomlKey.ROOT_CERTIFICATES: connection._root_certificates,
222
+ SuperLinkConnectionTomlKey.INSECURE: connection._insecure,
223
+ SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH: connection._enable_account_auth,
224
+ SuperLinkConnectionTomlKey.FEDERATION: connection._federation,
224
225
  }
225
226
  # Remove None values
226
227
  conn_dict = {k: v for k, v in conn_dict.items() if v is not None}
227
228
 
228
- if connection.options is not None:
229
- options_dict = _serialize_simulation_options(connection.options)
229
+ if connection._options is not None:
230
+ options_dict = _serialize_simulation_options(connection._options)
230
231
  conn_dict[SuperLinkConnectionTomlKey.OPTIONS] = options_dict
231
232
 
232
233
  return conn_dict
flwr/cli/pull.py CHANGED
@@ -40,10 +40,7 @@ from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
40
40
  def pull( # pylint: disable=R0914
41
41
  run_id: Annotated[
42
42
  int,
43
- typer.Option(
44
- "--run-id",
45
- help="Run ID to pull artifacts from.",
46
- ),
43
+ typer.Argument(help="Run ID to pull artifacts from."),
47
44
  ],
48
45
  app: Annotated[
49
46
  Path,
flwr/cli/typing.py CHANGED
@@ -23,6 +23,8 @@ from flwr.cli.constant import (
23
23
  SuperLinkConnectionTomlKey,
24
24
  )
25
25
 
26
+ _ERROR_MSG_FMT = "SuperLinkConnection.%s is None"
27
+
26
28
 
27
29
  @dataclass
28
30
  class SimulationClientResources:
@@ -94,59 +96,126 @@ class SuperLinkConnection:
94
96
  """SuperLink connection configuration for CLI commands."""
95
97
 
96
98
  name: str
97
- address: str | None = None
98
- root_certificates: str | None = None
99
- insecure: bool | None = None
100
- enable_account_auth: bool | None = None
101
- federation: str | None = None
102
- options: SuperLinkSimulationOptions | None = None
99
+ _address: str | None = None
100
+ _root_certificates: str | None = None
101
+ _insecure: bool | None = None
102
+ _enable_account_auth: bool | None = None
103
+ _federation: str | None = None
104
+ _options: SuperLinkSimulationOptions | None = None
105
+
106
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
107
+ def __init__(
108
+ self,
109
+ name: str,
110
+ address: str | None = None,
111
+ root_certificates: str | None = None,
112
+ insecure: bool | None = None,
113
+ enable_account_auth: bool | None = None,
114
+ federation: str | None = None,
115
+ options: SuperLinkSimulationOptions | None = None,
116
+ ) -> None:
117
+ self.name = name
118
+ self._address = address
119
+ self._root_certificates = root_certificates
120
+ self._insecure = insecure
121
+ self._enable_account_auth = enable_account_auth
122
+ self._federation = federation
123
+ self._options = options
124
+
125
+ self.__post_init__()
126
+
127
+ @property
128
+ def address(self) -> str:
129
+ """Return the address."""
130
+ if self._address is None:
131
+ raise ValueError(_ERROR_MSG_FMT % SuperLinkConnectionTomlKey.ADDRESS)
132
+ return self._address
133
+
134
+ @property
135
+ def root_certificates(self) -> str:
136
+ """Return the root certificates."""
137
+ if self._root_certificates is None:
138
+ raise ValueError(
139
+ _ERROR_MSG_FMT % SuperLinkConnectionTomlKey.ROOT_CERTIFICATES
140
+ )
141
+ return self._root_certificates
142
+
143
+ @property
144
+ def insecure(self) -> bool:
145
+ """Return the insecure flag."""
146
+ if self._insecure is None:
147
+ raise ValueError(_ERROR_MSG_FMT % SuperLinkConnectionTomlKey.INSECURE)
148
+ return self._insecure
149
+
150
+ @property
151
+ def enable_account_auth(self) -> bool:
152
+ """Return the enable_account_auth flag."""
153
+ if self._enable_account_auth is None:
154
+ raise ValueError(
155
+ _ERROR_MSG_FMT % SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH
156
+ )
157
+ return self._enable_account_auth
158
+
159
+ @property
160
+ def federation(self) -> str:
161
+ """Return the federation."""
162
+ if self._federation is None:
163
+ raise ValueError(_ERROR_MSG_FMT % SuperLinkConnectionTomlKey.FEDERATION)
164
+ return self._federation
165
+
166
+ @property
167
+ def options(self) -> SuperLinkSimulationOptions:
168
+ """Return the simulation options."""
169
+ if self._options is None:
170
+ raise ValueError(_ERROR_MSG_FMT % SuperLinkConnectionTomlKey.OPTIONS)
171
+ return self._options
103
172
 
104
173
  def __post_init__(self) -> None:
105
174
  """Validate SuperLink connection configuration."""
106
175
  err_prefix = f"Invalid value for key '%s' in connection '{self.name}': "
107
- if self.address is not None and not isinstance(self.address, str):
176
+ if self._address is not None and not isinstance(self._address, str):
108
177
  raise ValueError(
109
178
  err_prefix % SuperLinkConnectionTomlKey.ADDRESS
110
- + f"expected str, but got {type(self.address).__name__}."
179
+ + f"expected str, but got {type(self._address).__name__}."
111
180
  )
112
- if self.root_certificates is not None and not isinstance(
113
- self.root_certificates, str
181
+ if self._root_certificates is not None and not isinstance(
182
+ self._root_certificates, str
114
183
  ):
115
184
  raise ValueError(
116
185
  err_prefix % SuperLinkConnectionTomlKey.ROOT_CERTIFICATES
117
- + f"expected str, but got {type(self.root_certificates).__name__}."
186
+ + f"expected str, but got {type(self._root_certificates).__name__}."
118
187
  )
119
188
 
120
189
  # Ensure root certificates path is absolute
121
- if self.root_certificates is not None:
122
- if not Path(self.root_certificates).is_absolute():
190
+ if self._root_certificates is not None:
191
+ if not Path(self._root_certificates).is_absolute():
123
192
  raise ValueError(
124
193
  err_prefix % SuperLinkConnectionTomlKey.ROOT_CERTIFICATES
125
194
  + "expected absolute path, but got relative path "
126
- f"'{self.root_certificates}'."
195
+ f"'{self._root_certificates}'."
127
196
  )
128
197
 
129
- if self.insecure is not None and not isinstance(self.insecure, bool):
198
+ if self._insecure is not None and not isinstance(self._insecure, bool):
130
199
  raise ValueError(
131
200
  err_prefix % SuperLinkConnectionTomlKey.INSECURE
132
- + f"expected bool, but got {type(self.insecure).__name__}."
201
+ + f"expected bool, but got {type(self._insecure).__name__}."
133
202
  )
134
- if self.enable_account_auth is not None and not isinstance(
135
- self.enable_account_auth, bool
203
+ if self._enable_account_auth is not None and not isinstance(
204
+ self._enable_account_auth, bool
136
205
  ):
137
206
  raise ValueError(
138
207
  err_prefix % SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH
139
- + f"expected bool, but got {type(self.enable_account_auth).__name__}."
208
+ + f"expected bool, but got {type(self._enable_account_auth).__name__}."
140
209
  )
141
210
 
142
- if self.federation is not None and not isinstance(self.federation, str):
211
+ if self._federation is not None and not isinstance(self._federation, str):
143
212
  raise ValueError(
144
213
  err_prefix % SuperLinkConnectionTomlKey.FEDERATION
145
- + f"expected str, but got {type(self.federation).__name__}."
214
+ + f"expected str, but got {type(self._federation).__name__}."
146
215
  )
147
216
 
148
217
  # The connection needs to have either an address or options (or both).
149
- if self.address is None and self.options is None:
218
+ if self._address is None and self._options is None:
150
219
  raise ValueError(
151
220
  "Invalid SuperLink connection format: "
152
221
  f"'{SuperLinkConnectionTomlKey.ADDRESS}' and/or "
flwr/cli/utils.py CHANGED
@@ -458,13 +458,17 @@ def init_channel(
458
458
  return channel
459
459
 
460
460
 
461
- def init_channel_from_connection(connection: SuperLinkConnection) -> grpc.Channel:
461
+ def init_channel_from_connection(
462
+ connection: SuperLinkConnection, auth_plugin: CliAuthPlugin | None = None
463
+ ) -> grpc.Channel:
462
464
  """Initialize gRPC channel to the Control API.
463
465
 
464
466
  Parameters
465
467
  ----------
466
468
  connection : SuperLinkConnection
467
469
  SuperLink connection configuration.
470
+ auth_plugin : CliAuthPlugin | None (default: None)
471
+ Authentication plugin instance for handling credentials.
468
472
 
469
473
  Returns
470
474
  -------
@@ -474,18 +478,12 @@ def init_channel_from_connection(connection: SuperLinkConnection) -> grpc.Channe
474
478
  root_certificates_bytes = load_certificate_in_connection(connection)
475
479
 
476
480
  # Load authentication plugin
477
- auth_plugin = load_cli_auth_plugin_from_connection(connection)
481
+ if auth_plugin is None:
482
+ auth_plugin = load_cli_auth_plugin_from_connection(connection)
478
483
 
479
484
  # Load tokens
480
485
  auth_plugin.load_tokens()
481
486
 
482
- # Ensure address and insecure are set
483
- if connection.address is None or connection.insecure is None:
484
- raise ValueError(
485
- f"Couldn't create channel. SuperLink connection '{connection.name}'"
486
- " is missing address or insecure setting."
487
- )
488
-
489
487
  # Create the gRPC channel
490
488
  channel = create_channel(
491
489
  server_address=connection.address,
@@ -64,6 +64,16 @@ MESSAGE_TIME_ENTRY_MAX_AGE_SECONDS = 3600
64
64
  # System message type
65
65
  SYSTEM_MESSAGE_TYPE = "system"
66
66
 
67
+ # SQLite PRAGMA settings for optimal performance and correctness
68
+ SQLITE_PRAGMAS = (
69
+ ("journal_mode", "WAL"), # Enable Write-Ahead Logging for better concurrency
70
+ ("synchronous", "NORMAL"),
71
+ ("foreign_keys", "ON"),
72
+ ("cache_size", "-64000"), # 64MB cache
73
+ ("temp_store", "MEMORY"), # In-memory temp tables
74
+ ("mmap_size", "268435456"), # 256MB memory-mapped I/O
75
+ )
76
+
67
77
 
68
78
  class NodeStatus:
69
79
  """Event log writer types."""
@@ -0,0 +1,33 @@
1
+ # Copyright 2026 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
+ """Credential store for Flower."""
16
+
17
+
18
+ from .credential_store import CredentialStore
19
+ from .file_credential_store import FileCredentialStore
20
+
21
+
22
+ def get_credential_store() -> CredentialStore:
23
+ """Get the credential store instance.
24
+
25
+ Currently, only FileCredentialStore is implemented.
26
+ """
27
+ return FileCredentialStore()
28
+
29
+
30
+ __all__ = [
31
+ "CredentialStore",
32
+ "get_credential_store",
33
+ ]
@@ -0,0 +1,34 @@
1
+ # Copyright 2026 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
+ """Abstract base classes for credential store."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+
20
+
21
+ class CredentialStore(ABC):
22
+ """Abstract base class for credential store."""
23
+
24
+ @abstractmethod
25
+ def set(self, key: str, value: bytes) -> None:
26
+ """Set a credential in the store."""
27
+
28
+ @abstractmethod
29
+ def get(self, key: str) -> bytes | None:
30
+ """Get a credential from the store."""
31
+
32
+ @abstractmethod
33
+ def delete(self, key: str) -> None:
34
+ """Delete a credential from the store."""
@@ -0,0 +1,76 @@
1
+ # Copyright 2026 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
+ """File-based credential store implementation."""
16
+
17
+
18
+ import base64
19
+ from pathlib import Path
20
+ from typing import cast
21
+
22
+ import yaml
23
+
24
+ from ..utils import get_flwr_home
25
+ from .credential_store import CredentialStore
26
+
27
+ CREDENTIAL_FILE_PATH = get_flwr_home() / "credentials.yaml"
28
+
29
+
30
+ class FileCredentialStore(CredentialStore):
31
+ """File-based credential store implementation."""
32
+
33
+ def __init__(self, file_path: Path | None = None) -> None:
34
+ """Initialize the file credential store.
35
+
36
+ Parameters
37
+ ----------
38
+ file_path : Path | None
39
+ Path to the credentials file. If None, uses default path.
40
+ """
41
+ self.file_path = file_path or CREDENTIAL_FILE_PATH
42
+
43
+ def _load_credentials(self) -> dict[str, str]:
44
+ """Load credentials from file."""
45
+ if not self.file_path.exists():
46
+ return {}
47
+ with self.file_path.open("r", encoding="utf-8") as f:
48
+ data = yaml.safe_load(f)
49
+ return cast(dict[str, str], data) if data else {}
50
+
51
+ def _save_credentials(self, credentials: dict[str, str]) -> None:
52
+ """Save credentials to file."""
53
+ self.file_path.parent.mkdir(parents=True, exist_ok=True)
54
+ with self.file_path.open("w", encoding="utf-8") as f:
55
+ yaml.safe_dump(credentials, f)
56
+
57
+ def set(self, key: str, value: bytes) -> None:
58
+ """Set a credential in the store."""
59
+ credentials = self._load_credentials()
60
+ credentials[key] = base64.b64encode(value).decode("utf-8")
61
+ self._save_credentials(credentials)
62
+
63
+ def get(self, key: str) -> bytes | None:
64
+ """Get a credential from the store."""
65
+ credentials = self._load_credentials()
66
+ encoded_value = credentials.get(key)
67
+ if encoded_value is None:
68
+ return None
69
+ return base64.b64decode(encoded_value)
70
+
71
+ def delete(self, key: str) -> None:
72
+ """Delete a credential from the store."""
73
+ credentials = self._load_credentials()
74
+ if key in credentials:
75
+ del credentials[key]
76
+ self._save_credentials(credentials)
@@ -23,6 +23,7 @@ from logging import DEBUG, ERROR
23
23
  from typing import Any
24
24
 
25
25
  from flwr.common.logger import log
26
+ from flwr.supercore.constant import SQLITE_PRAGMAS
26
27
 
27
28
  DictOrTuple = tuple[Any, ...] | dict[str, Any]
28
29
 
@@ -92,13 +93,9 @@ class SqliteMixin(ABC):
92
93
  )
93
94
  """
94
95
  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
96
+ # Set SQLite pragmas for optimal performance and correctness
97
+ for pragma, value in SQLITE_PRAGMAS:
98
+ self._conn.execute(f"PRAGMA {pragma} = {value};")
102
99
  self._conn.row_factory = dict_factory
103
100
 
104
101
  if log_queries:
@@ -9,6 +9,62 @@
9
9
  layout: elk
10
10
  ---
11
11
  erDiagram
12
+ context {
13
+ INTEGER run_id FK "nullable"
14
+ BLOB context "nullable"
15
+ }
16
+
17
+ logs {
18
+ INTEGER run_id FK "nullable"
19
+ VARCHAR log "nullable"
20
+ INTEGER node_id "nullable"
21
+ FLOAT timestamp "nullable"
22
+ }
23
+
24
+ message_ins {
25
+ INTEGER run_id FK "nullable"
26
+ BLOB content "nullable"
27
+ FLOAT created_at "nullable"
28
+ VARCHAR delivered_at "nullable"
29
+ INTEGER dst_node_id "nullable"
30
+ BLOB error "nullable"
31
+ VARCHAR group_id "nullable"
32
+ VARCHAR message_id UK "nullable"
33
+ VARCHAR message_type "nullable"
34
+ VARCHAR reply_to_message_id "nullable"
35
+ INTEGER src_node_id "nullable"
36
+ FLOAT ttl "nullable"
37
+ }
38
+
39
+ message_res {
40
+ INTEGER run_id FK "nullable"
41
+ BLOB content "nullable"
42
+ FLOAT created_at "nullable"
43
+ VARCHAR delivered_at "nullable"
44
+ INTEGER dst_node_id "nullable"
45
+ BLOB error "nullable"
46
+ VARCHAR group_id "nullable"
47
+ VARCHAR message_id UK "nullable"
48
+ VARCHAR message_type "nullable"
49
+ VARCHAR reply_to_message_id "nullable"
50
+ INTEGER src_node_id "nullable"
51
+ FLOAT ttl "nullable"
52
+ }
53
+
54
+ node {
55
+ FLOAT heartbeat_interval "nullable"
56
+ VARCHAR last_activated_at "nullable"
57
+ VARCHAR last_deactivated_at "nullable"
58
+ INTEGER node_id UK "nullable"
59
+ TIMESTAMP online_until "nullable"
60
+ VARCHAR owner_aid "nullable"
61
+ VARCHAR owner_name "nullable"
62
+ BLOB public_key UK "nullable"
63
+ VARCHAR registered_at "nullable"
64
+ VARCHAR status "nullable"
65
+ VARCHAR unregistered_at "nullable"
66
+ }
67
+
12
68
  object_children {
13
69
  VARCHAR child_id PK,FK
14
70
  VARCHAR parent_id PK,FK
@@ -21,6 +77,29 @@ erDiagram
21
77
  INTEGER ref_count
22
78
  }
23
79
 
80
+ <<<<<<< HEAD
81
+ run {
82
+ INTEGER bytes_recv "nullable"
83
+ INTEGER bytes_sent "nullable"
84
+ FLOAT clientapp_runtime "nullable"
85
+ VARCHAR details "nullable"
86
+ VARCHAR fab_hash "nullable"
87
+ VARCHAR fab_id "nullable"
88
+ VARCHAR fab_version "nullable"
89
+ VARCHAR federation "nullable"
90
+ BLOB federation_options "nullable"
91
+ VARCHAR finished_at "nullable"
92
+ VARCHAR flwr_aid "nullable"
93
+ VARCHAR override_config "nullable"
94
+ VARCHAR pending_at "nullable"
95
+ INTEGER run_id UK "nullable"
96
+ VARCHAR running_at "nullable"
97
+ VARCHAR starting_at "nullable"
98
+ VARCHAR sub_status "nullable"
99
+ }
100
+
101
+ =======
102
+ >>>>>>> main
24
103
  run_objects {
25
104
  VARCHAR object_id PK,FK
26
105
  INTEGER run_id PK
@@ -32,6 +111,13 @@ erDiagram
32
111
  VARCHAR token UK
33
112
  }
34
113
 
114
+ <<<<<<< HEAD
115
+ run ||--o| context : run_id
116
+ run ||--o{ logs : run_id
117
+ run ||--o{ message_ins : run_id
118
+ run ||--o{ message_res : run_id
119
+ =======
120
+ >>>>>>> main
35
121
  objects ||--o| object_children : parent_id
36
122
  objects ||--o| object_children : child_id
37
123
  objects ||--o| run_objects : object_id
@@ -14,17 +14,23 @@
14
14
  # ==============================================================================
15
15
  """SQLAlchemy Core Table definitions for CoreState."""
16
16
 
17
+
17
18
  from sqlalchemy import Column, Float, Integer, MetaData, String, Table
18
19
 
19
- corestate_metadata = MetaData()
20
20
 
21
- # ------------------------------------------------------------------------------
22
- # Table: token_store
23
- # ------------------------------------------------------------------------------
24
- token_store = Table(
25
- "token_store",
26
- corestate_metadata,
27
- Column("run_id", Integer, primary_key=True, nullable=True),
28
- Column("token", String, unique=True, nullable=False),
29
- Column("active_until", Float),
30
- )
21
+ def create_corestate_metadata() -> MetaData:
22
+ """Create and return MetaData with CoreState table definitions."""
23
+ metadata = MetaData()
24
+
25
+ # --------------------------------------------------------------------------
26
+ # Table: token_store
27
+ # --------------------------------------------------------------------------
28
+ Table(
29
+ "token_store",
30
+ metadata,
31
+ Column("run_id", Integer, primary_key=True, nullable=True),
32
+ Column("token", String, unique=True, nullable=False),
33
+ Column("active_until", Float),
34
+ )
35
+
36
+ return metadata
@@ -0,0 +1,152 @@
1
+ # Copyright 2026 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
+ """SQLAlchemy Core Table definitions for LinkState."""
16
+
17
+
18
+ from sqlalchemy import (
19
+ TIMESTAMP,
20
+ Column,
21
+ Float,
22
+ ForeignKey,
23
+ Index,
24
+ Integer,
25
+ LargeBinary,
26
+ MetaData,
27
+ String,
28
+ Table,
29
+ UniqueConstraint,
30
+ )
31
+
32
+
33
+ def create_linkstate_metadata() -> MetaData:
34
+ """Create and return MetaData with LinkState table definitions."""
35
+ metadata = MetaData()
36
+
37
+ # --------------------------------------------------------------------------
38
+ # Table: node
39
+ # --------------------------------------------------------------------------
40
+ Table(
41
+ "node",
42
+ metadata,
43
+ Column("node_id", Integer, unique=True),
44
+ Column("owner_aid", String),
45
+ Column("owner_name", String),
46
+ Column("status", String),
47
+ Column("registered_at", String),
48
+ Column("last_activated_at", String, nullable=True),
49
+ Column("last_deactivated_at", String, nullable=True),
50
+ Column("unregistered_at", String, nullable=True),
51
+ Column("online_until", TIMESTAMP, nullable=True),
52
+ Column("heartbeat_interval", Float),
53
+ Column("public_key", LargeBinary, unique=True),
54
+ # Indexes
55
+ # Used in delete_node and get_node_info (security/filtering)
56
+ Index("idx_node_owner_aid", "owner_aid"),
57
+ # Used in get_nodes and activation checks (frequent filtering)
58
+ Index("idx_node_status", "status"),
59
+ # Used in heartbeat checks to efficiently find expired nodes
60
+ Index("idx_online_until", "online_until"),
61
+ )
62
+
63
+ # --------------------------------------------------------------------------
64
+ # Table: run
65
+ # --------------------------------------------------------------------------
66
+ Table(
67
+ "run",
68
+ metadata,
69
+ Column("run_id", Integer, unique=True),
70
+ Column("fab_id", String),
71
+ Column("fab_version", String),
72
+ Column("fab_hash", String),
73
+ Column("override_config", String),
74
+ Column("pending_at", String),
75
+ Column("starting_at", String),
76
+ Column("running_at", String),
77
+ Column("finished_at", String),
78
+ Column("sub_status", String),
79
+ Column("details", String),
80
+ Column("federation", String),
81
+ Column("federation_options", LargeBinary),
82
+ Column("flwr_aid", String),
83
+ Column("bytes_sent", Integer, server_default="0"),
84
+ Column("bytes_recv", Integer, server_default="0"),
85
+ Column("clientapp_runtime", Float, server_default="0.0"),
86
+ )
87
+
88
+ # --------------------------------------------------------------------------
89
+ # Table: logs
90
+ # --------------------------------------------------------------------------
91
+ Table(
92
+ "logs",
93
+ metadata,
94
+ Column("timestamp", Float),
95
+ Column("run_id", Integer, ForeignKey("run.run_id")),
96
+ Column("node_id", Integer),
97
+ Column("log", String),
98
+ # Composite PK
99
+ UniqueConstraint("timestamp", "run_id", "node_id"),
100
+ )
101
+
102
+ # --------------------------------------------------------------------------
103
+ # Table: context
104
+ # --------------------------------------------------------------------------
105
+ Table(
106
+ "context",
107
+ metadata,
108
+ Column("run_id", Integer, ForeignKey("run.run_id"), unique=True),
109
+ Column("context", LargeBinary),
110
+ )
111
+
112
+ # --------------------------------------------------------------------------
113
+ # Table: message_ins
114
+ # --------------------------------------------------------------------------
115
+ Table(
116
+ "message_ins",
117
+ metadata,
118
+ Column("message_id", String, unique=True),
119
+ Column("group_id", String),
120
+ Column("run_id", Integer, ForeignKey("run.run_id")),
121
+ Column("src_node_id", Integer),
122
+ Column("dst_node_id", Integer),
123
+ Column("reply_to_message_id", String),
124
+ Column("created_at", Float),
125
+ Column("delivered_at", String),
126
+ Column("ttl", Float),
127
+ Column("message_type", String),
128
+ Column("content", LargeBinary, nullable=True),
129
+ Column("error", LargeBinary, nullable=True),
130
+ )
131
+
132
+ # --------------------------------------------------------------------------
133
+ # Table: message_res
134
+ # --------------------------------------------------------------------------
135
+ Table(
136
+ "message_res",
137
+ metadata,
138
+ Column("message_id", String, unique=True),
139
+ Column("group_id", String),
140
+ Column("run_id", Integer, ForeignKey("run.run_id")),
141
+ Column("src_node_id", Integer),
142
+ Column("dst_node_id", Integer),
143
+ Column("reply_to_message_id", String),
144
+ Column("created_at", Float),
145
+ Column("delivered_at", String),
146
+ Column("ttl", Float),
147
+ Column("message_type", String),
148
+ Column("content", LargeBinary, nullable=True),
149
+ Column("error", LargeBinary, nullable=True),
150
+ )
151
+
152
+ return metadata
@@ -27,59 +27,64 @@ from sqlalchemy import (
27
27
  Table,
28
28
  )
29
29
 
30
- objectstore_metadata = MetaData()
31
30
 
32
- # ------------------------------------------------------------------------------
33
- # Table: objects
34
- # ------------------------------------------------------------------------------
35
- objects = Table(
36
- "objects",
37
- objectstore_metadata,
38
- Column("object_id", String, primary_key=True, nullable=True),
39
- Column("content", LargeBinary),
40
- Column(
41
- "is_available",
42
- Integer,
43
- nullable=False,
44
- server_default="0",
45
- ),
46
- Column("ref_count", Integer, nullable=False, server_default="0"),
47
- CheckConstraint("is_available IN (0, 1)", name="ck_objects_is_available"),
48
- )
31
+ def create_objectstore_metadata() -> MetaData:
32
+ """Create and return MetaData with ObjectStore table definitions."""
33
+ metadata = MetaData()
49
34
 
50
- # ------------------------------------------------------------------------------
51
- # Table: object_children
52
- # ------------------------------------------------------------------------------
53
- object_children = Table(
54
- "object_children",
55
- objectstore_metadata,
56
- Column(
57
- "parent_id",
58
- String,
59
- ForeignKey("objects.object_id", ondelete="CASCADE"),
60
- nullable=False,
61
- ),
62
- Column(
63
- "child_id",
64
- String,
65
- ForeignKey("objects.object_id", ondelete="CASCADE"),
66
- nullable=False,
67
- ),
68
- PrimaryKeyConstraint("parent_id", "child_id"),
69
- )
35
+ # --------------------------------------------------------------------------
36
+ # Table: objects
37
+ # --------------------------------------------------------------------------
38
+ Table(
39
+ "objects",
40
+ metadata,
41
+ Column("object_id", String, primary_key=True, nullable=True),
42
+ Column("content", LargeBinary),
43
+ Column(
44
+ "is_available",
45
+ Integer,
46
+ nullable=False,
47
+ server_default="0",
48
+ ),
49
+ Column("ref_count", Integer, nullable=False, server_default="0"),
50
+ CheckConstraint("is_available IN (0, 1)", name="ck_objects_is_available"),
51
+ )
70
52
 
71
- # ------------------------------------------------------------------------------
72
- # Table: run_objects
73
- # ------------------------------------------------------------------------------
74
- run_objects = Table(
75
- "run_objects",
76
- objectstore_metadata,
77
- Column("run_id", Integer, nullable=False),
78
- Column(
79
- "object_id",
80
- String,
81
- ForeignKey("objects.object_id", ondelete="CASCADE"),
82
- nullable=False,
83
- ),
84
- PrimaryKeyConstraint("run_id", "object_id"),
85
- )
53
+ # --------------------------------------------------------------------------
54
+ # Table: object_children
55
+ # --------------------------------------------------------------------------
56
+ Table(
57
+ "object_children",
58
+ metadata,
59
+ Column(
60
+ "parent_id",
61
+ String,
62
+ ForeignKey("objects.object_id", ondelete="CASCADE"),
63
+ nullable=False,
64
+ ),
65
+ Column(
66
+ "child_id",
67
+ String,
68
+ ForeignKey("objects.object_id", ondelete="CASCADE"),
69
+ nullable=False,
70
+ ),
71
+ PrimaryKeyConstraint("parent_id", "child_id"),
72
+ )
73
+
74
+ # --------------------------------------------------------------------------
75
+ # Table: run_objects
76
+ # --------------------------------------------------------------------------
77
+ Table(
78
+ "run_objects",
79
+ metadata,
80
+ Column("run_id", Integer, nullable=False),
81
+ Column(
82
+ "object_id",
83
+ String,
84
+ ForeignKey("objects.object_id", ondelete="CASCADE"),
85
+ nullable=False,
86
+ ),
87
+ PrimaryKeyConstraint("run_id", "object_id"),
88
+ )
89
+
90
+ return metadata
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.26.0.dev20260116
3
+ Version: 1.26.0.dev20260119
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  License: Apache-2.0
6
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
@@ -18,13 +18,13 @@ flwr/cli/build.py,sha256=A9lzUNuGqLNId6PQppSWSlbMACl_LeW5Ispp9DuuX6k,10383
18
18
  flwr/cli/cli_account_auth_interceptor.py,sha256=mXgxThpZjU_2Xlae9xT8ewOw60GeE64comDd57asLIY,3680
19
19
  flwr/cli/config/__init__.py,sha256=46z6whA3hvKkl9APRs-UG7Ym3K9VOqKx_pYcgelRjtE,788
20
20
  flwr/cli/config/ls.py,sha256=ktRga1KWt4IDK5TQNT6ixfP66zcspkf-F2j3CkkpsGo,3665
21
- flwr/cli/config_migration.py,sha256=Jr9HMyWGS3xeNf9SELuJhPl7NhtfJMHlw37yG4r4jw4,8169
22
- flwr/cli/config_utils.py,sha256=oxny7Gx2crsBYwpgcQ3OBw3hBE-o1oQNAg0o7UBB4u8,13015
21
+ flwr/cli/config_migration.py,sha256=5rMAT4f3DpvIS4Hon2O-_elnTPhpyP-mWqaC-pCPQBA,8191
22
+ flwr/cli/config_utils.py,sha256=PiYlb1bAwpxtMH3OQ7V0VeCATfX4gwhULpkfe61SRJI,12832
23
23
  flwr/cli/constant.py,sha256=b5WsK_KXg9yDDk2lxEiN7rggsnZYNu1fiBC9NJVnnNs,3291
24
24
  flwr/cli/example.py,sha256=SNTorkKPrx1rOryGREUyZu8TcOc1-vFv1zEddaysdY0,2216
25
25
  flwr/cli/federation/__init__.py,sha256=okxswL4fAjApI9gV_alU1lRkTUcQRbwlzvtUTLz61fE,793
26
26
  flwr/cli/federation/ls.py,sha256=dGNw6quUAbUmqTNIVAPgJ9UgCJddITjKr48ETXQ9LTE,11581
27
- flwr/cli/flower_config.py,sha256=Rfw7ThkIdNiEfU-MsPdV8PFxCRcB8gx4NWcWtWJFXeM,15741
27
+ flwr/cli/flower_config.py,sha256=vD6eUaHE-4iePA-vqRrTqcPkISJkXcAzKxDWFdYYp2I,15787
28
28
  flwr/cli/install.py,sha256=yhgUWd_Psyc18DaoQHEQ5Ji45ZQ-3LclueM9uRxUEPw,10063
29
29
  flwr/cli/log.py,sha256=ZWveENLdDC0Dm10YIMcc3qdGV1aO-2YZXyHBhnB6ofI,7848
30
30
  flwr/cli/login/__init__.py,sha256=B1SXKU3HCQhWfFDMJhlC7FOl8UsvH4mxysxeBnrfyUE,800
@@ -32,7 +32,7 @@ flwr/cli/login/login.py,sha256=_nJKmDNVUkWRW6GnzTTNm3BmwCVjSxswtngHQfcDmZ4,4623
32
32
  flwr/cli/ls.py,sha256=KQwQbF45TbhCNtBi9uZaTVuG56xVMSAMCmqN8IuPogU,13510
33
33
  flwr/cli/new/__init__.py,sha256=QA1E2QtzPvFCjLTUHnFnJbufuFiGyT_0Y53Wpbvg1F0,790
34
34
  flwr/cli/new/new.py,sha256=VKrwkpd_voU4YwCVPIBPeFZjZiLW9OGpMPX_tpvPAE0,8315
35
- flwr/cli/pull.py,sha256=WRQOVxovWNX0kRnjUwCyFwyVmAEQY54EpQKFGEGUVUk,3530
35
+ flwr/cli/pull.py,sha256=oc86kFKD_PGVj3eh6mulyaNudQNemS4mOkS0EFD8xME,3485
36
36
  flwr/cli/run/__init__.py,sha256=RPyB7KbYTFl6YRiilCch6oezxrLQrl1kijV7BMGkLbA,790
37
37
  flwr/cli/run/run.py,sha256=-o2Um7G5QSrPJ0kc2_36ANMuSc-e6k5UvmSBfJTQxFs,9983
38
38
  flwr/cli/run_utils.py,sha256=ZnlBOqFfRxVEPZKR_9zYrynNcohiCFY8QN6OFyOCrQs,5082
@@ -41,8 +41,8 @@ flwr/cli/supernode/__init__.py,sha256=DBkjoPo2hS2Y-ghJxwLbrAbCQFBgD82_Itl2_892UB
41
41
  flwr/cli/supernode/ls.py,sha256=LNx3C9-Vfmw1ffVPDmjmex8TD_e0sTAXEC97aqg4Lqk,8958
42
42
  flwr/cli/supernode/register.py,sha256=2aQTT2wEp2pxxuIacC6FQkN8q_QKifZhWjD7LNw1R2A,6527
43
43
  flwr/cli/supernode/unregister.py,sha256=PXugcJJrM7cP3HbULjgcO22DciDP2uWN7YNgce4hv5E,4632
44
- flwr/cli/typing.py,sha256=QMph442kkoXjJoubevuYueMNGcuCJo29ekmrtiYzPK8,6108
45
- flwr/cli/utils.py,sha256=pQtnKvMKGan-GaiKWTo98Oi4F6RW1ItqHaCgKEQuRvU,23348
44
+ flwr/cli/typing.py,sha256=1aCIvZOjK5BOWSZ7EpyPyYwvsy9NGpr0EaIp_9qXyeg,8469
45
+ flwr/cli/utils.py,sha256=2X-NxhY41rlpdzzUobMmf2jLjEWTZ5BuvyehDllt_cE,23267
46
46
  flwr/client/__init__.py,sha256=xwkPJfdeWxIIfmiPE5vnmnY_JbTlErP0Qs9eBP6qRFg,1252
47
47
  flwr/client/client.py,sha256=3HAchxvknKG9jYbB7swNyDj-e5vUWDuMKoLvbT7jCVM,7895
48
48
  flwr/client/dpfedavg_numpy_client.py,sha256=ELDHyEJcTB-FlLhHC-JXy8HuB3ZFHfT0HL3g1VSWY5w,7451
@@ -328,11 +328,14 @@ flwr/supercore/address.py,sha256=vVE5y-t77YL16lMDVTeK3g75N8IKbQ72sVcRI-RPPWs,298
328
328
  flwr/supercore/app_utils.py,sha256=K76Zt6R670b1hUmxOsNc1WUCVYvF7lejXPcCO9K0Q0g,1753
329
329
  flwr/supercore/cli/__init__.py,sha256=EDl2aO-fuQfxSbL-T1W9RAfA2N0hpWHmqX_GSwblJbQ,845
330
330
  flwr/supercore/cli/flower_superexec.py,sha256=IQIGzxgaeLNMNzGXGemfYK3lp8God5bTkXpVkbeP_ig,6109
331
- flwr/supercore/constant.py,sha256=hBToL2kkN9VcEdc-O8K9beQfZdYRzMi4tpm7MZldC_4,2341
331
+ flwr/supercore/constant.py,sha256=cFnUHvSAGU0UXUyxLGfU6wwGs_rR-FRObcjBceO2gK4,2727
332
332
  flwr/supercore/corestate/__init__.py,sha256=Vau6-L_JG5QzNqtCTa9xCKGGljc09wY8avZmIjSJemg,774
333
333
  flwr/supercore/corestate/corestate.py,sha256=EZg4gPXqVOXwS7t0tlPfedajoWj5T80oeDBNxpV2y2I,2874
334
334
  flwr/supercore/corestate/in_memory_corestate.py,sha256=9qa6RuRZfCp6vs-ARYdiZjCL31VOAAxw0a_VkBXR5zY,5116
335
335
  flwr/supercore/corestate/sqlite_corestate.py,sha256=2SAikyNG0dgxOUtQsy9WS5XCDibndw6y4xPGK61seWE,5740
336
+ flwr/supercore/credential_store/__init__.py,sha256=csDOy8YgD-DnmXc2HwatMfXqXATHe6OLsBPKtDndhXw,1077
337
+ flwr/supercore/credential_store/credential_store.py,sha256=m_YHW0_A8CBfuRVq-GJgGNYDldxo9grT_jwJ8oCM9xA,1194
338
+ flwr/supercore/credential_store/file_credential_store.py,sha256=PeWuigqtMGMYnj_tKnWtlmJtu7OLuVaOB4D9GS47lHc,2771
336
339
  flwr/supercore/date.py,sha256=NsadFTzy6LMRB9zNeEV1szvzwQT45wC639P9kBTRZK4,1213
337
340
  flwr/supercore/ffs/__init__.py,sha256=U3KXwG_SplEvchat27K0LYPoPHzh-cwwT_NHsGlYMt8,908
338
341
  flwr/supercore/ffs/disk_ffs.py,sha256=IWFjikDs8BOfHIgFgOGI6p4Xl7U3n8lGbocmIibepcg,3241
@@ -352,12 +355,13 @@ flwr/supercore/object_store/sqlite_object_store.py,sha256=oDfqGe6TEr1HT6_ZoxLoi7
352
355
  flwr/supercore/primitives/__init__.py,sha256=Tx8GOjnmMo8Y74RsDGrMpfr-E0Nl8dcUDF784_ge6F8,745
353
356
  flwr/supercore/primitives/asymmetric.py,sha256=1643niHYj3uEbfPd06VuMHwN3tKVwg0uVyR3RhTdWIU,3778
354
357
  flwr/supercore/primitives/asymmetric_ed25519.py,sha256=eIhOTMibQW0FJX4MXdplHdL3HcfCiKuFu2mQ8GQTUz8,5025
355
- flwr/supercore/sqlite_mixin.py,sha256=ZvtqDLRwCMzyrPsOTpAwJeHSpthrrtvEfUmWZmss6OA,5317
358
+ flwr/supercore/sqlite_mixin.py,sha256=KrHxJQlJSuTdfc8YlFTft-COzT6FvVtQDWAWkdJ-jGQ,5064
356
359
  flwr/supercore/state/__init__.py,sha256=FkKhsNVM4LjlRlOgXTz6twINmw5ohIUKS_OER0BNo_w,724
357
- flwr/supercore/state/schema/README.md,sha256=Xf2UVSkJaufZ90TFkA2Vy5s7KK6jhSf3fAhQzwFWExo,633
360
+ flwr/supercore/state/schema/README.md,sha256=-0QrXDhnv30gEYoIUJo7aLUolY0r_t_nC24yk7B2agM,2892
358
361
  flwr/supercore/state/schema/__init__.py,sha256=Egnde6OY01wrpT4PuhL4NGn_NY4jdAH7kf7ktagN4Ws,724
359
- flwr/supercore/state/schema/corestate_tables.py,sha256=BuzMfmUEFkuism75VMtjS8qYHjX_0kms7n3yIwYtKzk,1251
360
- flwr/supercore/state/schema/objectstore_tables.py,sha256=cCayjB9J1k9j4mcCQnl6Y-U68c__DMg_DUQxtaZRnpE,2637
362
+ flwr/supercore/state/schema/corestate_tables.py,sha256=TwonogWgEUtKeE-LY59wousWzhF0w3wMKAotRqvRxao,1392
363
+ flwr/supercore/state/schema/linkstate_tables.py,sha256=aKh9zR6cF4ecOJHS8SzniLXkcNGd4OG4xnZvwC_qers,5583
364
+ flwr/supercore/state/schema/objectstore_tables.py,sha256=lWiu5fIcyqBXl4AlzOwynY3q2s93tYwYQy4nWNvARbo,2877
361
365
  flwr/supercore/superexec/__init__.py,sha256=XKX208hZ6a9gZ4KT9kMqfpCtp_8VGxekzKFfHSu2esQ,707
362
366
  flwr/supercore/superexec/plugin/__init__.py,sha256=GNwq8uNdE8RB7ywEFRAvKjLFzgS3YXgz39-HBGsemWw,1035
363
367
  flwr/supercore/superexec/plugin/base_exec_plugin.py,sha256=QmTr7AJTZO1cfmoVRjZUEhSlxNCou-XotGFAXkCqwWQ,2043
@@ -398,7 +402,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
398
402
  flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
399
403
  flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=rRL4CQ0L78jF_p0ct4-JMGREt6wWRy__wy4czF4f54Y,11639
400
404
  flwr/supernode/start_client_internal.py,sha256=BYk69UBQ2gQJaDQxXhccUgfOWrb7ShAstrbcMOCZIIs,26173
401
- flwr_nightly-1.26.0.dev20260116.dist-info/METADATA,sha256=M_saf2QwuWy4n9SEdGDbvJ6MyiKxABJbtVLzeHvKJqg,14398
402
- flwr_nightly-1.26.0.dev20260116.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
403
- flwr_nightly-1.26.0.dev20260116.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
404
- flwr_nightly-1.26.0.dev20260116.dist-info/RECORD,,
405
+ flwr_nightly-1.26.0.dev20260119.dist-info/METADATA,sha256=QWoln95HbQwCmpCtN4waDrOah5wr1zjfTDSXBzUZgwI,14398
406
+ flwr_nightly-1.26.0.dev20260119.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
407
+ flwr_nightly-1.26.0.dev20260119.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
408
+ flwr_nightly-1.26.0.dev20260119.dist-info/RECORD,,