flwr-nightly 1.17.0.dev20250311__py3-none-any.whl → 1.17.0.dev20250312__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +1 -1
- flwr/superexec/exec_event_log_interceptor.py +135 -0
- flwr/superexec/exec_user_auth_interceptor.py +18 -2
- {flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/METADATA +1 -1
- {flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/RECORD +8 -7
- {flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/entry_points.txt +0 -0
@@ -35,7 +35,7 @@ warnings.filterwarnings("ignore", category=UserWarning)
|
|
35
35
|
# pylint: disable=too-many-arguments
|
36
36
|
# pylint: disable=too-many-instance-attributes
|
37
37
|
class FlowerClient(NumPyClient):
|
38
|
-
"""
|
38
|
+
"""Flower client for LLM fine-tuning."""
|
39
39
|
|
40
40
|
def __init__(
|
41
41
|
self,
|
@@ -0,0 +1,135 @@
|
|
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
|
+
"""Flower Exec API event log interceptor."""
|
16
|
+
|
17
|
+
|
18
|
+
from collections.abc import Iterator
|
19
|
+
from typing import Any, Callable, Union, cast
|
20
|
+
|
21
|
+
import grpc
|
22
|
+
from google.protobuf.message import Message as GrpcMessage
|
23
|
+
|
24
|
+
from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
|
25
|
+
from flwr.common.typing import LogEntry
|
26
|
+
|
27
|
+
from .exec_user_auth_interceptor import shared_user_info
|
28
|
+
|
29
|
+
|
30
|
+
class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
31
|
+
"""Exec API interceptor for logging events."""
|
32
|
+
|
33
|
+
def __init__(self, log_plugin: EventLogWriterPlugin) -> None:
|
34
|
+
self.log_plugin = log_plugin
|
35
|
+
|
36
|
+
def intercept_service(
|
37
|
+
self,
|
38
|
+
continuation: Callable[[Any], Any],
|
39
|
+
handler_call_details: grpc.HandlerCallDetails,
|
40
|
+
) -> grpc.RpcMethodHandler:
|
41
|
+
"""Flower server interceptor logging logic.
|
42
|
+
|
43
|
+
Intercept all unary-unary/unary-stream calls from users and log the event.
|
44
|
+
Continue RPC call if event logger is enabled on the SuperLink, else, terminate
|
45
|
+
RPC call by setting context to abort.
|
46
|
+
"""
|
47
|
+
# One of the method handlers in
|
48
|
+
# `flwr.superexec.exec_servicer.ExecServicer`
|
49
|
+
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
50
|
+
method_name: str = handler_call_details.method
|
51
|
+
return self._generic_event_log_unary_method_handler(method_handler, method_name)
|
52
|
+
|
53
|
+
def _generic_event_log_unary_method_handler(
|
54
|
+
self, method_handler: grpc.RpcMethodHandler, method_name: str
|
55
|
+
) -> grpc.RpcMethodHandler:
|
56
|
+
def _generic_method_handler(
|
57
|
+
request: GrpcMessage,
|
58
|
+
context: grpc.ServicerContext,
|
59
|
+
) -> Union[GrpcMessage, Iterator[GrpcMessage], Exception]:
|
60
|
+
log_entry: LogEntry
|
61
|
+
# Log before call
|
62
|
+
log_entry = self.log_plugin.compose_log_before_event(
|
63
|
+
request=request,
|
64
|
+
context=context,
|
65
|
+
user_info=shared_user_info.get(),
|
66
|
+
method_name=method_name,
|
67
|
+
)
|
68
|
+
self.log_plugin.write_log(log_entry)
|
69
|
+
|
70
|
+
# For unary-unary calls, log after the call immediately
|
71
|
+
if method_handler.unary_unary:
|
72
|
+
unary_response, error = None, None
|
73
|
+
try:
|
74
|
+
unary_response = cast(
|
75
|
+
GrpcMessage, method_handler.unary_unary(request, context)
|
76
|
+
)
|
77
|
+
except Exception as e: # pylint: disable=broad-except
|
78
|
+
error = e
|
79
|
+
raise
|
80
|
+
finally:
|
81
|
+
log_entry = self.log_plugin.compose_log_after_event(
|
82
|
+
request=request,
|
83
|
+
context=context,
|
84
|
+
user_info=shared_user_info.get(),
|
85
|
+
method_name=method_name,
|
86
|
+
response=unary_response or error,
|
87
|
+
)
|
88
|
+
self.log_plugin.write_log(log_entry)
|
89
|
+
return unary_response
|
90
|
+
|
91
|
+
# For unary-stream calls, wrap the response iterator and write the event log
|
92
|
+
# after iteration completes
|
93
|
+
if method_handler.unary_stream:
|
94
|
+
response_iterator = cast(
|
95
|
+
Iterator[GrpcMessage],
|
96
|
+
method_handler.unary_stream(request, context),
|
97
|
+
)
|
98
|
+
|
99
|
+
def response_wrapper() -> Iterator[GrpcMessage]:
|
100
|
+
stream_response, error = None, None
|
101
|
+
try:
|
102
|
+
# pylint: disable=use-yield-from
|
103
|
+
for stream_response in response_iterator:
|
104
|
+
yield stream_response
|
105
|
+
except Exception as e: # pylint: disable=broad-except
|
106
|
+
error = e
|
107
|
+
raise
|
108
|
+
finally:
|
109
|
+
# This block is executed after the client has consumed
|
110
|
+
# the entire stream, or if iteration is interrupted
|
111
|
+
log_entry = self.log_plugin.compose_log_after_event(
|
112
|
+
request=request,
|
113
|
+
context=context,
|
114
|
+
user_info=shared_user_info.get(),
|
115
|
+
method_name=method_name,
|
116
|
+
response=stream_response or error,
|
117
|
+
)
|
118
|
+
self.log_plugin.write_log(log_entry)
|
119
|
+
|
120
|
+
return response_wrapper()
|
121
|
+
|
122
|
+
raise RuntimeError() # This line is unreachable
|
123
|
+
|
124
|
+
if method_handler.unary_unary:
|
125
|
+
message_handler = grpc.unary_unary_rpc_method_handler
|
126
|
+
elif method_handler.unary_stream:
|
127
|
+
message_handler = grpc.unary_stream_rpc_method_handler
|
128
|
+
else:
|
129
|
+
# If the method type is not `unary_unary` or `unary_stream`, raise an error
|
130
|
+
raise NotImplementedError("This RPC method type is not supported.")
|
131
|
+
return message_handler(
|
132
|
+
_generic_method_handler,
|
133
|
+
request_deserializer=method_handler.request_deserializer,
|
134
|
+
response_serializer=method_handler.response_serializer,
|
135
|
+
)
|
@@ -15,11 +15,13 @@
|
|
15
15
|
"""Flower Exec API interceptor."""
|
16
16
|
|
17
17
|
|
18
|
-
|
18
|
+
import contextvars
|
19
|
+
from typing import Any, Callable, Union, cast
|
19
20
|
|
20
21
|
import grpc
|
21
22
|
|
22
23
|
from flwr.common.auth_plugin import ExecAuthPlugin
|
24
|
+
from flwr.common.typing import UserInfo
|
23
25
|
from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
24
26
|
GetAuthTokensRequest,
|
25
27
|
GetAuthTokensResponse,
|
@@ -43,6 +45,11 @@ Response = Union[
|
|
43
45
|
]
|
44
46
|
|
45
47
|
|
48
|
+
shared_user_info: contextvars.ContextVar[UserInfo] = contextvars.ContextVar(
|
49
|
+
"user_info", default=UserInfo(user_id=None, user_name=None)
|
50
|
+
)
|
51
|
+
|
52
|
+
|
46
53
|
class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
47
54
|
"""Exec API interceptor for user authentication."""
|
48
55
|
|
@@ -77,13 +84,22 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
77
84
|
) -> Response:
|
78
85
|
call = method_handler.unary_unary or method_handler.unary_stream
|
79
86
|
metadata = context.invocation_metadata()
|
87
|
+
|
88
|
+
# Intercept GetLoginDetails and GetAuthTokens requests, and return
|
89
|
+
# the response without authentication
|
80
90
|
if isinstance(request, (GetLoginDetailsRequest, GetAuthTokensRequest)):
|
81
91
|
return call(request, context) # type: ignore
|
82
92
|
|
83
|
-
|
93
|
+
# For other requests, check if the user is authenticated
|
94
|
+
valid_tokens, user_info = self.auth_plugin.validate_tokens_in_metadata(
|
95
|
+
metadata
|
96
|
+
)
|
84
97
|
if valid_tokens:
|
98
|
+
# Store user info in contextvars for authenticated users
|
99
|
+
shared_user_info.set(cast(UserInfo, user_info))
|
85
100
|
return call(request, context) # type: ignore
|
86
101
|
|
102
|
+
# If the user is not authenticated, refresh tokens
|
87
103
|
tokens = self.auth_plugin.refresh_tokens(context.invocation_metadata())
|
88
104
|
if tokens is not None:
|
89
105
|
context.send_initial_metadata(tokens)
|
{flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/RECORD
RENAMED
@@ -35,7 +35,7 @@ flwr/cli/new/templates/app/code/client.sklearn.py.tpl,sha256=MfhMN-hayGCc3cZ1XpN
|
|
35
35
|
flwr/cli/new/templates/app/code/client.tensorflow.py.tpl,sha256=yBiiU7B9Kf70U52cPkNs_dUpYrrTwbUi2os-PAyheaM,1680
|
36
36
|
flwr/cli/new/templates/app/code/dataset.baseline.py.tpl,sha256=jbd_exHAk2-Blu_kVutjPO6a_dkJQWb232zxSeXIZ1k,1453
|
37
37
|
flwr/cli/new/templates/app/code/flwr_tune/__init__.py,sha256=JgNgBtKdm1jKM9625WxappCAVUGtYAmcjKSsXJ1u3ZQ,748
|
38
|
-
flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl,sha256=
|
38
|
+
flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl,sha256=tUyEAmkuUYop7teKNUO2NVbEB7-sXZBNLks8s6Wedws,3759
|
39
39
|
flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl,sha256=1NA2Sf-EviNtOaYN4dnFk6v2tcZVsY3-eXY84wOXVng,3059
|
40
40
|
flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl,sha256=ONJw_BgBWEofVNGRDu8KAIThb8saRQlUEK4uS2u_6To,2449
|
41
41
|
flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl,sha256=xkmmBKr0oGmewP56SP3s_6FG6JOVlGlquhg3a9nYMis,3270
|
@@ -319,13 +319,14 @@ flwr/simulation/simulationio_connection.py,sha256=lcbEmdjb9RVEF2W5vSbf_J1zlTuv_Z
|
|
319
319
|
flwr/superexec/__init__.py,sha256=fcj366jh4RFby_vDwLroU4kepzqbnJgseZD_jUr_Mko,715
|
320
320
|
flwr/superexec/app.py,sha256=C0T2LMjuyF__I5V1FKfjtWtbsQPxK_EgL4vuhWIwG8s,1465
|
321
321
|
flwr/superexec/deployment.py,sha256=wZ9G42gGS91knfplswh95MnQ83Fzu-rs6wcuNgDmmvY,6735
|
322
|
+
flwr/superexec/exec_event_log_interceptor.py,sha256=zFu1MQu3XkWa9PUTQgt5wb3BL_eR2dFS5M3BR5hbX34,5805
|
322
323
|
flwr/superexec/exec_grpc.py,sha256=ttA9qoZzSLF0Mfk1L4hzOkMSNuj5rR58_kKBYwcyrAg,2864
|
323
324
|
flwr/superexec/exec_servicer.py,sha256=4UpzJqPUHkBG2PZNe2lrX7XFVDOL6yw_HcoBHxuXE9A,8349
|
324
|
-
flwr/superexec/exec_user_auth_interceptor.py,sha256=
|
325
|
+
flwr/superexec/exec_user_auth_interceptor.py,sha256=2kXjjJcrZyff893QTFLQD6zxC4pdVwtN4Rc66jHptfE,4440
|
325
326
|
flwr/superexec/executor.py,sha256=_B55WW2TD1fBINpabSSDRenVHXYmvlfhv-k8hJKU4lQ,3115
|
326
327
|
flwr/superexec/simulation.py,sha256=WQDon15oqpMopAZnwRZoTICYCfHqtkvFSqiTQ2hLD_g,4088
|
327
|
-
flwr_nightly-1.17.0.
|
328
|
-
flwr_nightly-1.17.0.
|
329
|
-
flwr_nightly-1.17.0.
|
330
|
-
flwr_nightly-1.17.0.
|
331
|
-
flwr_nightly-1.17.0.
|
328
|
+
flwr_nightly-1.17.0.dev20250312.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
329
|
+
flwr_nightly-1.17.0.dev20250312.dist-info/METADATA,sha256=uOd2BEE053PBIRj-JOYG48jU4qASw1D_XJLThzPbnW4,15877
|
330
|
+
flwr_nightly-1.17.0.dev20250312.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
331
|
+
flwr_nightly-1.17.0.dev20250312.dist-info/entry_points.txt,sha256=2-1L-GNKhwGw2_7_RoH55vHw2SIHjdAQy3HAVAWl9PY,374
|
332
|
+
flwr_nightly-1.17.0.dev20250312.dist-info/RECORD,,
|
{flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/LICENSE
RENAMED
File without changes
|
{flwr_nightly-1.17.0.dev20250311.dist-info → flwr_nightly-1.17.0.dev20250312.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|