flwr 1.23.0__py3-none-any.whl → 1.24.0__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/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +19 -0
- flwr/cli/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -0
- flwr/cli/auth_plugin/auth_plugin.py +4 -5
- flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
- flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
- flwr/cli/build.py +60 -18
- flwr/cli/cli_account_auth_interceptor.py +24 -7
- flwr/cli/config_utils.py +101 -13
- flwr/cli/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +170 -130
- flwr/cli/new/new.py +33 -50
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +10 -5
- flwr/cli/run/run.py +77 -30
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +25 -7
- flwr/cli/supernode/ls.py +16 -8
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +376 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +6 -7
- flwr/client/grpc_rere_client/connection.py +10 -11
- flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
- flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +12 -14
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/clientapp/utils.py +3 -3
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +5 -2
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +19 -0
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +38 -21
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +3 -3
- flwr/common/record/arrayrecord.py +18 -30
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +5 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +52 -37
- flwr/compat/client/app.py +38 -37
- flwr/compat/client/grpc_client/connection.py +11 -11
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +71 -52
- flwr/proto/control_pb2.pyi +277 -111
- flwr/proto/control_pb2_grpc.py +249 -40
- flwr/proto/control_pb2_grpc.pyi +185 -52
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +14 -4
- flwr/proto/fab_pb2.pyi +59 -31
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +14 -4
- flwr/proto/fleet_pb2.pyi +137 -61
- flwr/proto/fleet_pb2_grpc.py +189 -48
- flwr/proto/fleet_pb2_grpc.pyi +175 -61
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +15 -5
- flwr/proto/node_pb2.pyi +50 -25
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +149 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +38 -17
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +34 -28
- flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
- flwr/server/superlink/fleet/vce/vce_api.py +15 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +115 -150
- flwr/server/superlink/linkstate/linkstate.py +59 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +447 -438
- flwr/server/superlink/linkstate/utils.py +6 -6
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +10 -11
- flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
- flwr/simulation/run_simulation.py +43 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +31 -1
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -2
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +1 -2
- flwr/supercore/object_store/sqlite_object_store.py +8 -7
- flwr/supercore/primitives/asymmetric.py +1 -1
- flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
- flwr/supercore/sqlite_mixin.py +37 -34
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/superlink/auth_plugin/auth_plugin.py +6 -9
- flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
- flwr/superlink/federation/__init__.py +24 -0
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +5 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +102 -18
- flwr/supernode/cli/flower_supernode.py +58 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +158 -42
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.23.0.dist-info/RECORD +0 -439
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
flwr/cli/utils.py
CHANGED
|
@@ -18,15 +18,18 @@
|
|
|
18
18
|
import hashlib
|
|
19
19
|
import json
|
|
20
20
|
import re
|
|
21
|
-
from collections.abc import Iterator
|
|
21
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
22
22
|
from contextlib import contextmanager
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from typing import Any,
|
|
24
|
+
from typing import Any, cast
|
|
25
25
|
|
|
26
26
|
import grpc
|
|
27
|
+
import pathspec
|
|
28
|
+
import requests
|
|
27
29
|
import typer
|
|
28
30
|
|
|
29
31
|
from flwr.common.constant import (
|
|
32
|
+
ACCESS_TOKEN_KEY,
|
|
30
33
|
AUTHN_TYPE_JSON_KEY,
|
|
31
34
|
CREDENTIALS_DIR,
|
|
32
35
|
FLWR_DIR,
|
|
@@ -36,6 +39,7 @@ from flwr.common.constant import (
|
|
|
36
39
|
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
|
|
37
40
|
PUBLIC_KEY_NOT_VALID,
|
|
38
41
|
PULL_UNFINISHED_RUN_MESSAGE,
|
|
42
|
+
REFRESH_TOKEN_KEY,
|
|
39
43
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
40
44
|
AuthnType,
|
|
41
45
|
)
|
|
@@ -44,6 +48,8 @@ from flwr.common.grpc import (
|
|
|
44
48
|
create_channel,
|
|
45
49
|
on_channel_state_change,
|
|
46
50
|
)
|
|
51
|
+
from flwr.common.version import package_version as flwr_version
|
|
52
|
+
from flwr.supercore.constant import APP_ID_PATTERN, APP_VERSION_PATTERN
|
|
47
53
|
|
|
48
54
|
from .auth_plugin import CliAuthPlugin, get_cli_plugin_class
|
|
49
55
|
from .cli_account_auth_interceptor import CliAccountAuthInterceptor
|
|
@@ -53,9 +59,24 @@ from .config_utils import validate_certificate_in_federation_config
|
|
|
53
59
|
def prompt_text(
|
|
54
60
|
text: str,
|
|
55
61
|
predicate: Callable[[str], bool] = lambda _: True,
|
|
56
|
-
default:
|
|
62
|
+
default: str | None = None,
|
|
57
63
|
) -> str:
|
|
58
|
-
"""Ask user to enter text input.
|
|
64
|
+
"""Ask user to enter text input.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
text : str
|
|
69
|
+
The prompt text to display to the user.
|
|
70
|
+
predicate : Callable[[str], bool] (default: lambda _: True)
|
|
71
|
+
A function to validate the user input. Default accepts all non-empty strings.
|
|
72
|
+
default : str | None (default: None)
|
|
73
|
+
Default value to use if user presses enter without input.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
str
|
|
78
|
+
The validated user input.
|
|
79
|
+
"""
|
|
59
80
|
while True:
|
|
60
81
|
result = typer.prompt(
|
|
61
82
|
typer.style(f"\n💬 {text}", fg=typer.colors.MAGENTA, bold=True),
|
|
@@ -69,7 +90,20 @@ def prompt_text(
|
|
|
69
90
|
|
|
70
91
|
|
|
71
92
|
def prompt_options(text: str, options: list[str]) -> str:
|
|
72
|
-
"""Ask user to select one of the given options and return the selected item.
|
|
93
|
+
"""Ask user to select one of the given options and return the selected item.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
text : str
|
|
98
|
+
The prompt text to display to the user.
|
|
99
|
+
options : list[str]
|
|
100
|
+
List of options to present to the user.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
str
|
|
105
|
+
The selected option from the list.
|
|
106
|
+
"""
|
|
73
107
|
# Turn options into a list with index as in " [ 0] quickstart-pytorch"
|
|
74
108
|
options_formatted = [
|
|
75
109
|
" [ "
|
|
@@ -127,9 +161,19 @@ def is_valid_project_name(name: str) -> bool:
|
|
|
127
161
|
def sanitize_project_name(name: str) -> str:
|
|
128
162
|
"""Sanitize the given string to make it a valid Python project name.
|
|
129
163
|
|
|
130
|
-
This
|
|
164
|
+
This function replaces spaces, dots, slashes, and underscores with dashes, removes
|
|
131
165
|
any characters not allowed in Python project names, makes the string lowercase, and
|
|
132
166
|
ensures it starts with a valid character.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
name : str
|
|
171
|
+
The project name to sanitize.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
str
|
|
176
|
+
The sanitized project name that is valid for Python projects.
|
|
133
177
|
"""
|
|
134
178
|
# Replace whitespace with '_'
|
|
135
179
|
name_with_hyphens = re.sub(r"[ ./_]", "-", name)
|
|
@@ -154,8 +198,19 @@ def sanitize_project_name(name: str) -> str:
|
|
|
154
198
|
return sanitized_name
|
|
155
199
|
|
|
156
200
|
|
|
157
|
-
def get_sha256_hash(file_path_or_int:
|
|
158
|
-
"""Calculate the SHA-256 hash of a file.
|
|
201
|
+
def get_sha256_hash(file_path_or_int: Path | int) -> str:
|
|
202
|
+
"""Calculate the SHA-256 hash of a file or integer.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
file_path_or_int : Path | int
|
|
207
|
+
Either a path to a file to hash, or an integer to convert to string and hash.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
str
|
|
212
|
+
The SHA-256 hash as a hexadecimal string.
|
|
213
|
+
"""
|
|
159
214
|
sha256 = hashlib.sha256()
|
|
160
215
|
if isinstance(file_path_or_int, Path):
|
|
161
216
|
with open(file_path_or_int, "rb") as f:
|
|
@@ -214,6 +269,7 @@ def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
|
214
269
|
f"Please check the permissions of `{gitignore_path}` and try again.",
|
|
215
270
|
fg=typer.colors.RED,
|
|
216
271
|
bold=True,
|
|
272
|
+
err=True,
|
|
217
273
|
)
|
|
218
274
|
raise typer.Exit(code=1) from err
|
|
219
275
|
|
|
@@ -221,7 +277,18 @@ def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
|
221
277
|
|
|
222
278
|
|
|
223
279
|
def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
|
|
224
|
-
"""Check if account authentication is enabled in the federation config.
|
|
280
|
+
"""Check if account authentication is enabled in the federation config.
|
|
281
|
+
|
|
282
|
+
Parameters
|
|
283
|
+
----------
|
|
284
|
+
federation_config : dict[str, Any]
|
|
285
|
+
The federation configuration dictionary.
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
bool
|
|
290
|
+
True if account authentication is enabled, False otherwise.
|
|
291
|
+
"""
|
|
225
292
|
enabled: bool = federation_config.get("enable-user-auth", False)
|
|
226
293
|
enabled |= federation_config.get("enable-account-auth", False)
|
|
227
294
|
if "enable-user-auth" in federation_config:
|
|
@@ -235,7 +302,18 @@ def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
|
|
|
235
302
|
|
|
236
303
|
|
|
237
304
|
def retrieve_authn_type(config_path: Path) -> str:
|
|
238
|
-
"""Retrieve the auth type from the config file or return NOOP if not found.
|
|
305
|
+
"""Retrieve the auth type from the config file or return NOOP if not found.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
config_path : Path
|
|
310
|
+
Path to the authentication configuration file.
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
str
|
|
315
|
+
The authentication type string, or AuthnType.NOOP if not found.
|
|
316
|
+
"""
|
|
239
317
|
try:
|
|
240
318
|
with config_path.open("r", encoding="utf-8") as file:
|
|
241
319
|
json_file = json.load(file)
|
|
@@ -249,9 +327,31 @@ def load_cli_auth_plugin(
|
|
|
249
327
|
root_dir: Path,
|
|
250
328
|
federation: str,
|
|
251
329
|
federation_config: dict[str, Any],
|
|
252
|
-
authn_type:
|
|
330
|
+
authn_type: str | None = None,
|
|
253
331
|
) -> CliAuthPlugin:
|
|
254
|
-
"""Load the CLI-side account auth plugin for the given authn type.
|
|
332
|
+
"""Load the CLI-side account auth plugin for the given authn type.
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
root_dir : Path
|
|
337
|
+
Root directory of the Flower project.
|
|
338
|
+
federation : str
|
|
339
|
+
Name of the federation.
|
|
340
|
+
federation_config : dict[str, Any]
|
|
341
|
+
Federation configuration dictionary.
|
|
342
|
+
authn_type : str | None
|
|
343
|
+
Authentication type. If None, will be determined from config.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
CliAuthPlugin
|
|
348
|
+
The loaded authentication plugin instance.
|
|
349
|
+
|
|
350
|
+
Raises
|
|
351
|
+
------
|
|
352
|
+
typer.Exit
|
|
353
|
+
If the authentication type is unknown.
|
|
354
|
+
"""
|
|
255
355
|
# Find the path to the account auth config file
|
|
256
356
|
config_path = get_account_auth_config_path(root_dir, federation)
|
|
257
357
|
|
|
@@ -275,7 +375,22 @@ def load_cli_auth_plugin(
|
|
|
275
375
|
def init_channel(
|
|
276
376
|
app: Path, federation_config: dict[str, Any], auth_plugin: CliAuthPlugin
|
|
277
377
|
) -> grpc.Channel:
|
|
278
|
-
"""Initialize gRPC channel to the Control API.
|
|
378
|
+
"""Initialize gRPC channel to the Control API.
|
|
379
|
+
|
|
380
|
+
Parameters
|
|
381
|
+
----------
|
|
382
|
+
app : Path
|
|
383
|
+
Path to the Flower app directory.
|
|
384
|
+
federation_config : dict[str, Any]
|
|
385
|
+
Federation configuration dictionary containing address and TLS settings.
|
|
386
|
+
auth_plugin : CliAuthPlugin
|
|
387
|
+
Authentication plugin instance for handling credentials.
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
grpc.Channel
|
|
392
|
+
Configured gRPC channel with authentication interceptors.
|
|
393
|
+
"""
|
|
279
394
|
insecure, root_certificates_bytes = validate_certificate_in_federation_config(
|
|
280
395
|
app, federation_config
|
|
281
396
|
)
|
|
@@ -299,9 +414,22 @@ def init_channel(
|
|
|
299
414
|
def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-branches
|
|
300
415
|
"""Context manager to handle specific gRPC errors.
|
|
301
416
|
|
|
302
|
-
|
|
303
|
-
UNAVAILABLE,
|
|
304
|
-
application. All other exceptions will be
|
|
417
|
+
Catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
|
|
418
|
+
UNAVAILABLE, PERMISSION_DENIED, NOT_FOUND, and FAILED_PRECONDITION statuses,
|
|
419
|
+
informs the user, and exits the application. All other exceptions will be
|
|
420
|
+
allowed to escape.
|
|
421
|
+
|
|
422
|
+
Yields
|
|
423
|
+
------
|
|
424
|
+
None
|
|
425
|
+
Context manager yields nothing.
|
|
426
|
+
|
|
427
|
+
Raises
|
|
428
|
+
------
|
|
429
|
+
typer.Exit
|
|
430
|
+
On handled gRPC error statuses with appropriate exit code.
|
|
431
|
+
grpc.RpcError
|
|
432
|
+
For unhandled gRPC error statuses.
|
|
305
433
|
"""
|
|
306
434
|
try:
|
|
307
435
|
yield
|
|
@@ -312,6 +440,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
312
440
|
" to authenticate and try again.",
|
|
313
441
|
fg=typer.colors.RED,
|
|
314
442
|
bold=True,
|
|
443
|
+
err=True,
|
|
315
444
|
)
|
|
316
445
|
raise typer.Exit(code=1) from None
|
|
317
446
|
if e.code() == grpc.StatusCode.UNIMPLEMENTED:
|
|
@@ -320,12 +449,14 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
320
449
|
"❌ Account authentication is not enabled on this SuperLink.",
|
|
321
450
|
fg=typer.colors.RED,
|
|
322
451
|
bold=True,
|
|
452
|
+
err=True,
|
|
323
453
|
)
|
|
324
454
|
elif e.details() == NO_ARTIFACT_PROVIDER_MESSAGE: # pylint: disable=E1101
|
|
325
455
|
typer.secho(
|
|
326
456
|
"❌ The SuperLink does not support `flwr pull` command.",
|
|
327
457
|
fg=typer.colors.RED,
|
|
328
458
|
bold=True,
|
|
459
|
+
err=True,
|
|
329
460
|
)
|
|
330
461
|
else:
|
|
331
462
|
typer.secho(
|
|
@@ -335,6 +466,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
335
466
|
"the CLI and SuperLink are compatible.",
|
|
336
467
|
fg=typer.colors.RED,
|
|
337
468
|
bold=True,
|
|
469
|
+
err=True,
|
|
338
470
|
)
|
|
339
471
|
raise typer.Exit(code=1) from None
|
|
340
472
|
if e.code() == grpc.StatusCode.PERMISSION_DENIED:
|
|
@@ -342,6 +474,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
342
474
|
"❌ Permission denied.",
|
|
343
475
|
fg=typer.colors.RED,
|
|
344
476
|
bold=True,
|
|
477
|
+
err=True,
|
|
345
478
|
)
|
|
346
479
|
# pylint: disable-next=E1101
|
|
347
480
|
typer.secho(e.details(), fg=typer.colors.RED, bold=True)
|
|
@@ -352,6 +485,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
352
485
|
"connection and 'address' in the federation configuration.",
|
|
353
486
|
fg=typer.colors.RED,
|
|
354
487
|
bold=True,
|
|
488
|
+
err=True,
|
|
355
489
|
)
|
|
356
490
|
raise typer.Exit(code=1) from None
|
|
357
491
|
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
@@ -360,6 +494,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
360
494
|
"❌ Run ID not found.",
|
|
361
495
|
fg=typer.colors.RED,
|
|
362
496
|
bold=True,
|
|
497
|
+
err=True,
|
|
363
498
|
)
|
|
364
499
|
raise typer.Exit(code=1) from None
|
|
365
500
|
if e.details() == NODE_NOT_FOUND_MESSAGE: # pylint: disable=E1101
|
|
@@ -367,6 +502,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
367
502
|
"❌ Node ID not found for this account.",
|
|
368
503
|
fg=typer.colors.RED,
|
|
369
504
|
bold=True,
|
|
505
|
+
err=True,
|
|
370
506
|
)
|
|
371
507
|
raise typer.Exit(code=1) from None
|
|
372
508
|
if e.code() == grpc.StatusCode.FAILED_PRECONDITION:
|
|
@@ -376,6 +512,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
376
512
|
"the run is finished. You can check the run status with `flwr ls`.",
|
|
377
513
|
fg=typer.colors.RED,
|
|
378
514
|
bold=True,
|
|
515
|
+
err=True,
|
|
379
516
|
)
|
|
380
517
|
raise typer.Exit(code=1) from None
|
|
381
518
|
if (
|
|
@@ -386,6 +523,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
386
523
|
"SuperNode.",
|
|
387
524
|
fg=typer.colors.RED,
|
|
388
525
|
bold=True,
|
|
526
|
+
err=True,
|
|
389
527
|
)
|
|
390
528
|
raise typer.Exit(code=1) from None
|
|
391
529
|
if e.details() == PUBLIC_KEY_NOT_VALID: # pylint: disable=E1101
|
|
@@ -394,6 +532,228 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
394
532
|
"NIST EC public key.",
|
|
395
533
|
fg=typer.colors.RED,
|
|
396
534
|
bold=True,
|
|
535
|
+
err=True,
|
|
397
536
|
)
|
|
398
537
|
raise typer.Exit(code=1) from None
|
|
538
|
+
|
|
539
|
+
# Log details from grpc error directly
|
|
540
|
+
typer.secho(
|
|
541
|
+
f"❌ {e.details()}",
|
|
542
|
+
fg=typer.colors.RED,
|
|
543
|
+
bold=True,
|
|
544
|
+
err=True,
|
|
545
|
+
)
|
|
546
|
+
raise typer.Exit(code=1) from None
|
|
399
547
|
raise
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def request_download_link(
|
|
551
|
+
app_id: str, app_version: str | None, in_url: str, out_url: str
|
|
552
|
+
) -> str:
|
|
553
|
+
"""Request a download link for the given app from the Flower platform API.
|
|
554
|
+
|
|
555
|
+
Parameters
|
|
556
|
+
----------
|
|
557
|
+
app_id : str
|
|
558
|
+
The application identifier.
|
|
559
|
+
app_version : str | None
|
|
560
|
+
The application version, or None for latest.
|
|
561
|
+
in_url : str
|
|
562
|
+
The API endpoint URL.
|
|
563
|
+
out_url : str
|
|
564
|
+
The key name for the download URL in the response.
|
|
565
|
+
|
|
566
|
+
Returns
|
|
567
|
+
-------
|
|
568
|
+
str
|
|
569
|
+
The download URL for the application.
|
|
570
|
+
|
|
571
|
+
Raises
|
|
572
|
+
------
|
|
573
|
+
typer.Exit
|
|
574
|
+
If connection fails, app not found, or API request fails.
|
|
575
|
+
"""
|
|
576
|
+
headers = {
|
|
577
|
+
"Content-Type": "application/json",
|
|
578
|
+
"Accept": "application/json",
|
|
579
|
+
}
|
|
580
|
+
body = {
|
|
581
|
+
"app_id": app_id, # send raw string of app_id
|
|
582
|
+
"app_version": app_version,
|
|
583
|
+
"flwr_version": flwr_version,
|
|
584
|
+
}
|
|
585
|
+
try:
|
|
586
|
+
resp = requests.post(in_url, headers=headers, data=json.dumps(body), timeout=20)
|
|
587
|
+
except requests.RequestException as e:
|
|
588
|
+
typer.secho(
|
|
589
|
+
f"Unable to connect to Platform API: {e}",
|
|
590
|
+
fg=typer.colors.RED,
|
|
591
|
+
err=True,
|
|
592
|
+
)
|
|
593
|
+
raise typer.Exit(code=1) from e
|
|
594
|
+
|
|
595
|
+
if resp.status_code == 404:
|
|
596
|
+
error_message = resp.json()["detail"]
|
|
597
|
+
if isinstance(error_message, dict):
|
|
598
|
+
available_app_versions = error_message["available_app_versions"]
|
|
599
|
+
available_versions_str = (
|
|
600
|
+
", ".join(map(str, available_app_versions))
|
|
601
|
+
if available_app_versions
|
|
602
|
+
else "None"
|
|
603
|
+
)
|
|
604
|
+
typer.secho(
|
|
605
|
+
f"{app_id}=={app_version} not found in Platform API. "
|
|
606
|
+
f"Available app versions for {app_id}: {available_versions_str}",
|
|
607
|
+
fg=typer.colors.RED,
|
|
608
|
+
err=True,
|
|
609
|
+
)
|
|
610
|
+
else:
|
|
611
|
+
typer.secho(
|
|
612
|
+
f"{app_id} not found in Platform API.",
|
|
613
|
+
fg=typer.colors.RED,
|
|
614
|
+
err=True,
|
|
615
|
+
)
|
|
616
|
+
raise typer.Exit(code=1)
|
|
617
|
+
|
|
618
|
+
if not resp.ok:
|
|
619
|
+
typer.secho(
|
|
620
|
+
f"Platform API request failed with "
|
|
621
|
+
f"status {resp.status_code}. Details: {resp.text}",
|
|
622
|
+
fg=typer.colors.RED,
|
|
623
|
+
err=True,
|
|
624
|
+
)
|
|
625
|
+
raise typer.Exit(code=1)
|
|
626
|
+
|
|
627
|
+
data = resp.json()
|
|
628
|
+
if out_url not in data:
|
|
629
|
+
typer.secho(
|
|
630
|
+
"Invalid response from Platform API",
|
|
631
|
+
fg=typer.colors.RED,
|
|
632
|
+
err=True,
|
|
633
|
+
)
|
|
634
|
+
raise typer.Exit(code=1)
|
|
635
|
+
return str(data[out_url])
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def build_pathspec(patterns: Iterable[str]) -> pathspec.PathSpec:
|
|
639
|
+
"""Build a PathSpec from a list of GitIgnore-style patterns.
|
|
640
|
+
|
|
641
|
+
Parameters
|
|
642
|
+
----------
|
|
643
|
+
patterns : Iterable[str]
|
|
644
|
+
Iterable of GitIgnore-style pattern strings.
|
|
645
|
+
|
|
646
|
+
Returns
|
|
647
|
+
-------
|
|
648
|
+
pathspec.PathSpec
|
|
649
|
+
Compiled PathSpec object for pattern matching.
|
|
650
|
+
"""
|
|
651
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def load_gitignore_patterns(file: Path | bytes) -> list[str]:
|
|
655
|
+
"""Load gitignore patterns from .gitignore file bytes.
|
|
656
|
+
|
|
657
|
+
Parameters
|
|
658
|
+
----------
|
|
659
|
+
file : Path | bytes
|
|
660
|
+
The path to a .gitignore file or its bytes content.
|
|
661
|
+
|
|
662
|
+
Returns
|
|
663
|
+
-------
|
|
664
|
+
list[str]
|
|
665
|
+
List of gitignore patterns.
|
|
666
|
+
Returns empty list if content can't be decoded or the file does not exist.
|
|
667
|
+
"""
|
|
668
|
+
try:
|
|
669
|
+
if isinstance(file, Path):
|
|
670
|
+
content = file.read_text(encoding="utf-8")
|
|
671
|
+
else:
|
|
672
|
+
content = file.decode("utf-8")
|
|
673
|
+
patterns = [
|
|
674
|
+
line.strip()
|
|
675
|
+
for line in content.splitlines()
|
|
676
|
+
if line.strip() and not line.strip().startswith("#")
|
|
677
|
+
]
|
|
678
|
+
return patterns
|
|
679
|
+
except (UnicodeDecodeError, OSError):
|
|
680
|
+
return []
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def validate_credentials_content(creds_path: Path) -> str:
|
|
684
|
+
"""Load and validate the credentials file content.
|
|
685
|
+
|
|
686
|
+
Ensures required keys exist:
|
|
687
|
+
- AUTHN_TYPE_JSON_KEY
|
|
688
|
+
- ACCESS_TOKEN_KEY
|
|
689
|
+
- REFRESH_TOKEN_KEY
|
|
690
|
+
"""
|
|
691
|
+
try:
|
|
692
|
+
creds: dict[str, str] = json.loads(creds_path.read_text(encoding="utf-8"))
|
|
693
|
+
except (OSError, json.JSONDecodeError) as err:
|
|
694
|
+
typer.secho(
|
|
695
|
+
f"Invalid credentials file at '{creds_path}': {err}",
|
|
696
|
+
fg=typer.colors.RED,
|
|
697
|
+
err=True,
|
|
698
|
+
)
|
|
699
|
+
raise typer.Exit(code=1) from err
|
|
700
|
+
|
|
701
|
+
required_keys = [AUTHN_TYPE_JSON_KEY, ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY]
|
|
702
|
+
missing = [key for key in required_keys if key not in creds]
|
|
703
|
+
|
|
704
|
+
if missing:
|
|
705
|
+
typer.secho(
|
|
706
|
+
f"Credentials file '{creds_path}' is missing "
|
|
707
|
+
f"required key(s): {', '.join(missing)}. Please log in again.",
|
|
708
|
+
fg=typer.colors.RED,
|
|
709
|
+
err=True,
|
|
710
|
+
)
|
|
711
|
+
raise typer.Exit(code=1)
|
|
712
|
+
|
|
713
|
+
return creds[ACCESS_TOKEN_KEY]
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def parse_app_spec(app_spec: str) -> tuple[str, str | None]:
|
|
717
|
+
"""Parse app specification string into app ID and version.
|
|
718
|
+
|
|
719
|
+
Parameters
|
|
720
|
+
----------
|
|
721
|
+
app_spec : str
|
|
722
|
+
The app specification string in the format '@account/app' or
|
|
723
|
+
'@account/app==x.y.z' (digits only).
|
|
724
|
+
|
|
725
|
+
Returns
|
|
726
|
+
-------
|
|
727
|
+
tuple[str, str | None]
|
|
728
|
+
A tuple containing the app ID and optional version.
|
|
729
|
+
|
|
730
|
+
Raises
|
|
731
|
+
------
|
|
732
|
+
typer.Exit
|
|
733
|
+
If the app specification format is invalid.
|
|
734
|
+
"""
|
|
735
|
+
if "==" in app_spec:
|
|
736
|
+
app_id, app_version = app_spec.split("==")
|
|
737
|
+
|
|
738
|
+
# Validate app version format
|
|
739
|
+
if not re.match(APP_VERSION_PATTERN, app_version):
|
|
740
|
+
typer.secho(
|
|
741
|
+
"❌ Invalid app version. Expected format: x.y.z (digits only).",
|
|
742
|
+
fg=typer.colors.RED,
|
|
743
|
+
err=True,
|
|
744
|
+
)
|
|
745
|
+
raise typer.Exit(code=1)
|
|
746
|
+
else:
|
|
747
|
+
app_id = app_spec
|
|
748
|
+
app_version = None
|
|
749
|
+
|
|
750
|
+
# Validate app_id format
|
|
751
|
+
if not re.match(APP_ID_PATTERN, app_id):
|
|
752
|
+
typer.secho(
|
|
753
|
+
"❌ Invalid remote app ID. Expected format: '@account/app'.",
|
|
754
|
+
fg=typer.colors.RED,
|
|
755
|
+
err=True,
|
|
756
|
+
)
|
|
757
|
+
raise typer.Exit(code=1)
|
|
758
|
+
|
|
759
|
+
return app_id, app_version
|
flwr/client/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"""Flower client."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from flwr.clientapp import ClientApp
|
|
18
|
+
from flwr.clientapp.client_app import ClientApp
|
|
19
19
|
|
|
20
20
|
from ..compat.client.app import start_client as start_client # Deprecated
|
|
21
21
|
from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
|
|
@@ -120,7 +120,10 @@ class DPFedAvgNumPyClient(NumPyClient):
|
|
|
120
120
|
updated_params, num_examples, metrics = self.client.fit(parameters, config)
|
|
121
121
|
|
|
122
122
|
# Update = updated model - original model
|
|
123
|
-
update = [
|
|
123
|
+
update = [
|
|
124
|
+
np.subtract(x, y)
|
|
125
|
+
for (x, y) in zip(updated_params, original_params, strict=True)
|
|
126
|
+
]
|
|
124
127
|
|
|
125
128
|
if "dpfedavg_clip_norm" not in config:
|
|
126
129
|
raise KeyError("Clipping threshold not supplied by the server.")
|
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
"""Contextmanager for a GrpcAdapter channel to the Flower server."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from collections.abc import Iterator
|
|
18
|
+
from collections.abc import Callable, Iterator
|
|
19
19
|
from contextlib import contextmanager
|
|
20
20
|
from logging import ERROR
|
|
21
|
-
from typing import Callable, Optional, Union
|
|
22
21
|
|
|
23
22
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
24
23
|
|
|
@@ -38,14 +37,14 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
|
|
|
38
37
|
insecure: bool,
|
|
39
38
|
retry_invoker: RetryInvoker,
|
|
40
39
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
|
|
41
|
-
root_certificates:
|
|
42
|
-
authentication_keys:
|
|
43
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
44
|
-
|
|
40
|
+
root_certificates: bytes | str | None = None,
|
|
41
|
+
authentication_keys: (
|
|
42
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
43
|
+
) = None,
|
|
45
44
|
) -> Iterator[
|
|
46
45
|
tuple[
|
|
47
46
|
int,
|
|
48
|
-
Callable[[],
|
|
47
|
+
Callable[[], tuple[Message, ObjectTree] | None],
|
|
49
48
|
Callable[[Message, ObjectTree], set[str]],
|
|
50
49
|
Callable[[int], Run],
|
|
51
50
|
Callable[[str, int], Fab],
|
|
@@ -15,11 +15,10 @@
|
|
|
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
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
19
19
|
from contextlib import contextmanager
|
|
20
20
|
from logging import ERROR
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import Callable, Optional, Union
|
|
23
22
|
|
|
24
23
|
import grpc
|
|
25
24
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
@@ -27,7 +26,6 @@ from cryptography.hazmat.primitives.asymmetric import ec
|
|
|
27
26
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
28
27
|
from flwr.common.constant import HEARTBEAT_CALL_TIMEOUT, HEARTBEAT_DEFAULT_INTERVAL
|
|
29
28
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
30
|
-
from flwr.common.heartbeat import HeartbeatSender
|
|
31
29
|
from flwr.common.inflatable_protobuf_utils import (
|
|
32
30
|
make_confirm_message_received_fn_protobuf,
|
|
33
31
|
make_pull_object_fn_protobuf,
|
|
@@ -63,6 +61,7 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
|
|
|
63
61
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
64
62
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
65
63
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
|
64
|
+
from flwr.supercore.heartbeat import HeartbeatSender
|
|
66
65
|
from flwr.supercore.primitives.asymmetric import generate_key_pairs, public_key_to_bytes
|
|
67
66
|
|
|
68
67
|
from .grpc_adapter import GrpcAdapter
|
|
@@ -75,15 +74,15 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
75
74
|
insecure: bool,
|
|
76
75
|
retry_invoker: RetryInvoker,
|
|
77
76
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
|
|
78
|
-
root_certificates:
|
|
79
|
-
authentication_keys:
|
|
80
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
81
|
-
|
|
82
|
-
adapter_cls:
|
|
77
|
+
root_certificates: bytes | str | None = None,
|
|
78
|
+
authentication_keys: (
|
|
79
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
80
|
+
) = None,
|
|
81
|
+
adapter_cls: type[FleetStub] | type[GrpcAdapter] | None = None,
|
|
83
82
|
) -> Iterator[
|
|
84
83
|
tuple[
|
|
85
84
|
int,
|
|
86
|
-
Callable[[],
|
|
85
|
+
Callable[[], tuple[Message, ObjectTree] | None],
|
|
87
86
|
Callable[[Message, ObjectTree], set[str]],
|
|
88
87
|
Callable[[int], Run],
|
|
89
88
|
Callable[[str, int], Fab],
|
|
@@ -163,7 +162,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
163
162
|
if adapter_cls is None:
|
|
164
163
|
adapter_cls = FleetStub
|
|
165
164
|
stub = adapter_cls(channel)
|
|
166
|
-
node:
|
|
165
|
+
node: Node | None = None
|
|
167
166
|
|
|
168
167
|
# Wrap stub
|
|
169
168
|
_wrap_stub(stub, retry_invoker)
|
|
@@ -253,7 +252,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
253
252
|
# Cleanup
|
|
254
253
|
node = None
|
|
255
254
|
|
|
256
|
-
def receive() ->
|
|
255
|
+
def receive() -> tuple[Message, ObjectTree] | None:
|
|
257
256
|
"""Pull a message with its ObjectTree from SuperLink."""
|
|
258
257
|
# Get Node
|
|
259
258
|
if node is None:
|