flwr-nightly 1.21.0.dev20250730__py3-none-any.whl → 1.21.0.dev20250731__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.
- flwr/server/app.py +2 -2
- flwr/superexec/deployment.py +2 -173
- flwr/superexec/simulation.py +2 -111
- flwr/superlink/executor/__init__.py +28 -0
- flwr/{superexec → superlink/executor}/app.py +1 -1
- flwr/superlink/executor/deployment.py +188 -0
- flwr/superlink/executor/simulation.py +126 -0
- flwr/superlink/servicer/__init__.py +15 -0
- flwr/superlink/servicer/exec/__init__.py +22 -0
- flwr/{superexec → superlink/servicer/exec}/exec_event_log_interceptor.py +1 -1
- flwr/{superexec → superlink/servicer/exec}/exec_grpc.py +5 -5
- flwr/{superexec → superlink/servicer/exec}/exec_license_interceptor.py +1 -1
- flwr/{superexec → superlink/servicer/exec}/exec_servicer.py +3 -3
- flwr/{superexec → superlink/servicer/exec}/exec_user_auth_interceptor.py +1 -1
- {flwr_nightly-1.21.0.dev20250730.dist-info → flwr_nightly-1.21.0.dev20250731.dist-info}/METADATA +1 -1
- {flwr_nightly-1.21.0.dev20250730.dist-info → flwr_nightly-1.21.0.dev20250731.dist-info}/RECORD +19 -14
- /flwr/{superexec → superlink/executor}/executor.py +0 -0
- {flwr_nightly-1.21.0.dev20250730.dist-info → flwr_nightly-1.21.0.dev20250731.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.21.0.dev20250730.dist-info → flwr_nightly-1.21.0.dev20250731.dist-info}/entry_points.txt +0 -0
flwr/server/app.py
CHANGED
@@ -73,8 +73,8 @@ from flwr.server.serverapp.app import flwr_serverapp
|
|
73
73
|
from flwr.simulation.app import flwr_simulation
|
74
74
|
from flwr.supercore.ffs import FfsFactory
|
75
75
|
from flwr.supercore.object_store import ObjectStoreFactory
|
76
|
-
from flwr.
|
77
|
-
from flwr.
|
76
|
+
from flwr.superlink.executor import load_executor
|
77
|
+
from flwr.superlink.servicer.exec import run_exec_api_grpc
|
78
78
|
|
79
79
|
from .superlink.fleet.grpc_adapter.grpc_adapter_servicer import GrpcAdapterServicer
|
80
80
|
from .superlink.fleet.grpc_rere.fleet_servicer import FleetServicer
|
flwr/superexec/deployment.py
CHANGED
@@ -12,180 +12,9 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""Deployment engine executor."""
|
15
|
+
"""Deployment engine executor for backward compatibility."""
|
16
16
|
|
17
17
|
|
18
|
-
import
|
19
|
-
from logging import ERROR, INFO
|
20
|
-
from pathlib import Path
|
21
|
-
from typing import Optional
|
22
|
-
|
23
|
-
from typing_extensions import override
|
24
|
-
|
25
|
-
from flwr.cli.config_utils import get_fab_metadata
|
26
|
-
from flwr.common import ConfigRecord, Context, RecordDict
|
27
|
-
from flwr.common.constant import (
|
28
|
-
SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
29
|
-
Status,
|
30
|
-
SubStatus,
|
31
|
-
)
|
32
|
-
from flwr.common.logger import log
|
33
|
-
from flwr.common.typing import Fab, RunStatus, UserConfig
|
34
|
-
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
35
|
-
from flwr.supercore.ffs import Ffs, FfsFactory
|
36
|
-
|
37
|
-
from .executor import Executor
|
38
|
-
|
39
|
-
|
40
|
-
class DeploymentEngine(Executor):
|
41
|
-
"""Deployment engine executor.
|
42
|
-
|
43
|
-
Parameters
|
44
|
-
----------
|
45
|
-
serverappio_api_address: str (default: "127.0.0.1:9091")
|
46
|
-
Address of the SuperLink to connect to.
|
47
|
-
root_certificates: Optional[str] (default: None)
|
48
|
-
Specifies the path to the PEM-encoded root certificate file for
|
49
|
-
establishing secure HTTPS connections.
|
50
|
-
flwr_dir: Optional[str] (default: None)
|
51
|
-
The path containing installed Flower Apps.
|
52
|
-
"""
|
53
|
-
|
54
|
-
def __init__(
|
55
|
-
self,
|
56
|
-
serverappio_api_address: str = SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
57
|
-
root_certificates: Optional[str] = None,
|
58
|
-
flwr_dir: Optional[str] = None,
|
59
|
-
) -> None:
|
60
|
-
self.serverappio_api_address = serverappio_api_address
|
61
|
-
if root_certificates is None:
|
62
|
-
self.root_certificates = None
|
63
|
-
self.root_certificates_bytes = None
|
64
|
-
else:
|
65
|
-
self.root_certificates = root_certificates
|
66
|
-
self.root_certificates_bytes = Path(root_certificates).read_bytes()
|
67
|
-
self.flwr_dir = flwr_dir
|
68
|
-
self.linkstate_factory: Optional[LinkStateFactory] = None
|
69
|
-
self.ffs_factory: Optional[FfsFactory] = None
|
70
|
-
|
71
|
-
@override
|
72
|
-
def initialize(
|
73
|
-
self, linkstate_factory: LinkStateFactory, ffs_factory: FfsFactory
|
74
|
-
) -> None:
|
75
|
-
"""Initialize the executor with the necessary factories."""
|
76
|
-
self.linkstate_factory = linkstate_factory
|
77
|
-
self.ffs_factory = ffs_factory
|
78
|
-
|
79
|
-
@property
|
80
|
-
def linkstate(self) -> LinkState:
|
81
|
-
"""Return the LinkState."""
|
82
|
-
if self.linkstate_factory is None:
|
83
|
-
raise RuntimeError("Executor is not initialized.")
|
84
|
-
return self.linkstate_factory.state()
|
85
|
-
|
86
|
-
@property
|
87
|
-
def ffs(self) -> Ffs:
|
88
|
-
"""Return the Flower File Storage (FFS)."""
|
89
|
-
if self.ffs_factory is None:
|
90
|
-
raise RuntimeError("Executor is not initialized.")
|
91
|
-
return self.ffs_factory.ffs()
|
92
|
-
|
93
|
-
@override
|
94
|
-
def set_config(
|
95
|
-
self,
|
96
|
-
config: UserConfig,
|
97
|
-
) -> None:
|
98
|
-
"""Set executor config arguments.
|
99
|
-
|
100
|
-
Parameters
|
101
|
-
----------
|
102
|
-
config : UserConfig
|
103
|
-
A dictionary for configuration values.
|
104
|
-
Supported configuration key/value pairs:
|
105
|
-
- "superlink": str
|
106
|
-
The address of the SuperLink ServerAppIo API.
|
107
|
-
- "root-certificates": str
|
108
|
-
The path to the root certificates.
|
109
|
-
- "flwr-dir": str
|
110
|
-
The path to the Flower directory.
|
111
|
-
"""
|
112
|
-
if not config:
|
113
|
-
return
|
114
|
-
if superlink_address := config.get("superlink"):
|
115
|
-
if not isinstance(superlink_address, str):
|
116
|
-
raise ValueError("The `superlink` value should be of type `str`.")
|
117
|
-
self.serverappio_api_address = superlink_address
|
118
|
-
if root_certificates := config.get("root-certificates"):
|
119
|
-
if not isinstance(root_certificates, str):
|
120
|
-
raise ValueError(
|
121
|
-
"The `root-certificates` value should be of type `str`."
|
122
|
-
)
|
123
|
-
self.root_certificates = root_certificates
|
124
|
-
self.root_certificates_bytes = Path(str(root_certificates)).read_bytes()
|
125
|
-
if flwr_dir := config.get("flwr-dir"):
|
126
|
-
if not isinstance(flwr_dir, str):
|
127
|
-
raise ValueError("The `flwr-dir` value should be of type `str`.")
|
128
|
-
self.flwr_dir = str(flwr_dir)
|
129
|
-
|
130
|
-
def _create_run(
|
131
|
-
self,
|
132
|
-
fab: Fab,
|
133
|
-
override_config: UserConfig,
|
134
|
-
flwr_aid: Optional[str],
|
135
|
-
) -> int:
|
136
|
-
fab_hash = self.ffs.put(fab.content, {})
|
137
|
-
if fab_hash != fab.hash_str:
|
138
|
-
raise RuntimeError(
|
139
|
-
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
140
|
-
)
|
141
|
-
fab_id, fab_version = get_fab_metadata(fab.content)
|
142
|
-
|
143
|
-
run_id = self.linkstate.create_run(
|
144
|
-
fab_id, fab_version, fab_hash, override_config, ConfigRecord(), flwr_aid
|
145
|
-
)
|
146
|
-
return run_id
|
147
|
-
|
148
|
-
def _create_context(self, run_id: int) -> None:
|
149
|
-
"""Register a Context for a Run."""
|
150
|
-
# Create an empty context for the Run
|
151
|
-
context = Context(
|
152
|
-
run_id=run_id, node_id=0, node_config={}, state=RecordDict(), run_config={}
|
153
|
-
)
|
154
|
-
|
155
|
-
# Register the context at the LinkState
|
156
|
-
self.linkstate.set_serverapp_context(run_id=run_id, context=context)
|
157
|
-
|
158
|
-
@override
|
159
|
-
def start_run(
|
160
|
-
self,
|
161
|
-
fab_file: bytes,
|
162
|
-
override_config: UserConfig,
|
163
|
-
federation_options: ConfigRecord,
|
164
|
-
flwr_aid: Optional[str],
|
165
|
-
) -> Optional[int]:
|
166
|
-
"""Start run using the Flower Deployment Engine."""
|
167
|
-
run_id = None
|
168
|
-
try:
|
169
|
-
|
170
|
-
# Call SuperLink to create run
|
171
|
-
run_id = self._create_run(
|
172
|
-
Fab(hashlib.sha256(fab_file).hexdigest(), fab_file),
|
173
|
-
override_config,
|
174
|
-
flwr_aid,
|
175
|
-
)
|
176
|
-
|
177
|
-
# Register context for the Run
|
178
|
-
self._create_context(run_id=run_id)
|
179
|
-
log(INFO, "Created run %s", str(run_id))
|
180
|
-
|
181
|
-
return run_id
|
182
|
-
# pylint: disable-next=broad-except
|
183
|
-
except Exception as e:
|
184
|
-
log(ERROR, "Could not start run: %s", str(e))
|
185
|
-
if run_id:
|
186
|
-
run_status = RunStatus(Status.FINISHED, SubStatus.FAILED, str(e))
|
187
|
-
self.linkstate.update_run_status(run_id, new_status=run_status)
|
188
|
-
return None
|
189
|
-
|
18
|
+
from flwr.superlink.executor import DeploymentEngine
|
190
19
|
|
191
20
|
executor = DeploymentEngine()
|
flwr/superexec/simulation.py
CHANGED
@@ -12,118 +12,9 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""Simulation engine executor."""
|
15
|
+
"""Simulation engine executor for backward compatibility."""
|
16
16
|
|
17
17
|
|
18
|
-
import
|
19
|
-
from logging import ERROR, INFO
|
20
|
-
from typing import Optional
|
21
|
-
|
22
|
-
from typing_extensions import override
|
23
|
-
|
24
|
-
from flwr.cli.config_utils import get_fab_metadata
|
25
|
-
from flwr.common import ConfigRecord, Context, RecordDict
|
26
|
-
from flwr.common.logger import log
|
27
|
-
from flwr.common.typing import Fab, UserConfig
|
28
|
-
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
29
|
-
from flwr.supercore.ffs import Ffs, FfsFactory
|
30
|
-
|
31
|
-
from .executor import Executor
|
32
|
-
|
33
|
-
|
34
|
-
class SimulationEngine(Executor):
|
35
|
-
"""Simulation engine executor."""
|
36
|
-
|
37
|
-
def __init__(
|
38
|
-
self,
|
39
|
-
) -> None:
|
40
|
-
self.linkstate_factory: Optional[LinkStateFactory] = None
|
41
|
-
self.ffs_factory: Optional[FfsFactory] = None
|
42
|
-
|
43
|
-
@override
|
44
|
-
def initialize(
|
45
|
-
self, linkstate_factory: LinkStateFactory, ffs_factory: FfsFactory
|
46
|
-
) -> None:
|
47
|
-
"""Initialize the executor with the necessary factories."""
|
48
|
-
self.linkstate_factory = linkstate_factory
|
49
|
-
self.ffs_factory = ffs_factory
|
50
|
-
|
51
|
-
@property
|
52
|
-
def linkstate(self) -> LinkState:
|
53
|
-
"""Return the LinkState."""
|
54
|
-
if self.linkstate_factory is None:
|
55
|
-
raise RuntimeError("Executor is not initialized.")
|
56
|
-
return self.linkstate_factory.state()
|
57
|
-
|
58
|
-
@property
|
59
|
-
def ffs(self) -> Ffs:
|
60
|
-
"""Return the Flower File Storage (FFS)."""
|
61
|
-
if self.ffs_factory is None:
|
62
|
-
raise RuntimeError("Executor is not initialized.")
|
63
|
-
return self.ffs_factory.ffs()
|
64
|
-
|
65
|
-
@override
|
66
|
-
def set_config(
|
67
|
-
self,
|
68
|
-
config: UserConfig,
|
69
|
-
) -> None:
|
70
|
-
"""Set executor config arguments."""
|
71
|
-
|
72
|
-
# pylint: disable=too-many-locals
|
73
|
-
@override
|
74
|
-
def start_run(
|
75
|
-
self,
|
76
|
-
fab_file: bytes,
|
77
|
-
override_config: UserConfig,
|
78
|
-
federation_options: ConfigRecord,
|
79
|
-
flwr_aid: Optional[str],
|
80
|
-
) -> Optional[int]:
|
81
|
-
"""Start run using the Flower Simulation Engine."""
|
82
|
-
try:
|
83
|
-
# Check that num-supernodes is set
|
84
|
-
if "num-supernodes" not in federation_options:
|
85
|
-
raise ValueError(
|
86
|
-
"Federation options doesn't contain key `num-supernodes`."
|
87
|
-
)
|
88
|
-
|
89
|
-
# Create run
|
90
|
-
fab = Fab(hashlib.sha256(fab_file).hexdigest(), fab_file)
|
91
|
-
fab_hash = self.ffs.put(fab.content, {})
|
92
|
-
if fab_hash != fab.hash_str:
|
93
|
-
raise RuntimeError(
|
94
|
-
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
95
|
-
)
|
96
|
-
fab_id, fab_version = get_fab_metadata(fab.content)
|
97
|
-
|
98
|
-
run_id = self.linkstate.create_run(
|
99
|
-
fab_id,
|
100
|
-
fab_version,
|
101
|
-
fab_hash,
|
102
|
-
override_config,
|
103
|
-
federation_options,
|
104
|
-
flwr_aid,
|
105
|
-
)
|
106
|
-
|
107
|
-
# Create an empty context for the Run
|
108
|
-
context = Context(
|
109
|
-
run_id=run_id,
|
110
|
-
node_id=0,
|
111
|
-
node_config={},
|
112
|
-
state=RecordDict(),
|
113
|
-
run_config={},
|
114
|
-
)
|
115
|
-
|
116
|
-
# Register the context at the LinkState
|
117
|
-
self.linkstate.set_serverapp_context(run_id=run_id, context=context)
|
118
|
-
|
119
|
-
log(INFO, "Created run %s", str(run_id))
|
120
|
-
|
121
|
-
return run_id
|
122
|
-
|
123
|
-
# pylint: disable-next=broad-except
|
124
|
-
except Exception as e:
|
125
|
-
log(ERROR, "Could not start run: %s", str(e))
|
126
|
-
return None
|
127
|
-
|
18
|
+
from flwr.superlink.executor import SimulationEngine
|
128
19
|
|
129
20
|
executor = SimulationEngine()
|
@@ -0,0 +1,28 @@
|
|
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
|
+
"""Executor for Exec API."""
|
16
|
+
|
17
|
+
|
18
|
+
from .app import load_executor
|
19
|
+
from .deployment import DeploymentEngine
|
20
|
+
from .executor import Executor
|
21
|
+
from .simulation import SimulationEngine
|
22
|
+
|
23
|
+
__all__ = [
|
24
|
+
"DeploymentEngine",
|
25
|
+
"Executor",
|
26
|
+
"SimulationEngine",
|
27
|
+
"load_executor",
|
28
|
+
]
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""
|
15
|
+
"""Function for loading executor."""
|
16
16
|
|
17
17
|
|
18
18
|
import argparse
|
@@ -0,0 +1,188 @@
|
|
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
|
+
"""Deployment engine executor."""
|
16
|
+
|
17
|
+
|
18
|
+
import hashlib
|
19
|
+
from logging import ERROR, INFO
|
20
|
+
from pathlib import Path
|
21
|
+
from typing import Optional
|
22
|
+
|
23
|
+
from typing_extensions import override
|
24
|
+
|
25
|
+
from flwr.cli.config_utils import get_fab_metadata
|
26
|
+
from flwr.common import ConfigRecord, Context, RecordDict
|
27
|
+
from flwr.common.constant import (
|
28
|
+
SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
29
|
+
Status,
|
30
|
+
SubStatus,
|
31
|
+
)
|
32
|
+
from flwr.common.logger import log
|
33
|
+
from flwr.common.typing import Fab, RunStatus, UserConfig
|
34
|
+
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
35
|
+
from flwr.supercore.ffs import Ffs, FfsFactory
|
36
|
+
|
37
|
+
from .executor import Executor
|
38
|
+
|
39
|
+
|
40
|
+
class DeploymentEngine(Executor):
|
41
|
+
"""Deployment engine executor.
|
42
|
+
|
43
|
+
Parameters
|
44
|
+
----------
|
45
|
+
serverappio_api_address: str (default: "127.0.0.1:9091")
|
46
|
+
Address of the SuperLink to connect to.
|
47
|
+
root_certificates: Optional[str] (default: None)
|
48
|
+
Specifies the path to the PEM-encoded root certificate file for
|
49
|
+
establishing secure HTTPS connections.
|
50
|
+
flwr_dir: Optional[str] (default: None)
|
51
|
+
The path containing installed Flower Apps.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(
|
55
|
+
self,
|
56
|
+
serverappio_api_address: str = SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
57
|
+
root_certificates: Optional[str] = None,
|
58
|
+
flwr_dir: Optional[str] = None,
|
59
|
+
) -> None:
|
60
|
+
self.serverappio_api_address = serverappio_api_address
|
61
|
+
if root_certificates is None:
|
62
|
+
self.root_certificates = None
|
63
|
+
self.root_certificates_bytes = None
|
64
|
+
else:
|
65
|
+
self.root_certificates = root_certificates
|
66
|
+
self.root_certificates_bytes = Path(root_certificates).read_bytes()
|
67
|
+
self.flwr_dir = flwr_dir
|
68
|
+
self.linkstate_factory: Optional[LinkStateFactory] = None
|
69
|
+
self.ffs_factory: Optional[FfsFactory] = None
|
70
|
+
|
71
|
+
@override
|
72
|
+
def initialize(
|
73
|
+
self, linkstate_factory: LinkStateFactory, ffs_factory: FfsFactory
|
74
|
+
) -> None:
|
75
|
+
"""Initialize the executor with the necessary factories."""
|
76
|
+
self.linkstate_factory = linkstate_factory
|
77
|
+
self.ffs_factory = ffs_factory
|
78
|
+
|
79
|
+
@property
|
80
|
+
def linkstate(self) -> LinkState:
|
81
|
+
"""Return the LinkState."""
|
82
|
+
if self.linkstate_factory is None:
|
83
|
+
raise RuntimeError("Executor is not initialized.")
|
84
|
+
return self.linkstate_factory.state()
|
85
|
+
|
86
|
+
@property
|
87
|
+
def ffs(self) -> Ffs:
|
88
|
+
"""Return the Flower File Storage (FFS)."""
|
89
|
+
if self.ffs_factory is None:
|
90
|
+
raise RuntimeError("Executor is not initialized.")
|
91
|
+
return self.ffs_factory.ffs()
|
92
|
+
|
93
|
+
@override
|
94
|
+
def set_config(
|
95
|
+
self,
|
96
|
+
config: UserConfig,
|
97
|
+
) -> None:
|
98
|
+
"""Set executor config arguments.
|
99
|
+
|
100
|
+
Parameters
|
101
|
+
----------
|
102
|
+
config : UserConfig
|
103
|
+
A dictionary for configuration values.
|
104
|
+
Supported configuration key/value pairs:
|
105
|
+
- "superlink": str
|
106
|
+
The address of the SuperLink ServerAppIo API.
|
107
|
+
- "root-certificates": str
|
108
|
+
The path to the root certificates.
|
109
|
+
- "flwr-dir": str
|
110
|
+
The path to the Flower directory.
|
111
|
+
"""
|
112
|
+
if not config:
|
113
|
+
return
|
114
|
+
if superlink_address := config.get("superlink"):
|
115
|
+
if not isinstance(superlink_address, str):
|
116
|
+
raise ValueError("The `superlink` value should be of type `str`.")
|
117
|
+
self.serverappio_api_address = superlink_address
|
118
|
+
if root_certificates := config.get("root-certificates"):
|
119
|
+
if not isinstance(root_certificates, str):
|
120
|
+
raise ValueError(
|
121
|
+
"The `root-certificates` value should be of type `str`."
|
122
|
+
)
|
123
|
+
self.root_certificates = root_certificates
|
124
|
+
self.root_certificates_bytes = Path(str(root_certificates)).read_bytes()
|
125
|
+
if flwr_dir := config.get("flwr-dir"):
|
126
|
+
if not isinstance(flwr_dir, str):
|
127
|
+
raise ValueError("The `flwr-dir` value should be of type `str`.")
|
128
|
+
self.flwr_dir = str(flwr_dir)
|
129
|
+
|
130
|
+
def _create_run(
|
131
|
+
self,
|
132
|
+
fab: Fab,
|
133
|
+
override_config: UserConfig,
|
134
|
+
flwr_aid: Optional[str],
|
135
|
+
) -> int:
|
136
|
+
fab_hash = self.ffs.put(fab.content, {})
|
137
|
+
if fab_hash != fab.hash_str:
|
138
|
+
raise RuntimeError(
|
139
|
+
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
140
|
+
)
|
141
|
+
fab_id, fab_version = get_fab_metadata(fab.content)
|
142
|
+
|
143
|
+
run_id = self.linkstate.create_run(
|
144
|
+
fab_id, fab_version, fab_hash, override_config, ConfigRecord(), flwr_aid
|
145
|
+
)
|
146
|
+
return run_id
|
147
|
+
|
148
|
+
def _create_context(self, run_id: int) -> None:
|
149
|
+
"""Register a Context for a Run."""
|
150
|
+
# Create an empty context for the Run
|
151
|
+
context = Context(
|
152
|
+
run_id=run_id, node_id=0, node_config={}, state=RecordDict(), run_config={}
|
153
|
+
)
|
154
|
+
|
155
|
+
# Register the context at the LinkState
|
156
|
+
self.linkstate.set_serverapp_context(run_id=run_id, context=context)
|
157
|
+
|
158
|
+
@override
|
159
|
+
def start_run(
|
160
|
+
self,
|
161
|
+
fab_file: bytes,
|
162
|
+
override_config: UserConfig,
|
163
|
+
federation_options: ConfigRecord,
|
164
|
+
flwr_aid: Optional[str],
|
165
|
+
) -> Optional[int]:
|
166
|
+
"""Start run using the Flower Deployment Engine."""
|
167
|
+
run_id = None
|
168
|
+
try:
|
169
|
+
|
170
|
+
# Call SuperLink to create run
|
171
|
+
run_id = self._create_run(
|
172
|
+
Fab(hashlib.sha256(fab_file).hexdigest(), fab_file),
|
173
|
+
override_config,
|
174
|
+
flwr_aid,
|
175
|
+
)
|
176
|
+
|
177
|
+
# Register context for the Run
|
178
|
+
self._create_context(run_id=run_id)
|
179
|
+
log(INFO, "Created run %s", str(run_id))
|
180
|
+
|
181
|
+
return run_id
|
182
|
+
# pylint: disable-next=broad-except
|
183
|
+
except Exception as e:
|
184
|
+
log(ERROR, "Could not start run: %s", str(e))
|
185
|
+
if run_id:
|
186
|
+
run_status = RunStatus(Status.FINISHED, SubStatus.FAILED, str(e))
|
187
|
+
self.linkstate.update_run_status(run_id, new_status=run_status)
|
188
|
+
return None
|
@@ -0,0 +1,126 @@
|
|
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
|
+
"""Simulation engine executor."""
|
16
|
+
|
17
|
+
|
18
|
+
import hashlib
|
19
|
+
from logging import ERROR, INFO
|
20
|
+
from typing import Optional
|
21
|
+
|
22
|
+
from typing_extensions import override
|
23
|
+
|
24
|
+
from flwr.cli.config_utils import get_fab_metadata
|
25
|
+
from flwr.common import ConfigRecord, Context, RecordDict
|
26
|
+
from flwr.common.logger import log
|
27
|
+
from flwr.common.typing import Fab, UserConfig
|
28
|
+
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
29
|
+
from flwr.supercore.ffs import Ffs, FfsFactory
|
30
|
+
|
31
|
+
from .executor import Executor
|
32
|
+
|
33
|
+
|
34
|
+
class SimulationEngine(Executor):
|
35
|
+
"""Simulation engine executor."""
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
) -> None:
|
40
|
+
self.linkstate_factory: Optional[LinkStateFactory] = None
|
41
|
+
self.ffs_factory: Optional[FfsFactory] = None
|
42
|
+
|
43
|
+
@override
|
44
|
+
def initialize(
|
45
|
+
self, linkstate_factory: LinkStateFactory, ffs_factory: FfsFactory
|
46
|
+
) -> None:
|
47
|
+
"""Initialize the executor with the necessary factories."""
|
48
|
+
self.linkstate_factory = linkstate_factory
|
49
|
+
self.ffs_factory = ffs_factory
|
50
|
+
|
51
|
+
@property
|
52
|
+
def linkstate(self) -> LinkState:
|
53
|
+
"""Return the LinkState."""
|
54
|
+
if self.linkstate_factory is None:
|
55
|
+
raise RuntimeError("Executor is not initialized.")
|
56
|
+
return self.linkstate_factory.state()
|
57
|
+
|
58
|
+
@property
|
59
|
+
def ffs(self) -> Ffs:
|
60
|
+
"""Return the Flower File Storage (FFS)."""
|
61
|
+
if self.ffs_factory is None:
|
62
|
+
raise RuntimeError("Executor is not initialized.")
|
63
|
+
return self.ffs_factory.ffs()
|
64
|
+
|
65
|
+
@override
|
66
|
+
def set_config(
|
67
|
+
self,
|
68
|
+
config: UserConfig,
|
69
|
+
) -> None:
|
70
|
+
"""Set executor config arguments."""
|
71
|
+
|
72
|
+
# pylint: disable=too-many-locals
|
73
|
+
@override
|
74
|
+
def start_run(
|
75
|
+
self,
|
76
|
+
fab_file: bytes,
|
77
|
+
override_config: UserConfig,
|
78
|
+
federation_options: ConfigRecord,
|
79
|
+
flwr_aid: Optional[str],
|
80
|
+
) -> Optional[int]:
|
81
|
+
"""Start run using the Flower Simulation Engine."""
|
82
|
+
try:
|
83
|
+
# Check that num-supernodes is set
|
84
|
+
if "num-supernodes" not in federation_options:
|
85
|
+
raise ValueError(
|
86
|
+
"Federation options doesn't contain key `num-supernodes`."
|
87
|
+
)
|
88
|
+
|
89
|
+
# Create run
|
90
|
+
fab = Fab(hashlib.sha256(fab_file).hexdigest(), fab_file)
|
91
|
+
fab_hash = self.ffs.put(fab.content, {})
|
92
|
+
if fab_hash != fab.hash_str:
|
93
|
+
raise RuntimeError(
|
94
|
+
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
95
|
+
)
|
96
|
+
fab_id, fab_version = get_fab_metadata(fab.content)
|
97
|
+
|
98
|
+
run_id = self.linkstate.create_run(
|
99
|
+
fab_id,
|
100
|
+
fab_version,
|
101
|
+
fab_hash,
|
102
|
+
override_config,
|
103
|
+
federation_options,
|
104
|
+
flwr_aid,
|
105
|
+
)
|
106
|
+
|
107
|
+
# Create an empty context for the Run
|
108
|
+
context = Context(
|
109
|
+
run_id=run_id,
|
110
|
+
node_id=0,
|
111
|
+
node_config={},
|
112
|
+
state=RecordDict(),
|
113
|
+
run_config={},
|
114
|
+
)
|
115
|
+
|
116
|
+
# Register the context at the LinkState
|
117
|
+
self.linkstate.set_serverapp_context(run_id=run_id, context=context)
|
118
|
+
|
119
|
+
log(INFO, "Created run %s", str(run_id))
|
120
|
+
|
121
|
+
return run_id
|
122
|
+
|
123
|
+
# pylint: disable-next=broad-except
|
124
|
+
except Exception as e:
|
125
|
+
log(ERROR, "Could not start run: %s", str(e))
|
126
|
+
return None
|
@@ -0,0 +1,15 @@
|
|
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 SuperLink servicers."""
|
@@ -0,0 +1,22 @@
|
|
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
|
+
"""Exec API Servicer."""
|
16
|
+
|
17
|
+
|
18
|
+
from .exec_grpc import run_exec_api_grpc
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"run_exec_api_grpc",
|
22
|
+
]
|
@@ -49,7 +49,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
49
49
|
return continuation(handler_call_details)
|
50
50
|
|
51
51
|
# One of the method handlers in
|
52
|
-
# `flwr.
|
52
|
+
# `flwr.superlink.servicer.exec.ExecServicer`
|
53
53
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
54
54
|
method_name: str = handler_call_details.method
|
55
55
|
return self._generic_event_log_unary_method_handler(method_handler, method_name)
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""
|
15
|
+
"""Exec API server."""
|
16
16
|
|
17
17
|
|
18
18
|
from logging import INFO
|
@@ -32,12 +32,12 @@ from flwr.server.superlink.linkstate import LinkStateFactory
|
|
32
32
|
from flwr.supercore.ffs import FfsFactory
|
33
33
|
from flwr.supercore.license_plugin import LicensePlugin
|
34
34
|
from flwr.supercore.object_store import ObjectStoreFactory
|
35
|
-
from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
|
36
|
-
from flwr.superexec.exec_license_interceptor import ExecLicenseInterceptor
|
37
|
-
from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
|
38
35
|
|
36
|
+
from ...executor import Executor
|
37
|
+
from .exec_event_log_interceptor import ExecEventLogInterceptor
|
38
|
+
from .exec_license_interceptor import ExecLicenseInterceptor
|
39
39
|
from .exec_servicer import ExecServicer
|
40
|
-
from .
|
40
|
+
from .exec_user_auth_interceptor import ExecUserAuthInterceptor
|
41
41
|
|
42
42
|
try:
|
43
43
|
from flwr.ee import get_license_plugin
|
@@ -47,7 +47,7 @@ class ExecLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
47
47
|
return continuation(handler_call_details)
|
48
48
|
|
49
49
|
# One of the method handlers in
|
50
|
-
# `flwr.
|
50
|
+
# `flwr.superlink.servicer.exec.ExecServicer`
|
51
51
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
52
52
|
return self._generic_license_unary_method_handler(method_handler)
|
53
53
|
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""
|
15
|
+
"""Exec API servicer."""
|
16
16
|
|
17
17
|
|
18
18
|
import time
|
@@ -57,12 +57,12 @@ from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
57
57
|
from flwr.supercore.ffs import FfsFactory
|
58
58
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
59
59
|
|
60
|
+
from ...executor.executor import Executor
|
60
61
|
from .exec_user_auth_interceptor import shared_account_info
|
61
|
-
from .executor import Executor
|
62
62
|
|
63
63
|
|
64
64
|
class ExecServicer(exec_pb2_grpc.ExecServicer):
|
65
|
-
"""
|
65
|
+
"""Exec API servicer."""
|
66
66
|
|
67
67
|
def __init__( # pylint: disable=R0913, R0917
|
68
68
|
self,
|
@@ -77,7 +77,7 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
77
77
|
return continuation(handler_call_details)
|
78
78
|
|
79
79
|
# One of the method handlers in
|
80
|
-
# `flwr.
|
80
|
+
# `flwr.superlink.servicer.exec.ExecServicer`
|
81
81
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
82
82
|
return self._generic_auth_unary_method_handler(method_handler)
|
83
83
|
|
{flwr_nightly-1.21.0.dev20250730.dist-info → flwr_nightly-1.21.0.dev20250731.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.21.0.
|
3
|
+
Version: 1.21.0.dev20250731
|
4
4
|
Summary: Flower: A Friendly Federated AI Framework
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
|
{flwr_nightly-1.21.0.dev20250730.dist-info → flwr_nightly-1.21.0.dev20250731.dist-info}/RECORD
RENAMED
@@ -230,7 +230,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
|
|
230
230
|
flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
|
231
231
|
flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
232
232
|
flwr/server/__init__.py,sha256=LQQHiuL2jy7TpNaKastRdGsexlxSt5ZWAQNVqitDnrY,1598
|
233
|
-
flwr/server/app.py,sha256=
|
233
|
+
flwr/server/app.py,sha256=LdEZVa0FzRTMQstBidH1MBXkrXRIH2sqIX-aj7Iah9A,29493
|
234
234
|
flwr/server/client_manager.py,sha256=5jCGavVli7XdupvWWo7ru3PdFTlRU8IGvHFSSoUVLRs,6227
|
235
235
|
flwr/server/client_proxy.py,sha256=sv0E9AldBYOvc3pusqFh-GnyreeMfsXQ1cuTtxTq_wY,2399
|
236
236
|
flwr/server/compat/__init__.py,sha256=0IsttWvY15qO98_1GyzVC-vR1e_ZPXOdu2qUlOkYMPE,886
|
@@ -348,16 +348,21 @@ flwr/supercore/scheduler/__init__.py,sha256=E4GviiNJoZKz1dOao8ZGRHExsiM23GtOrkpM
|
|
348
348
|
flwr/supercore/scheduler/plugin.py,sha256=kIv0JUrHP-ghrcGT-pbporL9A2mUz8PxASw6KgxCRv8,2460
|
349
349
|
flwr/supercore/utils.py,sha256=ebuHMbeA8eXisX0oMPqBK3hk7uVnIE_yiqWVz8YbkpQ,1324
|
350
350
|
flwr/superexec/__init__.py,sha256=YFqER0IJc1XEWfsX6AxZ9LSRq0sawPYrNYki-brvTIc,715
|
351
|
-
flwr/superexec/
|
352
|
-
flwr/superexec/
|
353
|
-
flwr/superexec/exec_event_log_interceptor.py,sha256=bHBpjRDh-GgLxELNP9ofES7hCEAR_M78wuJZ06vXRLg,5942
|
354
|
-
flwr/superexec/exec_grpc.py,sha256=D7xPHEXjW4SXMb9_dWNZhbFFxUeKr8zE67LITZQbHU8,4148
|
355
|
-
flwr/superexec/exec_license_interceptor.py,sha256=rpnQmHE9FR11EJ3Lwd4_ducvlSHj5hS_SHAMbbZwXlk,3328
|
356
|
-
flwr/superexec/exec_servicer.py,sha256=c0nwdFBiS6CbKrRA7ffOpsgASOLeaRV_ICwxDfxNGAg,12498
|
357
|
-
flwr/superexec/exec_user_auth_interceptor.py,sha256=InqJo9QQu2QQYYusxtmb3EtKdvJECQ1xtvwy5DVF-c0,6196
|
358
|
-
flwr/superexec/executor.py,sha256=LaErHRJvNggjWV6FI6eajgKfnwOvSv2UqzFH253yDro,3265
|
359
|
-
flwr/superexec/simulation.py,sha256=62rSLcS-1wnMsMsafSQuIDLs5ZS6Ail1spkZ-alNNTg,4156
|
351
|
+
flwr/superexec/deployment.py,sha256=CEgWfkN_lH6Vci03RjwKLENw2z6kxNvUdVEErPbqYDY,830
|
352
|
+
flwr/superexec/simulation.py,sha256=wR3ERp_UdWJCkkLdJD7Rk5uVpWJtOqHuyhbq7Sm2tGE,830
|
360
353
|
flwr/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
|
354
|
+
flwr/superlink/executor/__init__.py,sha256=5ZAe4Nj8DflRFTvhAsQ3nr2b-mvNuBisuxTuMwvWsCs,958
|
355
|
+
flwr/superlink/executor/app.py,sha256=AALlkJKgGTeGOARf8G6TBEmO_WdOpmL4dHYpcGWX-Hc,1474
|
356
|
+
flwr/superlink/executor/deployment.py,sha256=whB1k_DE37SqplisFt0nu190BtoqPKRX9Xz86fqm3FU,6765
|
357
|
+
flwr/superlink/executor/executor.py,sha256=LaErHRJvNggjWV6FI6eajgKfnwOvSv2UqzFH253yDro,3265
|
358
|
+
flwr/superlink/executor/simulation.py,sha256=fLy18Bdsfxpxu41vAtBzTy1QLl0iJAdW7UmicBUnRJQ,4124
|
359
|
+
flwr/superlink/servicer/__init__.py,sha256=ZC-kILcUGeh6IxJsfu24cTzUqIGXmQfEKsGfhsnhBpM,717
|
360
|
+
flwr/superlink/servicer/exec/__init__.py,sha256=_06QLcljT-Ihf_9peAkjMm9y1tEDmDRfic_vfGwJk0o,791
|
361
|
+
flwr/superlink/servicer/exec/exec_event_log_interceptor.py,sha256=8sX0-GKYZwNftRr7hP0fBhy7LLzNzi9F4tcjrQ_a6Zo,5942
|
362
|
+
flwr/superlink/servicer/exec/exec_grpc.py,sha256=7l7LmL2LZTd1Vd5b7M_lOaTO9-otvqgWUKQrgl5CPls,4105
|
363
|
+
flwr/superlink/servicer/exec/exec_license_interceptor.py,sha256=cCK4pMaoEv6_pRs6psKsIuouLRGy9kkChQY3TcQDgg4,3328
|
364
|
+
flwr/superlink/servicer/exec/exec_servicer.py,sha256=89B0fwA3CX_4Dd7151qsHdgyEYQEnOq4hTZloA_zO10,12499
|
365
|
+
flwr/superlink/servicer/exec/exec_user_auth_interceptor.py,sha256=XMWX36JgVqsaIorQuenyIzImslCvg-UCC92yu44wneo,6196
|
361
366
|
flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
|
362
367
|
flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
|
363
368
|
flwr/supernode/cli/flower_supernode.py,sha256=fAkk9zGhoP8Sv05EkdXRiCtirTAzWkSZBqRoaDdgflk,8529
|
@@ -374,7 +379,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
|
|
374
379
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
375
380
|
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=ClPoKco7Tjj_cxRPhZlQSrOvcGa8sJwGs26LUNZnI3Y,10608
|
376
381
|
flwr/supernode/start_client_internal.py,sha256=VC7rV4QaX5Vk4Lw0DDodhwsKvGiHbr0mqrV0H77h1UI,21999
|
377
|
-
flwr_nightly-1.21.0.
|
378
|
-
flwr_nightly-1.21.0.
|
379
|
-
flwr_nightly-1.21.0.
|
380
|
-
flwr_nightly-1.21.0.
|
382
|
+
flwr_nightly-1.21.0.dev20250731.dist-info/METADATA,sha256=ZA8ZpC8q5me0iPeqzlGiEug4ppSypQG9zoe75ICtLvY,15966
|
383
|
+
flwr_nightly-1.21.0.dev20250731.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
384
|
+
flwr_nightly-1.21.0.dev20250731.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
|
385
|
+
flwr_nightly-1.21.0.dev20250731.dist-info/RECORD,,
|
File without changes
|
{flwr_nightly-1.21.0.dev20250730.dist-info → flwr_nightly-1.21.0.dev20250731.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|