flwr 1.13.0__py3-none-any.whl → 1.14.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.
Files changed (120) hide show
  1. flwr/cli/app.py +5 -0
  2. flwr/cli/build.py +1 -37
  3. flwr/cli/cli_user_auth_interceptor.py +86 -0
  4. flwr/cli/config_utils.py +19 -2
  5. flwr/cli/example.py +1 -0
  6. flwr/cli/install.py +2 -19
  7. flwr/cli/log.py +18 -36
  8. flwr/cli/login/__init__.py +22 -0
  9. flwr/cli/login/login.py +81 -0
  10. flwr/cli/ls.py +205 -106
  11. flwr/cli/new/__init__.py +1 -0
  12. flwr/cli/new/new.py +25 -14
  13. flwr/cli/new/templates/app/.gitignore.tpl +3 -0
  14. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  15. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +3 -3
  16. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  17. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  18. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +2 -3
  19. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  20. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  21. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  22. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  23. flwr/cli/run/__init__.py +1 -0
  24. flwr/cli/run/run.py +89 -39
  25. flwr/cli/stop.py +130 -0
  26. flwr/cli/utils.py +172 -8
  27. flwr/client/app.py +14 -3
  28. flwr/client/client.py +1 -32
  29. flwr/client/clientapp/app.py +4 -8
  30. flwr/client/clientapp/utils.py +1 -0
  31. flwr/client/grpc_adapter_client/connection.py +1 -1
  32. flwr/client/grpc_client/connection.py +1 -1
  33. flwr/client/grpc_rere_client/connection.py +13 -7
  34. flwr/client/message_handler/message_handler.py +1 -2
  35. flwr/client/mod/comms_mods.py +1 -0
  36. flwr/client/mod/localdp_mod.py +1 -1
  37. flwr/client/nodestate/__init__.py +1 -0
  38. flwr/client/nodestate/nodestate.py +1 -0
  39. flwr/client/nodestate/nodestate_factory.py +1 -0
  40. flwr/client/numpy_client.py +0 -44
  41. flwr/client/rest_client/connection.py +3 -3
  42. flwr/client/supernode/app.py +2 -2
  43. flwr/common/address.py +1 -0
  44. flwr/common/args.py +1 -0
  45. flwr/common/auth_plugin/__init__.py +24 -0
  46. flwr/common/auth_plugin/auth_plugin.py +111 -0
  47. flwr/common/config.py +3 -1
  48. flwr/common/constant.py +17 -1
  49. flwr/common/logger.py +40 -0
  50. flwr/common/message.py +1 -0
  51. flwr/common/object_ref.py +57 -54
  52. flwr/common/pyproject.py +1 -0
  53. flwr/common/record/__init__.py +1 -0
  54. flwr/common/record/parametersrecord.py +1 -0
  55. flwr/common/retry_invoker.py +77 -0
  56. flwr/common/secure_aggregation/secaggplus_utils.py +2 -2
  57. flwr/common/telemetry.py +15 -4
  58. flwr/common/typing.py +12 -0
  59. flwr/common/version.py +1 -0
  60. flwr/proto/exec_pb2.py +38 -14
  61. flwr/proto/exec_pb2.pyi +107 -2
  62. flwr/proto/exec_pb2_grpc.py +102 -0
  63. flwr/proto/exec_pb2_grpc.pyi +39 -0
  64. flwr/proto/fab_pb2.py +4 -4
  65. flwr/proto/fab_pb2.pyi +4 -1
  66. flwr/proto/serverappio_pb2.py +18 -18
  67. flwr/proto/serverappio_pb2.pyi +8 -2
  68. flwr/proto/serverappio_pb2_grpc.py +34 -0
  69. flwr/proto/serverappio_pb2_grpc.pyi +13 -0
  70. flwr/proto/simulationio_pb2.py +2 -2
  71. flwr/proto/simulationio_pb2_grpc.py +34 -0
  72. flwr/proto/simulationio_pb2_grpc.pyi +13 -0
  73. flwr/server/app.py +62 -7
  74. flwr/server/compat/app_utils.py +7 -1
  75. flwr/server/driver/grpc_driver.py +11 -63
  76. flwr/server/driver/inmemory_driver.py +5 -1
  77. flwr/server/run_serverapp.py +8 -9
  78. flwr/server/serverapp/app.py +25 -10
  79. flwr/server/strategy/dpfedavg_fixed.py +1 -0
  80. flwr/server/superlink/driver/serverappio_grpc.py +1 -0
  81. flwr/server/superlink/driver/serverappio_servicer.py +82 -23
  82. flwr/server/superlink/ffs/disk_ffs.py +1 -0
  83. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -0
  84. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -0
  85. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +32 -12
  86. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +12 -11
  87. flwr/server/superlink/fleet/message_handler/message_handler.py +32 -5
  88. flwr/server/superlink/fleet/rest_rere/rest_api.py +4 -1
  89. flwr/server/superlink/fleet/vce/__init__.py +1 -0
  90. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -0
  91. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -0
  92. flwr/server/superlink/linkstate/in_memory_linkstate.py +21 -30
  93. flwr/server/superlink/linkstate/linkstate.py +17 -2
  94. flwr/server/superlink/linkstate/sqlite_linkstate.py +30 -49
  95. flwr/server/superlink/simulation/simulationio_servicer.py +33 -0
  96. flwr/server/superlink/utils.py +65 -0
  97. flwr/simulation/app.py +59 -52
  98. flwr/simulation/ray_transport/ray_actor.py +1 -0
  99. flwr/simulation/ray_transport/utils.py +1 -0
  100. flwr/simulation/run_simulation.py +36 -22
  101. flwr/simulation/simulationio_connection.py +3 -0
  102. flwr/superexec/app.py +1 -0
  103. flwr/superexec/deployment.py +1 -0
  104. flwr/superexec/exec_grpc.py +19 -1
  105. flwr/superexec/exec_servicer.py +76 -2
  106. flwr/superexec/exec_user_auth_interceptor.py +101 -0
  107. flwr/superexec/executor.py +1 -0
  108. {flwr-1.13.0.dist-info → flwr-1.14.0.dist-info}/METADATA +8 -8
  109. {flwr-1.13.0.dist-info → flwr-1.14.0.dist-info}/RECORD +112 -112
  110. flwr/proto/common_pb2.py +0 -36
  111. flwr/proto/common_pb2.pyi +0 -121
  112. flwr/proto/common_pb2_grpc.py +0 -4
  113. flwr/proto/common_pb2_grpc.pyi +0 -4
  114. flwr/proto/control_pb2.py +0 -27
  115. flwr/proto/control_pb2.pyi +0 -7
  116. flwr/proto/control_pb2_grpc.py +0 -135
  117. flwr/proto/control_pb2_grpc.pyi +0 -53
  118. {flwr-1.13.0.dist-info → flwr-1.14.0.dist-info}/LICENSE +0 -0
  119. {flwr-1.13.0.dist-info → flwr-1.14.0.dist-info}/WHEEL +0 -0
  120. {flwr-1.13.0.dist-info → flwr-1.14.0.dist-info}/entry_points.txt +0 -0
@@ -48,7 +48,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
48
48
  Optional[Callable[[], Optional[int]]],
49
49
  Optional[Callable[[], None]],
50
50
  Optional[Callable[[int], Run]],
51
- Optional[Callable[[str], Fab]],
51
+ Optional[Callable[[str, int], Fab]],
52
52
  ]
53
53
  ]:
54
54
  """Primitives for request/response-based interaction with a server via GrpcAdapter.
@@ -76,7 +76,7 @@ def grpc_connection( # pylint: disable=R0913,R0915,too-many-positional-argument
76
76
  Optional[Callable[[], Optional[int]]],
77
77
  Optional[Callable[[], None]],
78
78
  Optional[Callable[[int], Run]],
79
- Optional[Callable[[str], Fab]],
79
+ Optional[Callable[[str, int], Fab]],
80
80
  ]
81
81
  ]:
82
82
  """Establish a gRPC connection to a gRPC server.
@@ -42,7 +42,7 @@ from flwr.common.logger import log
42
42
  from flwr.common.message import Message, Metadata
43
43
  from flwr.common.retry_invoker import RetryInvoker
44
44
  from flwr.common.serde import message_from_taskins, message_to_taskres, run_from_proto
45
- from flwr.common.typing import Fab, Run
45
+ from flwr.common.typing import Fab, Run, RunNotRunningException
46
46
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
47
47
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
48
48
  CreateNodeRequest,
@@ -84,7 +84,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
84
84
  Optional[Callable[[], Optional[int]]],
85
85
  Optional[Callable[[], None]],
86
86
  Optional[Callable[[int], Run]],
87
- Optional[Callable[[str], Fab]],
87
+ Optional[Callable[[str, int], Fab]],
88
88
  ]
89
89
  ]:
90
90
  """Primitives for request/response-based interaction with a server.
@@ -155,10 +155,16 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
155
155
  ping_thread: Optional[threading.Thread] = None
156
156
  ping_stop_event = threading.Event()
157
157
 
158
+ def _should_giveup_fn(e: Exception) -> bool:
159
+ if e.code() == grpc.StatusCode.PERMISSION_DENIED: # type: ignore
160
+ raise RunNotRunningException
161
+ if e.code() == grpc.StatusCode.UNAVAILABLE: # type: ignore
162
+ return False
163
+ return True
164
+
158
165
  # Restrict retries to cases where the status code is UNAVAILABLE
159
- retry_invoker.should_giveup = (
160
- lambda e: e.code() != grpc.StatusCode.UNAVAILABLE # type: ignore
161
- )
166
+ # If the status code is PERMISSION_DENIED, additionally raise RunNotRunningException
167
+ retry_invoker.should_giveup = _should_giveup_fn
162
168
 
163
169
  ###########################################################################
164
170
  # ping/create_node/delete_node/receive/send/get_run functions
@@ -290,9 +296,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
290
296
  # Return fab_id and fab_version
291
297
  return run_from_proto(get_run_response.run)
292
298
 
293
- def get_fab(fab_hash: str) -> Fab:
299
+ def get_fab(fab_hash: str, run_id: int) -> Fab:
294
300
  # Call FleetAPI
295
- get_fab_request = GetFabRequest(node=node, hash_str=fab_hash)
301
+ get_fab_request = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
296
302
  get_fab_response: GetFabResponse = retry_invoker.invoke(
297
303
  stub.GetFab,
298
304
  request=get_fab_request,
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Client-side message handler."""
16
16
 
17
+
17
18
  from logging import WARN
18
19
  from typing import Optional, cast
19
20
 
@@ -104,8 +105,6 @@ def handle_legacy_message_from_msgtype(
104
105
  "Please use `NumPyClient.to_client()` method to convert it to `Client`.",
105
106
  )
106
107
 
107
- client.set_context(context)
108
-
109
108
  message_type = message.metadata.message_type
110
109
 
111
110
  # Handle GetPropertiesIns
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Mods that report statistics about message communication."""
16
16
 
17
+
17
18
  from logging import INFO
18
19
 
19
20
  import numpy as np
@@ -136,7 +136,7 @@ class LocalDpMod:
136
136
  fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
137
137
 
138
138
  # Add noise to model params
139
- add_localdp_gaussian_noise_to_params(
139
+ fit_res.parameters = add_localdp_gaussian_noise_to_params(
140
140
  fit_res.parameters, self.sensitivity, self.epsilon, self.delta
141
141
  )
142
142
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower NodeState."""
16
16
 
17
+
17
18
  from .in_memory_nodestate import InMemoryNodeState as InMemoryNodeState
18
19
  from .nodestate import NodeState as NodeState
19
20
  from .nodestate_factory import NodeStateFactory as NodeStateFactory
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Abstract base class NodeState."""
16
16
 
17
+
17
18
  import abc
18
19
  from typing import Optional
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Factory class that creates NodeState instances."""
16
16
 
17
+
17
18
  import threading
18
19
  from typing import Optional
19
20
 
@@ -21,13 +21,11 @@ from typing import Callable
21
21
  from flwr.client.client import Client
22
22
  from flwr.common import (
23
23
  Config,
24
- Context,
25
24
  NDArrays,
26
25
  Scalar,
27
26
  ndarrays_to_parameters,
28
27
  parameters_to_ndarrays,
29
28
  )
30
- from flwr.common.logger import warn_deprecated_feature_with_example
31
29
  from flwr.common.typing import (
32
30
  Code,
33
31
  EvaluateIns,
@@ -71,8 +69,6 @@ Example
71
69
  class NumPyClient(ABC):
72
70
  """Abstract base class for Flower clients using NumPy."""
73
71
 
74
- _context: Context
75
-
76
72
  def get_properties(self, config: Config) -> dict[str, Scalar]:
77
73
  """Return a client's set of properties.
78
74
 
@@ -175,34 +171,6 @@ class NumPyClient(ABC):
175
171
  _ = (self, parameters, config)
176
172
  return 0.0, 0, {}
177
173
 
178
- @property
179
- def context(self) -> Context:
180
- """Getter for `Context` client attribute."""
181
- warn_deprecated_feature_with_example(
182
- "Accessing the context via the client's attribute is deprecated.",
183
- example_message="Instead, pass it to the client's "
184
- "constructor in your `client_fn()` which already "
185
- "receives a context object.",
186
- code_example="def client_fn(context: Context) -> Client:\n\n"
187
- "\t\t# Your existing client_fn\n\n"
188
- "\t\t# Pass `context` to the constructor\n"
189
- "\t\treturn FlowerClient(context).to_client()",
190
- )
191
- return self._context
192
-
193
- @context.setter
194
- def context(self, context: Context) -> None:
195
- """Setter for `Context` client attribute."""
196
- self._context = context
197
-
198
- def get_context(self) -> Context:
199
- """Get the run context from this client."""
200
- return self.context
201
-
202
- def set_context(self, context: Context) -> None:
203
- """Apply a run context to this client."""
204
- self.context = context
205
-
206
174
  def to_client(self) -> Client:
207
175
  """Convert to object to Client type and return it."""
208
176
  return _wrap_numpy_client(client=self)
@@ -299,21 +267,9 @@ def _evaluate(self: Client, ins: EvaluateIns) -> EvaluateRes:
299
267
  )
300
268
 
301
269
 
302
- def _get_context(self: Client) -> Context:
303
- """Return context of underlying NumPyClient."""
304
- return self.numpy_client.get_context() # type: ignore
305
-
306
-
307
- def _set_context(self: Client, context: Context) -> None:
308
- """Apply context to underlying NumPyClient."""
309
- self.numpy_client.set_context(context) # type: ignore
310
-
311
-
312
270
  def _wrap_numpy_client(client: NumPyClient) -> Client:
313
271
  member_dict: dict[str, Callable] = { # type: ignore
314
272
  "__init__": _constructor,
315
- "get_context": _get_context,
316
- "set_context": _set_context,
317
273
  }
318
274
 
319
275
  # Add wrapper type methods (if overridden)
@@ -96,7 +96,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
96
96
  Optional[Callable[[], Optional[int]]],
97
97
  Optional[Callable[[], None]],
98
98
  Optional[Callable[[int], Run]],
99
- Optional[Callable[[str], Fab]],
99
+ Optional[Callable[[str, int], Fab]],
100
100
  ]
101
101
  ]:
102
102
  """Primitives for request/response-based interaction with a server.
@@ -361,9 +361,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
361
361
 
362
362
  return run_from_proto(res.run)
363
363
 
364
- def get_fab(fab_hash: str) -> Fab:
364
+ def get_fab(fab_hash: str, run_id: int) -> Fab:
365
365
  # Construct the request
366
- req = GetFabRequest(node=node, hash_str=fab_hash)
366
+ req = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
367
367
 
368
368
  # Send the request
369
369
  res = _request(req, GetFabResponse, PATH_GET_FAB)
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower SuperNode."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  from logging import DEBUG, ERROR, INFO, WARN
@@ -113,9 +114,8 @@ def run_client_app() -> None:
113
114
  event(EventType.RUN_CLIENT_APP_ENTER)
114
115
  log(
115
116
  ERROR,
116
- "The command `flower-client-app` has been replaced by `flower-supernode`.",
117
+ "The command `flower-client-app` has been replaced by `flwr run`.",
117
118
  )
118
- log(INFO, "Execute `flower-supernode --help` to learn how to use it.")
119
119
  register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE)
120
120
 
121
121
 
flwr/common/address.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower IP address utils."""
16
16
 
17
+
17
18
  import socket
18
19
  from ipaddress import ip_address
19
20
  from typing import Optional
flwr/common/args.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Common Flower arguments."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  from logging import DEBUG, ERROR, WARN
@@ -0,0 +1,24 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Auth plugin components."""
16
+
17
+
18
+ from .auth_plugin import CliAuthPlugin as CliAuthPlugin
19
+ from .auth_plugin import ExecAuthPlugin as ExecAuthPlugin
20
+
21
+ __all__ = [
22
+ "CliAuthPlugin",
23
+ "ExecAuthPlugin",
24
+ ]
@@ -0,0 +1,111 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Abstract classes for Flower User Auth Plugin."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from collections.abc import Sequence
20
+ from pathlib import Path
21
+ from typing import Any, Optional, Union
22
+
23
+ from flwr.proto.exec_pb2_grpc import ExecStub
24
+
25
+
26
+ class ExecAuthPlugin(ABC):
27
+ """Abstract Flower Auth Plugin class for ExecServicer.
28
+
29
+ Parameters
30
+ ----------
31
+ config : dict[str, Any]
32
+ The authentication configuration loaded from a YAML file.
33
+ """
34
+
35
+ @abstractmethod
36
+ def __init__(self, config: dict[str, Any]):
37
+ """Abstract constructor."""
38
+
39
+ @abstractmethod
40
+ def get_login_details(self) -> dict[str, str]:
41
+ """Get the login details."""
42
+
43
+ @abstractmethod
44
+ def validate_tokens_in_metadata(
45
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
46
+ ) -> bool:
47
+ """Validate authentication tokens in the provided metadata."""
48
+
49
+ @abstractmethod
50
+ def get_auth_tokens(self, auth_details: dict[str, str]) -> dict[str, str]:
51
+ """Get authentication tokens."""
52
+
53
+ @abstractmethod
54
+ def refresh_tokens(
55
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
56
+ ) -> Optional[Sequence[tuple[str, Union[str, bytes]]]]:
57
+ """Refresh authentication tokens in the provided metadata."""
58
+
59
+
60
+ class CliAuthPlugin(ABC):
61
+ """Abstract Flower Auth Plugin class for CLI.
62
+
63
+ Parameters
64
+ ----------
65
+ user_auth_config_path : Path
66
+ The path to the user's authentication configuration file.
67
+ """
68
+
69
+ @staticmethod
70
+ @abstractmethod
71
+ def login(
72
+ login_details: dict[str, str],
73
+ exec_stub: ExecStub,
74
+ ) -> dict[str, Any]:
75
+ """Authenticate the user with the SuperLink.
76
+
77
+ Parameters
78
+ ----------
79
+ login_details : dict[str, str]
80
+ A dictionary containing the user's login details.
81
+ exec_stub : ExecStub
82
+ An instance of `ExecStub` used for communication with the SuperLink.
83
+
84
+ Returns
85
+ -------
86
+ user_auth_config : dict[str, Any]
87
+ A dictionary containing the user's authentication configuration
88
+ in JSON format.
89
+ """
90
+
91
+ @abstractmethod
92
+ def __init__(self, user_auth_config_path: Path):
93
+ """Abstract constructor."""
94
+
95
+ @abstractmethod
96
+ def store_tokens(self, user_auth_config: dict[str, Any]) -> None:
97
+ """Store authentication tokens from the provided user_auth_config.
98
+
99
+ The configuration, including tokens, will be saved as a JSON file
100
+ at `user_auth_config_path`.
101
+ """
102
+
103
+ @abstractmethod
104
+ def load_tokens(self) -> None:
105
+ """Load authentication tokens from the user_auth_config_path."""
106
+
107
+ @abstractmethod
108
+ def write_tokens_to_metadata(
109
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
110
+ ) -> Sequence[tuple[str, Union[str, bytes]]]:
111
+ """Write authentication tokens to the provided metadata."""
flwr/common/config.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Provide functions for managing global Flower config."""
16
16
 
17
+
17
18
  import os
18
19
  import re
19
20
  from pathlib import Path
@@ -27,6 +28,7 @@ from flwr.common.constant import (
27
28
  APP_DIR,
28
29
  FAB_CONFIG_FILE,
29
30
  FAB_HASH_TRUNCATION,
31
+ FLWR_DIR,
30
32
  FLWR_HOME,
31
33
  )
32
34
  from flwr.common.typing import Run, UserConfig, UserConfigValue
@@ -38,7 +40,7 @@ def get_flwr_dir(provided_path: Optional[str] = None) -> Path:
38
40
  return Path(
39
41
  os.getenv(
40
42
  FLWR_HOME,
41
- Path(f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}") / ".flwr",
43
+ Path(f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}") / FLWR_DIR,
42
44
  )
43
45
  )
44
46
  return Path(provided_path).absolute()
flwr/common/constant.py CHANGED
@@ -80,7 +80,8 @@ FAB_ALLOWED_EXTENSIONS = {".py", ".toml", ".md"}
80
80
  FAB_CONFIG_FILE = "pyproject.toml"
81
81
  FAB_DATE = (2024, 10, 1, 0, 0, 0)
82
82
  FAB_HASH_TRUNCATION = 8
83
- FLWR_HOME = "FLWR_HOME"
83
+ FLWR_DIR = ".flwr" # The default Flower directory: ~/.flwr/
84
+ FLWR_HOME = "FLWR_HOME" # If set, override the default Flower directory
84
85
 
85
86
  # Constants entries in Node config for Simulation
86
87
  PARTITION_ID_KEY = "partition-id"
@@ -110,6 +111,10 @@ LOG_UPLOAD_INTERVAL = 0.2 # Minimum interval between two log uploads
110
111
  # Retry configurations
111
112
  MAX_RETRY_DELAY = 20 # Maximum delay duration between two consecutive retries.
112
113
 
114
+ # Constants for user authentication
115
+ CREDENTIALS_DIR = ".credentials"
116
+ AUTH_TYPE = "auth_type"
117
+
113
118
 
114
119
  class MessageType:
115
120
  """Message type."""
@@ -181,3 +186,14 @@ class SubStatus:
181
186
  def __new__(cls) -> SubStatus:
182
187
  """Prevent instantiation."""
183
188
  raise TypeError(f"{cls.__name__} cannot be instantiated.")
189
+
190
+
191
+ class CliOutputFormat:
192
+ """Define output format for `flwr` CLI commands."""
193
+
194
+ DEFAULT = "default"
195
+ JSON = "json"
196
+
197
+ def __new__(cls) -> CliOutputFormat:
198
+ """Prevent instantiation."""
199
+ raise TypeError(f"{cls.__name__} cannot be instantiated.")
flwr/common/logger.py CHANGED
@@ -15,16 +15,21 @@
15
15
  """Flower Logger."""
16
16
 
17
17
 
18
+ import json as _json
18
19
  import logging
20
+ import re
19
21
  import sys
20
22
  import threading
21
23
  import time
24
+ from io import StringIO
22
25
  from logging import WARN, LogRecord
23
26
  from logging.handlers import HTTPHandler
24
27
  from queue import Empty, Queue
25
28
  from typing import TYPE_CHECKING, Any, Optional, TextIO, Union
26
29
 
27
30
  import grpc
31
+ import typer
32
+ from rich.console import Console
28
33
 
29
34
  from flwr.proto.log_pb2 import PushLogsRequest # pylint: disable=E0611
30
35
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
@@ -303,6 +308,13 @@ def restore_output() -> None:
303
308
  console_handler.stream = sys.stdout
304
309
 
305
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
+
306
318
  def _log_uploader(
307
319
  log_queue: Queue[Optional[str]], node_id: int, run_id: int, stub: ServerAppIoStub
308
320
  ) -> None:
@@ -366,3 +378,31 @@ def stop_log_uploader(
366
378
  """Stop the log uploader thread."""
367
379
  log_queue.put(None)
368
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
+ )
flwr/common/message.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Message."""
16
16
 
17
+
17
18
  from __future__ import annotations
18
19
 
19
20
  import time