flwr-nightly 1.16.0.dev20250226__py3-none-any.whl → 1.16.0.dev20250228__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.
- flwr/cli/log.py +20 -21
- flwr/client/client_app.py +89 -20
- flwr/common/auth_plugin/auth_plugin.py +2 -1
- flwr/common/constant.py +16 -0
- flwr/common/event_log_plugin/__init__.py +26 -0
- flwr/common/event_log_plugin/event_log_plugin.py +87 -0
- flwr/common/object_ref.py +0 -10
- flwr/common/record/recordset.py +97 -82
- flwr/common/secure_aggregation/quantization.py +5 -1
- flwr/common/typing.py +36 -0
- flwr/server/app.py +16 -1
- flwr/server/server_app.py +89 -20
- flwr/superexec/exec_servicer.py +1 -1
- flwr/superexec/exec_user_auth_interceptor.py +5 -3
- {flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/METADATA +1 -1
- {flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/RECORD +19 -17
- {flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/entry_points.txt +0 -0
flwr/cli/log.py
CHANGED
@@ -38,6 +38,10 @@ from flwr.proto.exec_pb2_grpc import ExecStub
|
|
38
38
|
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
39
39
|
|
40
40
|
|
41
|
+
class AllLogsRetrieved(BaseException):
|
42
|
+
"""Raised when all logs are retrieved."""
|
43
|
+
|
44
|
+
|
41
45
|
def start_stream(
|
42
46
|
run_id: int, channel: grpc.Channel, refresh_period: int = CONN_REFRESH_PERIOD
|
43
47
|
) -> None:
|
@@ -56,10 +60,10 @@ def start_stream(
|
|
56
60
|
# pylint: disable=E1101
|
57
61
|
if e.code() == grpc.StatusCode.NOT_FOUND:
|
58
62
|
logger(ERROR, "Invalid run_id `%s`, exiting", run_id)
|
59
|
-
if e.code() == grpc.StatusCode.CANCELLED:
|
60
|
-
pass
|
61
63
|
else:
|
62
64
|
raise e
|
65
|
+
except AllLogsRetrieved:
|
66
|
+
pass
|
63
67
|
finally:
|
64
68
|
channel.close()
|
65
69
|
|
@@ -94,6 +98,7 @@ def stream_logs(
|
|
94
98
|
with unauthenticated_exc_handler():
|
95
99
|
for res in stub.StreamLogs(req, timeout=duration):
|
96
100
|
print(res.log_output, end="")
|
101
|
+
raise AllLogsRetrieved()
|
97
102
|
except grpc.RpcError as e:
|
98
103
|
# pylint: disable=E1101
|
99
104
|
if e.code() != grpc.StatusCode.DEADLINE_EXCEEDED:
|
@@ -108,27 +113,21 @@ def stream_logs(
|
|
108
113
|
def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
|
109
114
|
"""Print logs from the beginning of a run."""
|
110
115
|
stub = ExecStub(channel)
|
111
|
-
req = StreamLogsRequest(run_id=run_id)
|
116
|
+
req = StreamLogsRequest(run_id=run_id, after_timestamp=0.0)
|
112
117
|
|
113
118
|
try:
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
break
|
127
|
-
if e.code() == grpc.StatusCode.CANCELLED:
|
128
|
-
break
|
129
|
-
raise e
|
130
|
-
except KeyboardInterrupt:
|
131
|
-
logger(DEBUG, "Stream interrupted by user")
|
119
|
+
with unauthenticated_exc_handler():
|
120
|
+
# Enforce timeout for graceful exit
|
121
|
+
for res in stub.StreamLogs(req, timeout=timeout):
|
122
|
+
print(res.log_output)
|
123
|
+
break
|
124
|
+
except grpc.RpcError as e:
|
125
|
+
if e.code() == grpc.StatusCode.NOT_FOUND: # pylint: disable=E1101
|
126
|
+
logger(ERROR, "Invalid run_id `%s`, exiting", run_id)
|
127
|
+
elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED: # pylint: disable=E1101
|
128
|
+
pass
|
129
|
+
else:
|
130
|
+
raise e
|
132
131
|
finally:
|
133
132
|
channel.close()
|
134
133
|
logger(DEBUG, "Channel closed")
|
flwr/client/client_app.py
CHANGED
@@ -16,6 +16,8 @@
|
|
16
16
|
|
17
17
|
|
18
18
|
import inspect
|
19
|
+
from collections.abc import Iterator
|
20
|
+
from contextlib import contextmanager
|
19
21
|
from typing import Callable, Optional
|
20
22
|
|
21
23
|
from flwr.client.client import Client
|
@@ -71,6 +73,11 @@ def _inspect_maybe_adapt_client_fn_signature(client_fn: ClientFnExt) -> ClientFn
|
|
71
73
|
return client_fn
|
72
74
|
|
73
75
|
|
76
|
+
@contextmanager
|
77
|
+
def _empty_lifecycle(_: Context) -> Iterator[None]:
|
78
|
+
yield
|
79
|
+
|
80
|
+
|
74
81
|
class ClientAppException(Exception):
|
75
82
|
"""Exception raised when an exception is raised while executing a ClientApp."""
|
76
83
|
|
@@ -135,29 +142,31 @@ class ClientApp:
|
|
135
142
|
self._train: Optional[ClientAppCallable] = None
|
136
143
|
self._evaluate: Optional[ClientAppCallable] = None
|
137
144
|
self._query: Optional[ClientAppCallable] = None
|
145
|
+
self._lifecycle = _empty_lifecycle
|
138
146
|
|
139
147
|
def __call__(self, message: Message, context: Context) -> Message:
|
140
148
|
"""Execute `ClientApp`."""
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
if
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
if
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
if
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
149
|
+
with self._lifecycle(context):
|
150
|
+
# Execute message using `client_fn`
|
151
|
+
if self._call:
|
152
|
+
return self._call(message, context)
|
153
|
+
|
154
|
+
# Execute message using a new
|
155
|
+
if message.metadata.message_type == MessageType.TRAIN:
|
156
|
+
if self._train:
|
157
|
+
return self._train(message, context)
|
158
|
+
raise ValueError("No `train` function registered")
|
159
|
+
if message.metadata.message_type == MessageType.EVALUATE:
|
160
|
+
if self._evaluate:
|
161
|
+
return self._evaluate(message, context)
|
162
|
+
raise ValueError("No `evaluate` function registered")
|
163
|
+
if message.metadata.message_type == MessageType.QUERY:
|
164
|
+
if self._query:
|
165
|
+
return self._query(message, context)
|
166
|
+
raise ValueError("No `query` function registered")
|
167
|
+
|
168
|
+
# Message type did not match one of the known message types abvoe
|
169
|
+
raise ValueError(f"Unknown message_type: {message.metadata.message_type}")
|
161
170
|
|
162
171
|
def train(
|
163
172
|
self, mods: Optional[list[Mod]] = None
|
@@ -296,6 +305,66 @@ class ClientApp:
|
|
296
305
|
|
297
306
|
return query_decorator
|
298
307
|
|
308
|
+
def lifecycle(
|
309
|
+
self,
|
310
|
+
) -> Callable[
|
311
|
+
[Callable[[Context], Iterator[None]]], Callable[[Context], Iterator[None]]
|
312
|
+
]:
|
313
|
+
"""Return a decorator that registers the lifecycle fn with the client app.
|
314
|
+
|
315
|
+
The decorated function should accept a `Context` object and use `yield`
|
316
|
+
to define enter and exit behavior.
|
317
|
+
|
318
|
+
Examples
|
319
|
+
--------
|
320
|
+
>>> app = ClientApp()
|
321
|
+
>>>
|
322
|
+
>>> @app.lifecycle()
|
323
|
+
>>> def lifecycle(context: Context) -> None:
|
324
|
+
>>> print("Initializing ClientApp")
|
325
|
+
>>> yield
|
326
|
+
>>> print("Cleaning up ClientApp")
|
327
|
+
"""
|
328
|
+
|
329
|
+
def lifecycle_decorator(
|
330
|
+
lifecycle_fn: Callable[[Context], Iterator[None]]
|
331
|
+
) -> Callable[[Context], Iterator[None]]:
|
332
|
+
"""Register the lifecycle fn with the ServerApp object."""
|
333
|
+
warn_preview_feature("ClientApp-register-lifecycle-function")
|
334
|
+
|
335
|
+
@contextmanager
|
336
|
+
def decorated_lifecycle(context: Context) -> Iterator[None]:
|
337
|
+
# Execute the code before `yield` in lifecycle_fn
|
338
|
+
try:
|
339
|
+
if not isinstance(it := lifecycle_fn(context), Iterator):
|
340
|
+
raise StopIteration
|
341
|
+
next(it)
|
342
|
+
except StopIteration:
|
343
|
+
raise RuntimeError(
|
344
|
+
"Lifecycle function should yield at least once."
|
345
|
+
) from None
|
346
|
+
|
347
|
+
try:
|
348
|
+
# Enter the context
|
349
|
+
yield
|
350
|
+
finally:
|
351
|
+
try:
|
352
|
+
# Execute the code after `yield` in lifecycle_fn
|
353
|
+
next(it)
|
354
|
+
except StopIteration:
|
355
|
+
pass
|
356
|
+
else:
|
357
|
+
raise RuntimeError("Lifecycle function should only yield once.")
|
358
|
+
|
359
|
+
# Register provided function with the ClientApp object
|
360
|
+
# Ignore mypy error because of different argument names (`_` vs `context`)
|
361
|
+
self._lifecycle = decorated_lifecycle # type: ignore
|
362
|
+
|
363
|
+
# Return provided function unmodified
|
364
|
+
return lifecycle_fn
|
365
|
+
|
366
|
+
return lifecycle_decorator
|
367
|
+
|
299
368
|
|
300
369
|
class LoadClientAppError(Exception):
|
301
370
|
"""Error when trying to load `ClientApp`."""
|
@@ -20,6 +20,7 @@ from collections.abc import Sequence
|
|
20
20
|
from pathlib import Path
|
21
21
|
from typing import Optional, Union
|
22
22
|
|
23
|
+
from flwr.common.typing import UserInfo
|
23
24
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
24
25
|
|
25
26
|
from ..typing import UserAuthCredentials, UserAuthLoginDetails
|
@@ -49,7 +50,7 @@ class ExecAuthPlugin(ABC):
|
|
49
50
|
@abstractmethod
|
50
51
|
def validate_tokens_in_metadata(
|
51
52
|
self, metadata: Sequence[tuple[str, Union[str, bytes]]]
|
52
|
-
) -> bool:
|
53
|
+
) -> tuple[bool, Optional[UserInfo]]:
|
53
54
|
"""Validate authentication tokens in the provided metadata."""
|
54
55
|
|
55
56
|
@abstractmethod
|
flwr/common/constant.py
CHANGED
@@ -212,3 +212,19 @@ class AuthType:
|
|
212
212
|
def __new__(cls) -> AuthType:
|
213
213
|
"""Prevent instantiation."""
|
214
214
|
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
215
|
+
|
216
|
+
|
217
|
+
class EventLogWriterType:
|
218
|
+
"""Event log writer types."""
|
219
|
+
|
220
|
+
FALSE = "false"
|
221
|
+
STDOUT = "stdout"
|
222
|
+
|
223
|
+
def __new__(cls) -> EventLogWriterType:
|
224
|
+
"""Prevent instantiation."""
|
225
|
+
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
226
|
+
|
227
|
+
@classmethod
|
228
|
+
def choices(cls) -> list[str]:
|
229
|
+
"""Return a list of available log writer choices."""
|
230
|
+
return [cls.FALSE, cls.STDOUT]
|
@@ -0,0 +1,26 @@
|
|
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
|
+
"""Event log plugin components."""
|
16
|
+
|
17
|
+
|
18
|
+
from .event_log_plugin import EventLogRequest as EventLogRequest
|
19
|
+
from .event_log_plugin import EventLogResponse as EventLogResponse
|
20
|
+
from .event_log_plugin import EventLogWriterPlugin as EventLogWriterPlugin
|
21
|
+
|
22
|
+
__all__ = [
|
23
|
+
"EventLogRequest",
|
24
|
+
"EventLogResponse",
|
25
|
+
"EventLogWriterPlugin",
|
26
|
+
]
|
@@ -0,0 +1,87 @@
|
|
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
|
+
"""Abstract class for Flower Event Log Writer Plugin."""
|
16
|
+
|
17
|
+
|
18
|
+
from abc import ABC, abstractmethod
|
19
|
+
from typing import Union
|
20
|
+
|
21
|
+
import grpc
|
22
|
+
|
23
|
+
from flwr.common.typing import LogEntry, UserInfo
|
24
|
+
from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
25
|
+
GetLoginDetailsRequest,
|
26
|
+
GetLoginDetailsResponse,
|
27
|
+
ListRunsRequest,
|
28
|
+
ListRunsResponse,
|
29
|
+
StartRunRequest,
|
30
|
+
StartRunResponse,
|
31
|
+
StopRunRequest,
|
32
|
+
StopRunResponse,
|
33
|
+
StreamLogsRequest,
|
34
|
+
StreamLogsResponse,
|
35
|
+
)
|
36
|
+
|
37
|
+
# Type variables for request and response messages
|
38
|
+
EventLogRequest = Union[
|
39
|
+
StartRunRequest,
|
40
|
+
ListRunsRequest,
|
41
|
+
StreamLogsRequest,
|
42
|
+
StopRunRequest,
|
43
|
+
GetLoginDetailsRequest,
|
44
|
+
]
|
45
|
+
EventLogResponse = Union[
|
46
|
+
StartRunResponse,
|
47
|
+
ListRunsResponse,
|
48
|
+
StreamLogsResponse,
|
49
|
+
StopRunResponse,
|
50
|
+
GetLoginDetailsResponse,
|
51
|
+
]
|
52
|
+
|
53
|
+
|
54
|
+
class EventLogWriterPlugin(ABC):
|
55
|
+
"""Abstract Flower Event Log Writer Plugin class for ExecServicer."""
|
56
|
+
|
57
|
+
@abstractmethod
|
58
|
+
def __init__(self) -> None:
|
59
|
+
"""Abstract constructor."""
|
60
|
+
|
61
|
+
@abstractmethod
|
62
|
+
def compose_log_before_event( # pylint: disable=too-many-arguments
|
63
|
+
self,
|
64
|
+
request: EventLogRequest,
|
65
|
+
context: grpc.ServicerContext,
|
66
|
+
user_info: UserInfo,
|
67
|
+
method_name: str,
|
68
|
+
) -> LogEntry:
|
69
|
+
"""Compose pre-event log entry from the provided request and context."""
|
70
|
+
|
71
|
+
@abstractmethod
|
72
|
+
def compose_log_after_event( # pylint: disable=too-many-arguments,R0917
|
73
|
+
self,
|
74
|
+
request: EventLogRequest,
|
75
|
+
context: grpc.ServicerContext,
|
76
|
+
user_info: UserInfo,
|
77
|
+
method_name: str,
|
78
|
+
response: EventLogResponse,
|
79
|
+
) -> LogEntry:
|
80
|
+
"""Compose post-event log entry from the provided response and context."""
|
81
|
+
|
82
|
+
@abstractmethod
|
83
|
+
def write_log(
|
84
|
+
self,
|
85
|
+
log_entry: LogEntry,
|
86
|
+
) -> None:
|
87
|
+
"""Write the event log to the specified data sink."""
|
flwr/common/object_ref.py
CHANGED
@@ -170,7 +170,6 @@ def load_app( # pylint: disable= too-many-branches
|
|
170
170
|
module = importlib.import_module(module_str)
|
171
171
|
else:
|
172
172
|
module = sys.modules[module_str]
|
173
|
-
_reload_modules(project_dir)
|
174
173
|
|
175
174
|
except ModuleNotFoundError as err:
|
176
175
|
raise error_type(
|
@@ -200,15 +199,6 @@ def _unload_modules(project_dir: Path) -> None:
|
|
200
199
|
del sys.modules[name]
|
201
200
|
|
202
201
|
|
203
|
-
def _reload_modules(project_dir: Path) -> None:
|
204
|
-
"""Reload modules from the project directory."""
|
205
|
-
dir_str = str(project_dir.absolute())
|
206
|
-
for m in list(sys.modules.values()):
|
207
|
-
path: Optional[str] = getattr(m, "__file__", None)
|
208
|
-
if path is not None and path.startswith(dir_str):
|
209
|
-
importlib.reload(m)
|
210
|
-
|
211
|
-
|
212
202
|
def _set_sys_path(directory: Optional[Union[str, Path]]) -> None:
|
213
203
|
"""Set the system path."""
|
214
204
|
if directory is None:
|
flwr/common/record/recordset.py
CHANGED
@@ -17,82 +17,79 @@
|
|
17
17
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
|
-
from
|
21
|
-
from
|
20
|
+
from logging import WARN
|
21
|
+
from textwrap import indent
|
22
|
+
from typing import TypeVar, Union, cast
|
22
23
|
|
24
|
+
from ..logger import log
|
23
25
|
from .configsrecord import ConfigsRecord
|
24
26
|
from .metricsrecord import MetricsRecord
|
25
27
|
from .parametersrecord import ParametersRecord
|
26
28
|
from .typeddict import TypedDict
|
27
29
|
|
30
|
+
RecordType = Union[ParametersRecord, MetricsRecord, ConfigsRecord]
|
28
31
|
|
29
|
-
|
30
|
-
class RecordSetData:
|
31
|
-
"""Inner data container for the RecordSet class."""
|
32
|
+
T = TypeVar("T")
|
32
33
|
|
33
|
-
parameters_records: TypedDict[str, ParametersRecord]
|
34
|
-
metrics_records: TypedDict[str, MetricsRecord]
|
35
|
-
configs_records: TypedDict[str, ConfigsRecord]
|
36
34
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
) -> None:
|
43
|
-
self.parameters_records = TypedDict[str, ParametersRecord](
|
44
|
-
self._check_fn_str, self._check_fn_params
|
45
|
-
)
|
46
|
-
self.metrics_records = TypedDict[str, MetricsRecord](
|
47
|
-
self._check_fn_str, self._check_fn_metrics
|
35
|
+
def _check_key(key: str) -> None:
|
36
|
+
if not isinstance(key, str):
|
37
|
+
raise TypeError(
|
38
|
+
f"Expected `{str.__name__}`, but "
|
39
|
+
f"received `{type(key).__name__}` for the key."
|
48
40
|
)
|
49
|
-
|
50
|
-
|
41
|
+
|
42
|
+
|
43
|
+
def _check_value(value: RecordType) -> None:
|
44
|
+
if not isinstance(value, (ParametersRecord, MetricsRecord, ConfigsRecord)):
|
45
|
+
raise TypeError(
|
46
|
+
f"Expected `{ParametersRecord.__name__}`, `{MetricsRecord.__name__}`, "
|
47
|
+
f"or `{ConfigsRecord.__name__}` but received "
|
48
|
+
f"`{type(value).__name__}` for the value."
|
51
49
|
)
|
52
|
-
if parameters_records is not None:
|
53
|
-
self.parameters_records.update(parameters_records)
|
54
|
-
if metrics_records is not None:
|
55
|
-
self.metrics_records.update(metrics_records)
|
56
|
-
if configs_records is not None:
|
57
|
-
self.configs_records.update(configs_records)
|
58
|
-
|
59
|
-
def _check_fn_str(self, key: str) -> None:
|
60
|
-
if not isinstance(key, str):
|
61
|
-
raise TypeError(
|
62
|
-
f"Expected `{str.__name__}`, but "
|
63
|
-
f"received `{type(key).__name__}` for the key."
|
64
|
-
)
|
65
50
|
|
66
|
-
def _check_fn_params(self, record: ParametersRecord) -> None:
|
67
|
-
if not isinstance(record, ParametersRecord):
|
68
|
-
raise TypeError(
|
69
|
-
f"Expected `{ParametersRecord.__name__}`, but "
|
70
|
-
f"received `{type(record).__name__}` for the value."
|
71
|
-
)
|
72
51
|
|
73
|
-
|
74
|
-
|
75
|
-
raise TypeError(
|
76
|
-
f"Expected `{MetricsRecord.__name__}`, but "
|
77
|
-
f"received `{type(record).__name__}` for the value."
|
78
|
-
)
|
52
|
+
class _SyncedDict(TypedDict[str, T]):
|
53
|
+
"""A synchronized dictionary that mirrors changes to an underlying RecordSet.
|
79
54
|
|
80
|
-
|
81
|
-
|
55
|
+
This dictionary ensures that any modifications (set or delete operations)
|
56
|
+
are automatically reflected in the associated `RecordSet`. Only values of
|
57
|
+
the specified `allowed_type` are permitted.
|
58
|
+
"""
|
59
|
+
|
60
|
+
def __init__(self, ref_recordset: RecordSet, allowed_type: type[T]) -> None:
|
61
|
+
if not issubclass(
|
62
|
+
allowed_type, (ParametersRecord, MetricsRecord, ConfigsRecord)
|
63
|
+
):
|
64
|
+
raise TypeError(f"{allowed_type} is not a valid type.")
|
65
|
+
super().__init__(_check_key, self.check_value)
|
66
|
+
self.recordset = ref_recordset
|
67
|
+
self.allowed_type = allowed_type
|
68
|
+
|
69
|
+
def __setitem__(self, key: str, value: T) -> None:
|
70
|
+
super().__setitem__(key, value)
|
71
|
+
self.recordset[key] = cast(RecordType, value)
|
72
|
+
|
73
|
+
def __delitem__(self, key: str) -> None:
|
74
|
+
super().__delitem__(key)
|
75
|
+
del self.recordset[key]
|
76
|
+
|
77
|
+
def check_value(self, value: T) -> None:
|
78
|
+
"""Check if value is of expected type."""
|
79
|
+
if not isinstance(value, self.allowed_type):
|
82
80
|
raise TypeError(
|
83
|
-
f"Expected `{
|
84
|
-
f"received `{type(
|
81
|
+
f"Expected `{self.allowed_type.__name__}`, but "
|
82
|
+
f"received `{type(value).__name__}` for the value."
|
85
83
|
)
|
86
84
|
|
87
85
|
|
88
|
-
class RecordSet:
|
86
|
+
class RecordSet(TypedDict[str, RecordType]):
|
89
87
|
"""RecordSet stores groups of parameters, metrics and configs.
|
90
88
|
|
91
|
-
A :
|
92
|
-
metrics and configs can be either stored as part of a
|
93
|
-
|
94
|
-
|
95
|
-
`flwr.common.Message <flwr.common.Message.html>`_ between your apps.
|
89
|
+
A :class:`RecordSet` is the unified mechanism by which parameters,
|
90
|
+
metrics and configs can be either stored as part of a :class:`Context`
|
91
|
+
in your apps or communicated as part of a :class:`Message` between
|
92
|
+
your apps.
|
96
93
|
|
97
94
|
Parameters
|
98
95
|
----------
|
@@ -127,12 +124,12 @@ class RecordSet:
|
|
127
124
|
>>> # We can create a ConfigsRecord
|
128
125
|
>>> c_record = ConfigsRecord({"lr": 0.1, "batch-size": 128})
|
129
126
|
>>> # Adding it to the record_set would look like this
|
130
|
-
>>> my_recordset
|
127
|
+
>>> my_recordset["my_config"] = c_record
|
131
128
|
>>>
|
132
129
|
>>> # We can create a MetricsRecord following a similar process
|
133
130
|
>>> m_record = MetricsRecord({"accuracy": 0.93, "losses": [0.23, 0.1]})
|
134
131
|
>>> # Adding it to the record_set would look like this
|
135
|
-
>>> my_recordset
|
132
|
+
>>> my_recordset["my_metrics"] = m_record
|
136
133
|
|
137
134
|
Adding a :code:`ParametersRecord` follows the same steps as above but first,
|
138
135
|
the array needs to be serialized and represented as a :code:`flwr.common.Array`.
|
@@ -151,7 +148,7 @@ class RecordSet:
|
|
151
148
|
>>> p_record = ParametersRecord({"my_array": arr})
|
152
149
|
>>>
|
153
150
|
>>> # Adding it to the record_set would look like this
|
154
|
-
>>> my_recordset
|
151
|
+
>>> my_recordset["my_parameters"] = p_record
|
155
152
|
|
156
153
|
For additional examples on how to construct each of the records types shown
|
157
154
|
above, please refer to the documentation for :code:`ConfigsRecord`,
|
@@ -164,39 +161,57 @@ class RecordSet:
|
|
164
161
|
metrics_records: dict[str, MetricsRecord] | None = None,
|
165
162
|
configs_records: dict[str, ConfigsRecord] | None = None,
|
166
163
|
) -> None:
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
164
|
+
super().__init__(_check_key, _check_value)
|
165
|
+
for key, p_record in (parameters_records or {}).items():
|
166
|
+
self[key] = p_record
|
167
|
+
for key, m_record in (metrics_records or {}).items():
|
168
|
+
self[key] = m_record
|
169
|
+
for key, c_record in (configs_records or {}).items():
|
170
|
+
self[key] = c_record
|
173
171
|
|
174
172
|
@property
|
175
173
|
def parameters_records(self) -> TypedDict[str, ParametersRecord]:
|
176
|
-
"""Dictionary holding ParametersRecord instances."""
|
177
|
-
|
178
|
-
|
174
|
+
"""Dictionary holding only ParametersRecord instances."""
|
175
|
+
synced_dict = _SyncedDict[ParametersRecord](self, ParametersRecord)
|
176
|
+
for key, record in self.items():
|
177
|
+
if isinstance(record, ParametersRecord):
|
178
|
+
synced_dict[key] = record
|
179
|
+
return synced_dict
|
179
180
|
|
180
181
|
@property
|
181
182
|
def metrics_records(self) -> TypedDict[str, MetricsRecord]:
|
182
|
-
"""Dictionary holding MetricsRecord instances."""
|
183
|
-
|
184
|
-
|
183
|
+
"""Dictionary holding only MetricsRecord instances."""
|
184
|
+
synced_dict = _SyncedDict[MetricsRecord](self, MetricsRecord)
|
185
|
+
for key, record in self.items():
|
186
|
+
if isinstance(record, MetricsRecord):
|
187
|
+
synced_dict[key] = record
|
188
|
+
return synced_dict
|
185
189
|
|
186
190
|
@property
|
187
191
|
def configs_records(self) -> TypedDict[str, ConfigsRecord]:
|
188
|
-
"""Dictionary holding ConfigsRecord instances."""
|
189
|
-
|
190
|
-
|
192
|
+
"""Dictionary holding only ConfigsRecord instances."""
|
193
|
+
synced_dict = _SyncedDict[ConfigsRecord](self, ConfigsRecord)
|
194
|
+
for key, record in self.items():
|
195
|
+
if isinstance(record, ConfigsRecord):
|
196
|
+
synced_dict[key] = record
|
197
|
+
return synced_dict
|
191
198
|
|
192
199
|
def __repr__(self) -> str:
|
193
200
|
"""Return a string representation of this instance."""
|
194
201
|
flds = ("parameters_records", "metrics_records", "configs_records")
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
202
|
+
fld_views = [f"{fld}={dict(getattr(self, fld))!r}" for fld in flds]
|
203
|
+
view = indent(",\n".join(fld_views), " ")
|
204
|
+
return f"{self.__class__.__qualname__}(\n{view}\n)"
|
205
|
+
|
206
|
+
def __setitem__(self, key: str, value: RecordType) -> None:
|
207
|
+
"""Set the given key to the given value after type checking."""
|
208
|
+
original_value = self.get(key, None)
|
209
|
+
super().__setitem__(key, value)
|
210
|
+
if original_value is not None and not isinstance(value, type(original_value)):
|
211
|
+
log(
|
212
|
+
WARN,
|
213
|
+
"Key '%s' was overwritten: record of type `%s` replaced with type `%s`",
|
214
|
+
key,
|
215
|
+
type(original_value).__name__,
|
216
|
+
type(value).__name__,
|
217
|
+
)
|
@@ -25,7 +25,11 @@ from flwr.common.typing import NDArrayFloat, NDArrayInt
|
|
25
25
|
def _stochastic_round(arr: NDArrayFloat) -> NDArrayInt:
|
26
26
|
ret: NDArrayInt = np.ceil(arr).astype(np.int32)
|
27
27
|
rand_arr = np.random.rand(*ret.shape)
|
28
|
-
|
28
|
+
if len(ret.shape) == 0:
|
29
|
+
if rand_arr < ret - arr:
|
30
|
+
ret -= 1
|
31
|
+
else:
|
32
|
+
ret[rand_arr < ret - arr] -= 1
|
29
33
|
return ret
|
30
34
|
|
31
35
|
|
flwr/common/typing.py
CHANGED
@@ -286,3 +286,39 @@ class UserAuthCredentials:
|
|
286
286
|
|
287
287
|
access_token: str
|
288
288
|
refresh_token: str
|
289
|
+
|
290
|
+
|
291
|
+
@dataclass
|
292
|
+
class UserInfo:
|
293
|
+
"""User information for event log."""
|
294
|
+
|
295
|
+
user_id: Optional[str]
|
296
|
+
user_name: Optional[str]
|
297
|
+
|
298
|
+
|
299
|
+
@dataclass
|
300
|
+
class Actor:
|
301
|
+
"""Event log actor."""
|
302
|
+
|
303
|
+
actor_id: Optional[str]
|
304
|
+
description: Optional[str]
|
305
|
+
ip_address: str
|
306
|
+
|
307
|
+
|
308
|
+
@dataclass
|
309
|
+
class Event:
|
310
|
+
"""Event log description."""
|
311
|
+
|
312
|
+
action: str
|
313
|
+
run_id: Optional[int]
|
314
|
+
fab_hash: Optional[str]
|
315
|
+
|
316
|
+
|
317
|
+
@dataclass
|
318
|
+
class LogEntry:
|
319
|
+
"""Event log record."""
|
320
|
+
|
321
|
+
timestamp: str
|
322
|
+
actor: Actor
|
323
|
+
event: Event
|
324
|
+
status: str
|
flwr/server/app.py
CHANGED
@@ -90,7 +90,11 @@ BASE_DIR = get_flwr_dir() / "superlink" / "ffs"
|
|
90
90
|
|
91
91
|
|
92
92
|
try:
|
93
|
-
from flwr.ee import
|
93
|
+
from flwr.ee import (
|
94
|
+
add_ee_args_superlink,
|
95
|
+
get_dashboard_server,
|
96
|
+
get_exec_auth_plugins,
|
97
|
+
)
|
94
98
|
except ImportError:
|
95
99
|
|
96
100
|
# pylint: disable-next=unused-argument
|
@@ -431,6 +435,17 @@ def run_superlink() -> None:
|
|
431
435
|
scheduler_th.start()
|
432
436
|
bckg_threads.append(scheduler_th)
|
433
437
|
|
438
|
+
# Add Dashboard server if available
|
439
|
+
if dashboard_address := getattr(args, "dashboard_address", None):
|
440
|
+
dashboard_address_str, _, _ = _format_address(dashboard_address)
|
441
|
+
dashboard_server = get_dashboard_server(
|
442
|
+
address=dashboard_address_str,
|
443
|
+
state_factory=state_factory,
|
444
|
+
certificates=None,
|
445
|
+
)
|
446
|
+
|
447
|
+
grpc_servers.append(dashboard_server)
|
448
|
+
|
434
449
|
# Graceful shutdown
|
435
450
|
register_exit_handlers(
|
436
451
|
event_type=EventType.RUN_SUPERLINK_LEAVE,
|
flwr/server/server_app.py
CHANGED
@@ -15,6 +15,8 @@
|
|
15
15
|
"""Flower ServerApp."""
|
16
16
|
|
17
17
|
|
18
|
+
from collections.abc import Iterator
|
19
|
+
from contextlib import contextmanager
|
18
20
|
from typing import Callable, Optional
|
19
21
|
|
20
22
|
from flwr.common import Context
|
@@ -45,7 +47,12 @@ SERVER_FN_USAGE_EXAMPLE = """
|
|
45
47
|
"""
|
46
48
|
|
47
49
|
|
48
|
-
|
50
|
+
@contextmanager
|
51
|
+
def _empty_lifecycle(_: Context) -> Iterator[None]:
|
52
|
+
yield
|
53
|
+
|
54
|
+
|
55
|
+
class ServerApp: # pylint: disable=too-many-instance-attributes
|
49
56
|
"""Flower ServerApp.
|
50
57
|
|
51
58
|
Examples
|
@@ -105,29 +112,31 @@ class ServerApp:
|
|
105
112
|
self._client_manager = client_manager
|
106
113
|
self._server_fn = server_fn
|
107
114
|
self._main: Optional[ServerAppCallable] = None
|
115
|
+
self._lifecycle = _empty_lifecycle
|
108
116
|
|
109
117
|
def __call__(self, driver: Driver, context: Context) -> None:
|
110
118
|
"""Execute `ServerApp`."""
|
111
|
-
|
112
|
-
|
113
|
-
if self.
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
119
|
+
with self._lifecycle(context):
|
120
|
+
# Compatibility mode
|
121
|
+
if not self._main:
|
122
|
+
if self._server_fn:
|
123
|
+
# Execute server_fn()
|
124
|
+
components = self._server_fn(context)
|
125
|
+
self._server = components.server
|
126
|
+
self._config = components.config
|
127
|
+
self._strategy = components.strategy
|
128
|
+
self._client_manager = components.client_manager
|
129
|
+
start_driver(
|
130
|
+
server=self._server,
|
131
|
+
config=self._config,
|
132
|
+
strategy=self._strategy,
|
133
|
+
client_manager=self._client_manager,
|
134
|
+
driver=driver,
|
135
|
+
)
|
136
|
+
return
|
128
137
|
|
129
|
-
|
130
|
-
|
138
|
+
# New execution mode
|
139
|
+
self._main(driver, context)
|
131
140
|
|
132
141
|
def main(self) -> Callable[[ServerAppCallable], ServerAppCallable]:
|
133
142
|
"""Return a decorator that registers the main fn with the server app.
|
@@ -177,6 +186,66 @@ class ServerApp:
|
|
177
186
|
|
178
187
|
return main_decorator
|
179
188
|
|
189
|
+
def lifecycle(
|
190
|
+
self,
|
191
|
+
) -> Callable[
|
192
|
+
[Callable[[Context], Iterator[None]]], Callable[[Context], Iterator[None]]
|
193
|
+
]:
|
194
|
+
"""Return a decorator that registers the lifecycle fn with the server app.
|
195
|
+
|
196
|
+
The decorated function should accept a `Context` object and use `yield`
|
197
|
+
to define enter and exit behavior.
|
198
|
+
|
199
|
+
Examples
|
200
|
+
--------
|
201
|
+
>>> app = ServerApp()
|
202
|
+
>>>
|
203
|
+
>>> @app.lifecycle()
|
204
|
+
>>> def lifecycle(context: Context) -> None:
|
205
|
+
>>> print("Initializing ServerApp")
|
206
|
+
>>> yield
|
207
|
+
>>> print("Cleaning up ServerApp")
|
208
|
+
"""
|
209
|
+
|
210
|
+
def lifecycle_decorator(
|
211
|
+
lifecycle_fn: Callable[[Context], Iterator[None]]
|
212
|
+
) -> Callable[[Context], Iterator[None]]:
|
213
|
+
"""Register the lifecycle fn with the ServerApp object."""
|
214
|
+
warn_preview_feature("ServerApp-register-lifecycle-function")
|
215
|
+
|
216
|
+
@contextmanager
|
217
|
+
def decorated_lifecycle(context: Context) -> Iterator[None]:
|
218
|
+
# Execute the code before `yield` in lifecycle_fn
|
219
|
+
try:
|
220
|
+
if not isinstance(it := lifecycle_fn(context), Iterator):
|
221
|
+
raise StopIteration
|
222
|
+
next(it)
|
223
|
+
except StopIteration:
|
224
|
+
raise RuntimeError(
|
225
|
+
"Lifecycle function should yield at least once."
|
226
|
+
) from None
|
227
|
+
|
228
|
+
try:
|
229
|
+
# Enter the context
|
230
|
+
yield
|
231
|
+
finally:
|
232
|
+
try:
|
233
|
+
# Execute the code after `yield` in lifecycle_fn
|
234
|
+
next(it)
|
235
|
+
except StopIteration:
|
236
|
+
pass
|
237
|
+
else:
|
238
|
+
raise RuntimeError("Lifecycle function should only yield once.")
|
239
|
+
|
240
|
+
# Register provided function with the ServerApp object
|
241
|
+
# Ignore mypy error because of different argument names (`_` vs `context`)
|
242
|
+
self._lifecycle = decorated_lifecycle # type: ignore
|
243
|
+
|
244
|
+
# Return provided function unmodified
|
245
|
+
return lifecycle_fn
|
246
|
+
|
247
|
+
return lifecycle_decorator
|
248
|
+
|
180
249
|
|
181
250
|
class LoadServerAppError(Exception):
|
182
251
|
"""Error when trying to load `ServerApp`."""
|
flwr/superexec/exec_servicer.py
CHANGED
@@ -120,7 +120,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
120
120
|
run_status = state.get_run_status({run_id})[run_id]
|
121
121
|
if run_status.status == Status.FINISHED:
|
122
122
|
log(INFO, "All logs for run ID `%s` returned", request.run_id)
|
123
|
-
|
123
|
+
break
|
124
124
|
|
125
125
|
time.sleep(LOG_STREAM_INTERVAL) # Sleep briefly to avoid busy waiting
|
126
126
|
|
@@ -77,9 +77,11 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
77
77
|
) -> Response:
|
78
78
|
call = method_handler.unary_unary or method_handler.unary_stream
|
79
79
|
metadata = context.invocation_metadata()
|
80
|
-
if isinstance(
|
81
|
-
|
82
|
-
|
80
|
+
if isinstance(request, (GetLoginDetailsRequest, GetAuthTokensRequest)):
|
81
|
+
return call(request, context) # type: ignore
|
82
|
+
|
83
|
+
valid_tokens, _ = self.auth_plugin.validate_tokens_in_metadata(metadata)
|
84
|
+
if valid_tokens:
|
83
85
|
return call(request, context) # type: ignore
|
84
86
|
|
85
87
|
tokens = self.auth_plugin.refresh_tokens(context.invocation_metadata())
|
{flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/RECORD
RENAMED
@@ -9,7 +9,7 @@ flwr/cli/config_utils.py,sha256=LelRR960I36n1IPw7BIu79fKoOh0JePA58kAtoXSTH0,7518
|
|
9
9
|
flwr/cli/constant.py,sha256=g7Ad7o3DJDkJNrWS0T3SSJETWSTkkVJWGpLM8zlbpcY,1289
|
10
10
|
flwr/cli/example.py,sha256=uk5CoD0ZITgpY_ffsTbEKf8XOOCSUzByjHPcMSPqV18,2216
|
11
11
|
flwr/cli/install.py,sha256=-RnrYGejN_zyXXp_CoddSQwoQfRTWWyt9WYlxphJzyU,8180
|
12
|
-
flwr/cli/log.py,sha256=
|
12
|
+
flwr/cli/log.py,sha256=Huy02o2xGbbBVIBZhcC18qXEo_IUvPcVas77MnDE9Vo,6529
|
13
13
|
flwr/cli/login/__init__.py,sha256=6_9zOzbPOAH72K2wX3-9dXTAbS7Mjpa5sEn2lA6eHHI,800
|
14
14
|
flwr/cli/login/login.py,sha256=iNnNF1bvV0n8Z-vNc89azFNA73JqKZlO1s5OF2atsTc,3917
|
15
15
|
flwr/cli/ls.py,sha256=cm3NuY1sFq51xVRhUG0MbAfVTrRSjJ1HMIubde80LuY,11433
|
@@ -74,7 +74,7 @@ flwr/cli/utils.py,sha256=D9XcpxzwkGPNdwX16o0kI-sYnRDMlWYyKNIpz6npRhQ,11236
|
|
74
74
|
flwr/client/__init__.py,sha256=DGDoO0AEAfz-0CUFmLdyUUweAS64-07AOnmDfWUefK4,1192
|
75
75
|
flwr/client/app.py,sha256=tNnef5wGVfqMiiGiWzAuULyy1QpvCKukiRmNi_a2cQc,34261
|
76
76
|
flwr/client/client.py,sha256=8o58nd9o6ZFcMIaVYPGcV4MSjBG4H0oFgWiv8ZEO3oA,7895
|
77
|
-
flwr/client/client_app.py,sha256=
|
77
|
+
flwr/client/client_app.py,sha256=WwKvP-22FRP1419oGYZdvRV5Z3JTNDDUse-Z8ikPkZA,14692
|
78
78
|
flwr/client/clientapp/__init__.py,sha256=kZqChGnTChQ1WGSUkIlW2S5bc0d0mzDubCAmZUGRpEY,800
|
79
79
|
flwr/client/clientapp/app.py,sha256=Us5Mw3wvGd_6P1zHOf3TNcRGBBulVZDo3LuZOs17WgM,8963
|
80
80
|
flwr/client/clientapp/clientappio_servicer.py,sha256=5L6bjw_j3Mnx9kRFwYwxDNABKurBO5q1jZOWE_X11wQ,8522
|
@@ -115,14 +115,16 @@ flwr/common/__init__.py,sha256=TVaoFEJE158aui1TPZQiJCDZX4RNHRyI8I55VC80HhI,3901
|
|
115
115
|
flwr/common/address.py,sha256=rRaN1JpiCJnit7ImEqZVxURQ69dPihRoyyWn_3I2wh4,4119
|
116
116
|
flwr/common/args.py,sha256=MgkTUXACuySHyNdxrb7-pK0_R-S2Q7W5MnE3onYUf5I,5183
|
117
117
|
flwr/common/auth_plugin/__init__.py,sha256=1Y8Oj3iB49IHDu9tvDih1J74Ygu7k85V9s2A4WORPyA,887
|
118
|
-
flwr/common/auth_plugin/auth_plugin.py,sha256=
|
118
|
+
flwr/common/auth_plugin/auth_plugin.py,sha256=dQU5U4uJIA5XqgOJ3PankHWq-uXCaMvO74khaMPGdiU,3938
|
119
119
|
flwr/common/config.py,sha256=SAkG3BztnA6iupXxF3GAIpGmWVVCH0ptyMpC9yjr_14,13965
|
120
|
-
flwr/common/constant.py,sha256=
|
120
|
+
flwr/common/constant.py,sha256=6NtDbh_RgQebtPfn01a8yN_7poMOuwy6jbtVLQdBbQc,7026
|
121
121
|
flwr/common/context.py,sha256=uJ-mnoC_8y_udEb3kAX-r8CPphNTWM72z1AlsvQEu54,2403
|
122
122
|
flwr/common/date.py,sha256=NHHpESce5wYqEwoDXf09gp9U9l_5Bmlh2BsOcwS-kDM,1554
|
123
123
|
flwr/common/differential_privacy.py,sha256=YA01NqjddKNAEVmf7hXmOVxOjhekgzvJudk3mBGq-2k,6148
|
124
124
|
flwr/common/differential_privacy_constants.py,sha256=c7b7tqgvT7yMK0XN9ndiTBs4mQf6d3qk6K7KBZGlV4Q,1074
|
125
125
|
flwr/common/dp.py,sha256=vddkvyjV2FhRoN4VuU2LeAM1UBn7dQB8_W-Qdiveal8,1978
|
126
|
+
flwr/common/event_log_plugin/__init__.py,sha256=iLGSlmIta-qY4Jm5Os8IBl5cvVYXyFGlqkUiUXQDlU0,1017
|
127
|
+
flwr/common/event_log_plugin/event_log_plugin.py,sha256=OdyYsBTqhLRRC0HL3_hv29LXJvHyhLCANXcLUqgAFTI,2568
|
126
128
|
flwr/common/exit/__init__.py,sha256=-ZOJYLaNnR729a7VzZiFsLiqngzKQh3xc27svYStZ_Q,826
|
127
129
|
flwr/common/exit/exit.py,sha256=DmZFyksp-w1sFDQekq5Z-qfnr-ivCAv78aQkqj-TDps,3458
|
128
130
|
flwr/common/exit/exit_code.py,sha256=PNEnCrZfOILjfDAFu5m-2YWEJBrk97xglq4zCUlqV7E,3470
|
@@ -130,7 +132,7 @@ flwr/common/exit_handlers.py,sha256=yclujry30954o0lI7vtknTajskPCvK8TXw2V3RdldXU,
|
|
130
132
|
flwr/common/grpc.py,sha256=K60AIvIqH0CvkkiqBfw5HoxQfbFOL2DrhKPjbZ8raIE,9786
|
131
133
|
flwr/common/logger.py,sha256=Hund1C6bEhMw3GemlzuFK22tXZ27YeHLrFB0b4LP5f8,13041
|
132
134
|
flwr/common/message.py,sha256=Zv4ID2BLQsbff0F03DI_MeFoHbSqVZAdDD9NcKYv6Zo,13832
|
133
|
-
flwr/common/object_ref.py,sha256=
|
135
|
+
flwr/common/object_ref.py,sha256=DXL8NtbN17DSYaR-Zc8WYhaG8rv0_D_cclvP7Sa66So,9134
|
134
136
|
flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
|
135
137
|
flwr/common/pyproject.py,sha256=vEAxl800XiJ1JNJDui8vuVV-08msnB6hLt7o95viZl0,1386
|
136
138
|
flwr/common/record/__init__.py,sha256=LUixpq0Z-lMJwCIu1-4u5HfvRPjRMRgoAc6YJQ6UEOs,1055
|
@@ -138,7 +140,7 @@ flwr/common/record/configsrecord.py,sha256=i40jOzBx04ysZKECwaw4FdUXMdY9HgdY8GAqK
|
|
138
140
|
flwr/common/record/conversion_utils.py,sha256=ZcsM-vTm_rVtLXLFD2RY3N47V_hUr3ywTdtnpVXnOGU,1202
|
139
141
|
flwr/common/record/metricsrecord.py,sha256=UywkEPbifiu_IyPUFoDJCi8WEVLujlqZERUWAWpc3vs,5752
|
140
142
|
flwr/common/record/parametersrecord.py,sha256=rR0LbeNrKrdK37CiAA56Z5WBq-ZzZ2YNSUkcmr5i2lI,12950
|
141
|
-
flwr/common/record/recordset.py,sha256=
|
143
|
+
flwr/common/record/recordset.py,sha256=gY3nmE--8sXSIPEFMReq7nh6hMiF-sr0HpfUtiB65E8,8890
|
142
144
|
flwr/common/record/typeddict.py,sha256=q5hL2xkXymuiCprHWb69mUmLpWQk_XXQq0hGQ69YPaw,3599
|
143
145
|
flwr/common/recordset_compat.py,sha256=ViSwA26h6Q55ZmV1LLjSJpcKiipV-p_JpCj4wxdE-Ow,14230
|
144
146
|
flwr/common/retry_invoker.py,sha256=UIDKsn0AitS3fOr43WTqZAdD-TaHkBeTj1QxD7SGba0,14481
|
@@ -147,12 +149,12 @@ flwr/common/secure_aggregation/crypto/__init__.py,sha256=nlHesCWy8xxE5s6qHWnauCt
|
|
147
149
|
flwr/common/secure_aggregation/crypto/shamir.py,sha256=wCSfEfeaPgJ9Om580-YPUF2ljiyRhq33TRC4HtwxYl8,2779
|
148
150
|
flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=J_pRkxbogc7e1fxRZStZFBdzzG5jeUycshJPpvyCt6g,5333
|
149
151
|
flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=zvVAIrIyI6OSzGhpCi8NNaTvPXmoMYQIPJT-NkBg8RU,3013
|
150
|
-
flwr/common/secure_aggregation/quantization.py,sha256=
|
152
|
+
flwr/common/secure_aggregation/quantization.py,sha256=NE_ltC3Fx5Z3bMKqJHA95wQf2tkGQlN0VZf3d1w5ABA,2400
|
151
153
|
flwr/common/secure_aggregation/secaggplus_constants.py,sha256=9MF-oQh62uD7rt9VeNB-rHf2gBLd5GL3S9OejCxmILY,2183
|
152
154
|
flwr/common/secure_aggregation/secaggplus_utils.py,sha256=OgYd68YBRaHQYLc-YdExj9CSpwL58bVTaPrdHoAj2AE,3214
|
153
155
|
flwr/common/serde.py,sha256=iDVS5IXGKqEuzQAvnroH9c6KFEHxKFEWmkGP89PMOO0,31064
|
154
156
|
flwr/common/telemetry.py,sha256=APKVubU_zJNrE-M_rip6S6Fsu41DxY3tAjFWNOgTmC0,9086
|
155
|
-
flwr/common/typing.py,sha256=
|
157
|
+
flwr/common/typing.py,sha256=Prl8_4tKnIl_Kh5UjJGbw1tnld543EkXrX0RWffJpiA,6900
|
156
158
|
flwr/common/version.py,sha256=aNSxLL49RKeLz8sPcZrsTEWtrAeQ0uxu6tjmfba4O60,1325
|
157
159
|
flwr/proto/__init__.py,sha256=hbY7JYakwZwCkYgCNlmHdc8rtvfoJbAZLalMdc--CGc,683
|
158
160
|
flwr/proto/clientappio_pb2.py,sha256=aroQDv0D2GquQ5Ujqml7n7l6ObZoXqMvDa0XVO-_8Cc,3703
|
@@ -217,7 +219,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
|
|
217
219
|
flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
|
218
220
|
flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
219
221
|
flwr/server/__init__.py,sha256=cEg1oecBu4cKB69iJCqWEylC8b5XW47bl7rQiJsdTvM,1528
|
220
|
-
flwr/server/app.py,sha256=
|
222
|
+
flwr/server/app.py,sha256=a_3hM-RktaPp3QTKDlBuHATB9oS2deGShZGEtmhlnX0,31005
|
221
223
|
flwr/server/client_manager.py,sha256=7Ese0tgrH-i-ms363feYZJKwB8gWnXSmg_hYF2Bju4U,6227
|
222
224
|
flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
|
223
225
|
flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
|
@@ -233,7 +235,7 @@ flwr/server/driver/inmemory_driver.py,sha256=7ZtWDDJa8xupPAHNaDdCE2DOIOIYgrffmJM
|
|
233
235
|
flwr/server/history.py,sha256=qSb5_pPTrwofpSYGsZWzMPkl_4uJ4mJFWesxXDrEvDU,5026
|
234
236
|
flwr/server/run_serverapp.py,sha256=vIPhvJx0i5sEZO4IKM6ruCXmx4ncat76rh0B4KhdhhM,2446
|
235
237
|
flwr/server/server.py,sha256=1ZsFEptmAV-L2vP2etNC9Ed5CLSxpuKzUFkAPQ4l5Xc,17893
|
236
|
-
flwr/server/server_app.py,sha256=
|
238
|
+
flwr/server/server_app.py,sha256=QtxZKdqP8GUDBIilKRLP7smLjabHPnzS8XhtdqRWbIU,8880
|
237
239
|
flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
|
238
240
|
flwr/server/serverapp/__init__.py,sha256=L0K-94UDdTyEZ8LDtYybGIIIv3HW6AhSVjXMUfYJQnQ,800
|
239
241
|
flwr/server/serverapp/app.py,sha256=5nFRYYzC2vh0l1fKJofBfKwFam93In4b80wvH9eFfQ8,8651
|
@@ -323,12 +325,12 @@ flwr/superexec/__init__.py,sha256=fcj366jh4RFby_vDwLroU4kepzqbnJgseZD_jUr_Mko,71
|
|
323
325
|
flwr/superexec/app.py,sha256=Z6kYHWd62YL0Q4YKyCAbt_BcefNfbKH6V-jCC-1NkZM,1842
|
324
326
|
flwr/superexec/deployment.py,sha256=wZ9G42gGS91knfplswh95MnQ83Fzu-rs6wcuNgDmmvY,6735
|
325
327
|
flwr/superexec/exec_grpc.py,sha256=ttA9qoZzSLF0Mfk1L4hzOkMSNuj5rR58_kKBYwcyrAg,2864
|
326
|
-
flwr/superexec/exec_servicer.py,sha256=
|
327
|
-
flwr/superexec/exec_user_auth_interceptor.py,sha256=
|
328
|
+
flwr/superexec/exec_servicer.py,sha256=72AL60LBbWD-OTxTvtPBrnb_M5rccMtU_JAYcEVQVNg,8330
|
329
|
+
flwr/superexec/exec_user_auth_interceptor.py,sha256=YtvcjrD2hMVmZ3y9wHuGI6FwmG_Y__q112t4fFnypy0,3793
|
328
330
|
flwr/superexec/executor.py,sha256=_B55WW2TD1fBINpabSSDRenVHXYmvlfhv-k8hJKU4lQ,3115
|
329
331
|
flwr/superexec/simulation.py,sha256=WQDon15oqpMopAZnwRZoTICYCfHqtkvFSqiTQ2hLD_g,4088
|
330
|
-
flwr_nightly-1.16.0.
|
331
|
-
flwr_nightly-1.16.0.
|
332
|
-
flwr_nightly-1.16.0.
|
333
|
-
flwr_nightly-1.16.0.
|
334
|
-
flwr_nightly-1.16.0.
|
332
|
+
flwr_nightly-1.16.0.dev20250228.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
333
|
+
flwr_nightly-1.16.0.dev20250228.dist-info/METADATA,sha256=UbfME6tkYpPD7ma1nlkMPd3lQY4A4rpArEPlyNQPp1w,15877
|
334
|
+
flwr_nightly-1.16.0.dev20250228.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
335
|
+
flwr_nightly-1.16.0.dev20250228.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
|
336
|
+
flwr_nightly-1.16.0.dev20250228.dist-info/RECORD,,
|
{flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/LICENSE
RENAMED
File without changes
|
{flwr_nightly-1.16.0.dev20250226.dist-info → flwr_nightly-1.16.0.dev20250228.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|