flwr-nightly 1.8.0.dev20240315__py3-none-any.whl → 1.15.0.dev20250115__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- flwr/cli/app.py +16 -2
- flwr/cli/build.py +181 -0
- flwr/cli/cli_user_auth_interceptor.py +90 -0
- flwr/cli/config_utils.py +343 -0
- flwr/cli/example.py +4 -1
- flwr/cli/install.py +253 -0
- flwr/cli/log.py +182 -0
- flwr/{server/superlink/state → cli/login}/__init__.py +4 -10
- flwr/cli/login/login.py +88 -0
- flwr/cli/ls.py +327 -0
- flwr/cli/new/__init__.py +1 -0
- flwr/cli/new/new.py +210 -66
- flwr/cli/new/templates/app/.gitignore.tpl +163 -0
- flwr/cli/new/templates/app/LICENSE.tpl +202 -0
- flwr/cli/new/templates/app/README.baseline.md.tpl +127 -0
- flwr/cli/new/templates/app/README.flowertune.md.tpl +66 -0
- flwr/cli/new/templates/app/README.md.tpl +16 -32
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/code/__init__.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +58 -0
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +55 -0
- flwr/cli/new/templates/app/code/client.jax.py.tpl +50 -0
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +73 -0
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +7 -7
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +30 -21
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +63 -0
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +57 -1
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +36 -0
- flwr/cli/new/templates/app/code/flwr_tune/__init__.py +15 -0
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +126 -0
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +87 -0
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +78 -0
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +94 -0
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +83 -0
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +80 -0
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +46 -0
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +38 -0
- flwr/cli/new/templates/app/code/server.jax.py.tpl +26 -0
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +31 -0
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +22 -9
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +21 -18
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +36 -0
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +29 -1
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +102 -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.numpy.py.tpl +7 -0
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +29 -24
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +67 -0
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +53 -0
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +138 -0
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +68 -0
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +46 -0
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +35 -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 +35 -0
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +29 -14
- flwr/cli/run/__init__.py +1 -0
- flwr/cli/run/run.py +212 -34
- flwr/cli/stop.py +130 -0
- flwr/cli/utils.py +240 -5
- flwr/client/__init__.py +3 -2
- flwr/client/app.py +432 -255
- flwr/client/client.py +1 -11
- flwr/client/client_app.py +74 -13
- flwr/client/clientapp/__init__.py +22 -0
- flwr/client/clientapp/app.py +259 -0
- flwr/client/clientapp/clientappio_servicer.py +244 -0
- flwr/client/clientapp/utils.py +115 -0
- flwr/client/dpfedavg_numpy_client.py +7 -8
- flwr/client/grpc_adapter_client/__init__.py +15 -0
- flwr/client/grpc_adapter_client/connection.py +98 -0
- flwr/client/grpc_client/connection.py +21 -7
- flwr/client/grpc_rere_client/__init__.py +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +176 -0
- flwr/client/grpc_rere_client/connection.py +163 -56
- flwr/client/grpc_rere_client/grpc_adapter.py +167 -0
- flwr/client/heartbeat.py +74 -0
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +10 -11
- flwr/client/mod/__init__.py +5 -5
- flwr/client/mod/centraldp_mods.py +4 -2
- flwr/client/mod/comms_mods.py +5 -4
- flwr/client/mod/localdp_mod.py +10 -5
- flwr/client/mod/secure_aggregation/__init__.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +26 -26
- flwr/client/mod/utils.py +2 -4
- flwr/client/nodestate/__init__.py +26 -0
- flwr/client/nodestate/in_memory_nodestate.py +38 -0
- flwr/client/nodestate/nodestate.py +31 -0
- flwr/client/nodestate/nodestate_factory.py +38 -0
- flwr/client/numpy_client.py +8 -31
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +199 -176
- flwr/client/run_info_store.py +112 -0
- flwr/client/supernode/__init__.py +24 -0
- flwr/client/supernode/app.py +321 -0
- flwr/client/typing.py +1 -0
- flwr/common/__init__.py +17 -11
- flwr/common/address.py +47 -3
- flwr/common/args.py +153 -0
- flwr/common/auth_plugin/__init__.py +24 -0
- flwr/common/auth_plugin/auth_plugin.py +121 -0
- flwr/common/config.py +243 -0
- flwr/common/constant.py +135 -1
- flwr/common/context.py +32 -2
- flwr/common/date.py +22 -4
- flwr/common/differential_privacy.py +2 -2
- flwr/common/dp.py +2 -4
- flwr/common/exit_handlers.py +3 -3
- flwr/common/grpc.py +164 -5
- flwr/common/logger.py +230 -12
- flwr/common/message.py +191 -106
- flwr/common/object_ref.py +179 -44
- flwr/common/pyproject.py +1 -0
- flwr/common/record/__init__.py +2 -1
- flwr/common/record/configsrecord.py +58 -18
- flwr/common/record/metricsrecord.py +57 -17
- flwr/common/record/parametersrecord.py +88 -20
- flwr/common/record/recordset.py +153 -30
- flwr/common/record/typeddict.py +30 -55
- flwr/common/recordset_compat.py +31 -12
- flwr/common/retry_invoker.py +123 -30
- flwr/common/secure_aggregation/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/shamir.py +11 -11
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +68 -4
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +17 -17
- flwr/common/secure_aggregation/quantization.py +8 -8
- flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
- flwr/common/secure_aggregation/secaggplus_utils.py +10 -12
- flwr/common/serde.py +304 -23
- flwr/common/telemetry.py +65 -29
- flwr/common/typing.py +120 -19
- flwr/common/version.py +17 -3
- flwr/proto/clientappio_pb2.py +45 -0
- flwr/proto/clientappio_pb2.pyi +132 -0
- flwr/proto/clientappio_pb2_grpc.py +135 -0
- flwr/proto/clientappio_pb2_grpc.pyi +53 -0
- flwr/proto/exec_pb2.py +62 -0
- flwr/proto/exec_pb2.pyi +212 -0
- flwr/proto/exec_pb2_grpc.py +237 -0
- flwr/proto/exec_pb2_grpc.pyi +93 -0
- flwr/proto/fab_pb2.py +31 -0
- flwr/proto/fab_pb2.pyi +65 -0
- flwr/proto/fab_pb2_grpc.py +4 -0
- flwr/proto/fab_pb2_grpc.pyi +4 -0
- flwr/proto/fleet_pb2.py +42 -23
- flwr/proto/fleet_pb2.pyi +123 -1
- flwr/proto/fleet_pb2_grpc.py +170 -0
- flwr/proto/fleet_pb2_grpc.pyi +61 -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/log_pb2.py +29 -0
- flwr/proto/log_pb2.pyi +39 -0
- flwr/proto/log_pb2_grpc.py +4 -0
- flwr/proto/log_pb2_grpc.pyi +4 -0
- flwr/proto/message_pb2.py +41 -0
- flwr/proto/message_pb2.pyi +128 -0
- flwr/proto/message_pb2_grpc.py +4 -0
- flwr/proto/message_pb2_grpc.pyi +4 -0
- flwr/proto/node_pb2.py +2 -2
- flwr/proto/node_pb2.pyi +1 -4
- flwr/proto/recordset_pb2.py +35 -33
- flwr/proto/recordset_pb2.pyi +40 -14
- flwr/proto/run_pb2.py +64 -0
- flwr/proto/run_pb2.pyi +268 -0
- flwr/proto/run_pb2_grpc.py +4 -0
- flwr/proto/run_pb2_grpc.pyi +4 -0
- flwr/proto/serverappio_pb2.py +52 -0
- flwr/proto/{driver_pb2.pyi → serverappio_pb2.pyi} +62 -20
- flwr/proto/serverappio_pb2_grpc.py +410 -0
- flwr/proto/serverappio_pb2_grpc.pyi +160 -0
- flwr/proto/simulationio_pb2.py +38 -0
- flwr/proto/simulationio_pb2.pyi +65 -0
- flwr/proto/simulationio_pb2_grpc.py +239 -0
- flwr/proto/simulationio_pb2_grpc.pyi +94 -0
- flwr/proto/task_pb2.py +7 -8
- flwr/proto/task_pb2.pyi +8 -5
- flwr/proto/transport_pb2.py +8 -8
- flwr/proto/transport_pb2.pyi +9 -6
- flwr/server/__init__.py +2 -10
- flwr/server/app.py +579 -402
- flwr/server/client_manager.py +8 -6
- flwr/server/compat/app.py +6 -62
- flwr/server/compat/app_utils.py +14 -9
- flwr/server/compat/driver_client_proxy.py +25 -59
- flwr/server/compat/legacy_context.py +5 -4
- flwr/server/driver/__init__.py +2 -0
- flwr/server/driver/driver.py +36 -131
- flwr/server/driver/grpc_driver.py +220 -81
- flwr/server/driver/inmemory_driver.py +183 -0
- flwr/server/history.py +28 -29
- flwr/server/run_serverapp.py +15 -126
- flwr/server/server.py +50 -44
- flwr/server/server_app.py +59 -10
- flwr/server/serverapp/__init__.py +22 -0
- flwr/server/serverapp/app.py +256 -0
- flwr/server/serverapp_components.py +52 -0
- flwr/server/strategy/__init__.py +2 -2
- flwr/server/strategy/aggregate.py +37 -23
- flwr/server/strategy/bulyan.py +9 -9
- flwr/server/strategy/dp_adaptive_clipping.py +25 -25
- flwr/server/strategy/dp_fixed_clipping.py +23 -22
- flwr/server/strategy/dpfedavg_adaptive.py +8 -8
- flwr/server/strategy/dpfedavg_fixed.py +13 -12
- flwr/server/strategy/fault_tolerant_fedavg.py +11 -11
- flwr/server/strategy/fedadagrad.py +9 -9
- flwr/server/strategy/fedadam.py +20 -10
- flwr/server/strategy/fedavg.py +16 -16
- flwr/server/strategy/fedavg_android.py +17 -17
- flwr/server/strategy/fedavgm.py +9 -9
- flwr/server/strategy/fedmedian.py +5 -5
- flwr/server/strategy/fedopt.py +6 -6
- flwr/server/strategy/fedprox.py +7 -7
- flwr/server/strategy/fedtrimmedavg.py +8 -8
- flwr/server/strategy/fedxgb_bagging.py +12 -12
- flwr/server/strategy/fedxgb_cyclic.py +10 -10
- flwr/server/strategy/fedxgb_nn_avg.py +6 -6
- flwr/server/strategy/fedyogi.py +9 -9
- flwr/server/strategy/krum.py +9 -9
- flwr/server/strategy/qfedavg.py +16 -16
- flwr/server/strategy/strategy.py +10 -10
- flwr/server/superlink/driver/__init__.py +2 -2
- flwr/server/superlink/driver/serverappio_grpc.py +61 -0
- flwr/server/superlink/driver/serverappio_servicer.py +361 -0
- flwr/server/superlink/ffs/__init__.py +24 -0
- flwr/server/superlink/ffs/disk_ffs.py +108 -0
- flwr/server/superlink/ffs/ffs.py +79 -0
- flwr/server/superlink/ffs/ffs_factory.py +47 -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 +162 -0
- flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +4 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +5 -154
- flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +120 -13
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +228 -0
- flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +156 -13
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +119 -81
- flwr/server/superlink/fleet/vce/__init__.py +1 -0
- flwr/server/superlink/fleet/vce/backend/__init__.py +4 -4
- flwr/server/superlink/fleet/vce/backend/backend.py +8 -9
- flwr/server/superlink/fleet/vce/backend/raybackend.py +87 -68
- flwr/server/superlink/fleet/vce/vce_api.py +208 -146
- flwr/server/superlink/linkstate/__init__.py +28 -0
- flwr/server/superlink/linkstate/in_memory_linkstate.py +569 -0
- flwr/server/superlink/linkstate/linkstate.py +376 -0
- flwr/server/superlink/{state/state_factory.py → linkstate/linkstate_factory.py} +19 -10
- flwr/server/superlink/linkstate/sqlite_linkstate.py +1196 -0
- flwr/server/superlink/linkstate/utils.py +399 -0
- flwr/server/superlink/simulation/__init__.py +15 -0
- flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
- flwr/server/superlink/simulation/simulationio_servicer.py +186 -0
- flwr/server/superlink/utils.py +65 -0
- flwr/server/typing.py +2 -0
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +5 -5
- flwr/server/utils/validator.py +40 -45
- flwr/server/workflow/default_workflows.py +70 -26
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -0
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +40 -27
- flwr/simulation/__init__.py +12 -5
- flwr/simulation/app.py +247 -315
- flwr/simulation/legacy_app.py +404 -0
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +42 -67
- flwr/simulation/ray_transport/ray_client_proxy.py +37 -17
- flwr/simulation/ray_transport/utils.py +1 -0
- flwr/simulation/run_simulation.py +306 -163
- flwr/simulation/simulationio_connection.py +89 -0
- flwr/superexec/__init__.py +15 -0
- flwr/superexec/app.py +59 -0
- flwr/superexec/deployment.py +188 -0
- flwr/superexec/exec_grpc.py +80 -0
- flwr/superexec/exec_servicer.py +231 -0
- flwr/superexec/exec_user_auth_interceptor.py +101 -0
- flwr/superexec/executor.py +96 -0
- flwr/superexec/simulation.py +124 -0
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/METADATA +33 -26
- flwr_nightly-1.15.0.dev20250115.dist-info/RECORD +328 -0
- flwr_nightly-1.15.0.dev20250115.dist-info/entry_points.txt +12 -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/client/node_state.py +0 -48
- flwr/client/node_state_tests.py +0 -65
- flwr/proto/driver_pb2.py +0 -44
- flwr/proto/driver_pb2_grpc.py +0 -169
- flwr/proto/driver_pb2_grpc.pyi +0 -66
- flwr/server/superlink/driver/driver_grpc.py +0 -54
- flwr/server/superlink/driver/driver_servicer.py +0 -129
- flwr/server/superlink/state/in_memory_state.py +0 -230
- flwr/server/superlink/state/sqlite_state.py +0 -630
- flwr/server/superlink/state/state.py +0 -154
- 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.15.0.dev20250115.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/WHEEL +0 -0
flwr/common/context.py
CHANGED
@@ -18,21 +18,51 @@
|
|
18
18
|
from dataclasses import dataclass
|
19
19
|
|
20
20
|
from .record import RecordSet
|
21
|
+
from .typing import UserConfig
|
21
22
|
|
22
23
|
|
23
24
|
@dataclass
|
24
25
|
class Context:
|
25
|
-
"""
|
26
|
+
"""Context of your run.
|
26
27
|
|
27
28
|
Parameters
|
28
29
|
----------
|
30
|
+
run_id : int
|
31
|
+
The ID that identifies the run.
|
32
|
+
node_id : int
|
33
|
+
The ID that identifies the node.
|
34
|
+
node_config : UserConfig
|
35
|
+
A config (key/value mapping) unique to the node and independent of the
|
36
|
+
`run_config`. This config persists across all runs this node participates in.
|
29
37
|
state : RecordSet
|
30
|
-
Holds records added by the entity in a given
|
38
|
+
Holds records added by the entity in a given `run_id` and that will stay local.
|
31
39
|
This means that the data it holds will never leave the system it's running from.
|
32
40
|
This can be used as an intermediate storage or scratchpad when
|
33
41
|
executing mods. It can also be used as a memory to access
|
34
42
|
at different points during the lifecycle of this entity (e.g. across
|
35
43
|
multiple rounds)
|
44
|
+
run_config : UserConfig
|
45
|
+
A config (key/value mapping) held by the entity in a given `run_id` and that
|
46
|
+
will stay local. It can be used at any point during the lifecycle of this entity
|
47
|
+
(e.g. across multiple rounds)
|
36
48
|
"""
|
37
49
|
|
50
|
+
run_id: int
|
51
|
+
node_id: int
|
52
|
+
node_config: UserConfig
|
38
53
|
state: RecordSet
|
54
|
+
run_config: UserConfig
|
55
|
+
|
56
|
+
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
57
|
+
self,
|
58
|
+
run_id: int,
|
59
|
+
node_id: int,
|
60
|
+
node_config: UserConfig,
|
61
|
+
state: RecordSet,
|
62
|
+
run_config: UserConfig,
|
63
|
+
) -> None:
|
64
|
+
self.run_id = run_id
|
65
|
+
self.node_id = node_id
|
66
|
+
self.node_config = node_config
|
67
|
+
self.state = state
|
68
|
+
self.run_config = run_config
|
flwr/common/date.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2023 Flower Labs GmbH. All Rights Reserved.
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -15,9 +15,27 @@
|
|
15
15
|
"""Flower date utils."""
|
16
16
|
|
17
17
|
|
18
|
-
|
18
|
+
import datetime
|
19
19
|
|
20
20
|
|
21
|
-
def now() -> datetime:
|
21
|
+
def now() -> datetime.datetime:
|
22
22
|
"""Construct a datetime from time.time() with time zone set to UTC."""
|
23
|
-
return datetime.now(tz=timezone.utc)
|
23
|
+
return datetime.datetime.now(tz=datetime.timezone.utc)
|
24
|
+
|
25
|
+
|
26
|
+
def format_timedelta(td: datetime.timedelta) -> str:
|
27
|
+
"""Format a timedelta as a string."""
|
28
|
+
days = td.days
|
29
|
+
hours, remainder = divmod(td.seconds, 3600)
|
30
|
+
minutes, seconds = divmod(remainder, 60)
|
31
|
+
|
32
|
+
if days > 0:
|
33
|
+
return f"{days}d {hours:02}:{minutes:02}:{seconds:02}"
|
34
|
+
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
35
|
+
|
36
|
+
|
37
|
+
def isoformat8601_utc(dt: datetime.datetime) -> str:
|
38
|
+
"""Return the datetime formatted as an ISO 8601 string with a trailing 'Z'."""
|
39
|
+
if dt.tzinfo != datetime.timezone.utc:
|
40
|
+
raise ValueError("Expected datetime with timezone set to UTC")
|
41
|
+
return dt.isoformat(timespec="seconds").replace("+00:00", "Z")
|
@@ -16,7 +16,7 @@
|
|
16
16
|
|
17
17
|
|
18
18
|
from logging import WARNING
|
19
|
-
from typing import Optional
|
19
|
+
from typing import Optional
|
20
20
|
|
21
21
|
import numpy as np
|
22
22
|
|
@@ -125,7 +125,7 @@ def compute_adaptive_noise_params(
|
|
125
125
|
noise_multiplier: float,
|
126
126
|
num_sampled_clients: float,
|
127
127
|
clipped_count_stddev: Optional[float],
|
128
|
-
) ->
|
128
|
+
) -> tuple[float, float]:
|
129
129
|
"""Compute noising parameters for the adaptive clipping.
|
130
130
|
|
131
131
|
Paper: https://arxiv.org/abs/1905.03871
|
flwr/common/dp.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2022 Flower Labs GmbH. All Rights Reserved.
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -15,8 +15,6 @@
|
|
15
15
|
"""Building block functions for DP algorithms."""
|
16
16
|
|
17
17
|
|
18
|
-
from typing import Tuple
|
19
|
-
|
20
18
|
import numpy as np
|
21
19
|
|
22
20
|
from flwr.common.logger import warn_deprecated_feature
|
@@ -41,7 +39,7 @@ def add_gaussian_noise(update: NDArrays, std_dev: float) -> NDArrays:
|
|
41
39
|
return update_noised
|
42
40
|
|
43
41
|
|
44
|
-
def clip_by_l2(update: NDArrays, threshold: float) ->
|
42
|
+
def clip_by_l2(update: NDArrays, threshold: float) -> tuple[NDArrays, bool]:
|
45
43
|
"""Scales the update so thats its L2 norm is upper-bound to threshold."""
|
46
44
|
warn_deprecated_feature("`clip_by_l2` method")
|
47
45
|
update_norm = _get_update_norm(update)
|
flwr/common/exit_handlers.py
CHANGED
@@ -19,7 +19,7 @@ import sys
|
|
19
19
|
from signal import SIGINT, SIGTERM, signal
|
20
20
|
from threading import Thread
|
21
21
|
from types import FrameType
|
22
|
-
from typing import
|
22
|
+
from typing import Optional
|
23
23
|
|
24
24
|
from grpc import Server
|
25
25
|
|
@@ -28,8 +28,8 @@ from flwr.common.telemetry import EventType, event
|
|
28
28
|
|
29
29
|
def register_exit_handlers(
|
30
30
|
event_type: EventType,
|
31
|
-
grpc_servers: Optional[
|
32
|
-
bckg_threads: Optional[
|
31
|
+
grpc_servers: Optional[list[Server]] = None,
|
32
|
+
bckg_threads: Optional[list[Thread]] = None,
|
33
33
|
) -> None:
|
34
34
|
"""Register exit handlers for `SIGINT` and `SIGTERM` signals.
|
35
35
|
|
flwr/common/grpc.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2022 Flower Labs GmbH. All Rights Reserved.
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -15,21 +15,33 @@
|
|
15
15
|
"""Utility functions for gRPC."""
|
16
16
|
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
import concurrent.futures
|
19
|
+
import sys
|
20
|
+
from collections.abc import Sequence
|
21
|
+
from logging import DEBUG, ERROR
|
22
|
+
from typing import Any, Callable, Optional
|
20
23
|
|
21
24
|
import grpc
|
22
25
|
|
23
|
-
from
|
26
|
+
from .address import is_port_in_use
|
27
|
+
from .logger import log
|
24
28
|
|
25
29
|
GRPC_MAX_MESSAGE_LENGTH: int = 536_870_912 # == 512 * 1024 * 1024
|
26
30
|
|
31
|
+
INVALID_CERTIFICATES_ERR_MSG = """
|
32
|
+
When setting any of root_certificate, certificate, or private_key,
|
33
|
+
all of them need to be set.
|
34
|
+
"""
|
35
|
+
|
36
|
+
AddServicerToServerFn = Callable[..., Any]
|
37
|
+
|
27
38
|
|
28
39
|
def create_channel(
|
29
40
|
server_address: str,
|
30
41
|
insecure: bool,
|
31
42
|
root_certificates: Optional[bytes] = None,
|
32
43
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
44
|
+
interceptors: Optional[Sequence[grpc.UnaryUnaryClientInterceptor]] = None,
|
33
45
|
) -> grpc.Channel:
|
34
46
|
"""Create a gRPC channel, either secure or insecure."""
|
35
47
|
# Check for conflicting parameters
|
@@ -51,10 +63,157 @@ def create_channel(
|
|
51
63
|
channel = grpc.insecure_channel(server_address, options=channel_options)
|
52
64
|
log(DEBUG, "Opened insecure gRPC connection (no certificates were passed)")
|
53
65
|
else:
|
54
|
-
|
66
|
+
try:
|
67
|
+
ssl_channel_credentials = grpc.ssl_channel_credentials(root_certificates)
|
68
|
+
except Exception as e:
|
69
|
+
raise ValueError(f"Failed to create SSL channel credentials: {e}") from e
|
55
70
|
channel = grpc.secure_channel(
|
56
71
|
server_address, ssl_channel_credentials, options=channel_options
|
57
72
|
)
|
58
73
|
log(DEBUG, "Opened secure gRPC connection using certificates")
|
59
74
|
|
75
|
+
if interceptors is not None:
|
76
|
+
channel = grpc.intercept_channel(channel, interceptors)
|
77
|
+
|
60
78
|
return channel
|
79
|
+
|
80
|
+
|
81
|
+
def valid_certificates(certificates: tuple[bytes, bytes, bytes]) -> bool:
|
82
|
+
"""Validate certificates tuple."""
|
83
|
+
is_valid = (
|
84
|
+
all(isinstance(certificate, bytes) for certificate in certificates)
|
85
|
+
and len(certificates) == 3
|
86
|
+
)
|
87
|
+
|
88
|
+
if not is_valid:
|
89
|
+
log(ERROR, INVALID_CERTIFICATES_ERR_MSG)
|
90
|
+
|
91
|
+
return is_valid
|
92
|
+
|
93
|
+
|
94
|
+
def generic_create_grpc_server( # pylint: disable=too-many-arguments,R0917
|
95
|
+
servicer_and_add_fn: tuple[Any, AddServicerToServerFn],
|
96
|
+
server_address: str,
|
97
|
+
max_concurrent_workers: int = 1000,
|
98
|
+
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
99
|
+
keepalive_time_ms: int = 210000,
|
100
|
+
certificates: Optional[tuple[bytes, bytes, bytes]] = None,
|
101
|
+
interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None,
|
102
|
+
) -> grpc.Server:
|
103
|
+
"""Create a gRPC server with a single servicer.
|
104
|
+
|
105
|
+
Parameters
|
106
|
+
----------
|
107
|
+
servicer_and_add_fn : tuple
|
108
|
+
A tuple holding a servicer implementation and a matching
|
109
|
+
add_Servicer_to_server function.
|
110
|
+
server_address : str
|
111
|
+
Server address in the form of HOST:PORT e.g. "[::]:8080"
|
112
|
+
max_concurrent_workers : int
|
113
|
+
Maximum number of clients the server can process before returning
|
114
|
+
RESOURCE_EXHAUSTED status (default: 1000)
|
115
|
+
max_message_length : int
|
116
|
+
Maximum message length that the server can send or receive.
|
117
|
+
Int valued in bytes. -1 means unlimited. (default: GRPC_MAX_MESSAGE_LENGTH)
|
118
|
+
keepalive_time_ms : int
|
119
|
+
Flower uses a default gRPC keepalive time of 210000ms (3 minutes 30 seconds)
|
120
|
+
because some cloud providers (for example, Azure) agressively clean up idle
|
121
|
+
TCP connections by terminating them after some time (4 minutes in the case
|
122
|
+
of Azure). Flower does not use application-level keepalive signals and relies
|
123
|
+
on the assumption that the transport layer will fail in cases where the
|
124
|
+
connection is no longer active. `keepalive_time_ms` can be used to customize
|
125
|
+
the keepalive interval for specific environments. The default Flower gRPC
|
126
|
+
keepalive of 210000 ms (3 minutes 30 seconds) ensures that Flower can keep
|
127
|
+
the long running streaming connection alive in most environments. The actual
|
128
|
+
gRPC default of this setting is 7200000 (2 hours), which results in dropped
|
129
|
+
connections in some cloud environments.
|
130
|
+
|
131
|
+
These settings are related to the issue described here:
|
132
|
+
- https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md
|
133
|
+
- https://github.com/grpc/grpc/blob/master/doc/keepalive.md
|
134
|
+
- https://grpc.io/docs/guides/performance/
|
135
|
+
|
136
|
+
Mobile Flower clients may choose to increase this value if their server
|
137
|
+
environment allows long-running idle TCP connections.
|
138
|
+
(default: 210000)
|
139
|
+
certificates : Tuple[bytes, bytes, bytes] (default: None)
|
140
|
+
Tuple containing root certificate, server certificate, and private key to
|
141
|
+
start a secure SSL-enabled server. The tuple is expected to have three bytes
|
142
|
+
elements in the following order:
|
143
|
+
|
144
|
+
* CA certificate.
|
145
|
+
* server certificate.
|
146
|
+
* server private key.
|
147
|
+
interceptors : Optional[Sequence[grpc.ServerInterceptor]] (default: None)
|
148
|
+
A list of gRPC interceptors.
|
149
|
+
|
150
|
+
Returns
|
151
|
+
-------
|
152
|
+
server : grpc.Server
|
153
|
+
A non-running instance of a gRPC server.
|
154
|
+
"""
|
155
|
+
# Check if port is in use
|
156
|
+
if is_port_in_use(server_address):
|
157
|
+
sys.exit(f"Port in server address {server_address} is already in use.")
|
158
|
+
|
159
|
+
# Deconstruct tuple into servicer and function
|
160
|
+
servicer, add_servicer_to_server_fn = servicer_and_add_fn
|
161
|
+
|
162
|
+
# Possible options:
|
163
|
+
# https://github.com/grpc/grpc/blob/v1.43.x/include/grpc/impl/codegen/grpc_types.h
|
164
|
+
options = [
|
165
|
+
# Maximum number of concurrent incoming streams to allow on a http2
|
166
|
+
# connection. Int valued.
|
167
|
+
("grpc.max_concurrent_streams", max(100, max_concurrent_workers)),
|
168
|
+
# Maximum message length that the channel can send.
|
169
|
+
# Int valued, bytes. -1 means unlimited.
|
170
|
+
("grpc.max_send_message_length", max_message_length),
|
171
|
+
# Maximum message length that the channel can receive.
|
172
|
+
# Int valued, bytes. -1 means unlimited.
|
173
|
+
("grpc.max_receive_message_length", max_message_length),
|
174
|
+
# The gRPC default for this setting is 7200000 (2 hours). Flower uses a
|
175
|
+
# customized default of 210000 (3 minutes and 30 seconds) to improve
|
176
|
+
# compatibility with popular cloud providers. Mobile Flower clients may
|
177
|
+
# choose to increase this value if their server environment allows
|
178
|
+
# long-running idle TCP connections.
|
179
|
+
("grpc.keepalive_time_ms", keepalive_time_ms),
|
180
|
+
# Setting this to zero will allow sending unlimited keepalive pings in between
|
181
|
+
# sending actual data frames.
|
182
|
+
("grpc.http2.max_pings_without_data", 0),
|
183
|
+
# Is it permissible to send keepalive pings from the client without
|
184
|
+
# any outstanding streams. More explanation here:
|
185
|
+
# https://github.com/adap/flower/pull/2197
|
186
|
+
("grpc.keepalive_permit_without_calls", 0),
|
187
|
+
]
|
188
|
+
|
189
|
+
server = grpc.server(
|
190
|
+
concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent_workers),
|
191
|
+
# Set the maximum number of concurrent RPCs this server will service before
|
192
|
+
# returning RESOURCE_EXHAUSTED status, or None to indicate no limit.
|
193
|
+
maximum_concurrent_rpcs=max_concurrent_workers,
|
194
|
+
options=options,
|
195
|
+
interceptors=interceptors,
|
196
|
+
)
|
197
|
+
add_servicer_to_server_fn(servicer, server)
|
198
|
+
|
199
|
+
if certificates is not None:
|
200
|
+
if not valid_certificates(certificates):
|
201
|
+
sys.exit(1)
|
202
|
+
|
203
|
+
root_certificate_b, certificate_b, private_key_b = certificates
|
204
|
+
|
205
|
+
server_credentials = grpc.ssl_server_credentials(
|
206
|
+
((private_key_b, certificate_b),),
|
207
|
+
root_certificates=root_certificate_b,
|
208
|
+
# A boolean indicating whether or not to require clients to be
|
209
|
+
# authenticated. May only be True if root_certificates is not None.
|
210
|
+
# We are explicitly setting the current gRPC default to document
|
211
|
+
# the option. For further reference see:
|
212
|
+
# https://grpc.github.io/grpc/python/grpc.html#create-server-credentials
|
213
|
+
require_client_auth=False,
|
214
|
+
)
|
215
|
+
server.add_secure_port(server_address, server_credentials)
|
216
|
+
else:
|
217
|
+
server.add_insecure_port(server_address)
|
218
|
+
|
219
|
+
return server
|
flwr/common/logger.py
CHANGED
@@ -15,10 +15,28 @@
|
|
15
15
|
"""Flower Logger."""
|
16
16
|
|
17
17
|
|
18
|
+
import json as _json
|
18
19
|
import logging
|
20
|
+
import re
|
21
|
+
import sys
|
22
|
+
import threading
|
23
|
+
import time
|
24
|
+
from io import StringIO
|
19
25
|
from logging import WARN, LogRecord
|
20
26
|
from logging.handlers import HTTPHandler
|
21
|
-
from
|
27
|
+
from queue import Empty, Queue
|
28
|
+
from typing import TYPE_CHECKING, Any, Optional, TextIO, Union
|
29
|
+
|
30
|
+
import grpc
|
31
|
+
import typer
|
32
|
+
from rich.console import Console
|
33
|
+
|
34
|
+
from flwr.proto.log_pb2 import PushLogsRequest # pylint: disable=E0611
|
35
|
+
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
36
|
+
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub # pylint: disable=E0611
|
37
|
+
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
|
38
|
+
|
39
|
+
from .constant import LOG_UPLOAD_INTERVAL
|
22
40
|
|
23
41
|
# Create logger
|
24
42
|
LOGGER_NAME = "flwr"
|
@@ -82,13 +100,20 @@ class ConsoleHandler(StreamHandler):
|
|
82
100
|
return formatter.format(record)
|
83
101
|
|
84
102
|
|
85
|
-
def update_console_handler(
|
103
|
+
def update_console_handler(
|
104
|
+
level: Optional[int] = None,
|
105
|
+
timestamps: Optional[bool] = None,
|
106
|
+
colored: Optional[bool] = None,
|
107
|
+
) -> None:
|
86
108
|
"""Update the logging handler."""
|
87
109
|
for handler in logging.getLogger(LOGGER_NAME).handlers:
|
88
110
|
if isinstance(handler, ConsoleHandler):
|
89
|
-
|
90
|
-
|
91
|
-
|
111
|
+
if level is not None:
|
112
|
+
handler.setLevel(level)
|
113
|
+
if timestamps is not None:
|
114
|
+
handler.timestamps = timestamps
|
115
|
+
if colored is not None:
|
116
|
+
handler.colored = colored
|
92
117
|
|
93
118
|
|
94
119
|
# Configure console logger
|
@@ -104,7 +129,7 @@ FLOWER_LOGGER.addHandler(console_handler)
|
|
104
129
|
class CustomHTTPHandler(HTTPHandler):
|
105
130
|
"""Custom HTTPHandler which overrides the mapLogRecords method."""
|
106
131
|
|
107
|
-
# pylint: disable=too-many-arguments,bad-option-value,R1725
|
132
|
+
# pylint: disable=too-many-arguments,bad-option-value,R1725,R0917
|
108
133
|
def __init__(
|
109
134
|
self,
|
110
135
|
identifier: str,
|
@@ -112,12 +137,12 @@ class CustomHTTPHandler(HTTPHandler):
|
|
112
137
|
url: str,
|
113
138
|
method: str = "GET",
|
114
139
|
secure: bool = False,
|
115
|
-
credentials: Optional[
|
140
|
+
credentials: Optional[tuple[str, str]] = None,
|
116
141
|
) -> None:
|
117
142
|
super().__init__(host, url, method, secure, credentials)
|
118
143
|
self.identifier = identifier
|
119
144
|
|
120
|
-
def mapLogRecord(self, record: LogRecord) ->
|
145
|
+
def mapLogRecord(self, record: LogRecord) -> dict[str, Any]:
|
121
146
|
"""Filter for the properties to be send to the logserver."""
|
122
147
|
record_dict = record.__dict__
|
123
148
|
return {
|
@@ -164,13 +189,13 @@ logger = logging.getLogger(LOGGER_NAME) # pylint: disable=invalid-name
|
|
164
189
|
log = logger.log # pylint: disable=invalid-name
|
165
190
|
|
166
191
|
|
167
|
-
def
|
168
|
-
"""Warn the user when they use
|
192
|
+
def warn_preview_feature(name: str) -> None:
|
193
|
+
"""Warn the user when they use a preview feature."""
|
169
194
|
log(
|
170
195
|
WARN,
|
171
|
-
"""
|
196
|
+
"""PREVIEW FEATURE: %s
|
172
197
|
|
173
|
-
This is
|
198
|
+
This is a preview feature. It could change significantly or be removed
|
174
199
|
entirely in future versions of Flower.
|
175
200
|
""",
|
176
201
|
name,
|
@@ -188,3 +213,196 @@ def warn_deprecated_feature(name: str) -> None:
|
|
188
213
|
""",
|
189
214
|
name,
|
190
215
|
)
|
216
|
+
|
217
|
+
|
218
|
+
def warn_deprecated_feature_with_example(
|
219
|
+
deprecation_message: str, example_message: str, code_example: str
|
220
|
+
) -> None:
|
221
|
+
"""Warn if a feature is deprecated and show code example."""
|
222
|
+
log(
|
223
|
+
WARN,
|
224
|
+
"""DEPRECATED FEATURE: %s
|
225
|
+
|
226
|
+
Check the following `FEATURE UPDATE` warning message for the preferred
|
227
|
+
new mechanism to use this feature in Flower.
|
228
|
+
""",
|
229
|
+
deprecation_message,
|
230
|
+
)
|
231
|
+
log(
|
232
|
+
WARN,
|
233
|
+
"""FEATURE UPDATE: %s
|
234
|
+
------------------------------------------------------------
|
235
|
+
%s
|
236
|
+
------------------------------------------------------------
|
237
|
+
""",
|
238
|
+
example_message,
|
239
|
+
code_example,
|
240
|
+
)
|
241
|
+
|
242
|
+
|
243
|
+
def warn_unsupported_feature(name: str) -> None:
|
244
|
+
"""Warn the user when they use an unsupported feature."""
|
245
|
+
log(
|
246
|
+
WARN,
|
247
|
+
"""UNSUPPORTED FEATURE: %s
|
248
|
+
|
249
|
+
This is an unsupported feature. It will be removed
|
250
|
+
entirely in future versions of Flower.
|
251
|
+
""",
|
252
|
+
name,
|
253
|
+
)
|
254
|
+
|
255
|
+
|
256
|
+
def set_logger_propagation(
|
257
|
+
child_logger: logging.Logger, value: bool = True
|
258
|
+
) -> logging.Logger:
|
259
|
+
"""Set the logger propagation attribute.
|
260
|
+
|
261
|
+
Parameters
|
262
|
+
----------
|
263
|
+
child_logger : logging.Logger
|
264
|
+
Child logger object
|
265
|
+
value : bool
|
266
|
+
Boolean setting for propagation. If True, both parent and child logger
|
267
|
+
display messages. Otherwise, only the child logger displays a message.
|
268
|
+
This False setting prevents duplicate logs in Colab notebooks.
|
269
|
+
Reference: https://stackoverflow.com/a/19561320
|
270
|
+
|
271
|
+
Returns
|
272
|
+
-------
|
273
|
+
logging.Logger
|
274
|
+
Child logger object with updated propagation setting
|
275
|
+
"""
|
276
|
+
child_logger.propagate = value
|
277
|
+
if not child_logger.propagate:
|
278
|
+
child_logger.log(logging.DEBUG, "Logger propagate set to False")
|
279
|
+
return child_logger
|
280
|
+
|
281
|
+
|
282
|
+
def mirror_output_to_queue(log_queue: Queue[Optional[str]]) -> None:
|
283
|
+
"""Mirror stdout and stderr output to the provided queue."""
|
284
|
+
|
285
|
+
def get_write_fn(stream: TextIO) -> Any:
|
286
|
+
original_write = stream.write
|
287
|
+
|
288
|
+
def fn(s: str) -> int:
|
289
|
+
ret = original_write(s)
|
290
|
+
stream.flush()
|
291
|
+
log_queue.put(s)
|
292
|
+
return ret
|
293
|
+
|
294
|
+
return fn
|
295
|
+
|
296
|
+
sys.stdout.write = get_write_fn(sys.stdout) # type: ignore[method-assign]
|
297
|
+
sys.stderr.write = get_write_fn(sys.stderr) # type: ignore[method-assign]
|
298
|
+
console_handler.stream = sys.stdout
|
299
|
+
|
300
|
+
|
301
|
+
def restore_output() -> None:
|
302
|
+
"""Restore stdout and stderr.
|
303
|
+
|
304
|
+
This will stop mirroring output to queues.
|
305
|
+
"""
|
306
|
+
sys.stdout = sys.__stdout__
|
307
|
+
sys.stderr = sys.__stderr__
|
308
|
+
console_handler.stream = sys.stdout
|
309
|
+
|
310
|
+
|
311
|
+
def redirect_output(output_buffer: StringIO) -> None:
|
312
|
+
"""Redirect stdout and stderr to text I/O buffer."""
|
313
|
+
sys.stdout = output_buffer
|
314
|
+
sys.stderr = output_buffer
|
315
|
+
console_handler.stream = sys.stdout
|
316
|
+
|
317
|
+
|
318
|
+
def _log_uploader(
|
319
|
+
log_queue: Queue[Optional[str]], node_id: int, run_id: int, stub: ServerAppIoStub
|
320
|
+
) -> None:
|
321
|
+
"""Upload logs to the SuperLink."""
|
322
|
+
exit_flag = False
|
323
|
+
node = Node(node_id=node_id)
|
324
|
+
msgs: list[str] = []
|
325
|
+
while True:
|
326
|
+
# Fetch all messages from the queue
|
327
|
+
try:
|
328
|
+
while True:
|
329
|
+
msg = log_queue.get_nowait()
|
330
|
+
# Quit the loops if the returned message is `None`
|
331
|
+
# This is a signal that the run has finished
|
332
|
+
if msg is None:
|
333
|
+
exit_flag = True
|
334
|
+
break
|
335
|
+
msgs.append(msg)
|
336
|
+
except Empty:
|
337
|
+
pass
|
338
|
+
|
339
|
+
# Upload if any logs
|
340
|
+
if msgs:
|
341
|
+
req = PushLogsRequest(
|
342
|
+
node=node,
|
343
|
+
run_id=run_id,
|
344
|
+
logs=msgs,
|
345
|
+
)
|
346
|
+
try:
|
347
|
+
stub.PushLogs(req)
|
348
|
+
msgs.clear()
|
349
|
+
except grpc.RpcError as e:
|
350
|
+
# Ignore minor network errors
|
351
|
+
# pylint: disable-next=no-member
|
352
|
+
if e.code() != grpc.StatusCode.UNAVAILABLE:
|
353
|
+
raise e
|
354
|
+
|
355
|
+
if exit_flag:
|
356
|
+
break
|
357
|
+
|
358
|
+
time.sleep(LOG_UPLOAD_INTERVAL)
|
359
|
+
|
360
|
+
|
361
|
+
def start_log_uploader(
|
362
|
+
log_queue: Queue[Optional[str]],
|
363
|
+
node_id: int,
|
364
|
+
run_id: int,
|
365
|
+
stub: Union[ServerAppIoStub, SimulationIoStub],
|
366
|
+
) -> threading.Thread:
|
367
|
+
"""Start the log uploader thread and return it."""
|
368
|
+
thread = threading.Thread(
|
369
|
+
target=_log_uploader, args=(log_queue, node_id, run_id, stub)
|
370
|
+
)
|
371
|
+
thread.start()
|
372
|
+
return thread
|
373
|
+
|
374
|
+
|
375
|
+
def stop_log_uploader(
|
376
|
+
log_queue: Queue[Optional[str]], log_uploader: threading.Thread
|
377
|
+
) -> None:
|
378
|
+
"""Stop the log uploader thread."""
|
379
|
+
log_queue.put(None)
|
380
|
+
log_uploader.join()
|
381
|
+
|
382
|
+
|
383
|
+
def _remove_emojis(text: str) -> str:
|
384
|
+
"""Remove emojis from the provided text."""
|
385
|
+
emoji_pattern = re.compile(
|
386
|
+
"["
|
387
|
+
"\U0001F600-\U0001F64F" # Emoticons
|
388
|
+
"\U0001F300-\U0001F5FF" # Symbols & Pictographs
|
389
|
+
"\U0001F680-\U0001F6FF" # Transport & Map Symbols
|
390
|
+
"\U0001F1E0-\U0001F1FF" # Flags
|
391
|
+
"\U00002702-\U000027B0" # Dingbats
|
392
|
+
"\U000024C2-\U0001F251"
|
393
|
+
"]+",
|
394
|
+
flags=re.UNICODE,
|
395
|
+
)
|
396
|
+
return emoji_pattern.sub(r"", text)
|
397
|
+
|
398
|
+
|
399
|
+
def print_json_error(msg: str, e: Union[typer.Exit, Exception]) -> None:
|
400
|
+
"""Print error message as JSON."""
|
401
|
+
Console().print_json(
|
402
|
+
_json.dumps(
|
403
|
+
{
|
404
|
+
"success": False,
|
405
|
+
"error-message": _remove_emojis(str(msg) + "\n" + str(e)),
|
406
|
+
}
|
407
|
+
)
|
408
|
+
)
|