flwr-nightly 1.8.0.dev20240315__py3-none-any.whl → 1.11.0.dev20240813__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/cli/app.py +7 -0
- flwr/cli/build.py +150 -0
- flwr/cli/config_utils.py +219 -0
- flwr/cli/example.py +3 -1
- flwr/cli/install.py +227 -0
- flwr/cli/new/new.py +179 -48
- flwr/cli/new/templates/app/.gitignore.tpl +160 -0
- flwr/cli/new/templates/app/README.flowertune.md.tpl +56 -0
- flwr/cli/new/templates/app/README.md.tpl +1 -5
- flwr/cli/new/templates/app/code/__init__.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +65 -0
- flwr/cli/new/templates/app/code/client.jax.py.tpl +56 -0
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +93 -0
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +3 -2
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +23 -11
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +97 -0
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +60 -1
- flwr/cli/new/templates/app/code/flwr_tune/__init__.py +15 -0
- flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +89 -0
- flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl +126 -0
- flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl +34 -0
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +57 -0
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +59 -0
- flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl +48 -0
- flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl +11 -0
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +23 -0
- flwr/cli/new/templates/app/code/server.jax.py.tpl +20 -0
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +20 -0
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +17 -9
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +21 -18
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +24 -0
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +29 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +99 -0
- flwr/cli/new/templates/app/code/task.jax.py.tpl +57 -0
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +102 -0
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +28 -23
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +53 -0
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +39 -0
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +38 -0
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +34 -0
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +39 -0
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +25 -12
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +29 -14
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +33 -0
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +29 -14
- flwr/cli/run/run.py +168 -17
- flwr/cli/utils.py +75 -4
- flwr/client/__init__.py +6 -1
- flwr/client/app.py +239 -248
- flwr/client/client_app.py +70 -9
- flwr/client/dpfedavg_numpy_client.py +1 -1
- flwr/client/grpc_adapter_client/__init__.py +15 -0
- flwr/client/grpc_adapter_client/connection.py +97 -0
- flwr/client/grpc_client/connection.py +18 -5
- flwr/client/grpc_rere_client/__init__.py +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +158 -0
- flwr/client/grpc_rere_client/connection.py +127 -33
- flwr/client/grpc_rere_client/grpc_adapter.py +140 -0
- flwr/client/heartbeat.py +74 -0
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +7 -7
- flwr/client/mod/__init__.py +5 -5
- flwr/client/mod/centraldp_mods.py +4 -2
- flwr/client/mod/comms_mods.py +4 -4
- flwr/client/mod/localdp_mod.py +9 -4
- flwr/client/mod/secure_aggregation/__init__.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
- flwr/client/mod/utils.py +1 -1
- flwr/client/node_state.py +60 -10
- flwr/client/node_state_tests.py +4 -3
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +177 -157
- flwr/client/supernode/__init__.py +26 -0
- flwr/client/supernode/app.py +464 -0
- flwr/client/typing.py +1 -0
- flwr/common/__init__.py +13 -11
- flwr/common/address.py +1 -1
- flwr/common/config.py +193 -0
- flwr/common/constant.py +42 -1
- flwr/common/context.py +26 -1
- flwr/common/date.py +1 -1
- flwr/common/dp.py +1 -1
- flwr/common/grpc.py +6 -2
- flwr/common/logger.py +79 -8
- flwr/common/message.py +167 -105
- flwr/common/object_ref.py +126 -25
- flwr/common/record/__init__.py +1 -1
- flwr/common/record/parametersrecord.py +0 -1
- flwr/common/record/recordset.py +78 -27
- flwr/common/recordset_compat.py +8 -1
- flwr/common/retry_invoker.py +25 -13
- flwr/common/secure_aggregation/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/shamir.py +1 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +21 -2
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
- flwr/common/secure_aggregation/quantization.py +1 -1
- flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
- flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
- flwr/common/serde.py +209 -3
- flwr/common/telemetry.py +25 -0
- flwr/common/typing.py +38 -0
- flwr/common/version.py +14 -0
- flwr/proto/clientappio_pb2.py +41 -0
- flwr/proto/clientappio_pb2.pyi +110 -0
- flwr/proto/clientappio_pb2_grpc.py +101 -0
- flwr/proto/clientappio_pb2_grpc.pyi +40 -0
- flwr/proto/common_pb2.py +36 -0
- flwr/proto/common_pb2.pyi +121 -0
- flwr/proto/common_pb2_grpc.py +4 -0
- flwr/proto/common_pb2_grpc.pyi +4 -0
- flwr/proto/driver_pb2.py +26 -19
- flwr/proto/driver_pb2.pyi +34 -0
- flwr/proto/driver_pb2_grpc.py +70 -0
- flwr/proto/driver_pb2_grpc.pyi +28 -0
- flwr/proto/exec_pb2.py +43 -0
- flwr/proto/exec_pb2.pyi +95 -0
- flwr/proto/exec_pb2_grpc.py +101 -0
- flwr/proto/exec_pb2_grpc.pyi +41 -0
- flwr/proto/fab_pb2.py +30 -0
- flwr/proto/fab_pb2.pyi +56 -0
- flwr/proto/fab_pb2_grpc.py +4 -0
- flwr/proto/fab_pb2_grpc.pyi +4 -0
- flwr/proto/fleet_pb2.py +29 -23
- flwr/proto/fleet_pb2.pyi +33 -0
- flwr/proto/fleet_pb2_grpc.py +102 -0
- flwr/proto/fleet_pb2_grpc.pyi +35 -0
- flwr/proto/grpcadapter_pb2.py +32 -0
- flwr/proto/grpcadapter_pb2.pyi +43 -0
- flwr/proto/grpcadapter_pb2_grpc.py +66 -0
- flwr/proto/grpcadapter_pb2_grpc.pyi +24 -0
- flwr/proto/message_pb2.py +41 -0
- flwr/proto/message_pb2.pyi +122 -0
- flwr/proto/message_pb2_grpc.py +4 -0
- flwr/proto/message_pb2_grpc.pyi +4 -0
- flwr/proto/run_pb2.py +35 -0
- flwr/proto/run_pb2.pyi +76 -0
- flwr/proto/run_pb2_grpc.py +4 -0
- flwr/proto/run_pb2_grpc.pyi +4 -0
- flwr/proto/task_pb2.py +7 -8
- flwr/proto/task_pb2.pyi +8 -5
- flwr/server/__init__.py +4 -8
- flwr/server/app.py +298 -350
- flwr/server/compat/app.py +6 -57
- flwr/server/compat/app_utils.py +5 -4
- flwr/server/compat/driver_client_proxy.py +29 -48
- flwr/server/compat/legacy_context.py +5 -4
- flwr/server/driver/__init__.py +2 -0
- flwr/server/driver/driver.py +22 -132
- flwr/server/driver/grpc_driver.py +224 -74
- flwr/server/driver/inmemory_driver.py +183 -0
- flwr/server/history.py +20 -20
- flwr/server/run_serverapp.py +121 -34
- flwr/server/server.py +11 -7
- flwr/server/server_app.py +59 -10
- flwr/server/serverapp_components.py +52 -0
- flwr/server/strategy/__init__.py +2 -2
- flwr/server/strategy/bulyan.py +1 -1
- flwr/server/strategy/dp_adaptive_clipping.py +3 -3
- flwr/server/strategy/dp_fixed_clipping.py +4 -3
- flwr/server/strategy/dpfedavg_adaptive.py +1 -1
- flwr/server/strategy/dpfedavg_fixed.py +1 -1
- flwr/server/strategy/fedadagrad.py +1 -1
- flwr/server/strategy/fedadam.py +1 -1
- flwr/server/strategy/fedavg_android.py +1 -1
- flwr/server/strategy/fedavgm.py +1 -1
- flwr/server/strategy/fedmedian.py +1 -1
- flwr/server/strategy/fedopt.py +1 -1
- flwr/server/strategy/fedprox.py +1 -1
- flwr/server/strategy/fedxgb_bagging.py +1 -1
- flwr/server/strategy/fedxgb_cyclic.py +1 -1
- flwr/server/strategy/fedxgb_nn_avg.py +1 -1
- flwr/server/strategy/fedyogi.py +1 -1
- flwr/server/strategy/krum.py +1 -1
- flwr/server/strategy/qfedavg.py +1 -1
- flwr/server/superlink/driver/__init__.py +1 -1
- flwr/server/superlink/driver/driver_grpc.py +1 -1
- flwr/server/superlink/driver/driver_servicer.py +51 -4
- flwr/server/superlink/ffs/__init__.py +24 -0
- flwr/server/superlink/ffs/disk_ffs.py +104 -0
- flwr/server/superlink/ffs/ffs.py +79 -0
- flwr/server/superlink/fleet/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/__init__.py +15 -0
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +131 -0
- flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +8 -2
- flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +30 -2
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +214 -0
- flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +42 -2
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +59 -1
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/backend.py +5 -5
- flwr/server/superlink/fleet/vce/backend/raybackend.py +53 -56
- flwr/server/superlink/fleet/vce/vce_api.py +190 -127
- flwr/server/superlink/state/__init__.py +1 -1
- flwr/server/superlink/state/in_memory_state.py +159 -42
- flwr/server/superlink/state/sqlite_state.py +243 -39
- flwr/server/superlink/state/state.py +81 -6
- flwr/server/superlink/state/state_factory.py +11 -2
- flwr/server/superlink/state/utils.py +62 -0
- flwr/server/typing.py +2 -0
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +1 -1
- flwr/server/utils/validator.py +23 -9
- flwr/server/workflow/default_workflows.py +67 -25
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +18 -6
- flwr/simulation/__init__.py +7 -4
- flwr/simulation/app.py +67 -36
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +20 -46
- flwr/simulation/ray_transport/ray_client_proxy.py +36 -16
- flwr/simulation/run_simulation.py +308 -92
- flwr/superexec/__init__.py +21 -0
- flwr/superexec/app.py +184 -0
- flwr/superexec/deployment.py +185 -0
- flwr/superexec/exec_grpc.py +55 -0
- flwr/superexec/exec_servicer.py +70 -0
- flwr/superexec/executor.py +75 -0
- flwr/superexec/simulation.py +193 -0
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/METADATA +10 -6
- flwr_nightly-1.11.0.dev20240813.dist-info/RECORD +288 -0
- flwr_nightly-1.11.0.dev20240813.dist-info/entry_points.txt +10 -0
- flwr/cli/flower_toml.py +0 -140
- flwr/cli/new/templates/app/flower.toml.tpl +0 -13
- flwr/cli/new/templates/app/requirements.numpy.txt.tpl +0 -2
- flwr/cli/new/templates/app/requirements.pytorch.txt.tpl +0 -4
- flwr/cli/new/templates/app/requirements.tensorflow.txt.tpl +0 -4
- flwr_nightly-1.8.0.dev20240315.dist-info/RECORD +0 -211
- flwr_nightly-1.8.0.dev20240315.dist-info/entry_points.txt +0 -9
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/WHEEL +0 -0
flwr/common/message.py
CHANGED
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
import time
|
|
20
|
+
import warnings
|
|
21
|
+
from typing import Optional, cast
|
|
20
22
|
|
|
21
23
|
from .record import RecordSet
|
|
22
24
|
|
|
25
|
+
DEFAULT_TTL = 3600
|
|
26
|
+
|
|
23
27
|
|
|
24
|
-
@dataclass
|
|
25
28
|
class Metadata: # pylint: disable=too-many-instance-attributes
|
|
26
29
|
"""A dataclass holding metadata associated with the current message.
|
|
27
30
|
|
|
@@ -40,27 +43,13 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
40
43
|
group_id : str
|
|
41
44
|
An identifier for grouping messages. In some settings,
|
|
42
45
|
this is used as the FL round.
|
|
43
|
-
ttl :
|
|
44
|
-
Time-to-live for this message.
|
|
46
|
+
ttl : float
|
|
47
|
+
Time-to-live for this message in seconds.
|
|
45
48
|
message_type : str
|
|
46
49
|
A string that encodes the action to be executed on
|
|
47
50
|
the receiving end.
|
|
48
|
-
partition_id : Optional[int]
|
|
49
|
-
An identifier that can be used when loading a particular
|
|
50
|
-
data partition for a ClientApp. Making use of this identifier
|
|
51
|
-
is more relevant when conducting simulations.
|
|
52
51
|
"""
|
|
53
52
|
|
|
54
|
-
_run_id: int
|
|
55
|
-
_message_id: str
|
|
56
|
-
_src_node_id: int
|
|
57
|
-
_dst_node_id: int
|
|
58
|
-
_reply_to_message: str
|
|
59
|
-
_group_id: str
|
|
60
|
-
_ttl: str
|
|
61
|
-
_message_type: str
|
|
62
|
-
_partition_id: int | None
|
|
63
|
-
|
|
64
53
|
def __init__( # pylint: disable=too-many-arguments
|
|
65
54
|
self,
|
|
66
55
|
run_id: int,
|
|
@@ -69,92 +58,103 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
69
58
|
dst_node_id: int,
|
|
70
59
|
reply_to_message: str,
|
|
71
60
|
group_id: str,
|
|
72
|
-
ttl:
|
|
61
|
+
ttl: float,
|
|
73
62
|
message_type: str,
|
|
74
|
-
partition_id: int | None = None,
|
|
75
63
|
) -> None:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
64
|
+
var_dict = {
|
|
65
|
+
"_run_id": run_id,
|
|
66
|
+
"_message_id": message_id,
|
|
67
|
+
"_src_node_id": src_node_id,
|
|
68
|
+
"_dst_node_id": dst_node_id,
|
|
69
|
+
"_reply_to_message": reply_to_message,
|
|
70
|
+
"_group_id": group_id,
|
|
71
|
+
"_ttl": ttl,
|
|
72
|
+
"_message_type": message_type,
|
|
73
|
+
}
|
|
74
|
+
self.__dict__.update(var_dict)
|
|
85
75
|
|
|
86
76
|
@property
|
|
87
77
|
def run_id(self) -> int:
|
|
88
78
|
"""An identifier for the current run."""
|
|
89
|
-
return self._run_id
|
|
79
|
+
return cast(int, self.__dict__["_run_id"])
|
|
90
80
|
|
|
91
81
|
@property
|
|
92
82
|
def message_id(self) -> str:
|
|
93
83
|
"""An identifier for the current message."""
|
|
94
|
-
return self._message_id
|
|
84
|
+
return cast(str, self.__dict__["_message_id"])
|
|
95
85
|
|
|
96
86
|
@property
|
|
97
87
|
def src_node_id(self) -> int:
|
|
98
88
|
"""An identifier for the node sending this message."""
|
|
99
|
-
return self._src_node_id
|
|
89
|
+
return cast(int, self.__dict__["_src_node_id"])
|
|
100
90
|
|
|
101
91
|
@property
|
|
102
92
|
def reply_to_message(self) -> str:
|
|
103
93
|
"""An identifier for the message this message replies to."""
|
|
104
|
-
return self._reply_to_message
|
|
94
|
+
return cast(str, self.__dict__["_reply_to_message"])
|
|
105
95
|
|
|
106
96
|
@property
|
|
107
97
|
def dst_node_id(self) -> int:
|
|
108
98
|
"""An identifier for the node receiving this message."""
|
|
109
|
-
return self._dst_node_id
|
|
99
|
+
return cast(int, self.__dict__["_dst_node_id"])
|
|
110
100
|
|
|
111
101
|
@dst_node_id.setter
|
|
112
102
|
def dst_node_id(self, value: int) -> None:
|
|
113
103
|
"""Set dst_node_id."""
|
|
114
|
-
self._dst_node_id = value
|
|
104
|
+
self.__dict__["_dst_node_id"] = value
|
|
115
105
|
|
|
116
106
|
@property
|
|
117
107
|
def group_id(self) -> str:
|
|
118
108
|
"""An identifier for grouping messages."""
|
|
119
|
-
return self._group_id
|
|
109
|
+
return cast(str, self.__dict__["_group_id"])
|
|
120
110
|
|
|
121
111
|
@group_id.setter
|
|
122
112
|
def group_id(self, value: str) -> None:
|
|
123
113
|
"""Set group_id."""
|
|
124
|
-
self._group_id = value
|
|
114
|
+
self.__dict__["_group_id"] = value
|
|
125
115
|
|
|
126
116
|
@property
|
|
127
|
-
def
|
|
117
|
+
def created_at(self) -> float:
|
|
118
|
+
"""Unix timestamp when the message was created."""
|
|
119
|
+
return cast(float, self.__dict__["_created_at"])
|
|
120
|
+
|
|
121
|
+
@created_at.setter
|
|
122
|
+
def created_at(self, value: float) -> None:
|
|
123
|
+
"""Set creation timestamp for this message."""
|
|
124
|
+
self.__dict__["_created_at"] = value
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def ttl(self) -> float:
|
|
128
128
|
"""Time-to-live for this message."""
|
|
129
|
-
return self._ttl
|
|
129
|
+
return cast(float, self.__dict__["_ttl"])
|
|
130
130
|
|
|
131
131
|
@ttl.setter
|
|
132
|
-
def ttl(self, value:
|
|
132
|
+
def ttl(self, value: float) -> None:
|
|
133
133
|
"""Set ttl."""
|
|
134
|
-
self._ttl = value
|
|
134
|
+
self.__dict__["_ttl"] = value
|
|
135
135
|
|
|
136
136
|
@property
|
|
137
137
|
def message_type(self) -> str:
|
|
138
138
|
"""A string that encodes the action to be executed on the receiving end."""
|
|
139
|
-
return self._message_type
|
|
139
|
+
return cast(str, self.__dict__["_message_type"])
|
|
140
140
|
|
|
141
141
|
@message_type.setter
|
|
142
142
|
def message_type(self, value: str) -> None:
|
|
143
143
|
"""Set message_type."""
|
|
144
|
-
self._message_type = value
|
|
144
|
+
self.__dict__["_message_type"] = value
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"""
|
|
149
|
-
return self.
|
|
146
|
+
def __repr__(self) -> str:
|
|
147
|
+
"""Return a string representation of this instance."""
|
|
148
|
+
view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
|
|
149
|
+
return f"{self.__class__.__qualname__}({view})"
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
def __eq__(self, other: object) -> bool:
|
|
152
|
+
"""Compare two instances of the class."""
|
|
153
|
+
if not isinstance(other, self.__class__):
|
|
154
|
+
raise NotImplementedError
|
|
155
|
+
return self.__dict__ == other.__dict__
|
|
155
156
|
|
|
156
157
|
|
|
157
|
-
@dataclass
|
|
158
158
|
class Error:
|
|
159
159
|
"""A dataclass that stores information about an error that occurred.
|
|
160
160
|
|
|
@@ -166,25 +166,35 @@ class Error:
|
|
|
166
166
|
A reason for why the error arose (e.g. an exception stack-trace)
|
|
167
167
|
"""
|
|
168
168
|
|
|
169
|
-
_code: int
|
|
170
|
-
_reason: str | None = None
|
|
171
|
-
|
|
172
169
|
def __init__(self, code: int, reason: str | None = None) -> None:
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
var_dict = {
|
|
171
|
+
"_code": code,
|
|
172
|
+
"_reason": reason,
|
|
173
|
+
}
|
|
174
|
+
self.__dict__.update(var_dict)
|
|
175
175
|
|
|
176
176
|
@property
|
|
177
177
|
def code(self) -> int:
|
|
178
178
|
"""Error code."""
|
|
179
|
-
return self._code
|
|
179
|
+
return cast(int, self.__dict__["_code"])
|
|
180
180
|
|
|
181
181
|
@property
|
|
182
182
|
def reason(self) -> str | None:
|
|
183
183
|
"""Reason reported about the error."""
|
|
184
|
-
return self._reason
|
|
184
|
+
return cast(Optional[str], self.__dict__["_reason"])
|
|
185
|
+
|
|
186
|
+
def __repr__(self) -> str:
|
|
187
|
+
"""Return a string representation of this instance."""
|
|
188
|
+
view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
|
|
189
|
+
return f"{self.__class__.__qualname__}({view})"
|
|
190
|
+
|
|
191
|
+
def __eq__(self, other: object) -> bool:
|
|
192
|
+
"""Compare two instances of the class."""
|
|
193
|
+
if not isinstance(other, self.__class__):
|
|
194
|
+
raise NotImplementedError
|
|
195
|
+
return self.__dict__ == other.__dict__
|
|
185
196
|
|
|
186
197
|
|
|
187
|
-
@dataclass
|
|
188
198
|
class Message:
|
|
189
199
|
"""State of your application from the viewpoint of the entity using it.
|
|
190
200
|
|
|
@@ -200,105 +210,108 @@ class Message:
|
|
|
200
210
|
when processing another message.
|
|
201
211
|
"""
|
|
202
212
|
|
|
203
|
-
_metadata: Metadata
|
|
204
|
-
_content: RecordSet | None = None
|
|
205
|
-
_error: Error | None = None
|
|
206
|
-
|
|
207
213
|
def __init__(
|
|
208
214
|
self,
|
|
209
215
|
metadata: Metadata,
|
|
210
216
|
content: RecordSet | None = None,
|
|
211
217
|
error: Error | None = None,
|
|
212
218
|
) -> None:
|
|
213
|
-
self._metadata = metadata
|
|
214
|
-
|
|
215
219
|
if not (content is None) ^ (error is None):
|
|
216
220
|
raise ValueError("Either `content` or `error` must be set, but not both.")
|
|
217
221
|
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
metadata.created_at = time.time() # Set the message creation timestamp
|
|
223
|
+
var_dict = {
|
|
224
|
+
"_metadata": metadata,
|
|
225
|
+
"_content": content,
|
|
226
|
+
"_error": error,
|
|
227
|
+
}
|
|
228
|
+
self.__dict__.update(var_dict)
|
|
220
229
|
|
|
221
230
|
@property
|
|
222
231
|
def metadata(self) -> Metadata:
|
|
223
232
|
"""A dataclass including information about the message to be executed."""
|
|
224
|
-
return self._metadata
|
|
233
|
+
return cast(Metadata, self.__dict__["_metadata"])
|
|
225
234
|
|
|
226
235
|
@property
|
|
227
236
|
def content(self) -> RecordSet:
|
|
228
237
|
"""The content of this message."""
|
|
229
|
-
if self._content is None:
|
|
238
|
+
if self.__dict__["_content"] is None:
|
|
230
239
|
raise ValueError(
|
|
231
240
|
"Message content is None. Use <message>.has_content() "
|
|
232
241
|
"to check if a message has content."
|
|
233
242
|
)
|
|
234
|
-
return self._content
|
|
243
|
+
return cast(RecordSet, self.__dict__["_content"])
|
|
235
244
|
|
|
236
245
|
@content.setter
|
|
237
246
|
def content(self, value: RecordSet) -> None:
|
|
238
247
|
"""Set content."""
|
|
239
|
-
if self._error is None:
|
|
240
|
-
self._content = value
|
|
248
|
+
if self.__dict__["_error"] is None:
|
|
249
|
+
self.__dict__["_content"] = value
|
|
241
250
|
else:
|
|
242
251
|
raise ValueError("A message with an error set cannot have content.")
|
|
243
252
|
|
|
244
253
|
@property
|
|
245
254
|
def error(self) -> Error:
|
|
246
255
|
"""Error captured by this message."""
|
|
247
|
-
if self._error is None:
|
|
256
|
+
if self.__dict__["_error"] is None:
|
|
248
257
|
raise ValueError(
|
|
249
258
|
"Message error is None. Use <message>.has_error() "
|
|
250
259
|
"to check first if a message carries an error."
|
|
251
260
|
)
|
|
252
|
-
return self._error
|
|
261
|
+
return cast(Error, self.__dict__["_error"])
|
|
253
262
|
|
|
254
263
|
@error.setter
|
|
255
264
|
def error(self, value: Error) -> None:
|
|
256
265
|
"""Set error."""
|
|
257
266
|
if self.has_content():
|
|
258
267
|
raise ValueError("A message with content set cannot carry an error.")
|
|
259
|
-
self._error = value
|
|
268
|
+
self.__dict__["_error"] = value
|
|
260
269
|
|
|
261
270
|
def has_content(self) -> bool:
|
|
262
271
|
"""Return True if message has content, else False."""
|
|
263
|
-
return self._content is not None
|
|
272
|
+
return self.__dict__["_content"] is not None
|
|
264
273
|
|
|
265
274
|
def has_error(self) -> bool:
|
|
266
275
|
"""Return True if message has an error, else False."""
|
|
267
|
-
return self._error is not None
|
|
268
|
-
|
|
269
|
-
def _create_reply_metadata(self, ttl: str) -> Metadata:
|
|
270
|
-
"""Construct metadata for a reply message."""
|
|
271
|
-
return Metadata(
|
|
272
|
-
run_id=self.metadata.run_id,
|
|
273
|
-
message_id="",
|
|
274
|
-
src_node_id=self.metadata.dst_node_id,
|
|
275
|
-
dst_node_id=self.metadata.src_node_id,
|
|
276
|
-
reply_to_message=self.metadata.message_id,
|
|
277
|
-
group_id=self.metadata.group_id,
|
|
278
|
-
ttl=ttl,
|
|
279
|
-
message_type=self.metadata.message_type,
|
|
280
|
-
partition_id=self.metadata.partition_id,
|
|
281
|
-
)
|
|
276
|
+
return self.__dict__["_error"] is not None
|
|
282
277
|
|
|
283
|
-
def create_error_reply(
|
|
284
|
-
self,
|
|
285
|
-
error: Error,
|
|
286
|
-
ttl: str,
|
|
287
|
-
) -> Message:
|
|
278
|
+
def create_error_reply(self, error: Error, ttl: float | None = None) -> Message:
|
|
288
279
|
"""Construct a reply message indicating an error happened.
|
|
289
280
|
|
|
290
281
|
Parameters
|
|
291
282
|
----------
|
|
292
283
|
error : Error
|
|
293
284
|
The error that was encountered.
|
|
294
|
-
ttl :
|
|
295
|
-
Time-to-live for this message.
|
|
285
|
+
ttl : Optional[float] (default: None)
|
|
286
|
+
Time-to-live for this message in seconds. If unset, it will be set based
|
|
287
|
+
on the remaining time for the received message before it expires. This
|
|
288
|
+
follows the equation:
|
|
289
|
+
|
|
290
|
+
ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)
|
|
296
291
|
"""
|
|
292
|
+
if ttl:
|
|
293
|
+
warnings.warn(
|
|
294
|
+
"A custom TTL was set, but note that the SuperLink does not enforce "
|
|
295
|
+
"the TTL yet. The SuperLink will start enforcing the TTL in a future "
|
|
296
|
+
"version of Flower.",
|
|
297
|
+
stacklevel=2,
|
|
298
|
+
)
|
|
299
|
+
# If no TTL passed, use default for message creation (will update after
|
|
300
|
+
# message creation)
|
|
301
|
+
ttl_ = DEFAULT_TTL if ttl is None else ttl
|
|
297
302
|
# Create reply with error
|
|
298
|
-
message = Message(metadata=
|
|
303
|
+
message = Message(metadata=_create_reply_metadata(self, ttl_), error=error)
|
|
304
|
+
|
|
305
|
+
if ttl is None:
|
|
306
|
+
# Set TTL equal to the remaining time for the received message to expire
|
|
307
|
+
ttl = self.metadata.ttl - (
|
|
308
|
+
message.metadata.created_at - self.metadata.created_at
|
|
309
|
+
)
|
|
310
|
+
message.metadata.ttl = ttl
|
|
311
|
+
|
|
299
312
|
return message
|
|
300
313
|
|
|
301
|
-
def create_reply(self, content: RecordSet, ttl:
|
|
314
|
+
def create_reply(self, content: RecordSet, ttl: float | None = None) -> Message:
|
|
302
315
|
"""Create a reply to this message with specified content and TTL.
|
|
303
316
|
|
|
304
317
|
The method generates a new `Message` as a reply to this message.
|
|
@@ -309,15 +322,64 @@ class Message:
|
|
|
309
322
|
----------
|
|
310
323
|
content : RecordSet
|
|
311
324
|
The content for the reply message.
|
|
312
|
-
ttl :
|
|
313
|
-
Time-to-live for this message.
|
|
325
|
+
ttl : Optional[float] (default: None)
|
|
326
|
+
Time-to-live for this message in seconds. If unset, it will be set based
|
|
327
|
+
on the remaining time for the received message before it expires. This
|
|
328
|
+
follows the equation:
|
|
329
|
+
|
|
330
|
+
ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)
|
|
314
331
|
|
|
315
332
|
Returns
|
|
316
333
|
-------
|
|
317
334
|
Message
|
|
318
335
|
A new `Message` instance representing the reply.
|
|
319
336
|
"""
|
|
320
|
-
|
|
321
|
-
|
|
337
|
+
if ttl:
|
|
338
|
+
warnings.warn(
|
|
339
|
+
"A custom TTL was set, but note that the SuperLink does not enforce "
|
|
340
|
+
"the TTL yet. The SuperLink will start enforcing the TTL in a future "
|
|
341
|
+
"version of Flower.",
|
|
342
|
+
stacklevel=2,
|
|
343
|
+
)
|
|
344
|
+
# If no TTL passed, use default for message creation (will update after
|
|
345
|
+
# message creation)
|
|
346
|
+
ttl_ = DEFAULT_TTL if ttl is None else ttl
|
|
347
|
+
|
|
348
|
+
message = Message(
|
|
349
|
+
metadata=_create_reply_metadata(self, ttl_),
|
|
322
350
|
content=content,
|
|
323
351
|
)
|
|
352
|
+
|
|
353
|
+
if ttl is None:
|
|
354
|
+
# Set TTL equal to the remaining time for the received message to expire
|
|
355
|
+
ttl = self.metadata.ttl - (
|
|
356
|
+
message.metadata.created_at - self.metadata.created_at
|
|
357
|
+
)
|
|
358
|
+
message.metadata.ttl = ttl
|
|
359
|
+
|
|
360
|
+
return message
|
|
361
|
+
|
|
362
|
+
def __repr__(self) -> str:
|
|
363
|
+
"""Return a string representation of this instance."""
|
|
364
|
+
view = ", ".join(
|
|
365
|
+
[
|
|
366
|
+
f"{k.lstrip('_')}={v!r}"
|
|
367
|
+
for k, v in self.__dict__.items()
|
|
368
|
+
if v is not None
|
|
369
|
+
]
|
|
370
|
+
)
|
|
371
|
+
return f"{self.__class__.__qualname__}({view})"
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _create_reply_metadata(msg: Message, ttl: float) -> Metadata:
|
|
375
|
+
"""Construct metadata for a reply message."""
|
|
376
|
+
return Metadata(
|
|
377
|
+
run_id=msg.metadata.run_id,
|
|
378
|
+
message_id="",
|
|
379
|
+
src_node_id=msg.metadata.dst_node_id,
|
|
380
|
+
dst_node_id=msg.metadata.src_node_id,
|
|
381
|
+
reply_to_message=msg.metadata.message_id,
|
|
382
|
+
group_id=msg.metadata.group_id,
|
|
383
|
+
ttl=ttl,
|
|
384
|
+
message_type=msg.metadata.message_type,
|
|
385
|
+
)
|
flwr/common/object_ref.py
CHANGED
|
@@ -17,8 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
import ast
|
|
19
19
|
import importlib
|
|
20
|
+
import sys
|
|
20
21
|
from importlib.util import find_spec
|
|
21
|
-
from
|
|
22
|
+
from logging import WARN
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Optional, Tuple, Type, Union
|
|
25
|
+
|
|
26
|
+
from .logger import log
|
|
22
27
|
|
|
23
28
|
OBJECT_REF_HELP_STR = """
|
|
24
29
|
\n\nThe object reference string should have the form <module>:<attribute>. Valid
|
|
@@ -28,21 +33,41 @@ attribute.
|
|
|
28
33
|
"""
|
|
29
34
|
|
|
30
35
|
|
|
36
|
+
_current_sys_path: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
|
|
31
39
|
def validate(
|
|
32
40
|
module_attribute_str: str,
|
|
41
|
+
check_module: bool = True,
|
|
42
|
+
project_dir: Optional[Union[str, Path]] = None,
|
|
33
43
|
) -> Tuple[bool, Optional[str]]:
|
|
34
44
|
"""Validate object reference.
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
module_attribute_str : str
|
|
49
|
+
The reference to the object. It should have the form `<module>:<attribute>`.
|
|
50
|
+
Valid examples include `client:app` and `project.package.module:wrapper.app`.
|
|
51
|
+
It must refer to a module on the PYTHONPATH or in the provided `project_dir`
|
|
52
|
+
and the module needs to have the specified attribute.
|
|
53
|
+
check_module : bool (default: True)
|
|
54
|
+
Flag indicating whether to verify the existence of the module and the
|
|
55
|
+
specified attribute within it.
|
|
56
|
+
project_dir : Optional[Union[str, Path]] (default: None)
|
|
57
|
+
The directory containing the module. If None, the current working directory
|
|
58
|
+
is used. If `check_module` is True, the `project_dir` will be inserted into
|
|
59
|
+
the system path, and the previously inserted `project_dir` will be removed.
|
|
40
60
|
|
|
41
61
|
Returns
|
|
42
62
|
-------
|
|
43
63
|
Tuple[bool, Optional[str]]
|
|
44
64
|
A boolean indicating whether an object reference is valid and
|
|
45
65
|
the reason why it might not be.
|
|
66
|
+
|
|
67
|
+
Note
|
|
68
|
+
----
|
|
69
|
+
This function will modify `sys.path` by inserting the provided `project_dir`
|
|
70
|
+
and removing the previously inserted `project_dir`.
|
|
46
71
|
"""
|
|
47
72
|
module_str, _, attributes_str = module_attribute_str.partition(":")
|
|
48
73
|
if not module_str:
|
|
@@ -56,15 +81,21 @@ def validate(
|
|
|
56
81
|
f"Missing attribute in {module_attribute_str}{OBJECT_REF_HELP_STR}",
|
|
57
82
|
)
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
if check_module:
|
|
85
|
+
# Set the system path
|
|
86
|
+
_set_sys_path(project_dir)
|
|
87
|
+
|
|
88
|
+
# Load module
|
|
89
|
+
module = find_spec(module_str)
|
|
90
|
+
if module and module.origin:
|
|
91
|
+
if not _find_attribute_in_module(module.origin, attributes_str):
|
|
92
|
+
return (
|
|
93
|
+
False,
|
|
94
|
+
f"Unable to find attribute {attributes_str} in module {module_str}"
|
|
95
|
+
f"{OBJECT_REF_HELP_STR}",
|
|
96
|
+
)
|
|
97
|
+
return (True, None)
|
|
98
|
+
else:
|
|
68
99
|
return (True, None)
|
|
69
100
|
|
|
70
101
|
return (
|
|
@@ -73,44 +104,114 @@ def validate(
|
|
|
73
104
|
)
|
|
74
105
|
|
|
75
106
|
|
|
76
|
-
def load_app(
|
|
107
|
+
def load_app( # pylint: disable= too-many-branches
|
|
77
108
|
module_attribute_str: str,
|
|
78
109
|
error_type: Type[Exception],
|
|
110
|
+
project_dir: Optional[Union[str, Path]] = None,
|
|
79
111
|
) -> Any:
|
|
80
112
|
"""Return the object specified in a module attribute string.
|
|
81
113
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
module_attribute_str : str
|
|
117
|
+
The reference to the object. It should have the form `<module>:<attribute>`.
|
|
118
|
+
Valid examples include `client:app` and `project.package.module:wrapper.app`.
|
|
119
|
+
It must refer to a module on the PYTHONPATH or in the provided `project_dir`
|
|
120
|
+
and the module needs to have the specified attribute.
|
|
121
|
+
error_type : Type[Exception]
|
|
122
|
+
The type of exception to be raised if the provided `module_attribute_str` is
|
|
123
|
+
in an invalid format.
|
|
124
|
+
project_dir : Optional[Union[str, Path]], optional (default=None)
|
|
125
|
+
The directory containing the module. If None, the current working directory
|
|
126
|
+
is used. The `project_dir` will be inserted into the system path, and the
|
|
127
|
+
previously inserted `project_dir` will be removed.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
Any
|
|
132
|
+
The object specified by the module attribute string.
|
|
133
|
+
|
|
134
|
+
Note
|
|
135
|
+
----
|
|
136
|
+
This function will modify `sys.path` by inserting the provided `project_dir`
|
|
137
|
+
and removing the previously inserted `project_dir`.
|
|
86
138
|
"""
|
|
87
|
-
valid, error_msg = validate(module_attribute_str)
|
|
139
|
+
valid, error_msg = validate(module_attribute_str, check_module=False)
|
|
88
140
|
if not valid and error_msg:
|
|
89
141
|
raise error_type(error_msg) from None
|
|
90
142
|
|
|
91
143
|
module_str, _, attributes_str = module_attribute_str.partition(":")
|
|
92
144
|
|
|
93
145
|
try:
|
|
94
|
-
|
|
95
|
-
|
|
146
|
+
_set_sys_path(project_dir)
|
|
147
|
+
|
|
148
|
+
if module_str not in sys.modules:
|
|
149
|
+
module = importlib.import_module(module_str)
|
|
150
|
+
# Hack: `tabnet` does not work with `importlib.reload`
|
|
151
|
+
elif "tabnet" in sys.modules:
|
|
152
|
+
log(
|
|
153
|
+
WARN,
|
|
154
|
+
"Cannot reload module `%s` from disk due to compatibility issues "
|
|
155
|
+
"with the `tabnet` library. The module will be loaded from the "
|
|
156
|
+
"cache instead. If you experience issues, consider restarting "
|
|
157
|
+
"the application.",
|
|
158
|
+
module_str,
|
|
159
|
+
)
|
|
160
|
+
module = sys.modules[module_str]
|
|
161
|
+
else:
|
|
162
|
+
module = sys.modules[module_str]
|
|
163
|
+
|
|
164
|
+
if project_dir is None:
|
|
165
|
+
project_dir = Path.cwd()
|
|
166
|
+
|
|
167
|
+
# Reload cached modules in the project directory
|
|
168
|
+
for m in list(sys.modules.values()):
|
|
169
|
+
path: Optional[str] = getattr(m, "__file__", None)
|
|
170
|
+
if path is not None and path.startswith(str(project_dir)):
|
|
171
|
+
importlib.reload(m)
|
|
172
|
+
|
|
173
|
+
except ModuleNotFoundError as err:
|
|
96
174
|
raise error_type(
|
|
97
175
|
f"Unable to load module {module_str}{OBJECT_REF_HELP_STR}",
|
|
98
|
-
) from
|
|
176
|
+
) from err
|
|
99
177
|
|
|
100
178
|
# Recursively load attribute
|
|
101
179
|
attribute = module
|
|
102
180
|
try:
|
|
103
181
|
for attribute_str in attributes_str.split("."):
|
|
104
182
|
attribute = getattr(attribute, attribute_str)
|
|
105
|
-
except AttributeError:
|
|
183
|
+
except AttributeError as err:
|
|
106
184
|
raise error_type(
|
|
107
185
|
f"Unable to load attribute {attributes_str} from module {module_str}"
|
|
108
186
|
f"{OBJECT_REF_HELP_STR}",
|
|
109
|
-
) from
|
|
187
|
+
) from err
|
|
110
188
|
|
|
111
189
|
return attribute
|
|
112
190
|
|
|
113
191
|
|
|
192
|
+
def _set_sys_path(directory: Optional[Union[str, Path]]) -> None:
|
|
193
|
+
"""Set the system path."""
|
|
194
|
+
if directory is None:
|
|
195
|
+
directory = Path.cwd()
|
|
196
|
+
else:
|
|
197
|
+
directory = Path(directory).absolute()
|
|
198
|
+
|
|
199
|
+
# If the directory has already been added to `sys.path`, return
|
|
200
|
+
if str(directory) in sys.path:
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# Remove the old path if it exists and is not `""`.
|
|
204
|
+
global _current_sys_path # pylint: disable=global-statement
|
|
205
|
+
if _current_sys_path is not None:
|
|
206
|
+
sys.path.remove(_current_sys_path)
|
|
207
|
+
|
|
208
|
+
# Add the new path to sys.path
|
|
209
|
+
sys.path.insert(0, str(directory))
|
|
210
|
+
|
|
211
|
+
# Update the current_sys_path
|
|
212
|
+
_current_sys_path = str(directory)
|
|
213
|
+
|
|
214
|
+
|
|
114
215
|
def _find_attribute_in_module(file_path: str, attribute_name: str) -> bool:
|
|
115
216
|
"""Check if attribute_name exists in module's abstract symbolic tree."""
|
|
116
217
|
with open(file_path, encoding="utf-8") as file:
|
flwr/common/record/__init__.py
CHANGED