flwr-nightly 1.19.0.dev20250610__py3-none-any.whl → 1.19.0.dev20250611__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/grpc_rere_client/connection.py +1 -0
- flwr/client/rest_client/connection.py +118 -26
- flwr/common/auth_plugin/auth_plugin.py +6 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -3
- flwr/common/inflatable.py +46 -1
- flwr/common/inflatable_grpc_utils.py +1 -1
- flwr/common/inflatable_rest_utils.py +99 -0
- flwr/common/typing.py +3 -3
- flwr/server/fleet_event_log_interceptor.py +2 -2
- flwr/server/superlink/fleet/rest_rere/rest_api.py +30 -0
- flwr/superexec/exec_event_log_interceptor.py +4 -4
- flwr/superexec/exec_user_auth_interceptor.py +11 -11
- {flwr_nightly-1.19.0.dev20250610.dist-info → flwr_nightly-1.19.0.dev20250611.dist-info}/METADATA +1 -1
- {flwr_nightly-1.19.0.dev20250610.dist-info → flwr_nightly-1.19.0.dev20250611.dist-info}/RECORD +16 -15
- {flwr_nightly-1.19.0.dev20250610.dist-info → flwr_nightly-1.19.0.dev20250611.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.19.0.dev20250610.dist-info → flwr_nightly-1.19.0.dev20250611.dist-info}/entry_points.txt +0 -0
@@ -14,6 +14,7 @@
|
|
14
14
|
# ==============================================================================
|
15
15
|
"""Contextmanager for a gRPC request-response channel to the Flower server."""
|
16
16
|
|
17
|
+
|
17
18
|
from collections.abc import Iterator, Sequence
|
18
19
|
from contextlib import contextmanager
|
19
20
|
from copy import copy
|
@@ -14,12 +14,11 @@
|
|
14
14
|
# ==============================================================================
|
15
15
|
"""Contextmanager for a REST request-response channel to the Flower server."""
|
16
16
|
|
17
|
-
|
18
17
|
from collections.abc import Iterator
|
19
18
|
from contextlib import contextmanager
|
20
19
|
from copy import copy
|
21
|
-
from logging import ERROR, INFO, WARN
|
22
|
-
from typing import Callable, Optional, TypeVar, Union
|
20
|
+
from logging import DEBUG, ERROR, INFO, WARN
|
21
|
+
from typing import Callable, Optional, TypeVar, Union, cast
|
23
22
|
|
24
23
|
from cryptography.hazmat.primitives.asymmetric import ec
|
25
24
|
from google.protobuf.message import Message as GrpcMessage
|
@@ -31,10 +30,20 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
31
30
|
from flwr.common.constant import HEARTBEAT_DEFAULT_INTERVAL
|
32
31
|
from flwr.common.exit import ExitCode, flwr_exit
|
33
32
|
from flwr.common.heartbeat import HeartbeatSender
|
33
|
+
from flwr.common.inflatable import get_all_nested_objects
|
34
|
+
from flwr.common.inflatable_rest_utils import (
|
35
|
+
make_pull_object_fn_rest,
|
36
|
+
make_push_object_fn_rest,
|
37
|
+
)
|
38
|
+
from flwr.common.inflatable_utils import (
|
39
|
+
inflate_object_from_contents,
|
40
|
+
pull_objects,
|
41
|
+
push_objects,
|
42
|
+
)
|
34
43
|
from flwr.common.logger import log
|
35
|
-
from flwr.common.message import Message
|
44
|
+
from flwr.common.message import Message, remove_content_from_message
|
36
45
|
from flwr.common.retry_invoker import RetryInvoker
|
37
|
-
from flwr.common.serde import
|
46
|
+
from flwr.common.serde import message_to_proto, run_from_proto
|
38
47
|
from flwr.common.typing import Fab, Run
|
39
48
|
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
40
49
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
@@ -51,6 +60,13 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
|
|
51
60
|
SendNodeHeartbeatRequest,
|
52
61
|
SendNodeHeartbeatResponse,
|
53
62
|
)
|
63
|
+
from flwr.proto.message_pb2 import ObjectIDs # pylint: disable=E0611
|
64
|
+
from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
65
|
+
PullObjectRequest,
|
66
|
+
PullObjectResponse,
|
67
|
+
PushObjectRequest,
|
68
|
+
PushObjectResponse,
|
69
|
+
)
|
54
70
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
55
71
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
56
72
|
|
@@ -64,6 +80,8 @@ PATH_CREATE_NODE: str = "api/v0/fleet/create-node"
|
|
64
80
|
PATH_DELETE_NODE: str = "api/v0/fleet/delete-node"
|
65
81
|
PATH_PULL_MESSAGES: str = "/api/v0/fleet/pull-messages"
|
66
82
|
PATH_PUSH_MESSAGES: str = "/api/v0/fleet/push-messages"
|
83
|
+
PATH_PULL_OBJECT: str = "/api/v0/fleet/pull-object"
|
84
|
+
PATH_PUSH_OBJECT: str = "/api/v0/fleet/push-object"
|
67
85
|
PATH_SEND_NODE_HEARTBEAT: str = "api/v0/fleet/send-node-heartbeat"
|
68
86
|
PATH_GET_RUN: str = "/api/v0/fleet/get-run"
|
69
87
|
PATH_GET_FAB: str = "/api/v0/fleet/get-fab"
|
@@ -296,14 +314,48 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
296
314
|
):
|
297
315
|
message_proto = None
|
298
316
|
|
299
|
-
#
|
300
|
-
|
301
|
-
|
302
|
-
if message_proto
|
303
|
-
message = message_from_proto(message_proto)
|
304
|
-
metadata = copy(message.metadata)
|
317
|
+
# Construct the Message
|
318
|
+
in_message: Optional[Message] = None
|
319
|
+
|
320
|
+
if message_proto:
|
305
321
|
log(INFO, "[Node] POST /%s: success", PATH_PULL_MESSAGES)
|
306
|
-
|
322
|
+
msg_id = message_proto.metadata.message_id
|
323
|
+
|
324
|
+
def fn(request: PullObjectRequest) -> PullObjectResponse:
|
325
|
+
res = _request(
|
326
|
+
req=request, res_type=PullObjectResponse, api_path=PATH_PULL_OBJECT
|
327
|
+
)
|
328
|
+
if res is None:
|
329
|
+
raise ValueError("PushObjectResponse is None.")
|
330
|
+
return res
|
331
|
+
|
332
|
+
try:
|
333
|
+
all_object_contents = pull_objects(
|
334
|
+
list(res.objects_to_pull[msg_id].object_ids) + [msg_id],
|
335
|
+
pull_object_fn=make_pull_object_fn_rest(
|
336
|
+
pull_object_rest=fn,
|
337
|
+
node=node,
|
338
|
+
run_id=message_proto.metadata.run_id,
|
339
|
+
),
|
340
|
+
)
|
341
|
+
except ValueError as e:
|
342
|
+
log(
|
343
|
+
ERROR,
|
344
|
+
"Pulling objects failed. Potential irrecoverable error: %s",
|
345
|
+
str(e),
|
346
|
+
)
|
347
|
+
in_message = cast(
|
348
|
+
Message, inflate_object_from_contents(msg_id, all_object_contents)
|
349
|
+
)
|
350
|
+
# The deflated message doesn't contain the message_id (its own object_id)
|
351
|
+
# Inject
|
352
|
+
in_message.metadata.__dict__["_message_id"] = msg_id
|
353
|
+
|
354
|
+
# Remember `metadata` of the in message
|
355
|
+
nonlocal metadata
|
356
|
+
metadata = copy(in_message.metadata) if in_message else None
|
357
|
+
|
358
|
+
return in_message
|
307
359
|
|
308
360
|
def send(message: Message) -> None:
|
309
361
|
"""Send Message result back to server."""
|
@@ -318,29 +370,69 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
318
370
|
log(ERROR, "No current message")
|
319
371
|
return
|
320
372
|
|
373
|
+
# Set message_id
|
374
|
+
message.metadata.__dict__["_message_id"] = message.object_id
|
321
375
|
# Validate out message
|
322
376
|
if not validate_out_message(message, metadata):
|
323
377
|
log(ERROR, "Invalid out message")
|
324
378
|
return
|
325
|
-
metadata = None
|
326
379
|
|
327
|
-
#
|
328
|
-
|
329
|
-
|
330
|
-
#
|
331
|
-
|
380
|
+
# Get all nested objects
|
381
|
+
all_objects = get_all_nested_objects(message)
|
382
|
+
all_object_ids = list(all_objects.keys())
|
383
|
+
msg_id = all_object_ids[-1] # Last object is the message itself
|
384
|
+
descendant_ids = all_object_ids[:-1] # All but the last object are descendants
|
385
|
+
|
386
|
+
# Serialize Message
|
387
|
+
message_proto = message_to_proto(message=remove_content_from_message(message))
|
388
|
+
req = PushMessagesRequest(
|
389
|
+
node=node,
|
390
|
+
messages_list=[message_proto],
|
391
|
+
msg_to_descendant_mapping={msg_id: ObjectIDs(object_ids=descendant_ids)},
|
392
|
+
)
|
332
393
|
|
333
394
|
# Send the request
|
334
395
|
res = _request(req, PushMessagesResponse, PATH_PUSH_MESSAGES)
|
335
|
-
if res
|
336
|
-
|
396
|
+
if res:
|
397
|
+
log(
|
398
|
+
INFO,
|
399
|
+
"[Node] POST /%s: success, created result %s",
|
400
|
+
PATH_PUSH_MESSAGES,
|
401
|
+
res.results, # pylint: disable=no-member
|
402
|
+
)
|
337
403
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
404
|
+
if res and res.objects_to_push:
|
405
|
+
objs_to_push = set(res.objects_to_push[message.object_id].object_ids)
|
406
|
+
|
407
|
+
def fn(request: PushObjectRequest) -> PushObjectResponse:
|
408
|
+
res = _request(
|
409
|
+
req=request, res_type=PushObjectResponse, api_path=PATH_PUSH_OBJECT
|
410
|
+
)
|
411
|
+
if res is None:
|
412
|
+
raise ValueError("PushObjectResponse is None.")
|
413
|
+
return res
|
414
|
+
|
415
|
+
try:
|
416
|
+
push_objects(
|
417
|
+
all_objects,
|
418
|
+
push_object_fn=make_push_object_fn_rest(
|
419
|
+
push_object_rest=fn,
|
420
|
+
node=node,
|
421
|
+
run_id=message_proto.metadata.run_id,
|
422
|
+
),
|
423
|
+
object_ids_to_push=objs_to_push,
|
424
|
+
)
|
425
|
+
log(DEBUG, "Pushed %s objects to servicer.", len(objs_to_push))
|
426
|
+
except ValueError as e:
|
427
|
+
log(
|
428
|
+
ERROR,
|
429
|
+
"Pushing objects failed. Potential irrecoverable error: %s",
|
430
|
+
str(e),
|
431
|
+
)
|
432
|
+
log(ERROR, str(e))
|
433
|
+
|
434
|
+
# Cleanup
|
435
|
+
metadata = None
|
344
436
|
|
345
437
|
def get_run(run_id: int) -> Run:
|
346
438
|
# Construct the request
|
@@ -20,7 +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
|
23
|
+
from flwr.common.typing import AccountInfo
|
24
24
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
25
25
|
|
26
26
|
from ..typing import UserAuthCredentials, UserAuthLoginDetails
|
@@ -53,7 +53,7 @@ class ExecAuthPlugin(ABC):
|
|
53
53
|
@abstractmethod
|
54
54
|
def validate_tokens_in_metadata(
|
55
55
|
self, metadata: Sequence[tuple[str, Union[str, bytes]]]
|
56
|
-
) -> tuple[bool, Optional[
|
56
|
+
) -> tuple[bool, Optional[AccountInfo]]:
|
57
57
|
"""Validate authentication tokens in the provided metadata."""
|
58
58
|
|
59
59
|
@abstractmethod
|
@@ -63,7 +63,9 @@ class ExecAuthPlugin(ABC):
|
|
63
63
|
@abstractmethod
|
64
64
|
def refresh_tokens(
|
65
65
|
self, metadata: Sequence[tuple[str, Union[str, bytes]]]
|
66
|
-
) -> tuple[
|
66
|
+
) -> tuple[
|
67
|
+
Optional[Sequence[tuple[str, Union[str, bytes]]]], Optional[AccountInfo]
|
68
|
+
]:
|
67
69
|
"""Refresh authentication tokens in the provided metadata."""
|
68
70
|
|
69
71
|
|
@@ -84,7 +86,7 @@ class ExecAuthzPlugin(ABC): # pylint: disable=too-few-public-methods
|
|
84
86
|
"""Abstract constructor."""
|
85
87
|
|
86
88
|
@abstractmethod
|
87
|
-
def verify_user_authorization(self,
|
89
|
+
def verify_user_authorization(self, account_info: AccountInfo) -> bool:
|
88
90
|
"""Verify user authorization request."""
|
89
91
|
|
90
92
|
|
@@ -21,7 +21,7 @@ from typing import Optional, Union
|
|
21
21
|
import grpc
|
22
22
|
from google.protobuf.message import Message as GrpcMessage
|
23
23
|
|
24
|
-
from flwr.common.typing import
|
24
|
+
from flwr.common.typing import AccountInfo, LogEntry
|
25
25
|
|
26
26
|
|
27
27
|
class EventLogWriterPlugin(ABC):
|
@@ -36,7 +36,7 @@ class EventLogWriterPlugin(ABC):
|
|
36
36
|
self,
|
37
37
|
request: GrpcMessage,
|
38
38
|
context: grpc.ServicerContext,
|
39
|
-
|
39
|
+
account_info: Optional[AccountInfo],
|
40
40
|
method_name: str,
|
41
41
|
) -> LogEntry:
|
42
42
|
"""Compose pre-event log entry from the provided request and context."""
|
@@ -46,7 +46,7 @@ class EventLogWriterPlugin(ABC):
|
|
46
46
|
self,
|
47
47
|
request: GrpcMessage,
|
48
48
|
context: grpc.ServicerContext,
|
49
|
-
|
49
|
+
account_info: Optional[AccountInfo],
|
50
50
|
method_name: str,
|
51
51
|
response: Optional[Union[GrpcMessage, BaseException]],
|
52
52
|
) -> LogEntry:
|
flwr/common/inflatable.py
CHANGED
@@ -18,6 +18,9 @@
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
20
|
import hashlib
|
21
|
+
import threading
|
22
|
+
from collections.abc import Iterator
|
23
|
+
from contextlib import contextmanager
|
21
24
|
from typing import TypeVar, cast
|
22
25
|
|
23
26
|
from .constant import HEAD_BODY_DIVIDER, HEAD_VALUE_DIVIDER
|
@@ -33,6 +36,33 @@ class UnexpectedObjectContentError(Exception):
|
|
33
36
|
)
|
34
37
|
|
35
38
|
|
39
|
+
_ctx = threading.local()
|
40
|
+
|
41
|
+
|
42
|
+
def _is_recompute_enabled() -> bool:
|
43
|
+
"""Check if recomputing object IDs is enabled."""
|
44
|
+
return getattr(_ctx, "recompute_object_id_enabled", True)
|
45
|
+
|
46
|
+
|
47
|
+
def _get_computed_object_ids() -> set[str]:
|
48
|
+
"""Get the set of computed object IDs."""
|
49
|
+
return getattr(_ctx, "computed_object_ids", set())
|
50
|
+
|
51
|
+
|
52
|
+
@contextmanager
|
53
|
+
def no_object_id_recompute() -> Iterator[None]:
|
54
|
+
"""Context manager to disable recomputing object IDs."""
|
55
|
+
old_value = _is_recompute_enabled()
|
56
|
+
old_set = _get_computed_object_ids()
|
57
|
+
_ctx.recompute_object_id_enabled = False
|
58
|
+
_ctx.computed_object_ids = set()
|
59
|
+
try:
|
60
|
+
yield
|
61
|
+
finally:
|
62
|
+
_ctx.recompute_object_id_enabled = old_value
|
63
|
+
_ctx.computed_object_ids = old_set
|
64
|
+
|
65
|
+
|
36
66
|
class InflatableObject:
|
37
67
|
"""Base class for inflatable objects."""
|
38
68
|
|
@@ -65,8 +95,23 @@ class InflatableObject:
|
|
65
95
|
@property
|
66
96
|
def object_id(self) -> str:
|
67
97
|
"""Get object_id."""
|
98
|
+
# If recomputing object ID is disabled and the object ID is already computed,
|
99
|
+
# return the cached object ID.
|
100
|
+
if (
|
101
|
+
not _is_recompute_enabled()
|
102
|
+
and (obj_id := self.__dict__.get("_object_id"))
|
103
|
+
in _get_computed_object_ids()
|
104
|
+
):
|
105
|
+
return cast(str, obj_id)
|
106
|
+
|
68
107
|
if self.is_dirty or "_object_id" not in self.__dict__:
|
69
|
-
|
108
|
+
obj_id = get_object_id(self.deflate())
|
109
|
+
self.__dict__["_object_id"] = obj_id
|
110
|
+
|
111
|
+
# If recomputing object ID is disabled, add the object ID to the set of
|
112
|
+
# computed object IDs to avoid recomputing it within the context.
|
113
|
+
if not _is_recompute_enabled():
|
114
|
+
_get_computed_object_ids().add(obj_id)
|
70
115
|
return cast(str, self.__dict__["_object_id"])
|
71
116
|
|
72
117
|
@property
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""InflatableObject utils."""
|
15
|
+
"""InflatableObject gRPC utils."""
|
16
16
|
|
17
17
|
|
18
18
|
from typing import Callable
|
@@ -0,0 +1,99 @@
|
|
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
|
+
"""InflatableObject REST utils."""
|
16
|
+
|
17
|
+
|
18
|
+
from typing import Callable
|
19
|
+
|
20
|
+
from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
21
|
+
PullObjectRequest,
|
22
|
+
PullObjectResponse,
|
23
|
+
PushObjectRequest,
|
24
|
+
PushObjectResponse,
|
25
|
+
)
|
26
|
+
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
27
|
+
|
28
|
+
from .inflatable_utils import ObjectIdNotPreregisteredError, ObjectUnavailableError
|
29
|
+
|
30
|
+
|
31
|
+
def make_pull_object_fn_rest(
|
32
|
+
pull_object_rest: Callable[[PullObjectRequest], PullObjectResponse],
|
33
|
+
node: Node,
|
34
|
+
run_id: int,
|
35
|
+
) -> Callable[[str], bytes]:
|
36
|
+
"""Create a pull object function that uses REST to pull objects.
|
37
|
+
|
38
|
+
Parameters
|
39
|
+
----------
|
40
|
+
pull_object_rest : Callable[[PullObjectRequest], PullObjectResponse]
|
41
|
+
A function that makes a POST request against the `/push-object` REST endpoint
|
42
|
+
node : Node
|
43
|
+
The node making the request.
|
44
|
+
run_id : int
|
45
|
+
The run ID for the current operation.
|
46
|
+
|
47
|
+
Returns
|
48
|
+
-------
|
49
|
+
Callable[[str], bytes]
|
50
|
+
A function that takes an object ID and returns the object content as bytes.
|
51
|
+
The function raises `ObjectIdNotPreregisteredError` if the object ID is not
|
52
|
+
pre-registered, or `ObjectUnavailableError` if the object is not yet available.
|
53
|
+
"""
|
54
|
+
|
55
|
+
def pull_object_fn(object_id: str) -> bytes:
|
56
|
+
request = PullObjectRequest(node=node, run_id=run_id, object_id=object_id)
|
57
|
+
response: PullObjectResponse = pull_object_rest(request)
|
58
|
+
if not response.object_found:
|
59
|
+
raise ObjectIdNotPreregisteredError(object_id)
|
60
|
+
if not response.object_available:
|
61
|
+
raise ObjectUnavailableError(object_id)
|
62
|
+
return response.object_content
|
63
|
+
|
64
|
+
return pull_object_fn
|
65
|
+
|
66
|
+
|
67
|
+
def make_push_object_fn_rest(
|
68
|
+
push_object_rest: Callable[[PushObjectRequest], PushObjectResponse],
|
69
|
+
node: Node,
|
70
|
+
run_id: int,
|
71
|
+
) -> Callable[[str, bytes], None]:
|
72
|
+
"""Create a push object function that uses REST to push objects.
|
73
|
+
|
74
|
+
Parameters
|
75
|
+
----------
|
76
|
+
push_object_rest : Callable[[PushObjectRequest], PushObjectResponse]
|
77
|
+
A function that makes a POST request against the `/pull-object` REST endpoint
|
78
|
+
node : Node
|
79
|
+
The node making the request.
|
80
|
+
run_id : int
|
81
|
+
The run ID for the current operation.
|
82
|
+
|
83
|
+
Returns
|
84
|
+
-------
|
85
|
+
Callable[[str, bytes], None]
|
86
|
+
A function that takes an object ID and its content as bytes, and pushes it
|
87
|
+
to the servicer. The function raises `ObjectIdNotPreregisteredError` if
|
88
|
+
the object ID is not pre-registered.
|
89
|
+
"""
|
90
|
+
|
91
|
+
def push_object_fn(object_id: str, object_content: bytes) -> None:
|
92
|
+
request = PushObjectRequest(
|
93
|
+
node=node, run_id=run_id, object_id=object_id, object_content=object_content
|
94
|
+
)
|
95
|
+
response: PushObjectResponse = push_object_rest(request)
|
96
|
+
if not response.stored:
|
97
|
+
raise ObjectIdNotPreregisteredError(object_id)
|
98
|
+
|
99
|
+
return push_object_fn
|
flwr/common/typing.py
CHANGED
@@ -289,11 +289,11 @@ class UserAuthCredentials:
|
|
289
289
|
|
290
290
|
|
291
291
|
@dataclass
|
292
|
-
class
|
292
|
+
class AccountInfo:
|
293
293
|
"""User information for event log."""
|
294
294
|
|
295
|
-
|
296
|
-
|
295
|
+
flwr_aid: Optional[str]
|
296
|
+
account_name: Optional[str]
|
297
297
|
|
298
298
|
|
299
299
|
@dataclass
|
@@ -59,7 +59,7 @@ class FleetEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
59
59
|
log_entry = self.log_plugin.compose_log_before_event(
|
60
60
|
request=request,
|
61
61
|
context=context,
|
62
|
-
|
62
|
+
account_info=None,
|
63
63
|
method_name=method_name,
|
64
64
|
)
|
65
65
|
self.log_plugin.write_log(log_entry)
|
@@ -75,7 +75,7 @@ class FleetEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
75
75
|
log_entry = self.log_plugin.compose_log_after_event(
|
76
76
|
request=request,
|
77
77
|
context=context,
|
78
|
-
|
78
|
+
account_info=None,
|
79
79
|
method_name=method_name,
|
80
80
|
response=unary_response or error,
|
81
81
|
)
|
@@ -38,6 +38,12 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
|
|
38
38
|
SendNodeHeartbeatRequest,
|
39
39
|
SendNodeHeartbeatResponse,
|
40
40
|
)
|
41
|
+
from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
42
|
+
PullObjectRequest,
|
43
|
+
PullObjectResponse,
|
44
|
+
PushObjectRequest,
|
45
|
+
PushObjectResponse,
|
46
|
+
)
|
41
47
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
42
48
|
from flwr.server.superlink.ffs.ffs import Ffs
|
43
49
|
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
@@ -131,6 +137,28 @@ async def push_message(request: PushMessagesRequest) -> PushMessagesResponse:
|
|
131
137
|
return message_handler.push_messages(request=request, state=state, store=store)
|
132
138
|
|
133
139
|
|
140
|
+
@rest_request_response(PullObjectRequest)
|
141
|
+
async def pull_object(request: PullObjectRequest) -> PullObjectResponse:
|
142
|
+
"""Pull PullObject."""
|
143
|
+
# Get state from app
|
144
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
145
|
+
store: ObjectStore = cast(ObjectStoreFactory, app.state.OBJECTSTORE_FACTORY).store()
|
146
|
+
|
147
|
+
# Handle message
|
148
|
+
return message_handler.pull_object(request=request, state=state, store=store)
|
149
|
+
|
150
|
+
|
151
|
+
@rest_request_response(PushObjectRequest)
|
152
|
+
async def push_object(request: PushObjectRequest) -> PushObjectResponse:
|
153
|
+
"""Pull PushObject."""
|
154
|
+
# Get state from app
|
155
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
156
|
+
store: ObjectStore = cast(ObjectStoreFactory, app.state.OBJECTSTORE_FACTORY).store()
|
157
|
+
|
158
|
+
# Handle message
|
159
|
+
return message_handler.push_object(request=request, state=state, store=store)
|
160
|
+
|
161
|
+
|
134
162
|
@rest_request_response(SendNodeHeartbeatRequest)
|
135
163
|
async def send_node_heartbeat(
|
136
164
|
request: SendNodeHeartbeatRequest,
|
@@ -171,6 +199,8 @@ routes = [
|
|
171
199
|
Route("/api/v0/fleet/delete-node", delete_node, methods=["POST"]),
|
172
200
|
Route("/api/v0/fleet/pull-messages", pull_message, methods=["POST"]),
|
173
201
|
Route("/api/v0/fleet/push-messages", push_message, methods=["POST"]),
|
202
|
+
Route("/api/v0/fleet/pull-object", pull_object, methods=["POST"]),
|
203
|
+
Route("/api/v0/fleet/push-object", push_object, methods=["POST"]),
|
174
204
|
Route("/api/v0/fleet/send-node-heartbeat", send_node_heartbeat, methods=["POST"]),
|
175
205
|
Route("/api/v0/fleet/get-run", get_run, methods=["POST"]),
|
176
206
|
Route("/api/v0/fleet/get-fab", get_fab, methods=["POST"]),
|
@@ -24,7 +24,7 @@ from google.protobuf.message import Message as GrpcMessage
|
|
24
24
|
from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
|
25
25
|
from flwr.common.typing import LogEntry
|
26
26
|
|
27
|
-
from .exec_user_auth_interceptor import
|
27
|
+
from .exec_user_auth_interceptor import shared_account_info
|
28
28
|
|
29
29
|
|
30
30
|
class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
@@ -62,7 +62,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
62
62
|
log_entry = self.log_plugin.compose_log_before_event(
|
63
63
|
request=request,
|
64
64
|
context=context,
|
65
|
-
|
65
|
+
account_info=shared_account_info.get(),
|
66
66
|
method_name=method_name,
|
67
67
|
)
|
68
68
|
self.log_plugin.write_log(log_entry)
|
@@ -81,7 +81,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
81
81
|
log_entry = self.log_plugin.compose_log_after_event(
|
82
82
|
request=request,
|
83
83
|
context=context,
|
84
|
-
|
84
|
+
account_info=shared_account_info.get(),
|
85
85
|
method_name=method_name,
|
86
86
|
response=unary_response or error,
|
87
87
|
)
|
@@ -111,7 +111,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
111
111
|
log_entry = self.log_plugin.compose_log_after_event(
|
112
112
|
request=request,
|
113
113
|
context=context,
|
114
|
-
|
114
|
+
account_info=shared_account_info.get(),
|
115
115
|
method_name=method_name,
|
116
116
|
response=stream_response or error,
|
117
117
|
)
|
@@ -21,7 +21,7 @@ from typing import Any, Callable, Union
|
|
21
21
|
import grpc
|
22
22
|
|
23
23
|
from flwr.common.auth_plugin import ExecAuthPlugin, ExecAuthzPlugin
|
24
|
-
from flwr.common.typing import
|
24
|
+
from flwr.common.typing import AccountInfo
|
25
25
|
from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
26
26
|
GetAuthTokensRequest,
|
27
27
|
GetAuthTokensResponse,
|
@@ -45,8 +45,8 @@ Response = Union[
|
|
45
45
|
]
|
46
46
|
|
47
47
|
|
48
|
-
|
49
|
-
"
|
48
|
+
shared_account_info: contextvars.ContextVar[AccountInfo] = contextvars.ContextVar(
|
49
|
+
"account_info", default=AccountInfo(flwr_aid=None, account_name=None)
|
50
50
|
)
|
51
51
|
|
52
52
|
|
@@ -93,20 +93,20 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
93
93
|
return call(request, context) # type: ignore
|
94
94
|
|
95
95
|
# For other requests, check if the user is authenticated
|
96
|
-
valid_tokens,
|
96
|
+
valid_tokens, account_info = self.auth_plugin.validate_tokens_in_metadata(
|
97
97
|
metadata
|
98
98
|
)
|
99
99
|
if valid_tokens:
|
100
|
-
if
|
100
|
+
if account_info is None:
|
101
101
|
context.abort(
|
102
102
|
grpc.StatusCode.UNAUTHENTICATED,
|
103
103
|
"Tokens validated, but user info not found",
|
104
104
|
)
|
105
105
|
raise grpc.RpcError()
|
106
106
|
# Store user info in contextvars for authenticated users
|
107
|
-
|
107
|
+
shared_account_info.set(account_info)
|
108
108
|
# Check if the user is authorized
|
109
|
-
if not self.authz_plugin.verify_user_authorization(
|
109
|
+
if not self.authz_plugin.verify_user_authorization(account_info):
|
110
110
|
context.abort(
|
111
111
|
grpc.StatusCode.PERMISSION_DENIED, "User not authorized"
|
112
112
|
)
|
@@ -114,18 +114,18 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
114
114
|
return call(request, context) # type: ignore
|
115
115
|
|
116
116
|
# If the user is not authenticated, refresh tokens
|
117
|
-
tokens,
|
117
|
+
tokens, account_info = self.auth_plugin.refresh_tokens(metadata)
|
118
118
|
if tokens is not None:
|
119
|
-
if
|
119
|
+
if account_info is None:
|
120
120
|
context.abort(
|
121
121
|
grpc.StatusCode.UNAUTHENTICATED,
|
122
122
|
"Tokens refreshed, but user info not found",
|
123
123
|
)
|
124
124
|
raise grpc.RpcError()
|
125
125
|
# Store user info in contextvars for authenticated users
|
126
|
-
|
126
|
+
shared_account_info.set(account_info)
|
127
127
|
# Check if the user is authorized
|
128
|
-
if not self.authz_plugin.verify_user_authorization(
|
128
|
+
if not self.authz_plugin.verify_user_authorization(account_info):
|
129
129
|
context.abort(
|
130
130
|
grpc.StatusCode.PERMISSION_DENIED, "User not authorized"
|
131
131
|
)
|
{flwr_nightly-1.19.0.dev20250610.dist-info → flwr_nightly-1.19.0.dev20250611.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.19.0.
|
3
|
+
Version: 1.19.0.dev20250611
|
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
|
{flwr_nightly-1.19.0.dev20250610.dist-info → flwr_nightly-1.19.0.dev20250611.dist-info}/RECORD
RENAMED
@@ -84,7 +84,7 @@ flwr/client/grpc_adapter_client/__init__.py,sha256=RQWP5mFPROLHKgombiRvPXVWSoVrQ
|
|
84
84
|
flwr/client/grpc_adapter_client/connection.py,sha256=aj5tTYyE8z2hQLXPPydsJiz8gBDIWLUhfWvqYkAL1L4,3966
|
85
85
|
flwr/client/grpc_rere_client/__init__.py,sha256=i7iS0Lt8B7q0E2L72e4F_YrKm6ClRKnd71PNA6PW2O0,752
|
86
86
|
flwr/client/grpc_rere_client/client_interceptor.py,sha256=zFaVHw6AxeNO-7eCKKb-RxrPa7zbM5Z-2-1Efc4adQY,2451
|
87
|
-
flwr/client/grpc_rere_client/connection.py,sha256=
|
87
|
+
flwr/client/grpc_rere_client/connection.py,sha256=eJJpfD0jE0X63W5ASUEcC5Gvn2xPeM5TBryMbNJD4Wc,14040
|
88
88
|
flwr/client/grpc_rere_client/grpc_adapter.py,sha256=JvMZ7vCFTaTEo6AzKYh3zDmeQAU7VSjdysbC6t3ufWg,6351
|
89
89
|
flwr/client/message_handler/__init__.py,sha256=0lyljDVqre3WljiZbPcwCCf8GiIaSVI_yo_ylEyPwSE,719
|
90
90
|
flwr/client/message_handler/message_handler.py,sha256=X9SXX6et97Lw9_DGD93HKsEBGNjXClcFgc_5aLK0oiU,6541
|
@@ -98,7 +98,7 @@ flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=aKqjZCrikF73y3E-7h40
|
|
98
98
|
flwr/client/mod/utils.py,sha256=FUgD2TfcWqSeF6jUKZ4i6Ke56U4Nrv85AeVb93s6R9g,1201
|
99
99
|
flwr/client/numpy_client.py,sha256=Qq6ghsIAop2slKqAfgiI5NiHJ4LIxGmrik3Ror4_XVc,9581
|
100
100
|
flwr/client/rest_client/__init__.py,sha256=MBiuK62hj439m9rtwSwI184Hth6Tt5GbmpNMyl3zkZY,735
|
101
|
-
flwr/client/rest_client/connection.py,sha256=
|
101
|
+
flwr/client/rest_client/connection.py,sha256=vTunkzFe9eouw8bSEww3zigl-QmTu9-hyeKCNKPS6PY,16388
|
102
102
|
flwr/client/run_info_store.py,sha256=MaJ3UQ-07hWtK67wnWu0zR29jrk0fsfgJX506dvEOfE,4042
|
103
103
|
flwr/client/typing.py,sha256=Jw3rawDzI_-ZDcRmEQcs5gZModY7oeQlEeltYsdOhlU,1048
|
104
104
|
flwr/clientapp/__init__.py,sha256=zGW4z49Ojzoi1hDiRC7kyhLjijUilc6fqHhtM_ATRVA,719
|
@@ -106,7 +106,7 @@ flwr/common/__init__.py,sha256=5GCLVk399Az_rTJHNticRlL0Sl_oPw_j5_LuFKfX7-M,4171
|
|
106
106
|
flwr/common/address.py,sha256=9JucdTwlc-jpeJkRKeUboZoacUtErwSVtnDR9kAtLqE,4119
|
107
107
|
flwr/common/args.py,sha256=-aX_jVnSaDrJR2KZ8Wq0Y3dQHII4R4MJtJOIXzVUA0c,5417
|
108
108
|
flwr/common/auth_plugin/__init__.py,sha256=3rzPkVLn9WyB5n7HLk1XGDw3SLCqRWAU1_CnglcWPfw,970
|
109
|
-
flwr/common/auth_plugin/auth_plugin.py,sha256=
|
109
|
+
flwr/common/auth_plugin/auth_plugin.py,sha256=kXx5o39vJchaPv28sK9qO6H_UXSWym6zRBbCa7sUwtQ,4825
|
110
110
|
flwr/common/config.py,sha256=glcZDjco-amw1YfQcYTFJ4S1pt9APoexT-mf1QscuHs,13960
|
111
111
|
flwr/common/constant.py,sha256=A8rWHZZUH4Rxbx-bCKW5pNJ_BG4W9Xfw4fLevNhZ8l0,8077
|
112
112
|
flwr/common/context.py,sha256=Be8obQR_OvEDy1OmshuUKxGRQ7Qx89mf5F4xlhkR10s,2407
|
@@ -115,15 +115,16 @@ flwr/common/differential_privacy.py,sha256=FdlpdpPl_H_2HJa8CQM1iCUGBBQ5Dc8CzxmHE
|
|
115
115
|
flwr/common/differential_privacy_constants.py,sha256=ruEjH4qF_S2bgxRI6brWCGWQPxFk-P7pviFguy9KvQ0,1074
|
116
116
|
flwr/common/dp.py,sha256=ftqWheOICK5N_zPaofnbFb474lMb5w9lclwxf5DKY0w,1978
|
117
117
|
flwr/common/event_log_plugin/__init__.py,sha256=ts3VAL3Fk6Grp1EK_1Qg_V-BfOof9F86iBx4rbrEkyo,838
|
118
|
-
flwr/common/event_log_plugin/event_log_plugin.py,sha256=
|
118
|
+
flwr/common/event_log_plugin/event_log_plugin.py,sha256=eK8OaDFagQRwqpb9eV0cJcm2ErtEBpMxFbhxJNx6n5w,2061
|
119
119
|
flwr/common/exit/__init__.py,sha256=-ZOJYLaNnR729a7VzZiFsLiqngzKQh3xc27svYStZ_Q,826
|
120
120
|
flwr/common/exit/exit.py,sha256=mJgbqMlVlwAgYtq-Vedj53wO4VxcDcy_P-GzqGK-1GQ,3452
|
121
121
|
flwr/common/exit/exit_code.py,sha256=PNEnCrZfOILjfDAFu5m-2YWEJBrk97xglq4zCUlqV7E,3470
|
122
122
|
flwr/common/exit_handlers.py,sha256=IaqJ60fXZuu7McaRYnoYKtlbH9t4Yl9goNExKqtmQbs,4304
|
123
123
|
flwr/common/grpc.py,sha256=manTaHaPiyYngUq1ErZvvV2B2GxlXUUUGRy3jc3TBIQ,9798
|
124
124
|
flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
|
125
|
-
flwr/common/inflatable.py,sha256=
|
126
|
-
flwr/common/inflatable_grpc_utils.py,sha256
|
125
|
+
flwr/common/inflatable.py,sha256=RmGq0FF_zH0OyTSVANhQw9xUmS_4f_c5CZTZkPeMnH4,8726
|
126
|
+
flwr/common/inflatable_grpc_utils.py,sha256=ZpwtgF1tGD6NwQkCidbhbeBPDBZ1Nx9eGMHQ04eNEE8,3554
|
127
|
+
flwr/common/inflatable_rest_utils.py,sha256=KiZd06XRiXcl_WewOrag0JTvUQt5kZ74UIsQ3FCAXGc,3580
|
127
128
|
flwr/common/inflatable_utils.py,sha256=-GTdgR1zLS9WtXrbOGJMpaoyVEL8KmoQ2yF4HeLxTI0,12406
|
128
129
|
flwr/common/logger.py,sha256=JbRf6E2vQxXzpDBq1T8IDUJo_usu3gjWEBPQ6uKcmdg,13049
|
129
130
|
flwr/common/message.py,sha256=xAL7iZN5-n-xPQpgoSFvxNrzs8fmiiPfoU0DjNQEhRw,19953
|
@@ -151,7 +152,7 @@ flwr/common/secure_aggregation/secaggplus_utils.py,sha256=E_xU-Zd45daO1em7M6C2wO
|
|
151
152
|
flwr/common/serde.py,sha256=hHqXbAF-MtSRWsROz4v-P_C4dMDSIt1XJ3Hecxp8os0,23020
|
152
153
|
flwr/common/serde_utils.py,sha256=krx2C_W31KpfmDqnDCtULoTkT8WKweWTJ7FHYWtF1r4,5815
|
153
154
|
flwr/common/telemetry.py,sha256=jF47v0SbnBd43XamHtl3wKxs3knFUY2p77cm_2lzZ8M,8762
|
154
|
-
flwr/common/typing.py,sha256=
|
155
|
+
flwr/common/typing.py,sha256=Mi95fKobBHfAoqMh-xQB1QgXTxuWtpJ-z1lR7Bxp63o,6893
|
155
156
|
flwr/common/version.py,sha256=7GAGzPn73Mkh09qhrjbmjZQtcqVhBuzhFBaK4Mk4VRk,1325
|
156
157
|
flwr/compat/__init__.py,sha256=gbfDQKKKMZzi3GswyVRgyLdDlHiWj3wU6dg7y6m5O_s,752
|
157
158
|
flwr/compat/client/__init__.py,sha256=qpbo0lcxdNL4qy5KHqiGm8OLxSxkYgI_-dLh5rwhtcI,746
|
@@ -234,7 +235,7 @@ flwr/server/compat/app_utils.py,sha256=Uz6m-NV90cQ6k-gW6wlWQuhoFoK78qlMr0CEmALKX
|
|
234
235
|
flwr/server/compat/grid_client_proxy.py,sha256=FxMgGtrzBoBAPby2ZjTWzpCJsCht8FwMm4PzqW80dmQ,4956
|
235
236
|
flwr/server/compat/legacy_context.py,sha256=94JsRQxyOdLI6lZZkFjS3-TMd0YJGAgSBBO0M_s_9ts,1804
|
236
237
|
flwr/server/criterion.py,sha256=G4e-6B48Pc7d5rmGVUpIzNKb6UF88O3VmTRuUltgjzM,1061
|
237
|
-
flwr/server/fleet_event_log_interceptor.py,sha256=
|
238
|
+
flwr/server/fleet_event_log_interceptor.py,sha256=ifV4gUB_hSg7QPLIrAyDpjciqZBOKb0L0abZno3GTwA,3780
|
238
239
|
flwr/server/grid/__init__.py,sha256=aWZHezoR2UGMJISB_gPMCm2N_2GSbm97A3lAp7ruhRQ,888
|
239
240
|
flwr/server/grid/grid.py,sha256=naGCYt5J6dnmUvrcGkdNyKPe3MBd-0awGm1ALmgahqY,6625
|
240
241
|
flwr/server/grid/grpc_grid.py,sha256=qhJPS4tCWATHx24dDZoxtEPq6rjFu0lNggX70lstLkw,13302
|
@@ -290,7 +291,7 @@ flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=DrHubsaLgJCwC
|
|
290
291
|
flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
|
291
292
|
flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=hbaukJ7EpfBiaXHa_R50MZKTkesxosx5IwFtDyIAO-0,8076
|
292
293
|
flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=Lzc93nA7tDqoy-zRUaPG316oqFiZX1HUCL5ELaXY_xw,735
|
293
|
-
flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=
|
294
|
+
flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=cmNBbqb1aHgGgu_8Cuqvf7FAf2i7xs1evTuJRgRaSnI,8381
|
294
295
|
flwr/server/superlink/fleet/vce/__init__.py,sha256=XOKbAWOzlCqEOQ3M2cBYkH7HKA7PxlbCJMunt-ty-DY,784
|
295
296
|
flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=PPH89Yqd1XKm-sRJN6R0WQlKT_b4v54Kzl2yzHAFzM8,1437
|
296
297
|
flwr/server/superlink/fleet/vce/backend/backend.py,sha256=-wDHjgAy5mrfEgXj0GxkJI7lhEbgSUyPwmNAf9ZcDzc,2193
|
@@ -337,10 +338,10 @@ flwr/supercore/object_store/object_store_factory.py,sha256=QVwE2ywi7vsj2iKfvWWnN
|
|
337
338
|
flwr/superexec/__init__.py,sha256=YFqER0IJc1XEWfsX6AxZ9LSRq0sawPYrNYki-brvTIc,715
|
338
339
|
flwr/superexec/app.py,sha256=U2jjOHb2LGWoU7vrl9_czTzre9O2mPxu3CPGUZ86sK4,1465
|
339
340
|
flwr/superexec/deployment.py,sha256=2wBBZgdNAn1Ik1M3HGg4t23CV8oZqzDz1zkOBzHjZLE,6734
|
340
|
-
flwr/superexec/exec_event_log_interceptor.py,sha256=
|
341
|
+
flwr/superexec/exec_event_log_interceptor.py,sha256=7aBjZ4lkpOIyWut0s394OpMePr16g_Te594s-9aDM9Q,5774
|
341
342
|
flwr/superexec/exec_grpc.py,sha256=LS-CrwBayHQAvJz-zmzV5JsaEC49VumsS25nC0NgYXg,3364
|
342
343
|
flwr/superexec/exec_servicer.py,sha256=nSqAzrWDQFQm9xE6oejoFZqgWhPchbdkC2mCMrWTbhE,8324
|
343
|
-
flwr/superexec/exec_user_auth_interceptor.py,sha256=
|
344
|
+
flwr/superexec/exec_user_auth_interceptor.py,sha256=HpGHTcDKzB7XUiQHXgntNVFYL-VfP9Wj5tEVc04VOOw,5820
|
344
345
|
flwr/superexec/executor.py,sha256=M5ucqSE53jfRtuCNf59WFLqQvA1Mln4741TySeZE7qQ,3112
|
345
346
|
flwr/superexec/simulation.py,sha256=j6YwUvBN7EQ09ID7MYOCVZ70PGbuyBy8f9bXU0EszEM,4088
|
346
347
|
flwr/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
|
@@ -358,7 +359,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
|
|
358
359
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=vJyOjO2FXZ2URbnthmdsgs6948wbYfdq1L1V8Um-Lr8,895
|
359
360
|
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=LmzkxtNQBn5vVrHc0Bhq2WqaK6-LM2v4kfLBN0PiNNM,8522
|
360
361
|
flwr/supernode/start_client_internal.py,sha256=AkJ1FsBK6EpK7cmIGcae5WZazPhU71gileiSQogTZ-k,18164
|
361
|
-
flwr_nightly-1.19.0.
|
362
|
-
flwr_nightly-1.19.0.
|
363
|
-
flwr_nightly-1.19.0.
|
364
|
-
flwr_nightly-1.19.0.
|
362
|
+
flwr_nightly-1.19.0.dev20250611.dist-info/METADATA,sha256=QYsNnMDXwVNpA9FWBxANzI_NTfAHv-AlvxHMxmeeiTE,15910
|
363
|
+
flwr_nightly-1.19.0.dev20250611.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
364
|
+
flwr_nightly-1.19.0.dev20250611.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
|
365
|
+
flwr_nightly-1.19.0.dev20250611.dist-info/RECORD,,
|
{flwr_nightly-1.19.0.dev20250610.dist-info → flwr_nightly-1.19.0.dev20250611.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|