flwr-nightly 1.16.0.dev20250225__py3-none-any.whl → 1.16.0.dev20250227__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/client/client_app.py +53 -6
- 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/record/conversion_utils.py +8 -17
- flwr/common/record/parametersrecord.py +151 -16
- 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/superexec/exec_user_auth_interceptor.py +1 -1
- {flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/METADATA +1 -1
- {flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/RECORD +17 -15
- {flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/entry_points.txt +0 -0
flwr/client/client_app.py
CHANGED
@@ -159,11 +159,15 @@ class ClientApp:
|
|
159
159
|
# Message type did not match one of the known message types abvoe
|
160
160
|
raise ValueError(f"Unknown message_type: {message.metadata.message_type}")
|
161
161
|
|
162
|
-
def train(
|
162
|
+
def train(
|
163
|
+
self, mods: Optional[list[Mod]] = None
|
164
|
+
) -> Callable[[ClientAppCallable], ClientAppCallable]:
|
163
165
|
"""Return a decorator that registers the train fn with the client app.
|
164
166
|
|
165
167
|
Examples
|
166
168
|
--------
|
169
|
+
Registering a train function:
|
170
|
+
|
167
171
|
>>> app = ClientApp()
|
168
172
|
>>>
|
169
173
|
>>> @app.train()
|
@@ -171,6 +175,17 @@ class ClientApp:
|
|
171
175
|
>>> print("ClientApp training running")
|
172
176
|
>>> # Create and return an echo reply message
|
173
177
|
>>> return message.create_reply(content=message.content())
|
178
|
+
|
179
|
+
Registering a train function with a function-specific modifier:
|
180
|
+
|
181
|
+
>>> from flwr.client.mod import message_size_mod
|
182
|
+
>>>
|
183
|
+
>>> app = ClientApp()
|
184
|
+
>>>
|
185
|
+
>>> @app.train(mods=[message_size_mod])
|
186
|
+
>>> def train(message: Message, context: Context) -> Message:
|
187
|
+
>>> print("ClientApp training running with message size mod")
|
188
|
+
>>> return message.create_reply(content=message.content())
|
174
189
|
"""
|
175
190
|
|
176
191
|
def train_decorator(train_fn: ClientAppCallable) -> ClientAppCallable:
|
@@ -182,18 +197,22 @@ class ClientApp:
|
|
182
197
|
|
183
198
|
# Register provided function with the ClientApp object
|
184
199
|
# Wrap mods around the wrapped step function
|
185
|
-
self._train = make_ffn(train_fn, self._mods)
|
200
|
+
self._train = make_ffn(train_fn, self._mods + (mods or []))
|
186
201
|
|
187
202
|
# Return provided function unmodified
|
188
203
|
return train_fn
|
189
204
|
|
190
205
|
return train_decorator
|
191
206
|
|
192
|
-
def evaluate(
|
207
|
+
def evaluate(
|
208
|
+
self, mods: Optional[list[Mod]] = None
|
209
|
+
) -> Callable[[ClientAppCallable], ClientAppCallable]:
|
193
210
|
"""Return a decorator that registers the evaluate fn with the client app.
|
194
211
|
|
195
212
|
Examples
|
196
213
|
--------
|
214
|
+
Registering an evaluate function:
|
215
|
+
|
197
216
|
>>> app = ClientApp()
|
198
217
|
>>>
|
199
218
|
>>> @app.evaluate()
|
@@ -201,6 +220,18 @@ class ClientApp:
|
|
201
220
|
>>> print("ClientApp evaluation running")
|
202
221
|
>>> # Create and return an echo reply message
|
203
222
|
>>> return message.create_reply(content=message.content())
|
223
|
+
|
224
|
+
Registering an evaluate function with a function-specific modifier:
|
225
|
+
|
226
|
+
>>> from flwr.client.mod import message_size_mod
|
227
|
+
>>>
|
228
|
+
>>> app = ClientApp()
|
229
|
+
>>>
|
230
|
+
>>> @app.evaluate(mods=[message_size_mod])
|
231
|
+
>>> def evaluate(message: Message, context: Context) -> Message:
|
232
|
+
>>> print("ClientApp evaluation running with message size mod")
|
233
|
+
>>> # Create and return an echo reply message
|
234
|
+
>>> return message.create_reply(content=message.content())
|
204
235
|
"""
|
205
236
|
|
206
237
|
def evaluate_decorator(evaluate_fn: ClientAppCallable) -> ClientAppCallable:
|
@@ -212,18 +243,22 @@ class ClientApp:
|
|
212
243
|
|
213
244
|
# Register provided function with the ClientApp object
|
214
245
|
# Wrap mods around the wrapped step function
|
215
|
-
self._evaluate = make_ffn(evaluate_fn, self._mods)
|
246
|
+
self._evaluate = make_ffn(evaluate_fn, self._mods + (mods or []))
|
216
247
|
|
217
248
|
# Return provided function unmodified
|
218
249
|
return evaluate_fn
|
219
250
|
|
220
251
|
return evaluate_decorator
|
221
252
|
|
222
|
-
def query(
|
253
|
+
def query(
|
254
|
+
self, mods: Optional[list[Mod]] = None
|
255
|
+
) -> Callable[[ClientAppCallable], ClientAppCallable]:
|
223
256
|
"""Return a decorator that registers the query fn with the client app.
|
224
257
|
|
225
258
|
Examples
|
226
259
|
--------
|
260
|
+
Registering a query function:
|
261
|
+
|
227
262
|
>>> app = ClientApp()
|
228
263
|
>>>
|
229
264
|
>>> @app.query()
|
@@ -231,6 +266,18 @@ class ClientApp:
|
|
231
266
|
>>> print("ClientApp query running")
|
232
267
|
>>> # Create and return an echo reply message
|
233
268
|
>>> return message.create_reply(content=message.content())
|
269
|
+
|
270
|
+
Registering a query function with a function-specific modifier:
|
271
|
+
|
272
|
+
>>> from flwr.client.mod import message_size_mod
|
273
|
+
>>>
|
274
|
+
>>> app = ClientApp()
|
275
|
+
>>>
|
276
|
+
>>> @app.query(mods=[message_size_mod])
|
277
|
+
>>> def query(message: Message, context: Context) -> Message:
|
278
|
+
>>> print("ClientApp query running with message size mod")
|
279
|
+
>>> # Create and return an echo reply message
|
280
|
+
>>> return message.create_reply(content=message.content())
|
234
281
|
"""
|
235
282
|
|
236
283
|
def query_decorator(query_fn: ClientAppCallable) -> ClientAppCallable:
|
@@ -242,7 +289,7 @@ class ClientApp:
|
|
242
289
|
|
243
290
|
# Register provided function with the ClientApp object
|
244
291
|
# Wrap mods around the wrapped step function
|
245
|
-
self._query = make_ffn(query_fn, self._mods)
|
292
|
+
self._query = make_ffn(query_fn, self._mods + (mods or []))
|
246
293
|
|
247
294
|
# Return provided function unmodified
|
248
295
|
return query_fn
|
@@ -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."""
|
@@ -15,26 +15,17 @@
|
|
15
15
|
"""Conversion utility functions for Records."""
|
16
16
|
|
17
17
|
|
18
|
-
from
|
19
|
-
|
20
|
-
import numpy as np
|
21
|
-
|
22
|
-
from ..constant import SType
|
18
|
+
from ..logger import warn_deprecated_feature
|
23
19
|
from ..typing import NDArray
|
24
20
|
from .parametersrecord import Array
|
25
21
|
|
22
|
+
WARN_DEPRECATED_MESSAGE = (
|
23
|
+
"`array_from_numpy` is deprecated. Instead, use the `Array(ndarray)` class "
|
24
|
+
"directly or `Array.from_numpy_ndarray(ndarray)`."
|
25
|
+
)
|
26
|
+
|
26
27
|
|
27
28
|
def array_from_numpy(ndarray: NDArray) -> Array:
|
28
29
|
"""Create Array from NumPy ndarray."""
|
29
|
-
|
30
|
-
|
31
|
-
# Reason: loading pickled data can execute arbitrary code
|
32
|
-
# Source: https://numpy.org/doc/stable/reference/generated/numpy.save.html
|
33
|
-
np.save(buffer, ndarray, allow_pickle=False)
|
34
|
-
data = buffer.getvalue()
|
35
|
-
return Array(
|
36
|
-
dtype=str(ndarray.dtype),
|
37
|
-
shape=list(ndarray.shape),
|
38
|
-
stype=SType.NUMPY,
|
39
|
-
data=data,
|
40
|
-
)
|
30
|
+
warn_deprecated_feature(WARN_DEPRECATED_MESSAGE)
|
31
|
+
return Array.from_numpy_ndarray(ndarray)
|
@@ -15,10 +15,12 @@
|
|
15
15
|
"""ParametersRecord and Array."""
|
16
16
|
|
17
17
|
|
18
|
+
from __future__ import annotations
|
19
|
+
|
18
20
|
from collections import OrderedDict
|
19
21
|
from dataclasses import dataclass
|
20
22
|
from io import BytesIO
|
21
|
-
from typing import
|
23
|
+
from typing import Any, cast, overload
|
22
24
|
|
23
25
|
import numpy as np
|
24
26
|
|
@@ -27,29 +29,64 @@ from ..typing import NDArray
|
|
27
29
|
from .typeddict import TypedDict
|
28
30
|
|
29
31
|
|
32
|
+
def _raise_array_init_error() -> None:
|
33
|
+
raise TypeError(
|
34
|
+
f"Invalid arguments for {Array.__qualname__}. Expected either a "
|
35
|
+
"NumPy ndarray, or explicit dtype/shape/stype/data values."
|
36
|
+
)
|
37
|
+
|
38
|
+
|
30
39
|
@dataclass
|
31
40
|
class Array:
|
32
41
|
"""Array type.
|
33
42
|
|
34
43
|
A dataclass containing serialized data from an array-like or tensor-like object
|
35
|
-
along with
|
44
|
+
along with metadata about it. The class can be initialized in one of two ways:
|
45
|
+
|
46
|
+
1. By specifying explicit values for `dtype`, `shape`, `stype`, and `data`.
|
47
|
+
2. By providing a NumPy ndarray (via the `ndarray` argument).
|
48
|
+
|
49
|
+
In scenario (2), the `dtype`, `shape`, `stype`, and `data` are automatically
|
50
|
+
derived from the input. In scenario (1), these fields must be specified manually.
|
36
51
|
|
37
52
|
Parameters
|
38
53
|
----------
|
39
|
-
dtype : str
|
40
|
-
A string representing the data type of the
|
54
|
+
dtype : Optional[str] (default: None)
|
55
|
+
A string representing the data type of the serialized object (e.g. `"float32"`).
|
56
|
+
Only required if you are not passing in a ndarray.
|
41
57
|
|
42
|
-
shape :
|
43
|
-
A list representing the shape of the unserialized array-like object.
|
44
|
-
|
45
|
-
as a metadata field.
|
58
|
+
shape : Optional[list[int]] (default: None)
|
59
|
+
A list representing the shape of the unserialized array-like object. Only
|
60
|
+
required if you are not passing in a ndarray.
|
46
61
|
|
47
|
-
stype : str
|
48
|
-
A string indicating the
|
49
|
-
|
62
|
+
stype : Optional[str] (default: None)
|
63
|
+
A string indicating the serialization mechanism used to generate the bytes in
|
64
|
+
`data` from an array-like or tensor-like object. Only required if you are not
|
65
|
+
passing in a ndarray.
|
50
66
|
|
51
|
-
data: bytes
|
52
|
-
A buffer of bytes containing the data.
|
67
|
+
data : Optional[bytes] (default: None)
|
68
|
+
A buffer of bytes containing the data. Only required if you are not passing in
|
69
|
+
a ndarray.
|
70
|
+
|
71
|
+
ndarray : Optional[NDArray] (default: None)
|
72
|
+
A NumPy ndarray. If provided, the `dtype`, `shape`, `stype`, and `data`
|
73
|
+
fields are derived automatically from it.
|
74
|
+
|
75
|
+
Examples
|
76
|
+
--------
|
77
|
+
Initializing by specifying all fields directly:
|
78
|
+
|
79
|
+
>>> arr1 = Array(
|
80
|
+
>>> dtype="float32",
|
81
|
+
>>> shape=[3, 3],
|
82
|
+
>>> stype="numpy.ndarray",
|
83
|
+
>>> data=b"serialized_data...",
|
84
|
+
>>> )
|
85
|
+
|
86
|
+
Initializing with a NumPy ndarray:
|
87
|
+
|
88
|
+
>>> import numpy as np
|
89
|
+
>>> arr2 = Array(np.random.randn(3, 3))
|
53
90
|
"""
|
54
91
|
|
55
92
|
dtype: str
|
@@ -57,6 +94,105 @@ class Array:
|
|
57
94
|
stype: str
|
58
95
|
data: bytes
|
59
96
|
|
97
|
+
@overload
|
98
|
+
def __init__( # noqa: E704
|
99
|
+
self, dtype: str, shape: list[int], stype: str, data: bytes
|
100
|
+
) -> None: ...
|
101
|
+
|
102
|
+
@overload
|
103
|
+
def __init__(self, ndarray: NDArray) -> None: ... # noqa: E704
|
104
|
+
|
105
|
+
def __init__( # pylint: disable=too-many-arguments, too-many-locals
|
106
|
+
self,
|
107
|
+
*args: Any,
|
108
|
+
dtype: str | None = None,
|
109
|
+
shape: list[int] | None = None,
|
110
|
+
stype: str | None = None,
|
111
|
+
data: bytes | None = None,
|
112
|
+
ndarray: NDArray | None = None,
|
113
|
+
) -> None:
|
114
|
+
# Determine the initialization method and validate input arguments.
|
115
|
+
# Support two initialization formats:
|
116
|
+
# 1. Array(dtype: str, shape: list[int], stype: str, data: bytes)
|
117
|
+
# 2. Array(ndarray: NDArray)
|
118
|
+
|
119
|
+
# Initialize all arguments
|
120
|
+
# If more than 4 positional arguments are provided, raise an error.
|
121
|
+
if len(args) > 4:
|
122
|
+
_raise_array_init_error()
|
123
|
+
all_args = [None] * 4
|
124
|
+
for i, arg in enumerate(args):
|
125
|
+
all_args[i] = arg
|
126
|
+
init_method: str | None = None # Track which init method is being used
|
127
|
+
|
128
|
+
# Try to assign a value to all_args[index] if it's not already set.
|
129
|
+
# If an initialization method is provided, update init_method.
|
130
|
+
def _try_set_arg(index: int, arg: Any, method: str) -> None:
|
131
|
+
# Skip if arg is None
|
132
|
+
if arg is None:
|
133
|
+
return
|
134
|
+
# Raise an error if all_args[index] is already set
|
135
|
+
if all_args[index] is not None:
|
136
|
+
_raise_array_init_error()
|
137
|
+
# Raise an error if a different initialization method is already set
|
138
|
+
nonlocal init_method
|
139
|
+
if init_method is not None and init_method != method:
|
140
|
+
_raise_array_init_error()
|
141
|
+
# Set init_method and all_args[index]
|
142
|
+
if init_method is None:
|
143
|
+
init_method = method
|
144
|
+
all_args[index] = arg
|
145
|
+
|
146
|
+
# Try to set keyword arguments in all_args
|
147
|
+
_try_set_arg(0, dtype, "direct")
|
148
|
+
_try_set_arg(1, shape, "direct")
|
149
|
+
_try_set_arg(2, stype, "direct")
|
150
|
+
_try_set_arg(3, data, "direct")
|
151
|
+
_try_set_arg(0, ndarray, "ndarray")
|
152
|
+
|
153
|
+
# Check if all arguments are correctly set
|
154
|
+
all_args = [arg for arg in all_args if arg is not None]
|
155
|
+
|
156
|
+
# Handle direct field initialization
|
157
|
+
if not init_method or init_method == "direct":
|
158
|
+
if (
|
159
|
+
len(all_args) == 4 # pylint: disable=too-many-boolean-expressions
|
160
|
+
and isinstance(all_args[0], str)
|
161
|
+
and isinstance(all_args[1], list)
|
162
|
+
and all(isinstance(i, int) for i in all_args[1])
|
163
|
+
and isinstance(all_args[2], str)
|
164
|
+
and isinstance(all_args[3], bytes)
|
165
|
+
):
|
166
|
+
self.dtype, self.shape, self.stype, self.data = all_args
|
167
|
+
return
|
168
|
+
|
169
|
+
# Handle NumPy array
|
170
|
+
if not init_method or init_method == "ndarray":
|
171
|
+
if len(all_args) == 1 and isinstance(all_args[0], np.ndarray):
|
172
|
+
self.__dict__.update(self.from_numpy_ndarray(all_args[0]).__dict__)
|
173
|
+
return
|
174
|
+
|
175
|
+
_raise_array_init_error()
|
176
|
+
|
177
|
+
@classmethod
|
178
|
+
def from_numpy_ndarray(cls, ndarray: NDArray) -> Array:
|
179
|
+
"""Create Array from NumPy ndarray."""
|
180
|
+
assert isinstance(
|
181
|
+
ndarray, np.ndarray
|
182
|
+
), f"Expected NumPy ndarray, got {type(ndarray)}"
|
183
|
+
buffer = BytesIO()
|
184
|
+
# WARNING: NEVER set allow_pickle to true.
|
185
|
+
# Reason: loading pickled data can execute arbitrary code
|
186
|
+
# Source: https://numpy.org/doc/stable/reference/generated/numpy.save.html
|
187
|
+
np.save(buffer, ndarray, allow_pickle=False)
|
188
|
+
data = buffer.getvalue()
|
189
|
+
return Array(
|
190
|
+
dtype=str(ndarray.dtype),
|
191
|
+
shape=list(ndarray.shape),
|
192
|
+
stype=SType.NUMPY,
|
193
|
+
data=data,
|
194
|
+
)
|
195
|
+
|
60
196
|
def numpy(self) -> NDArray:
|
61
197
|
"""Return the array as a NumPy array."""
|
62
198
|
if self.stype != SType.NUMPY:
|
@@ -117,7 +253,6 @@ class ParametersRecord(TypedDict[str, Array]):
|
|
117
253
|
|
118
254
|
>>> import numpy as np
|
119
255
|
>>> from flwr.common import ParametersRecord
|
120
|
-
>>> from flwr.common import array_from_numpy
|
121
256
|
>>>
|
122
257
|
>>> # Let's create a simple NumPy array
|
123
258
|
>>> arr_np = np.random.randn(3, 3)
|
@@ -128,7 +263,7 @@ class ParametersRecord(TypedDict[str, Array]):
|
|
128
263
|
>>> [-0.10758364, 1.97619858, -0.37120501]])
|
129
264
|
>>>
|
130
265
|
>>> # Let's create an Array out of it
|
131
|
-
>>> arr =
|
266
|
+
>>> arr = Array(arr_np)
|
132
267
|
>>>
|
133
268
|
>>> # If we print it you'll see (note the binary data)
|
134
269
|
>>> Array(dtype='float64', shape=[3,3], stype='numpy.ndarray', data=b'@\x99\x18...')
|
@@ -176,7 +311,7 @@ class ParametersRecord(TypedDict[str, Array]):
|
|
176
311
|
|
177
312
|
def __init__(
|
178
313
|
self,
|
179
|
-
array_dict:
|
314
|
+
array_dict: OrderedDict[str, Array] | None = None,
|
180
315
|
keep_input: bool = False,
|
181
316
|
) -> None:
|
182
317
|
super().__init__(_check_key, _check_value)
|
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,
|
@@ -85,7 +85,7 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
85
85
|
tokens = self.auth_plugin.refresh_tokens(context.invocation_metadata())
|
86
86
|
if tokens is not None:
|
87
87
|
context.send_initial_metadata(tokens)
|
88
|
-
return call(request, context)
|
88
|
+
return call(request, context)
|
89
89
|
|
90
90
|
context.abort(grpc.StatusCode.UNAUTHENTICATED, "Access denied")
|
91
91
|
raise grpc.RpcError() # This line is unreachable
|
{flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/RECORD
RENAMED
@@ -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=Vv4rfDcV9ycb9ZuUkhT_8wX7W1GIrALwlvRcUeVel3Y,12161
|
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
|
@@ -135,10 +137,10 @@ 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
|
137
139
|
flwr/common/record/configsrecord.py,sha256=i40jOzBx04ysZKECwaw4FdUXMdY9HgdY8GAqKdTO1Lw,6486
|
138
|
-
flwr/common/record/conversion_utils.py,sha256=
|
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
|
-
flwr/common/record/parametersrecord.py,sha256=
|
141
|
-
flwr/common/record/recordset.py,sha256=
|
142
|
+
flwr/common/record/parametersrecord.py,sha256=rR0LbeNrKrdK37CiAA56Z5WBq-ZzZ2YNSUkcmr5i2lI,12950
|
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
|
@@ -324,11 +326,11 @@ 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
328
|
flwr/superexec/exec_servicer.py,sha256=X10ILT-AoGMrB3IgI2mBe9i-QcIVUAl9bucuqVOPYkU,8341
|
327
|
-
flwr/superexec/exec_user_auth_interceptor.py,sha256=
|
329
|
+
flwr/superexec/exec_user_auth_interceptor.py,sha256=XvLRxIsZ5m90pg6COx-tKkixFWxF-FaBeP3PqJFtksw,3688
|
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.dev20250227.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
333
|
+
flwr_nightly-1.16.0.dev20250227.dist-info/METADATA,sha256=E--UFuJfhErjNYm6D4cvC1efJ_ZXAFNGII1jKTbC6mc,15877
|
334
|
+
flwr_nightly-1.16.0.dev20250227.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
335
|
+
flwr_nightly-1.16.0.dev20250227.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
|
336
|
+
flwr_nightly-1.16.0.dev20250227.dist-info/RECORD,,
|
{flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/LICENSE
RENAMED
File without changes
|
{flwr_nightly-1.16.0.dev20250225.dist-info → flwr_nightly-1.16.0.dev20250227.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|