flwr-nightly 1.11.0.dev20240724__py3-none-any.whl → 1.11.0.dev20240811__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/build.py +22 -20
- flwr/cli/config_utils.py +27 -8
- flwr/cli/new/new.py +23 -22
- flwr/cli/new/templates/app/README.md.tpl +1 -1
- flwr/cli/new/templates/app/code/__init__.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.jax.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +9 -8
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +5 -8
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.jax.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +7 -6
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +4 -5
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +1 -1
- flwr/cli/new/templates/app/code/task.jax.py.tpl +2 -2
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +2 -1
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +14 -20
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +16 -4
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -2
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/run/run.py +15 -12
- flwr/client/grpc_rere_client/grpc_adapter.py +7 -0
- flwr/client/supernode/app.py +36 -28
- flwr/common/config.py +30 -0
- flwr/common/typing.py +8 -0
- flwr/proto/driver_pb2.py +22 -21
- flwr/proto/driver_pb2.pyi +7 -1
- flwr/proto/driver_pb2_grpc.py +35 -0
- flwr/proto/driver_pb2_grpc.pyi +14 -0
- flwr/proto/fab_pb2.py +6 -6
- flwr/proto/fab_pb2.pyi +8 -8
- flwr/proto/fleet_pb2.py +28 -27
- flwr/proto/fleet_pb2_grpc.py +35 -0
- flwr/proto/fleet_pb2_grpc.pyi +14 -0
- flwr/proto/run_pb2.py +8 -8
- flwr/proto/run_pb2.pyi +4 -1
- flwr/server/run_serverapp.py +28 -46
- flwr/server/superlink/driver/driver_servicer.py +7 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +7 -0
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +4 -35
- flwr/server/superlink/fleet/vce/vce_api.py +3 -3
- flwr/superexec/simulation.py +15 -3
- {flwr_nightly-1.11.0.dev20240724.dist-info → flwr_nightly-1.11.0.dev20240811.dist-info}/METADATA +2 -2
- {flwr_nightly-1.11.0.dev20240724.dist-info → flwr_nightly-1.11.0.dev20240811.dist-info}/RECORD +59 -59
- {flwr_nightly-1.11.0.dev20240724.dist-info → flwr_nightly-1.11.0.dev20240811.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.11.0.dev20240724.dist-info → flwr_nightly-1.11.0.dev20240811.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.11.0.dev20240724.dist-info → flwr_nightly-1.11.0.dev20240811.dist-info}/entry_points.txt +0 -0
flwr/proto/fleet_pb2.py
CHANGED
|
@@ -15,9 +15,10 @@ _sym_db = _symbol_database.Default()
|
|
|
15
15
|
from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2
|
|
16
16
|
from flwr.proto import task_pb2 as flwr_dot_proto_dot_task__pb2
|
|
17
17
|
from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
|
|
18
|
+
from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x66lwr/proto/fleet.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\"*\n\x11\x43reateNodeRequest\x12\x15\n\rping_interval\x18\x01 \x01(\x01\"4\n\x12\x43reateNodeResponse\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"3\n\x11\x44\x65leteNodeRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"\x14\n\x12\x44\x65leteNodeResponse\"D\n\x0bPingRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x15\n\rping_interval\x18\x02 \x01(\x01\"\x1f\n\x0cPingResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"F\n\x12PullTaskInsRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"k\n\x13PullTaskInsResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12*\n\rtask_ins_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"@\n\x12PushTaskResRequest\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes\"\xae\x01\n\x13PushTaskResResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12=\n\x07results\x18\x02 \x03(\x0b\x32,.flwr.proto.PushTaskResResponse.ResultsEntry\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\r:\x02\x38\x01\"\x1e\n\tReconnect\x12\x11\n\treconnect\x18\x01 \x01(\x04\x32\
|
|
21
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x66lwr/proto/fleet.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"*\n\x11\x43reateNodeRequest\x12\x15\n\rping_interval\x18\x01 \x01(\x01\"4\n\x12\x43reateNodeResponse\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"3\n\x11\x44\x65leteNodeRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"\x14\n\x12\x44\x65leteNodeResponse\"D\n\x0bPingRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x15\n\rping_interval\x18\x02 \x01(\x01\"\x1f\n\x0cPingResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"F\n\x12PullTaskInsRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"k\n\x13PullTaskInsResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12*\n\rtask_ins_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"@\n\x12PushTaskResRequest\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes\"\xae\x01\n\x13PushTaskResResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12=\n\x07results\x18\x02 \x03(\x0b\x32,.flwr.proto.PushTaskResResponse.ResultsEntry\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\r:\x02\x38\x01\"\x1e\n\tReconnect\x12\x11\n\treconnect\x18\x01 \x01(\x04\x32\x8c\x04\n\x05\x46leet\x12M\n\nCreateNode\x12\x1d.flwr.proto.CreateNodeRequest\x1a\x1e.flwr.proto.CreateNodeResponse\"\x00\x12M\n\nDeleteNode\x12\x1d.flwr.proto.DeleteNodeRequest\x1a\x1e.flwr.proto.DeleteNodeResponse\"\x00\x12;\n\x04Ping\x12\x17.flwr.proto.PingRequest\x1a\x18.flwr.proto.PingResponse\"\x00\x12P\n\x0bPullTaskIns\x12\x1e.flwr.proto.PullTaskInsRequest\x1a\x1f.flwr.proto.PullTaskInsResponse\"\x00\x12P\n\x0bPushTaskRes\x12\x1e.flwr.proto.PushTaskResRequest\x1a\x1f.flwr.proto.PushTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x62\x06proto3')
|
|
21
22
|
|
|
22
23
|
_globals = globals()
|
|
23
24
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -26,30 +27,30 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
|
26
27
|
DESCRIPTOR._options = None
|
|
27
28
|
_globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._options = None
|
|
28
29
|
_globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_options = b'8\001'
|
|
29
|
-
_globals['_CREATENODEREQUEST']._serialized_start=
|
|
30
|
-
_globals['_CREATENODEREQUEST']._serialized_end=
|
|
31
|
-
_globals['_CREATENODERESPONSE']._serialized_start=
|
|
32
|
-
_globals['_CREATENODERESPONSE']._serialized_end=
|
|
33
|
-
_globals['_DELETENODEREQUEST']._serialized_start=
|
|
34
|
-
_globals['_DELETENODEREQUEST']._serialized_end=
|
|
35
|
-
_globals['_DELETENODERESPONSE']._serialized_start=
|
|
36
|
-
_globals['_DELETENODERESPONSE']._serialized_end=
|
|
37
|
-
_globals['_PINGREQUEST']._serialized_start=
|
|
38
|
-
_globals['_PINGREQUEST']._serialized_end=
|
|
39
|
-
_globals['_PINGRESPONSE']._serialized_start=
|
|
40
|
-
_globals['_PINGRESPONSE']._serialized_end=
|
|
41
|
-
_globals['_PULLTASKINSREQUEST']._serialized_start=
|
|
42
|
-
_globals['_PULLTASKINSREQUEST']._serialized_end=
|
|
43
|
-
_globals['_PULLTASKINSRESPONSE']._serialized_start=
|
|
44
|
-
_globals['_PULLTASKINSRESPONSE']._serialized_end=
|
|
45
|
-
_globals['_PUSHTASKRESREQUEST']._serialized_start=
|
|
46
|
-
_globals['_PUSHTASKRESREQUEST']._serialized_end=
|
|
47
|
-
_globals['_PUSHTASKRESRESPONSE']._serialized_start=
|
|
48
|
-
_globals['_PUSHTASKRESRESPONSE']._serialized_end=
|
|
49
|
-
_globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_start=
|
|
50
|
-
_globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_end=
|
|
51
|
-
_globals['_RECONNECT']._serialized_start=
|
|
52
|
-
_globals['_RECONNECT']._serialized_end=
|
|
53
|
-
_globals['_FLEET']._serialized_start=
|
|
54
|
-
_globals['_FLEET']._serialized_end=
|
|
30
|
+
_globals['_CREATENODEREQUEST']._serialized_start=128
|
|
31
|
+
_globals['_CREATENODEREQUEST']._serialized_end=170
|
|
32
|
+
_globals['_CREATENODERESPONSE']._serialized_start=172
|
|
33
|
+
_globals['_CREATENODERESPONSE']._serialized_end=224
|
|
34
|
+
_globals['_DELETENODEREQUEST']._serialized_start=226
|
|
35
|
+
_globals['_DELETENODEREQUEST']._serialized_end=277
|
|
36
|
+
_globals['_DELETENODERESPONSE']._serialized_start=279
|
|
37
|
+
_globals['_DELETENODERESPONSE']._serialized_end=299
|
|
38
|
+
_globals['_PINGREQUEST']._serialized_start=301
|
|
39
|
+
_globals['_PINGREQUEST']._serialized_end=369
|
|
40
|
+
_globals['_PINGRESPONSE']._serialized_start=371
|
|
41
|
+
_globals['_PINGRESPONSE']._serialized_end=402
|
|
42
|
+
_globals['_PULLTASKINSREQUEST']._serialized_start=404
|
|
43
|
+
_globals['_PULLTASKINSREQUEST']._serialized_end=474
|
|
44
|
+
_globals['_PULLTASKINSRESPONSE']._serialized_start=476
|
|
45
|
+
_globals['_PULLTASKINSRESPONSE']._serialized_end=583
|
|
46
|
+
_globals['_PUSHTASKRESREQUEST']._serialized_start=585
|
|
47
|
+
_globals['_PUSHTASKRESREQUEST']._serialized_end=649
|
|
48
|
+
_globals['_PUSHTASKRESRESPONSE']._serialized_start=652
|
|
49
|
+
_globals['_PUSHTASKRESRESPONSE']._serialized_end=826
|
|
50
|
+
_globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_start=780
|
|
51
|
+
_globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_end=826
|
|
52
|
+
_globals['_RECONNECT']._serialized_start=828
|
|
53
|
+
_globals['_RECONNECT']._serialized_end=858
|
|
54
|
+
_globals['_FLEET']._serialized_start=861
|
|
55
|
+
_globals['_FLEET']._serialized_end=1385
|
|
55
56
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/fleet_pb2_grpc.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
3
|
import grpc
|
|
4
4
|
|
|
5
|
+
from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
|
|
5
6
|
from flwr.proto import fleet_pb2 as flwr_dot_proto_dot_fleet__pb2
|
|
6
7
|
from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
|
|
7
8
|
|
|
@@ -45,6 +46,11 @@ class FleetStub(object):
|
|
|
45
46
|
request_serializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.SerializeToString,
|
|
46
47
|
response_deserializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString,
|
|
47
48
|
)
|
|
49
|
+
self.GetFab = channel.unary_unary(
|
|
50
|
+
'/flwr.proto.Fleet/GetFab',
|
|
51
|
+
request_serializer=flwr_dot_proto_dot_fab__pb2.GetFabRequest.SerializeToString,
|
|
52
|
+
response_deserializer=flwr_dot_proto_dot_fab__pb2.GetFabResponse.FromString,
|
|
53
|
+
)
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
class FleetServicer(object):
|
|
@@ -92,6 +98,13 @@ class FleetServicer(object):
|
|
|
92
98
|
context.set_details('Method not implemented!')
|
|
93
99
|
raise NotImplementedError('Method not implemented!')
|
|
94
100
|
|
|
101
|
+
def GetFab(self, request, context):
|
|
102
|
+
"""Get FAB
|
|
103
|
+
"""
|
|
104
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
105
|
+
context.set_details('Method not implemented!')
|
|
106
|
+
raise NotImplementedError('Method not implemented!')
|
|
107
|
+
|
|
95
108
|
|
|
96
109
|
def add_FleetServicer_to_server(servicer, server):
|
|
97
110
|
rpc_method_handlers = {
|
|
@@ -125,6 +138,11 @@ def add_FleetServicer_to_server(servicer, server):
|
|
|
125
138
|
request_deserializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.FromString,
|
|
126
139
|
response_serializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.SerializeToString,
|
|
127
140
|
),
|
|
141
|
+
'GetFab': grpc.unary_unary_rpc_method_handler(
|
|
142
|
+
servicer.GetFab,
|
|
143
|
+
request_deserializer=flwr_dot_proto_dot_fab__pb2.GetFabRequest.FromString,
|
|
144
|
+
response_serializer=flwr_dot_proto_dot_fab__pb2.GetFabResponse.SerializeToString,
|
|
145
|
+
),
|
|
128
146
|
}
|
|
129
147
|
generic_handler = grpc.method_handlers_generic_handler(
|
|
130
148
|
'flwr.proto.Fleet', rpc_method_handlers)
|
|
@@ -236,3 +254,20 @@ class Fleet(object):
|
|
|
236
254
|
flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString,
|
|
237
255
|
options, channel_credentials,
|
|
238
256
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
257
|
+
|
|
258
|
+
@staticmethod
|
|
259
|
+
def GetFab(request,
|
|
260
|
+
target,
|
|
261
|
+
options=(),
|
|
262
|
+
channel_credentials=None,
|
|
263
|
+
call_credentials=None,
|
|
264
|
+
insecure=False,
|
|
265
|
+
compression=None,
|
|
266
|
+
wait_for_ready=None,
|
|
267
|
+
timeout=None,
|
|
268
|
+
metadata=None):
|
|
269
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/GetFab',
|
|
270
|
+
flwr_dot_proto_dot_fab__pb2.GetFabRequest.SerializeToString,
|
|
271
|
+
flwr_dot_proto_dot_fab__pb2.GetFabResponse.FromString,
|
|
272
|
+
options, channel_credentials,
|
|
273
|
+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
flwr/proto/fleet_pb2_grpc.pyi
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
isort:skip_file
|
|
4
4
|
"""
|
|
5
5
|
import abc
|
|
6
|
+
import flwr.proto.fab_pb2
|
|
6
7
|
import flwr.proto.fleet_pb2
|
|
7
8
|
import flwr.proto.run_pb2
|
|
8
9
|
import grpc
|
|
@@ -41,6 +42,11 @@ class FleetStub:
|
|
|
41
42
|
flwr.proto.run_pb2.GetRunRequest,
|
|
42
43
|
flwr.proto.run_pb2.GetRunResponse]
|
|
43
44
|
|
|
45
|
+
GetFab: grpc.UnaryUnaryMultiCallable[
|
|
46
|
+
flwr.proto.fab_pb2.GetFabRequest,
|
|
47
|
+
flwr.proto.fab_pb2.GetFabResponse]
|
|
48
|
+
"""Get FAB"""
|
|
49
|
+
|
|
44
50
|
|
|
45
51
|
class FleetServicer(metaclass=abc.ABCMeta):
|
|
46
52
|
@abc.abstractmethod
|
|
@@ -89,5 +95,13 @@ class FleetServicer(metaclass=abc.ABCMeta):
|
|
|
89
95
|
context: grpc.ServicerContext,
|
|
90
96
|
) -> flwr.proto.run_pb2.GetRunResponse: ...
|
|
91
97
|
|
|
98
|
+
@abc.abstractmethod
|
|
99
|
+
def GetFab(self,
|
|
100
|
+
request: flwr.proto.fab_pb2.GetFabRequest,
|
|
101
|
+
context: grpc.ServicerContext,
|
|
102
|
+
) -> flwr.proto.fab_pb2.GetFabResponse:
|
|
103
|
+
"""Get FAB"""
|
|
104
|
+
pass
|
|
105
|
+
|
|
92
106
|
|
|
93
107
|
def add_FleetServicer_to_server(servicer: FleetServicer, server: grpc.Server) -> None: ...
|
flwr/proto/run_pb2.py
CHANGED
|
@@ -15,7 +15,7 @@ _sym_db = _symbol_database.Default()
|
|
|
15
15
|
from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\
|
|
18
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd5\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3')
|
|
19
19
|
|
|
20
20
|
_globals = globals()
|
|
21
21
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -25,11 +25,11 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
|
25
25
|
_globals['_RUN_OVERRIDECONFIGENTRY']._options = None
|
|
26
26
|
_globals['_RUN_OVERRIDECONFIGENTRY']._serialized_options = b'8\001'
|
|
27
27
|
_globals['_RUN']._serialized_start=65
|
|
28
|
-
_globals['_RUN']._serialized_end=
|
|
29
|
-
_globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=
|
|
30
|
-
_globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=
|
|
31
|
-
_globals['_GETRUNREQUEST']._serialized_start=
|
|
32
|
-
_globals['_GETRUNREQUEST']._serialized_end=
|
|
33
|
-
_globals['_GETRUNRESPONSE']._serialized_start=
|
|
34
|
-
_globals['_GETRUNRESPONSE']._serialized_end=
|
|
28
|
+
_globals['_RUN']._serialized_end=278
|
|
29
|
+
_globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=205
|
|
30
|
+
_globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=278
|
|
31
|
+
_globals['_GETRUNREQUEST']._serialized_start=280
|
|
32
|
+
_globals['_GETRUNREQUEST']._serialized_end=311
|
|
33
|
+
_globals['_GETRUNRESPONSE']._serialized_start=313
|
|
34
|
+
_globals['_GETRUNRESPONSE']._serialized_end=359
|
|
35
35
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/run_pb2.pyi
CHANGED
|
@@ -33,19 +33,22 @@ class Run(google.protobuf.message.Message):
|
|
|
33
33
|
FAB_ID_FIELD_NUMBER: builtins.int
|
|
34
34
|
FAB_VERSION_FIELD_NUMBER: builtins.int
|
|
35
35
|
OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int
|
|
36
|
+
FAB_HASH_FIELD_NUMBER: builtins.int
|
|
36
37
|
run_id: builtins.int
|
|
37
38
|
fab_id: typing.Text
|
|
38
39
|
fab_version: typing.Text
|
|
39
40
|
@property
|
|
40
41
|
def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ...
|
|
42
|
+
fab_hash: typing.Text
|
|
41
43
|
def __init__(self,
|
|
42
44
|
*,
|
|
43
45
|
run_id: builtins.int = ...,
|
|
44
46
|
fab_id: typing.Text = ...,
|
|
45
47
|
fab_version: typing.Text = ...,
|
|
46
48
|
override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ...,
|
|
49
|
+
fab_hash: typing.Text = ...,
|
|
47
50
|
) -> None: ...
|
|
48
|
-
def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config","run_id",b"run_id"]) -> None: ...
|
|
51
|
+
def ClearField(self, field_name: typing_extensions.Literal["fab_hash",b"fab_hash","fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config","run_id",b"run_id"]) -> None: ...
|
|
49
52
|
global___Run = Run
|
|
50
53
|
|
|
51
54
|
class GetRunRequest(google.protobuf.message.Message):
|
flwr/server/run_serverapp.py
CHANGED
|
@@ -24,7 +24,8 @@ from typing import Optional
|
|
|
24
24
|
from flwr.common import Context, EventType, RecordSet, event
|
|
25
25
|
from flwr.common.config import (
|
|
26
26
|
get_flwr_dir,
|
|
27
|
-
|
|
27
|
+
get_fused_config_from_dir,
|
|
28
|
+
get_metadata_from_config,
|
|
28
29
|
get_project_config,
|
|
29
30
|
get_project_dir,
|
|
30
31
|
)
|
|
@@ -146,51 +147,50 @@ def run_server_app() -> None: # pylint: disable=too-many-branches
|
|
|
146
147
|
cert_path,
|
|
147
148
|
)
|
|
148
149
|
|
|
149
|
-
|
|
150
|
-
if not (
|
|
150
|
+
app_path: Optional[str] = args.app
|
|
151
|
+
if not (app_path is None) ^ (args.run_id is None):
|
|
151
152
|
raise sys.exit(
|
|
152
|
-
"Please provide either a
|
|
153
|
+
"Please provide either a Flower App path or a Run ID, but not both. "
|
|
153
154
|
"For more details, use: ``flower-server-app -h``"
|
|
154
155
|
)
|
|
155
156
|
|
|
156
157
|
# Initialize GrpcDriver
|
|
157
|
-
if
|
|
158
|
-
# User provided `--run-id`, but not `
|
|
158
|
+
if app_path is None:
|
|
159
|
+
# User provided `--run-id`, but not `app_dir`
|
|
159
160
|
driver = GrpcDriver(
|
|
160
161
|
run_id=args.run_id,
|
|
161
162
|
driver_service_address=args.superlink,
|
|
162
163
|
root_certificates=root_certificates,
|
|
163
164
|
)
|
|
165
|
+
flwr_dir = get_flwr_dir(args.flwr_dir)
|
|
166
|
+
run_ = driver.run
|
|
167
|
+
app_path = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir))
|
|
168
|
+
config = get_project_config(app_path)
|
|
164
169
|
else:
|
|
165
|
-
# User provided `
|
|
170
|
+
# User provided `app_dir`, but not `--run-id`
|
|
166
171
|
# Create run if run_id is not provided
|
|
167
172
|
driver = GrpcDriver(
|
|
168
173
|
run_id=0, # Will be overwritten
|
|
169
174
|
driver_service_address=args.superlink,
|
|
170
175
|
root_certificates=root_certificates,
|
|
171
176
|
)
|
|
177
|
+
# Load config from the project directory
|
|
178
|
+
config = get_project_config(app_path)
|
|
179
|
+
fab_version, fab_id = get_metadata_from_config(config)
|
|
180
|
+
|
|
172
181
|
# Create run
|
|
173
|
-
req = CreateRunRequest(fab_id=
|
|
182
|
+
req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version)
|
|
174
183
|
res: CreateRunResponse = driver._stub.CreateRun(req) # pylint: disable=W0212
|
|
175
184
|
# Overwrite driver._run_id
|
|
176
185
|
driver._run_id = res.run_id # pylint: disable=W0212
|
|
177
186
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
flwr_dir = get_flwr_dir(args.flwr_dir)
|
|
184
|
-
run_ = driver.run
|
|
185
|
-
server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir))
|
|
186
|
-
config = get_project_config(server_app_dir)
|
|
187
|
-
server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
|
|
188
|
-
server_app_run_config = get_fused_config(run_, flwr_dir)
|
|
189
|
-
else:
|
|
190
|
-
# User provided `server-app`, but not `--run-id`
|
|
191
|
-
server_app_dir = str(Path(args.dir).absolute())
|
|
187
|
+
# Obtain server app reference and the run config
|
|
188
|
+
server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
|
|
189
|
+
server_app_run_config = get_fused_config_from_dir(
|
|
190
|
+
Path(app_path), driver.run.override_config
|
|
191
|
+
)
|
|
192
192
|
|
|
193
|
-
log(DEBUG, "Flower will load ServerApp `%s` in %s", server_app_attr,
|
|
193
|
+
log(DEBUG, "Flower will load ServerApp `%s` in %s", server_app_attr, app_path)
|
|
194
194
|
|
|
195
195
|
log(
|
|
196
196
|
DEBUG,
|
|
@@ -201,7 +201,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches
|
|
|
201
201
|
# Run the ServerApp with the Driver
|
|
202
202
|
run(
|
|
203
203
|
driver=driver,
|
|
204
|
-
server_app_dir=
|
|
204
|
+
server_app_dir=app_path,
|
|
205
205
|
server_app_run_config=server_app_run_config,
|
|
206
206
|
server_app_attr=server_app_attr,
|
|
207
207
|
)
|
|
@@ -219,15 +219,16 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser:
|
|
|
219
219
|
)
|
|
220
220
|
|
|
221
221
|
parser.add_argument(
|
|
222
|
-
"
|
|
222
|
+
"app",
|
|
223
223
|
nargs="?",
|
|
224
224
|
default=None,
|
|
225
|
-
help="
|
|
225
|
+
help="Load and run the `ServerApp` from the specified Flower App path. "
|
|
226
|
+
"The `pyproject.toml` file must be located in the root of this path.",
|
|
226
227
|
)
|
|
227
228
|
parser.add_argument(
|
|
228
229
|
"--insecure",
|
|
229
230
|
action="store_true",
|
|
230
|
-
help="Run the
|
|
231
|
+
help="Run the `ServerApp` without HTTPS. By default, the app runs with "
|
|
231
232
|
"HTTPS enabled. Use this flag only if you understand the risks.",
|
|
232
233
|
)
|
|
233
234
|
parser.add_argument(
|
|
@@ -252,25 +253,6 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser:
|
|
|
252
253
|
default=ADDRESS_DRIVER_API,
|
|
253
254
|
help="SuperLink Driver API (gRPC-rere) address (IPv4, IPv6, or a domain name)",
|
|
254
255
|
)
|
|
255
|
-
parser.add_argument(
|
|
256
|
-
"--dir",
|
|
257
|
-
default="",
|
|
258
|
-
help="Add specified directory to the PYTHONPATH and load Flower "
|
|
259
|
-
"app from there."
|
|
260
|
-
" Default: current working directory.",
|
|
261
|
-
)
|
|
262
|
-
parser.add_argument(
|
|
263
|
-
"--fab-id",
|
|
264
|
-
default=None,
|
|
265
|
-
type=str,
|
|
266
|
-
help="The identifier of the FAB used in the run.",
|
|
267
|
-
)
|
|
268
|
-
parser.add_argument(
|
|
269
|
-
"--fab-version",
|
|
270
|
-
default=None,
|
|
271
|
-
type=str,
|
|
272
|
-
help="The version of the FAB used in the run.",
|
|
273
|
-
)
|
|
274
256
|
parser.add_argument(
|
|
275
257
|
"--run-id",
|
|
276
258
|
default=None,
|
|
@@ -35,6 +35,7 @@ from flwr.proto.driver_pb2 import ( # pylint: disable=E0611
|
|
|
35
35
|
PushTaskInsRequest,
|
|
36
36
|
PushTaskInsResponse,
|
|
37
37
|
)
|
|
38
|
+
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
38
39
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
39
40
|
from flwr.proto.run_pb2 import ( # pylint: disable=E0611
|
|
40
41
|
GetRunRequest,
|
|
@@ -163,6 +164,12 @@ class DriverServicer(driver_pb2_grpc.DriverServicer):
|
|
|
163
164
|
)
|
|
164
165
|
)
|
|
165
166
|
|
|
167
|
+
def GetFab(
|
|
168
|
+
self, request: GetFabRequest, context: grpc.ServicerContext
|
|
169
|
+
) -> GetFabResponse:
|
|
170
|
+
"""Will be implemented later."""
|
|
171
|
+
raise NotImplementedError
|
|
172
|
+
|
|
166
173
|
|
|
167
174
|
def _raise_if(validation_error: bool, detail: str) -> None:
|
|
168
175
|
if validation_error:
|
|
@@ -21,6 +21,7 @@ import grpc
|
|
|
21
21
|
|
|
22
22
|
from flwr.common.logger import log
|
|
23
23
|
from flwr.proto import fleet_pb2_grpc # pylint: disable=E0611
|
|
24
|
+
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
24
25
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
25
26
|
CreateNodeRequest,
|
|
26
27
|
CreateNodeResponse,
|
|
@@ -101,3 +102,9 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
|
|
|
101
102
|
request=request,
|
|
102
103
|
state=self.state_factory.state(),
|
|
103
104
|
)
|
|
105
|
+
|
|
106
|
+
def GetFab(
|
|
107
|
+
self, request: GetFabRequest, context: grpc.ServicerContext
|
|
108
|
+
) -> GetFabResponse:
|
|
109
|
+
"""Will be implemented later."""
|
|
110
|
+
raise NotImplementedError
|
|
@@ -29,7 +29,7 @@ BackendConfig = Dict[str, Dict[str, ConfigsRecordValues]]
|
|
|
29
29
|
class Backend(ABC):
|
|
30
30
|
"""Abstract base class for a Simulation Engine Backend."""
|
|
31
31
|
|
|
32
|
-
def __init__(self, backend_config: BackendConfig
|
|
32
|
+
def __init__(self, backend_config: BackendConfig) -> None:
|
|
33
33
|
"""Construct a backend."""
|
|
34
34
|
|
|
35
35
|
@abstractmethod
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Ray backend for the Fleet API using the Simulation Engine."""
|
|
16
16
|
|
|
17
|
-
import pathlib
|
|
18
17
|
from logging import DEBUG, ERROR
|
|
19
|
-
from typing import Callable, Dict,
|
|
18
|
+
from typing import Callable, Dict, Tuple, Union
|
|
20
19
|
|
|
21
20
|
import ray
|
|
22
21
|
|
|
@@ -33,7 +32,6 @@ from .backend import Backend, BackendConfig
|
|
|
33
32
|
|
|
34
33
|
ClientResourcesDict = Dict[str, Union[int, float]]
|
|
35
34
|
ActorArgsDict = Dict[str, Union[int, float, Callable[[], None]]]
|
|
36
|
-
RunTimeEnvDict = Dict[str, Union[str, List[str]]]
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
class RayBackend(Backend):
|
|
@@ -42,18 +40,14 @@ class RayBackend(Backend):
|
|
|
42
40
|
def __init__(
|
|
43
41
|
self,
|
|
44
42
|
backend_config: BackendConfig,
|
|
45
|
-
work_dir: str,
|
|
46
43
|
) -> None:
|
|
47
44
|
"""Prepare RayBackend by initialising Ray and creating the ActorPool."""
|
|
48
45
|
log(DEBUG, "Initialising: %s", self.__class__.__name__)
|
|
49
46
|
log(DEBUG, "Backend config: %s", backend_config)
|
|
50
47
|
|
|
51
|
-
if not pathlib.Path(work_dir).exists():
|
|
52
|
-
raise ValueError(f"Specified work_dir {work_dir} does not exist.")
|
|
53
|
-
|
|
54
48
|
# Initialise ray
|
|
55
49
|
self.init_args_key = "init_args"
|
|
56
|
-
self.init_ray(backend_config
|
|
50
|
+
self.init_ray(backend_config)
|
|
57
51
|
|
|
58
52
|
# Validate client resources
|
|
59
53
|
self.client_resources_key = "client_resources"
|
|
@@ -68,23 +62,6 @@ class RayBackend(Backend):
|
|
|
68
62
|
actor_kwargs=actor_kwargs,
|
|
69
63
|
)
|
|
70
64
|
|
|
71
|
-
def _configure_runtime_env(self, work_dir: str) -> RunTimeEnvDict:
|
|
72
|
-
"""Return list of files/subdirectories to exclude relative to work_dir.
|
|
73
|
-
|
|
74
|
-
Without this, Ray will push everything to the Ray Cluster.
|
|
75
|
-
"""
|
|
76
|
-
runtime_env: RunTimeEnvDict = {"working_dir": work_dir}
|
|
77
|
-
|
|
78
|
-
excludes = []
|
|
79
|
-
path = pathlib.Path(work_dir)
|
|
80
|
-
for p in path.rglob("*"):
|
|
81
|
-
# Exclude files need to be relative to the working_dir
|
|
82
|
-
if p.is_file() and not str(p).endswith(".py"):
|
|
83
|
-
excludes.append(str(p.relative_to(path)))
|
|
84
|
-
runtime_env["excludes"] = excludes
|
|
85
|
-
|
|
86
|
-
return runtime_env
|
|
87
|
-
|
|
88
65
|
def _validate_client_resources(self, config: BackendConfig) -> ClientResourcesDict:
|
|
89
66
|
client_resources_config = config.get(self.client_resources_key)
|
|
90
67
|
client_resources: ClientResourcesDict = {}
|
|
@@ -123,26 +100,18 @@ class RayBackend(Backend):
|
|
|
123
100
|
actor_args["on_actor_init_fn"] = enable_tf_gpu_growth
|
|
124
101
|
return actor_args
|
|
125
102
|
|
|
126
|
-
def init_ray(self, backend_config: BackendConfig
|
|
103
|
+
def init_ray(self, backend_config: BackendConfig) -> None:
|
|
127
104
|
"""Intialises Ray if not already initialised."""
|
|
128
105
|
if not ray.is_initialized():
|
|
129
|
-
# Init ray and append working dir if needed
|
|
130
|
-
runtime_env = (
|
|
131
|
-
self._configure_runtime_env(work_dir=work_dir) if work_dir else None
|
|
132
|
-
)
|
|
133
|
-
|
|
134
106
|
ray_init_args: Dict[
|
|
135
107
|
str,
|
|
136
|
-
|
|
108
|
+
ConfigsRecordValues,
|
|
137
109
|
] = {}
|
|
138
110
|
|
|
139
111
|
if backend_config.get(self.init_args_key):
|
|
140
112
|
for k, v in backend_config[self.init_args_key].items():
|
|
141
113
|
ray_init_args[k] = v
|
|
142
114
|
|
|
143
|
-
if runtime_env is not None:
|
|
144
|
-
ray_init_args["runtime_env"] = runtime_env
|
|
145
|
-
|
|
146
115
|
ray.init(**ray_init_args)
|
|
147
116
|
|
|
148
117
|
@property
|
|
@@ -339,7 +339,7 @@ def start_vce(
|
|
|
339
339
|
|
|
340
340
|
def backend_fn() -> Backend:
|
|
341
341
|
"""Instantiate a Backend."""
|
|
342
|
-
return backend_type(backend_config
|
|
342
|
+
return backend_type(backend_config)
|
|
343
343
|
|
|
344
344
|
# Load ClientApp if needed
|
|
345
345
|
def _load() -> ClientApp:
|
|
@@ -347,9 +347,9 @@ def start_vce(
|
|
|
347
347
|
if client_app_attr:
|
|
348
348
|
app = _get_load_client_app_fn(
|
|
349
349
|
default_app_ref=client_app_attr,
|
|
350
|
-
|
|
350
|
+
app_path=app_dir,
|
|
351
351
|
flwr_dir=flwr_dir,
|
|
352
|
-
multi_app=
|
|
352
|
+
multi_app=False,
|
|
353
353
|
)(run.fab_id, run.fab_version)
|
|
354
354
|
|
|
355
355
|
if client_app:
|
flwr/superexec/simulation.py
CHANGED
|
@@ -63,8 +63,10 @@ class SimulationEngine(Executor):
|
|
|
63
63
|
def __init__(
|
|
64
64
|
self,
|
|
65
65
|
num_supernodes: Optional[int] = None,
|
|
66
|
+
verbose: Optional[bool] = False,
|
|
66
67
|
) -> None:
|
|
67
68
|
self.num_supernodes = num_supernodes
|
|
69
|
+
self.verbose = verbose
|
|
68
70
|
|
|
69
71
|
@override
|
|
70
72
|
def set_config(
|
|
@@ -80,6 +82,8 @@ class SimulationEngine(Executor):
|
|
|
80
82
|
Supported configuration key/value pairs:
|
|
81
83
|
- "num-supernodes": int
|
|
82
84
|
Number of nodes to register for the simulation.
|
|
85
|
+
- "verbose": bool
|
|
86
|
+
Set verbosity of logs.
|
|
83
87
|
"""
|
|
84
88
|
if num_supernodes := config.get("num-supernodes"):
|
|
85
89
|
if not isinstance(num_supernodes, int):
|
|
@@ -97,6 +101,13 @@ class SimulationEngine(Executor):
|
|
|
97
101
|
"positive integer."
|
|
98
102
|
)
|
|
99
103
|
|
|
104
|
+
if verbose := config.get("verbose"):
|
|
105
|
+
if not isinstance(verbose, bool):
|
|
106
|
+
raise ValueError(
|
|
107
|
+
"The `verbose` value must be a string `true` or `false`."
|
|
108
|
+
)
|
|
109
|
+
self.verbose = verbose
|
|
110
|
+
|
|
100
111
|
@override
|
|
101
112
|
def start_run(
|
|
102
113
|
self,
|
|
@@ -121,10 +132,11 @@ class SimulationEngine(Executor):
|
|
|
121
132
|
fab_path = install_from_fab(fab_file, None, True)
|
|
122
133
|
|
|
123
134
|
# Install FAB Python package
|
|
124
|
-
subprocess.
|
|
135
|
+
subprocess.run(
|
|
125
136
|
[sys.executable, "-m", "pip", "install", "--no-deps", str(fab_path)],
|
|
126
|
-
stdout=subprocess.DEVNULL,
|
|
127
|
-
stderr=subprocess.DEVNULL,
|
|
137
|
+
stdout=None if self.verbose else subprocess.DEVNULL,
|
|
138
|
+
stderr=None if self.verbose else subprocess.DEVNULL,
|
|
139
|
+
check=True,
|
|
128
140
|
)
|
|
129
141
|
|
|
130
142
|
# Load and validate config
|
{flwr_nightly-1.11.0.dev20240724.dist-info → flwr_nightly-1.11.0.dev20240811.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.11.0.
|
|
3
|
+
Version: 1.11.0.dev20240811
|
|
4
4
|
Summary: Flower: A Friendly Federated Learning Framework
|
|
5
5
|
Home-page: https://flower.ai
|
|
6
6
|
License: Apache-2.0
|
|
@@ -33,7 +33,7 @@ Classifier: Typing :: Typed
|
|
|
33
33
|
Provides-Extra: rest
|
|
34
34
|
Provides-Extra: simulation
|
|
35
35
|
Requires-Dist: cryptography (>=42.0.4,<43.0.0)
|
|
36
|
-
Requires-Dist: grpcio (>=1.60.0,<2.0.0,!=1.64.2,!=1.65.1)
|
|
36
|
+
Requires-Dist: grpcio (>=1.60.0,<2.0.0,!=1.64.2,!=1.65.1,!=1.65.2,!=1.65.4)
|
|
37
37
|
Requires-Dist: iterators (>=0.0.2,<0.0.3)
|
|
38
38
|
Requires-Dist: numpy (>=1.21.0,<2.0.0)
|
|
39
39
|
Requires-Dist: pathspec (>=0.12.1,<0.13.0)
|