flwr 1.15.2__py3-none-any.whl → 1.17.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/build.py +2 -0
  2. flwr/cli/log.py +20 -21
  3. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +1 -1
  4. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  5. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  6. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  7. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  8. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  9. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  10. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  11. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  12. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  13. flwr/cli/run/run.py +5 -9
  14. flwr/client/app.py +6 -4
  15. flwr/client/client_app.py +260 -86
  16. flwr/client/clientapp/app.py +6 -2
  17. flwr/client/grpc_client/connection.py +24 -21
  18. flwr/client/message_handler/message_handler.py +28 -28
  19. flwr/client/mod/__init__.py +2 -2
  20. flwr/client/mod/centraldp_mods.py +7 -7
  21. flwr/client/mod/comms_mods.py +16 -22
  22. flwr/client/mod/localdp_mod.py +4 -4
  23. flwr/client/mod/secure_aggregation/secaggplus_mod.py +31 -31
  24. flwr/client/rest_client/connection.py +4 -6
  25. flwr/client/run_info_store.py +2 -2
  26. flwr/client/supernode/__init__.py +0 -2
  27. flwr/client/supernode/app.py +1 -11
  28. flwr/common/__init__.py +12 -4
  29. flwr/common/address.py +35 -0
  30. flwr/common/args.py +8 -2
  31. flwr/common/auth_plugin/auth_plugin.py +2 -1
  32. flwr/common/config.py +4 -4
  33. flwr/common/constant.py +16 -0
  34. flwr/common/context.py +4 -4
  35. flwr/common/event_log_plugin/__init__.py +22 -0
  36. flwr/common/event_log_plugin/event_log_plugin.py +60 -0
  37. flwr/common/grpc.py +1 -1
  38. flwr/common/logger.py +2 -2
  39. flwr/common/message.py +338 -102
  40. flwr/common/object_ref.py +0 -10
  41. flwr/common/record/__init__.py +8 -4
  42. flwr/common/record/arrayrecord.py +626 -0
  43. flwr/common/record/{configsrecord.py → configrecord.py} +75 -29
  44. flwr/common/record/conversion_utils.py +9 -18
  45. flwr/common/record/{metricsrecord.py → metricrecord.py} +78 -32
  46. flwr/common/record/recorddict.py +288 -0
  47. flwr/common/recorddict_compat.py +410 -0
  48. flwr/common/secure_aggregation/quantization.py +5 -1
  49. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  50. flwr/common/serde.py +67 -190
  51. flwr/common/telemetry.py +0 -10
  52. flwr/common/typing.py +44 -8
  53. flwr/proto/exec_pb2.py +3 -3
  54. flwr/proto/exec_pb2.pyi +3 -3
  55. flwr/proto/message_pb2.py +12 -12
  56. flwr/proto/message_pb2.pyi +9 -9
  57. flwr/proto/recorddict_pb2.py +70 -0
  58. flwr/proto/{recordset_pb2.pyi → recorddict_pb2.pyi} +35 -35
  59. flwr/proto/run_pb2.py +31 -31
  60. flwr/proto/run_pb2.pyi +3 -3
  61. flwr/server/__init__.py +3 -1
  62. flwr/server/app.py +74 -3
  63. flwr/server/compat/__init__.py +2 -2
  64. flwr/server/compat/app.py +15 -12
  65. flwr/server/compat/app_utils.py +26 -18
  66. flwr/server/compat/{driver_client_proxy.py → grid_client_proxy.py} +41 -41
  67. flwr/server/fleet_event_log_interceptor.py +94 -0
  68. flwr/server/{driver → grid}/__init__.py +8 -7
  69. flwr/server/{driver/driver.py → grid/grid.py} +48 -19
  70. flwr/server/{driver/grpc_driver.py → grid/grpc_grid.py} +88 -56
  71. flwr/server/{driver/inmemory_driver.py → grid/inmemory_grid.py} +41 -54
  72. flwr/server/run_serverapp.py +6 -17
  73. flwr/server/server_app.py +126 -33
  74. flwr/server/serverapp/app.py +10 -10
  75. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +2 -2
  76. flwr/server/superlink/fleet/message_handler/message_handler.py +8 -12
  77. flwr/server/superlink/fleet/vce/backend/backend.py +3 -3
  78. flwr/server/superlink/fleet/vce/backend/raybackend.py +2 -2
  79. flwr/server/superlink/fleet/vce/vce_api.py +33 -38
  80. flwr/server/superlink/linkstate/in_memory_linkstate.py +171 -132
  81. flwr/server/superlink/linkstate/linkstate.py +51 -64
  82. flwr/server/superlink/linkstate/sqlite_linkstate.py +253 -285
  83. flwr/server/superlink/linkstate/utils.py +171 -133
  84. flwr/server/superlink/{driver → serverappio}/__init__.py +1 -1
  85. flwr/server/superlink/{driver → serverappio}/serverappio_grpc.py +1 -1
  86. flwr/server/superlink/{driver → serverappio}/serverappio_servicer.py +27 -29
  87. flwr/server/superlink/simulation/simulationio_servicer.py +2 -2
  88. flwr/server/typing.py +3 -3
  89. flwr/server/utils/__init__.py +2 -2
  90. flwr/server/utils/validator.py +53 -68
  91. flwr/server/workflow/default_workflows.py +52 -58
  92. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +48 -50
  93. flwr/simulation/app.py +2 -2
  94. flwr/simulation/ray_transport/ray_actor.py +4 -2
  95. flwr/simulation/ray_transport/ray_client_proxy.py +34 -32
  96. flwr/simulation/run_simulation.py +15 -15
  97. flwr/superexec/app.py +0 -14
  98. flwr/superexec/deployment.py +4 -4
  99. flwr/superexec/exec_event_log_interceptor.py +135 -0
  100. flwr/superexec/exec_grpc.py +10 -4
  101. flwr/superexec/exec_servicer.py +6 -6
  102. flwr/superexec/exec_user_auth_interceptor.py +22 -4
  103. flwr/superexec/executor.py +3 -3
  104. flwr/superexec/simulation.py +3 -3
  105. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/METADATA +5 -5
  106. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/RECORD +111 -112
  107. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/entry_points.txt +0 -3
  108. flwr/client/message_handler/task_handler.py +0 -37
  109. flwr/common/record/parametersrecord.py +0 -204
  110. flwr/common/record/recordset.py +0 -202
  111. flwr/common/recordset_compat.py +0 -418
  112. flwr/proto/recordset_pb2.py +0 -70
  113. flwr/proto/task_pb2.py +0 -33
  114. flwr/proto/task_pb2.pyi +0 -100
  115. flwr/proto/task_pb2_grpc.py +0 -4
  116. flwr/proto/task_pb2_grpc.pyi +0 -4
  117. /flwr/proto/{recordset_pb2_grpc.py → recorddict_pb2_grpc.py} +0 -0
  118. /flwr/proto/{recordset_pb2_grpc.pyi → recorddict_pb2_grpc.pyi} +0 -0
  119. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/LICENSE +0 -0
  120. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,135 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower Exec API event log interceptor."""
16
+
17
+
18
+ from collections.abc import Iterator
19
+ from typing import Any, Callable, Union, cast
20
+
21
+ import grpc
22
+ from google.protobuf.message import Message as GrpcMessage
23
+
24
+ from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
25
+ from flwr.common.typing import LogEntry
26
+
27
+ from .exec_user_auth_interceptor import shared_user_info
28
+
29
+
30
+ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
31
+ """Exec API interceptor for logging events."""
32
+
33
+ def __init__(self, log_plugin: EventLogWriterPlugin) -> None:
34
+ self.log_plugin = log_plugin
35
+
36
+ def intercept_service(
37
+ self,
38
+ continuation: Callable[[Any], Any],
39
+ handler_call_details: grpc.HandlerCallDetails,
40
+ ) -> grpc.RpcMethodHandler:
41
+ """Flower server interceptor logging logic.
42
+
43
+ Intercept all unary-unary/unary-stream calls from users and log the event.
44
+ Continue RPC call if event logger is enabled on the SuperLink, else, terminate
45
+ RPC call by setting context to abort.
46
+ """
47
+ # One of the method handlers in
48
+ # `flwr.superexec.exec_servicer.ExecServicer`
49
+ method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
50
+ method_name: str = handler_call_details.method
51
+ return self._generic_event_log_unary_method_handler(method_handler, method_name)
52
+
53
+ def _generic_event_log_unary_method_handler(
54
+ self, method_handler: grpc.RpcMethodHandler, method_name: str
55
+ ) -> grpc.RpcMethodHandler:
56
+ def _generic_method_handler(
57
+ request: GrpcMessage,
58
+ context: grpc.ServicerContext,
59
+ ) -> Union[GrpcMessage, Iterator[GrpcMessage], BaseException]:
60
+ log_entry: LogEntry
61
+ # Log before call
62
+ log_entry = self.log_plugin.compose_log_before_event(
63
+ request=request,
64
+ context=context,
65
+ user_info=shared_user_info.get(),
66
+ method_name=method_name,
67
+ )
68
+ self.log_plugin.write_log(log_entry)
69
+
70
+ # For unary-unary calls, log after the call immediately
71
+ if method_handler.unary_unary:
72
+ unary_response, error = None, None
73
+ try:
74
+ unary_response = cast(
75
+ GrpcMessage, method_handler.unary_unary(request, context)
76
+ )
77
+ except BaseException as e:
78
+ error = e
79
+ raise
80
+ finally:
81
+ log_entry = self.log_plugin.compose_log_after_event(
82
+ request=request,
83
+ context=context,
84
+ user_info=shared_user_info.get(),
85
+ method_name=method_name,
86
+ response=unary_response or error,
87
+ )
88
+ self.log_plugin.write_log(log_entry)
89
+ return unary_response
90
+
91
+ # For unary-stream calls, wrap the response iterator and write the event log
92
+ # after iteration completes
93
+ if method_handler.unary_stream:
94
+ response_iterator = cast(
95
+ Iterator[GrpcMessage],
96
+ method_handler.unary_stream(request, context),
97
+ )
98
+
99
+ def response_wrapper() -> Iterator[GrpcMessage]:
100
+ stream_response, error = None, None
101
+ try:
102
+ # pylint: disable=use-yield-from
103
+ for stream_response in response_iterator:
104
+ yield stream_response
105
+ except BaseException as e:
106
+ error = e
107
+ raise
108
+ finally:
109
+ # This block is executed after the client has consumed
110
+ # the entire stream, or if iteration is interrupted
111
+ log_entry = self.log_plugin.compose_log_after_event(
112
+ request=request,
113
+ context=context,
114
+ user_info=shared_user_info.get(),
115
+ method_name=method_name,
116
+ response=stream_response or error,
117
+ )
118
+ self.log_plugin.write_log(log_entry)
119
+
120
+ return response_wrapper()
121
+
122
+ raise RuntimeError() # This line is unreachable
123
+
124
+ if method_handler.unary_unary:
125
+ message_handler = grpc.unary_unary_rpc_method_handler
126
+ elif method_handler.unary_stream:
127
+ message_handler = grpc.unary_stream_rpc_method_handler
128
+ else:
129
+ # If the method type is not `unary_unary` or `unary_stream`, raise an error
130
+ raise NotImplementedError("This RPC method type is not supported.")
131
+ return message_handler(
132
+ _generic_method_handler,
133
+ request_deserializer=method_handler.request_deserializer,
134
+ response_serializer=method_handler.response_serializer,
135
+ )
@@ -15,7 +15,6 @@
15
15
  """SuperExec gRPC API."""
16
16
 
17
17
 
18
- from collections.abc import Sequence
19
18
  from logging import INFO
20
19
  from typing import Optional
21
20
 
@@ -23,12 +22,14 @@ import grpc
23
22
 
24
23
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
25
24
  from flwr.common.auth_plugin import ExecAuthPlugin
25
+ from flwr.common.event_log_plugin import EventLogWriterPlugin
26
26
  from flwr.common.grpc import generic_create_grpc_server
27
27
  from flwr.common.logger import log
28
28
  from flwr.common.typing import UserConfig
29
29
  from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
30
30
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
31
31
  from flwr.server.superlink.linkstate import LinkStateFactory
32
+ from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
32
33
  from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
33
34
 
34
35
  from .exec_servicer import ExecServicer
@@ -44,6 +45,7 @@ def run_exec_api_grpc(
44
45
  certificates: Optional[tuple[bytes, bytes, bytes]],
45
46
  config: UserConfig,
46
47
  auth_plugin: Optional[ExecAuthPlugin] = None,
48
+ event_log_plugin: Optional[EventLogWriterPlugin] = None,
47
49
  ) -> grpc.Server:
48
50
  """Run Exec API (gRPC, request-response)."""
49
51
  executor.set_config(config)
@@ -54,16 +56,20 @@ def run_exec_api_grpc(
54
56
  executor=executor,
55
57
  auth_plugin=auth_plugin,
56
58
  )
57
- interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None
59
+ interceptors: list[grpc.ServerInterceptor] = []
58
60
  if auth_plugin is not None:
59
- interceptors = [ExecUserAuthInterceptor(auth_plugin)]
61
+ interceptors.append(ExecUserAuthInterceptor(auth_plugin))
62
+ # Event log interceptor must be added after user auth interceptor
63
+ if event_log_plugin is not None:
64
+ interceptors.append(ExecEventLogInterceptor(event_log_plugin))
65
+ log(INFO, "Flower event logging enabled")
60
66
  exec_add_servicer_to_server_fn = add_ExecServicer_to_server
61
67
  exec_grpc_server = generic_create_grpc_server(
62
68
  servicer_and_add_fn=(exec_servicer, exec_add_servicer_to_server_fn),
63
69
  server_address=address,
64
70
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
65
71
  certificates=certificates,
66
- interceptors=interceptors,
72
+ interceptors=interceptors or None,
67
73
  )
68
74
 
69
75
  if auth_plugin is None:
@@ -28,7 +28,7 @@ from flwr.common.auth_plugin import ExecAuthPlugin
28
28
  from flwr.common.constant import LOG_STREAM_INTERVAL, Status, SubStatus
29
29
  from flwr.common.logger import log
30
30
  from flwr.common.serde import (
31
- configs_record_from_proto,
31
+ config_record_from_proto,
32
32
  run_to_proto,
33
33
  user_config_from_proto,
34
34
  )
@@ -79,7 +79,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
79
79
  run_id = self.executor.start_run(
80
80
  request.fab.content,
81
81
  user_config_from_proto(request.override_config),
82
- configs_record_from_proto(request.federation_options),
82
+ config_record_from_proto(request.federation_options),
83
83
  )
84
84
 
85
85
  if run_id is None:
@@ -120,7 +120,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
120
120
  run_status = state.get_run_status({run_id})[run_id]
121
121
  if run_status.status == Status.FINISHED:
122
122
  log(INFO, "All logs for run ID `%s` returned", request.run_id)
123
- context.cancel()
123
+ break
124
124
 
125
125
  time.sleep(LOG_STREAM_INTERVAL) # Sleep briefly to avoid busy waiting
126
126
 
@@ -163,10 +163,10 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
163
163
  )
164
164
 
165
165
  if update_success:
166
- task_ids: set[UUID] = state.get_task_ids_from_run_id(request.run_id)
166
+ message_ids: set[UUID] = state.get_message_ids_from_run_id(request.run_id)
167
167
 
168
- # Delete TaskIns and TaskRes for the `run_id`
169
- state.delete_tasks(task_ids)
168
+ # Delete Messages and their replies for the `run_id`
169
+ state.delete_messages(message_ids)
170
170
 
171
171
  return StopRunResponse(success=update_success)
172
172
 
@@ -15,11 +15,13 @@
15
15
  """Flower Exec API interceptor."""
16
16
 
17
17
 
18
- from typing import Any, Callable, Union
18
+ import contextvars
19
+ from typing import Any, Callable, Union, cast
19
20
 
20
21
  import grpc
21
22
 
22
23
  from flwr.common.auth_plugin import ExecAuthPlugin
24
+ from flwr.common.typing import UserInfo
23
25
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
24
26
  GetAuthTokensRequest,
25
27
  GetAuthTokensResponse,
@@ -43,6 +45,11 @@ Response = Union[
43
45
  ]
44
46
 
45
47
 
48
+ shared_user_info: contextvars.ContextVar[UserInfo] = contextvars.ContextVar(
49
+ "user_info", default=UserInfo(user_id=None, user_name=None)
50
+ )
51
+
52
+
46
53
  class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
47
54
  """Exec API interceptor for user authentication."""
48
55
 
@@ -77,11 +84,22 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
77
84
  ) -> Response:
78
85
  call = method_handler.unary_unary or method_handler.unary_stream
79
86
  metadata = context.invocation_metadata()
80
- if isinstance(
81
- request, (GetLoginDetailsRequest, GetAuthTokensRequest)
82
- ) or self.auth_plugin.validate_tokens_in_metadata(metadata):
87
+
88
+ # Intercept GetLoginDetails and GetAuthTokens requests, and return
89
+ # the response without authentication
90
+ if isinstance(request, (GetLoginDetailsRequest, GetAuthTokensRequest)):
91
+ return call(request, context) # type: ignore
92
+
93
+ # For other requests, check if the user is authenticated
94
+ valid_tokens, user_info = self.auth_plugin.validate_tokens_in_metadata(
95
+ metadata
96
+ )
97
+ if valid_tokens:
98
+ # Store user info in contextvars for authenticated users
99
+ shared_user_info.set(cast(UserInfo, user_info))
83
100
  return call(request, context) # type: ignore
84
101
 
102
+ # If the user is not authenticated, refresh tokens
85
103
  tokens = self.auth_plugin.refresh_tokens(context.invocation_metadata())
86
104
  if tokens is not None:
87
105
  context.send_initial_metadata(tokens)
@@ -20,7 +20,7 @@ from dataclasses import dataclass, field
20
20
  from subprocess import Popen
21
21
  from typing import Optional
22
22
 
23
- from flwr.common import ConfigsRecord
23
+ from flwr.common import ConfigRecord
24
24
  from flwr.common.typing import UserConfig
25
25
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
26
26
  from flwr.server.superlink.linkstate import LinkStateFactory
@@ -73,7 +73,7 @@ class Executor(ABC):
73
73
  self,
74
74
  fab_file: bytes,
75
75
  override_config: UserConfig,
76
- federation_options: ConfigsRecord,
76
+ federation_options: ConfigRecord,
77
77
  ) -> Optional[int]:
78
78
  """Start a run using the given Flower FAB ID and version.
79
79
 
@@ -86,7 +86,7 @@ class Executor(ABC):
86
86
  The Flower App Bundle file bytes.
87
87
  override_config: UserConfig
88
88
  The config overrides dict sent by the user (using `flwr run`).
89
- federation_options: ConfigsRecord
89
+ federation_options: ConfigRecord
90
90
  The federation options sent by the user (using `flwr run`).
91
91
 
92
92
  Returns
@@ -22,7 +22,7 @@ from typing import Optional
22
22
  from typing_extensions import override
23
23
 
24
24
  from flwr.cli.config_utils import get_fab_metadata
25
- from flwr.common import ConfigsRecord, Context, RecordSet
25
+ from flwr.common import ConfigRecord, Context, RecordDict
26
26
  from flwr.common.logger import log
27
27
  from flwr.common.typing import Fab, UserConfig
28
28
  from flwr.server.superlink.ffs import Ffs
@@ -76,7 +76,7 @@ class SimulationEngine(Executor):
76
76
  self,
77
77
  fab_file: bytes,
78
78
  override_config: UserConfig,
79
- federation_options: ConfigsRecord,
79
+ federation_options: ConfigRecord,
80
80
  ) -> Optional[int]:
81
81
  """Start run using the Flower Simulation Engine."""
82
82
  try:
@@ -104,7 +104,7 @@ class SimulationEngine(Executor):
104
104
  run_id=run_id,
105
105
  node_id=0,
106
106
  node_config={},
107
- state=RecordSet(),
107
+ state=RecordDict(),
108
108
  run_config={},
109
109
  )
110
110
 
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr
3
- Version: 1.15.2
3
+ Version: 1.17.0
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
7
7
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
8
8
  Author: The Flower Authors
9
9
  Author-email: hello@flower.ai
10
- Requires-Python: >=3.9,<4.0
10
+ Requires-Python: >=3.9.2,<4.0.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Intended Audience :: Science/Research
@@ -16,12 +16,12 @@ Classifier: Operating System :: MacOS :: MacOS X
16
16
  Classifier: Operating System :: POSIX :: Linux
17
17
  Classifier: Programming Language :: Python
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
23
22
  Classifier: Programming Language :: Python :: 3 :: Only
24
23
  Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.9
25
25
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
26
  Classifier: Topic :: Scientific/Engineering
27
27
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -32,7 +32,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
32
  Classifier: Typing :: Typed
33
33
  Provides-Extra: rest
34
34
  Provides-Extra: simulation
35
- Requires-Dist: cryptography (>=43.0.1,<44.0.0)
35
+ Requires-Dist: cryptography (>=44.0.1,<45.0.0)
36
36
  Requires-Dist: grpcio (>=1.62.3,<2.0.0,!=1.65.0)
37
37
  Requires-Dist: iterators (>=0.0.2,<0.0.3)
38
38
  Requires-Dist: numpy (>=1.26.0,<3.0.0)
@@ -63,7 +63,7 @@ Description-Content-Type: text/markdown
63
63
  <a href="https://flower.ai/">Website</a> |
64
64
  <a href="https://flower.ai/blog">Blog</a> |
65
65
  <a href="https://flower.ai/docs/">Docs</a> |
66
- <a href="https://flower.ai/conf/flower-summit-2022">Conference</a> |
66
+ <a href="https://flower.ai/events/flower-ai-summit-2025">Summit</a> |
67
67
  <a href="https://flower.ai/join-slack">Slack</a>
68
68
  <br /><br />
69
69
  </p>