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.
@@ -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 message_from_proto, message_to_proto, run_from_proto
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
- # Return the Message if available
300
- nonlocal metadata
301
- message = None
302
- if message_proto is not None:
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
- return message
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
- # Serialize ProtoBuf to bytes
328
- message_proto = message_to_proto(message=message)
329
-
330
- # Serialize ProtoBuf to bytes
331
- req = PushMessagesRequest(node=node, messages_list=[message_proto])
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 is None:
336
- return
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
- log(
339
- INFO,
340
- "[Node] POST /%s: success, created result %s",
341
- PATH_PUSH_MESSAGES,
342
- res.results, # pylint: disable=no-member
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 UserInfo
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[UserInfo]]:
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[Optional[Sequence[tuple[str, Union[str, bytes]]]], Optional[UserInfo]]:
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, user_info: UserInfo) -> bool:
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 LogEntry, UserInfo
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
- user_info: Optional[UserInfo],
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
- user_info: Optional[UserInfo],
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
- self.__dict__["_object_id"] = get_object_id(self.deflate())
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 UserInfo:
292
+ class AccountInfo:
293
293
  """User information for event log."""
294
294
 
295
- user_id: Optional[str]
296
- user_name: Optional[str]
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
- user_info=None,
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
- user_info=None,
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 shared_user_info
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
- user_info=shared_user_info.get(),
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
- user_info=shared_user_info.get(),
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
- user_info=shared_user_info.get(),
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 UserInfo
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
- shared_user_info: contextvars.ContextVar[UserInfo] = contextvars.ContextVar(
49
- "user_info", default=UserInfo(user_id=None, user_name=None)
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, user_info = self.auth_plugin.validate_tokens_in_metadata(
96
+ valid_tokens, account_info = self.auth_plugin.validate_tokens_in_metadata(
97
97
  metadata
98
98
  )
99
99
  if valid_tokens:
100
- if user_info is None:
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
- shared_user_info.set(user_info)
107
+ shared_account_info.set(account_info)
108
108
  # Check if the user is authorized
109
- if not self.authz_plugin.verify_user_authorization(user_info):
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, user_info = self.auth_plugin.refresh_tokens(metadata)
117
+ tokens, account_info = self.auth_plugin.refresh_tokens(metadata)
118
118
  if tokens is not None:
119
- if user_info is None:
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
- shared_user_info.set(user_info)
126
+ shared_account_info.set(account_info)
127
127
  # Check if the user is authorized
128
- if not self.authz_plugin.verify_user_authorization(user_info):
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
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.19.0.dev20250610
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
@@ -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=zxnr1Ppp3PpffvqhRlTBwTWHnek15lIDEBLfgqCSW40,14039
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=6yBh2Eeso0XLtinAs2kOHkSnge7C-co_a_QfBaAEudU,12766
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=yEIapwim14tn99oGhkQKun_WifoY8hscAtU8Z6fcGXE,4796
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=up7a9H0UxmxdDKlEMNLBhUn5APVJ0fFS2qH0IO3j_rg,2046
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=gdAICtXklkQRMrxoTYEbzJl7AeFqZtUm4JU6f2it9FM,7264
126
- flwr/common/inflatable_grpc_utils.py,sha256=-cdu-VNBK7aNdYRtjtTz3pnXmK-_q3XIzpr14vhanto,3549
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=97QRfRRS7sQnjkAI5FDZ01-38oQUSz4i1qqewQmBWRg,6886
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=AkL7Y5d3xm2vRhL3ahmEVVoOvAP7PA7dRgB-je4v-Ys,3774
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=jIljUNMvZ8dDvSlkyn1c2y9sDAf_QoBr-q_o3BWxJ7o,7199
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=IlQ_w-GpGNMJ4g1gTI3H4LHfWli1-8O7a2-pCqB3yeA,5753
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=L-oYmcf35HJFnqNVyfAPwhbkyaTEjkNM_ooioOVATCQ,5771
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.dev20250610.dist-info/METADATA,sha256=98gf7-LsujpWAZw4bsDkaitvVM4M0NT44a2lIGFtjCE,15910
362
- flwr_nightly-1.19.0.dev20250610.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
363
- flwr_nightly-1.19.0.dev20250610.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
364
- flwr_nightly-1.19.0.dev20250610.dist-info/RECORD,,
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,,