flwr-nightly 1.9.0.dev20240417__py3-none-any.whl → 1.9.0.dev20240418__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/new/new.py +9 -4
- flwr/cli/new/templates/app/.gitignore.tpl +160 -0
- flwr/client/rest_client/connection.py +6 -6
- flwr/common/record/recordset.py +67 -28
- flwr/server/driver/abc_driver.py +140 -0
- flwr/server/superlink/driver/driver_servicer.py +1 -1
- flwr/server/superlink/state/in_memory_state.py +13 -4
- flwr/server/superlink/state/sqlite_state.py +17 -5
- flwr/server/superlink/state/state.py +21 -3
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240418.dist-info}/METADATA +1 -1
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240418.dist-info}/RECORD +14 -12
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240418.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240418.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240418.dist-info}/entry_points.txt +0 -0
flwr/cli/new/new.py
CHANGED
|
@@ -58,8 +58,9 @@ def render_template(template: str, data: Dict[str, str]) -> str:
|
|
|
58
58
|
"""Render template."""
|
|
59
59
|
tpl_file = load_template(template)
|
|
60
60
|
tpl = Template(tpl_file)
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if ".gitignore" not in template:
|
|
62
|
+
return tpl.substitute(data)
|
|
63
|
+
return tpl.template
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def create_file(file_path: str, content: str) -> None:
|
|
@@ -127,6 +128,7 @@ def new(
|
|
|
127
128
|
|
|
128
129
|
# List of files to render
|
|
129
130
|
files = {
|
|
131
|
+
".gitignore": {"template": "app/.gitignore.tpl"},
|
|
130
132
|
"README.md": {"template": "app/README.md.tpl"},
|
|
131
133
|
"pyproject.toml": {"template": f"app/pyproject.{framework_str}.toml.tpl"},
|
|
132
134
|
f"{pnl}/__init__.py": {"template": "app/code/__init__.py.tpl"},
|
|
@@ -134,8 +136,11 @@ def new(
|
|
|
134
136
|
f"{pnl}/client.py": {"template": f"app/code/client.{framework_str}.py.tpl"},
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
+
# Depending on the framework, generate task.py file
|
|
140
|
+
frameworks_with_tasks = [
|
|
141
|
+
MlFramework.PYTORCH.value.lower(),
|
|
142
|
+
]
|
|
143
|
+
if framework_str in frameworks_with_tasks:
|
|
139
144
|
files[f"{pnl}/task.py"] = {"template": f"app/code/task.{framework_str}.py.tpl"}
|
|
140
145
|
|
|
141
146
|
context = {"project_name": project_name}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
env/
|
|
126
|
+
venv/
|
|
127
|
+
ENV/
|
|
128
|
+
env.bak/
|
|
129
|
+
venv.bak/
|
|
130
|
+
|
|
131
|
+
# Spyder project settings
|
|
132
|
+
.spyderproject
|
|
133
|
+
.spyproject
|
|
134
|
+
|
|
135
|
+
# Rope project settings
|
|
136
|
+
.ropeproject
|
|
137
|
+
|
|
138
|
+
# mkdocs documentation
|
|
139
|
+
/site
|
|
140
|
+
|
|
141
|
+
# mypy
|
|
142
|
+
.mypy_cache/
|
|
143
|
+
.dmypy.json
|
|
144
|
+
dmypy.json
|
|
145
|
+
|
|
146
|
+
# Pyre type checker
|
|
147
|
+
.pyre/
|
|
148
|
+
|
|
149
|
+
# pytype static type analyzer
|
|
150
|
+
.pytype/
|
|
151
|
+
|
|
152
|
+
# Cython debug symbols
|
|
153
|
+
cython_debug/
|
|
154
|
+
|
|
155
|
+
# PyCharm
|
|
156
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
157
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
158
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
159
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
160
|
+
#.idea/
|
|
@@ -173,14 +173,14 @@ def http_request_response( # pylint: disable=R0914, R0915
|
|
|
173
173
|
log(
|
|
174
174
|
WARN,
|
|
175
175
|
"[Node] POST /%s: missing header `Content-Type`",
|
|
176
|
-
|
|
176
|
+
PATH_PING,
|
|
177
177
|
)
|
|
178
178
|
return
|
|
179
179
|
if res.headers["content-type"] != "application/protobuf":
|
|
180
180
|
log(
|
|
181
181
|
WARN,
|
|
182
182
|
"[Node] POST /%s: header `Content-Type` has wrong value",
|
|
183
|
-
|
|
183
|
+
PATH_PING,
|
|
184
184
|
)
|
|
185
185
|
return
|
|
186
186
|
|
|
@@ -223,14 +223,14 @@ def http_request_response( # pylint: disable=R0914, R0915
|
|
|
223
223
|
log(
|
|
224
224
|
WARN,
|
|
225
225
|
"[Node] POST /%s: missing header `Content-Type`",
|
|
226
|
-
|
|
226
|
+
PATH_CREATE_NODE,
|
|
227
227
|
)
|
|
228
228
|
return
|
|
229
229
|
if res.headers["content-type"] != "application/protobuf":
|
|
230
230
|
log(
|
|
231
231
|
WARN,
|
|
232
232
|
"[Node] POST /%s: header `Content-Type` has wrong value",
|
|
233
|
-
|
|
233
|
+
PATH_CREATE_NODE,
|
|
234
234
|
)
|
|
235
235
|
return
|
|
236
236
|
|
|
@@ -277,14 +277,14 @@ def http_request_response( # pylint: disable=R0914, R0915
|
|
|
277
277
|
log(
|
|
278
278
|
WARN,
|
|
279
279
|
"[Node] POST /%s: missing header `Content-Type`",
|
|
280
|
-
|
|
280
|
+
PATH_DELETE_NODE,
|
|
281
281
|
)
|
|
282
282
|
return
|
|
283
283
|
if res.headers["content-type"] != "application/protobuf":
|
|
284
284
|
log(
|
|
285
285
|
WARN,
|
|
286
286
|
"[Node] POST /%s: header `Content-Type` has wrong value",
|
|
287
|
-
|
|
287
|
+
PATH_DELETE_NODE,
|
|
288
288
|
)
|
|
289
289
|
|
|
290
290
|
# Cleanup
|
flwr/common/record/recordset.py
CHANGED
|
@@ -16,23 +16,20 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
|
-
from typing import
|
|
19
|
+
from typing import Dict, Optional, cast
|
|
20
20
|
|
|
21
21
|
from .configsrecord import ConfigsRecord
|
|
22
22
|
from .metricsrecord import MetricsRecord
|
|
23
23
|
from .parametersrecord import ParametersRecord
|
|
24
24
|
from .typeddict import TypedDict
|
|
25
25
|
|
|
26
|
-
T = TypeVar("T")
|
|
27
26
|
|
|
27
|
+
class RecordSetData:
|
|
28
|
+
"""Inner data container for the RecordSet class."""
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
_parameters_records: TypedDict[str, ParametersRecord]
|
|
34
|
-
_metrics_records: TypedDict[str, MetricsRecord]
|
|
35
|
-
_configs_records: TypedDict[str, ConfigsRecord]
|
|
30
|
+
parameters_records: TypedDict[str, ParametersRecord]
|
|
31
|
+
metrics_records: TypedDict[str, MetricsRecord]
|
|
32
|
+
configs_records: TypedDict[str, ConfigsRecord]
|
|
36
33
|
|
|
37
34
|
def __init__(
|
|
38
35
|
self,
|
|
@@ -40,40 +37,82 @@ class RecordSet:
|
|
|
40
37
|
metrics_records: Optional[Dict[str, MetricsRecord]] = None,
|
|
41
38
|
configs_records: Optional[Dict[str, ConfigsRecord]] = None,
|
|
42
39
|
) -> None:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if not isinstance(__v, __t):
|
|
46
|
-
raise TypeError(f"Expected `{__t}`, but `{type(__v)}` was passed.")
|
|
47
|
-
|
|
48
|
-
return _check_fn
|
|
49
|
-
|
|
50
|
-
self._parameters_records = TypedDict[str, ParametersRecord](
|
|
51
|
-
_get_check_fn(str), _get_check_fn(ParametersRecord)
|
|
40
|
+
self.parameters_records = TypedDict[str, ParametersRecord](
|
|
41
|
+
self._check_fn_str, self._check_fn_params
|
|
52
42
|
)
|
|
53
|
-
self.
|
|
54
|
-
|
|
43
|
+
self.metrics_records = TypedDict[str, MetricsRecord](
|
|
44
|
+
self._check_fn_str, self._check_fn_metrics
|
|
55
45
|
)
|
|
56
|
-
self.
|
|
57
|
-
|
|
46
|
+
self.configs_records = TypedDict[str, ConfigsRecord](
|
|
47
|
+
self._check_fn_str, self._check_fn_configs
|
|
58
48
|
)
|
|
59
49
|
if parameters_records is not None:
|
|
60
|
-
self.
|
|
50
|
+
self.parameters_records.update(parameters_records)
|
|
61
51
|
if metrics_records is not None:
|
|
62
|
-
self.
|
|
52
|
+
self.metrics_records.update(metrics_records)
|
|
63
53
|
if configs_records is not None:
|
|
64
|
-
self.
|
|
54
|
+
self.configs_records.update(configs_records)
|
|
55
|
+
|
|
56
|
+
def _check_fn_str(self, key: str) -> None:
|
|
57
|
+
if not isinstance(key, str):
|
|
58
|
+
raise TypeError(
|
|
59
|
+
f"Expected `{str.__name__}`, but "
|
|
60
|
+
f"received `{type(key).__name__}` for the key."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def _check_fn_params(self, record: ParametersRecord) -> None:
|
|
64
|
+
if not isinstance(record, ParametersRecord):
|
|
65
|
+
raise TypeError(
|
|
66
|
+
f"Expected `{ParametersRecord.__name__}`, but "
|
|
67
|
+
f"received `{type(record).__name__}` for the value."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def _check_fn_metrics(self, record: MetricsRecord) -> None:
|
|
71
|
+
if not isinstance(record, MetricsRecord):
|
|
72
|
+
raise TypeError(
|
|
73
|
+
f"Expected `{MetricsRecord.__name__}`, but "
|
|
74
|
+
f"received `{type(record).__name__}` for the value."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _check_fn_configs(self, record: ConfigsRecord) -> None:
|
|
78
|
+
if not isinstance(record, ConfigsRecord):
|
|
79
|
+
raise TypeError(
|
|
80
|
+
f"Expected `{ConfigsRecord.__name__}`, but "
|
|
81
|
+
f"received `{type(record).__name__}` for the value."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class RecordSet:
|
|
87
|
+
"""RecordSet stores groups of parameters, metrics and configs."""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
parameters_records: Optional[Dict[str, ParametersRecord]] = None,
|
|
92
|
+
metrics_records: Optional[Dict[str, MetricsRecord]] = None,
|
|
93
|
+
configs_records: Optional[Dict[str, ConfigsRecord]] = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
data = RecordSetData(
|
|
96
|
+
parameters_records=parameters_records,
|
|
97
|
+
metrics_records=metrics_records,
|
|
98
|
+
configs_records=configs_records,
|
|
99
|
+
)
|
|
100
|
+
setattr(self, "_data", data) # noqa
|
|
65
101
|
|
|
66
102
|
@property
|
|
67
103
|
def parameters_records(self) -> TypedDict[str, ParametersRecord]:
|
|
68
104
|
"""Dictionary holding ParametersRecord instances."""
|
|
69
|
-
|
|
105
|
+
data = cast(RecordSetData, getattr(self, "_data")) # noqa
|
|
106
|
+
return data.parameters_records
|
|
70
107
|
|
|
71
108
|
@property
|
|
72
109
|
def metrics_records(self) -> TypedDict[str, MetricsRecord]:
|
|
73
110
|
"""Dictionary holding MetricsRecord instances."""
|
|
74
|
-
|
|
111
|
+
data = cast(RecordSetData, getattr(self, "_data")) # noqa
|
|
112
|
+
return data.metrics_records
|
|
75
113
|
|
|
76
114
|
@property
|
|
77
115
|
def configs_records(self) -> TypedDict[str, ConfigsRecord]:
|
|
78
116
|
"""Dictionary holding ConfigsRecord instances."""
|
|
79
|
-
|
|
117
|
+
data = cast(RecordSetData, getattr(self, "_data")) # noqa
|
|
118
|
+
return data.configs_records
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Driver (abstract base class)."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Iterable, List, Optional
|
|
20
|
+
|
|
21
|
+
from flwr.common import Message, RecordSet
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Driver(ABC):
|
|
25
|
+
"""Abstract base Driver class for the Driver API."""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def create_message( # pylint: disable=too-many-arguments
|
|
29
|
+
self,
|
|
30
|
+
content: RecordSet,
|
|
31
|
+
message_type: str,
|
|
32
|
+
dst_node_id: int,
|
|
33
|
+
group_id: str,
|
|
34
|
+
ttl: Optional[float] = None,
|
|
35
|
+
) -> Message:
|
|
36
|
+
"""Create a new message with specified parameters.
|
|
37
|
+
|
|
38
|
+
This method constructs a new `Message` with given content and metadata.
|
|
39
|
+
The `run_id` and `src_node_id` will be set automatically.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
content : RecordSet
|
|
44
|
+
The content for the new message. This holds records that are to be sent
|
|
45
|
+
to the destination node.
|
|
46
|
+
message_type : str
|
|
47
|
+
The type of the message, defining the action to be executed on
|
|
48
|
+
the receiving end.
|
|
49
|
+
dst_node_id : int
|
|
50
|
+
The ID of the destination node to which the message is being sent.
|
|
51
|
+
group_id : str
|
|
52
|
+
The ID of the group to which this message is associated. In some settings,
|
|
53
|
+
this is used as the FL round.
|
|
54
|
+
ttl : Optional[float] (default: None)
|
|
55
|
+
Time-to-live for the round trip of this message, i.e., the time from sending
|
|
56
|
+
this message to receiving a reply. It specifies in seconds the duration for
|
|
57
|
+
which the message and its potential reply are considered valid. If unset,
|
|
58
|
+
the default TTL (i.e., `common.DEFAULT_TTL`) will be used.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
message : Message
|
|
63
|
+
A new `Message` instance with the specified content and metadata.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def get_node_ids(self) -> List[int]:
|
|
68
|
+
"""Get node IDs."""
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def push_messages(self, messages: Iterable[Message]) -> Iterable[str]:
|
|
72
|
+
"""Push messages to specified node IDs.
|
|
73
|
+
|
|
74
|
+
This method takes an iterable of messages and sends each message
|
|
75
|
+
to the node specified in `dst_node_id`.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
messages : Iterable[Message]
|
|
80
|
+
An iterable of messages to be sent.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
message_ids : Iterable[str]
|
|
85
|
+
An iterable of IDs for the messages that were sent, which can be used
|
|
86
|
+
to pull replies.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]:
|
|
91
|
+
"""Pull messages based on message IDs.
|
|
92
|
+
|
|
93
|
+
This method is used to collect messages from the SuperLink
|
|
94
|
+
that correspond to a set of given message IDs.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
message_ids : Iterable[str]
|
|
99
|
+
An iterable of message IDs for which reply messages are to be retrieved.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
messages : Iterable[Message]
|
|
104
|
+
An iterable of messages received.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def send_and_receive(
|
|
109
|
+
self,
|
|
110
|
+
messages: Iterable[Message],
|
|
111
|
+
*,
|
|
112
|
+
timeout: Optional[float] = None,
|
|
113
|
+
) -> Iterable[Message]:
|
|
114
|
+
"""Push messages to specified node IDs and pull the reply messages.
|
|
115
|
+
|
|
116
|
+
This method sends a list of messages to their destination node IDs and then
|
|
117
|
+
waits for the replies. It continues to pull replies until either all
|
|
118
|
+
replies are received or the specified timeout duration is exceeded.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
messages : Iterable[Message]
|
|
123
|
+
An iterable of messages to be sent.
|
|
124
|
+
timeout : Optional[float] (default: None)
|
|
125
|
+
The timeout duration in seconds. If specified, the method will wait for
|
|
126
|
+
replies for this duration. If `None`, there is no time limit and the method
|
|
127
|
+
will wait until replies for all messages are received.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
replies : Iterable[Message]
|
|
132
|
+
An iterable of reply messages received from the SuperLink.
|
|
133
|
+
|
|
134
|
+
Notes
|
|
135
|
+
-----
|
|
136
|
+
This method uses `push_messages` to send the messages and `pull_messages`
|
|
137
|
+
to collect the replies. If `timeout` is set, the method may not return
|
|
138
|
+
replies for all sent messages. A message remains valid until its TTL,
|
|
139
|
+
which is not affected by `timeout`.
|
|
140
|
+
"""
|
|
@@ -64,7 +64,7 @@ class DriverServicer(driver_pb2_grpc.DriverServicer):
|
|
|
64
64
|
"""Create run ID."""
|
|
65
65
|
log(INFO, "DriverServicer.CreateRun")
|
|
66
66
|
state: State = self.state_factory.state()
|
|
67
|
-
run_id = state.create_run()
|
|
67
|
+
run_id = state.create_run("None/None", "None")
|
|
68
68
|
return CreateRunResponse(run_id=run_id)
|
|
69
69
|
|
|
70
70
|
def PushTaskIns(
|
|
@@ -36,7 +36,8 @@ class InMemoryState(State):
|
|
|
36
36
|
def __init__(self) -> None:
|
|
37
37
|
# Map node_id to (online_until, ping_interval)
|
|
38
38
|
self.node_ids: Dict[int, Tuple[float, float]] = {}
|
|
39
|
-
|
|
39
|
+
# Map run_id to (fab_id, fab_version)
|
|
40
|
+
self.run_ids: Dict[int, Tuple[str, str]] = {}
|
|
40
41
|
self.task_ins_store: Dict[UUID, TaskIns] = {}
|
|
41
42
|
self.task_res_store: Dict[UUID, TaskRes] = {}
|
|
42
43
|
self.lock = threading.Lock()
|
|
@@ -238,18 +239,26 @@ class InMemoryState(State):
|
|
|
238
239
|
if online_until > current_time
|
|
239
240
|
}
|
|
240
241
|
|
|
241
|
-
def create_run(self) -> int:
|
|
242
|
-
"""Create
|
|
242
|
+
def create_run(self, fab_id: str, fab_version: str) -> int:
|
|
243
|
+
"""Create a new run for the specified `fab_id` and `fab_version`."""
|
|
243
244
|
# Sample a random int64 as run_id
|
|
244
245
|
with self.lock:
|
|
245
246
|
run_id: int = int.from_bytes(os.urandom(8), "little", signed=True)
|
|
246
247
|
|
|
247
248
|
if run_id not in self.run_ids:
|
|
248
|
-
self.run_ids
|
|
249
|
+
self.run_ids[run_id] = (fab_id, fab_version)
|
|
249
250
|
return run_id
|
|
250
251
|
log(ERROR, "Unexpected run creation failure.")
|
|
251
252
|
return 0
|
|
252
253
|
|
|
254
|
+
def get_run(self, run_id: int) -> Tuple[int, str, str]:
|
|
255
|
+
"""Retrieve information about the run with the specified `run_id`."""
|
|
256
|
+
with self.lock:
|
|
257
|
+
if run_id not in self.run_ids:
|
|
258
|
+
log(ERROR, "`run_id` is invalid")
|
|
259
|
+
return 0, "", ""
|
|
260
|
+
return run_id, *self.run_ids[run_id]
|
|
261
|
+
|
|
253
262
|
def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
|
|
254
263
|
"""Acknowledge a ping received from a node, serving as a heartbeat."""
|
|
255
264
|
with self.lock:
|
|
@@ -46,7 +46,9 @@ CREATE INDEX IF NOT EXISTS idx_online_until ON node (online_until);
|
|
|
46
46
|
|
|
47
47
|
SQL_CREATE_TABLE_RUN = """
|
|
48
48
|
CREATE TABLE IF NOT EXISTS run(
|
|
49
|
-
run_id
|
|
49
|
+
run_id INTEGER UNIQUE,
|
|
50
|
+
fab_id TEXT,
|
|
51
|
+
fab_version TEXT
|
|
50
52
|
);
|
|
51
53
|
"""
|
|
52
54
|
|
|
@@ -558,8 +560,8 @@ class SqliteState(State):
|
|
|
558
560
|
result: Set[int] = {row["node_id"] for row in rows}
|
|
559
561
|
return result
|
|
560
562
|
|
|
561
|
-
def create_run(self) -> int:
|
|
562
|
-
"""Create
|
|
563
|
+
def create_run(self, fab_id: str, fab_version: str) -> int:
|
|
564
|
+
"""Create a new run for the specified `fab_id` and `fab_version`."""
|
|
563
565
|
# Sample a random int64 as run_id
|
|
564
566
|
run_id: int = int.from_bytes(os.urandom(8), "little", signed=True)
|
|
565
567
|
|
|
@@ -567,12 +569,22 @@ class SqliteState(State):
|
|
|
567
569
|
query = "SELECT COUNT(*) FROM run WHERE run_id = ?;"
|
|
568
570
|
# If run_id does not exist
|
|
569
571
|
if self.query(query, (run_id,))[0]["COUNT(*)"] == 0:
|
|
570
|
-
query = "INSERT INTO run
|
|
571
|
-
self.query(query,
|
|
572
|
+
query = "INSERT INTO run (run_id, fab_id, fab_version) VALUES (?, ?, ?);"
|
|
573
|
+
self.query(query, (run_id, fab_id, fab_version))
|
|
572
574
|
return run_id
|
|
573
575
|
log(ERROR, "Unexpected run creation failure.")
|
|
574
576
|
return 0
|
|
575
577
|
|
|
578
|
+
def get_run(self, run_id: int) -> Tuple[int, str, str]:
|
|
579
|
+
"""Retrieve information about the run with the specified `run_id`."""
|
|
580
|
+
query = "SELECT * FROM run WHERE run_id = ?;"
|
|
581
|
+
try:
|
|
582
|
+
row = self.query(query, (run_id,))[0]
|
|
583
|
+
return run_id, row["fab_id"], row["fab_version"]
|
|
584
|
+
except sqlite3.IntegrityError:
|
|
585
|
+
log(ERROR, "`run_id` does not exist.")
|
|
586
|
+
return 0, "", ""
|
|
587
|
+
|
|
576
588
|
def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
|
|
577
589
|
"""Acknowledge a ping received from a node, serving as a heartbeat."""
|
|
578
590
|
# Update `online_until` and `ping_interval` for the given `node_id`
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import abc
|
|
19
|
-
from typing import List, Optional, Set
|
|
19
|
+
from typing import List, Optional, Set, Tuple
|
|
20
20
|
from uuid import UUID
|
|
21
21
|
|
|
22
22
|
from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
|
|
@@ -150,8 +150,26 @@ class State(abc.ABC):
|
|
|
150
150
|
"""
|
|
151
151
|
|
|
152
152
|
@abc.abstractmethod
|
|
153
|
-
def create_run(self) -> int:
|
|
154
|
-
"""Create
|
|
153
|
+
def create_run(self, fab_id: str, fab_version: str) -> int:
|
|
154
|
+
"""Create a new run for the specified `fab_id` and `fab_version`."""
|
|
155
|
+
|
|
156
|
+
@abc.abstractmethod
|
|
157
|
+
def get_run(self, run_id: int) -> Tuple[int, str, str]:
|
|
158
|
+
"""Retrieve information about the run with the specified `run_id`.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
run_id : int
|
|
163
|
+
The identifier of the run.
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
Tuple[int, str, str]
|
|
168
|
+
A tuple containing three elements:
|
|
169
|
+
- `run_id`: The identifier of the run, same as the specified `run_id`.
|
|
170
|
+
- `fab_id`: The identifier of the FAB used in the specified run.
|
|
171
|
+
- `fab_version`: The version of the FAB used in the specified run.
|
|
172
|
+
"""
|
|
155
173
|
|
|
156
174
|
@abc.abstractmethod
|
|
157
175
|
def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
|
{flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240418.dist-info}/RECORD
RENAMED
|
@@ -4,8 +4,9 @@ flwr/cli/app.py,sha256=38thPnMydBmNAxNE9mz4By-KdRUhJfoUgeDuAxMYF_U,1095
|
|
|
4
4
|
flwr/cli/config_utils.py,sha256=fTKBxsXauE8JWdwTb8yLxOm69sAcURefoif-DGUDNII,4696
|
|
5
5
|
flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
|
|
6
6
|
flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
|
|
7
|
-
flwr/cli/new/new.py,sha256=
|
|
7
|
+
flwr/cli/new/new.py,sha256=OHTOpuHRqmafsoV_Hv1V1544mZz54Z0qDRRtMT3dR-M,5380
|
|
8
8
|
flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
|
|
9
|
+
flwr/cli/new/templates/app/.gitignore.tpl,sha256=XixnHdyeMB2vwkGtGnwHqoWpH-9WChdyG0GXe57duhc,3078
|
|
9
10
|
flwr/cli/new/templates/app/README.md.tpl,sha256=_qGtgpKYKoCJVjQnvlBMKvFs_1gzTcL908I3KJg0oAM,668
|
|
10
11
|
flwr/cli/new/templates/app/__init__.py,sha256=DU7QMY7IhMQyuwm_tja66xU0KXTWQFqzfTqwg-_NJdE,729
|
|
11
12
|
flwr/cli/new/templates/app/code/__init__.py,sha256=EM6vfvgAILKPaPn7H1wMV1Wi01WyZCP_Eg6NxD6oWg8,736
|
|
@@ -48,7 +49,7 @@ flwr/client/node_state.py,sha256=KTTs_l4I0jBM7IsSsbAGjhfL_yZC3QANbzyvyfZBRDM,177
|
|
|
48
49
|
flwr/client/node_state_tests.py,sha256=gPwz0zf2iuDSa11jedkur_u3Xm7lokIDG5ALD2MCvSw,2195
|
|
49
50
|
flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,10283
|
|
50
51
|
flwr/client/rest_client/__init__.py,sha256=ThwOnkMdzxo_UuyTI47Q7y9oSpuTgNT2OuFvJCfuDiw,735
|
|
51
|
-
flwr/client/rest_client/connection.py,sha256=
|
|
52
|
+
flwr/client/rest_client/connection.py,sha256=f5jYUC4d3swo39-RJAe56tKjS9P-NBp6hONC9k3QKns,14456
|
|
52
53
|
flwr/client/typing.py,sha256=c9EvjlEjasxn1Wqx6bGl6Xg6vM1gMFfmXht-E2i5J-k,1006
|
|
53
54
|
flwr/common/__init__.py,sha256=dHOptgKxna78CEQLD5Yu0QIsoSgpIIw5AhIUZCHDWAU,3721
|
|
54
55
|
flwr/common/address.py,sha256=iTAN9jtmIGMrWFnx9XZQl45ZEtQJVZZLYPRBSNVARGI,1882
|
|
@@ -70,7 +71,7 @@ flwr/common/record/configsrecord.py,sha256=VKeFEYa6cneyStqQlUOaKj12by5ZI_NXYR25L
|
|
|
70
71
|
flwr/common/record/conversion_utils.py,sha256=n3I3SI2P6hUjyxbWNc0QAch-SEhfMK6Hm-UUaplAlUc,1393
|
|
71
72
|
flwr/common/record/metricsrecord.py,sha256=Yv99oRa3LzFgSfwl903S8sB8rAgr3Sv6i6ovW7pdHsA,3923
|
|
72
73
|
flwr/common/record/parametersrecord.py,sha256=WSqtRrYvI-mRzkEwv5s-EG-yE5uizJ8zy9aczwRG-1E,4849
|
|
73
|
-
flwr/common/record/recordset.py,sha256=
|
|
74
|
+
flwr/common/record/recordset.py,sha256=o3cXGGEFYRqzO8AzYmFxf5cb4CZIkaw-_lSk4kfTg0Q,4553
|
|
74
75
|
flwr/common/record/typeddict.py,sha256=2NW8JF27p1uNWaqDbJ7bMkItA5x4ygYT8aHrf8NaqnE,3879
|
|
75
76
|
flwr/common/recordset_compat.py,sha256=BjxeuvlCaP94yIiKOyFFTRBUH_lprFWSLo8U8q3BDbs,13798
|
|
76
77
|
flwr/common/retry_invoker.py,sha256=dQY5fPIKhy9OiFswZhLxA9fB455u-DYCvDVcFJmrPDk,11707
|
|
@@ -127,6 +128,7 @@ flwr/server/compat/driver_client_proxy.py,sha256=QWLl5YJwI6NVADwjQGQJqkLtCfPNT-a
|
|
|
127
128
|
flwr/server/compat/legacy_context.py,sha256=D2s7PvQoDnTexuRmf1uG9Von7GUj4Qqyr7qLklSlKAM,1766
|
|
128
129
|
flwr/server/criterion.py,sha256=ypbAexbztzGUxNen9RCHF91QeqiEQix4t4Ih3E-42MM,1061
|
|
129
130
|
flwr/server/driver/__init__.py,sha256=yYyVX1FcDiDFM6rw0-DSZpuRy0EoWRfG9puwlQUswFA,820
|
|
131
|
+
flwr/server/driver/abc_driver.py,sha256=t9SSSDlo9wT_y2Nl7waGYMTm2VlkvK3_bOb7ggPPlho,5090
|
|
130
132
|
flwr/server/driver/driver.py,sha256=AwAxgYRx-FI6NvI5ukmdGlEmQRyp5GZSElFnDZhelj8,10106
|
|
131
133
|
flwr/server/driver/grpc_driver.py,sha256=D2n3_Es_DHFgQsq_TjYVEz8RYJJJYoe24E1vozaTFiE,4586
|
|
132
134
|
flwr/server/history.py,sha256=hDsoBaA4kUa6d1yvDVXuLluBqOBKSm0_fVDtUtYJkmg,5121
|
|
@@ -161,7 +163,7 @@ flwr/server/strategy/strategy.py,sha256=g6VoIFogEviRub6G4QsKdIp6M_Ek6GhBhqcdNx5u
|
|
|
161
163
|
flwr/server/superlink/__init__.py,sha256=8tHYCfodUlRD8PCP9fHgvu8cz5N31A2QoRVL0jDJ15E,707
|
|
162
164
|
flwr/server/superlink/driver/__init__.py,sha256=STB1_DASVEg7Cu6L7VYxTzV7UMkgtBkFim09Z82Dh8I,712
|
|
163
165
|
flwr/server/superlink/driver/driver_grpc.py,sha256=1qSGDs1k_OVPWxp2ofxvQgtYXExrMeC3N_rNPVWH65M,1932
|
|
164
|
-
flwr/server/superlink/driver/driver_servicer.py,sha256=
|
|
166
|
+
flwr/server/superlink/driver/driver_servicer.py,sha256=IKx3rC8s2193iCJxLEc_njndTtidkVM7Vk-RWjGngl0,4780
|
|
165
167
|
flwr/server/superlink/fleet/__init__.py,sha256=C6GCSD5eP5Of6_dIeSe1jx9HnV0icsvWyQ5EKAUHJRU,711
|
|
166
168
|
flwr/server/superlink/fleet/grpc_bidi/__init__.py,sha256=mgGJGjwT6VU7ovC1gdnnqttjyBPlNIcZnYRqx4K3IBQ,735
|
|
167
169
|
flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py,sha256=57b3UL5-baGdLwgCtB0dCUTTSbmmfMAXcXV5bjPZNWQ,5993
|
|
@@ -180,9 +182,9 @@ flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LJsKl7oixVvptcG98Rd9ej
|
|
|
180
182
|
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=TaT2EpbVEsIY0EDzF8obadyZaSXjD38TFGdDPI-ytD0,6375
|
|
181
183
|
flwr/server/superlink/fleet/vce/vce_api.py,sha256=c2J2m6v1jDyuAhiBArdZNIk4cbiZNFJkpKlBJFEQq-c,12454
|
|
182
184
|
flwr/server/superlink/state/__init__.py,sha256=ij-7Ms-hyordQdRmGQxY1-nVa4OhixJ0jr7_YDkys0s,1003
|
|
183
|
-
flwr/server/superlink/state/in_memory_state.py,sha256=
|
|
184
|
-
flwr/server/superlink/state/sqlite_state.py,sha256=
|
|
185
|
-
flwr/server/superlink/state/state.py,sha256=
|
|
185
|
+
flwr/server/superlink/state/in_memory_state.py,sha256=OXpTb7ER7fnI55cFmcux2cLN6U_ACYjmRHkhYVHW2Ww,10083
|
|
186
|
+
flwr/server/superlink/state/sqlite_state.py,sha256=xDyvtuInAsLq65czbqLrLOv4ec61XxH_FhW_Q2NXrgM,24580
|
|
187
|
+
flwr/server/superlink/state/state.py,sha256=AsORTtR5Y5sRpxKPG0iueWOvnY0uISXgpAsyPSMgZXY,6762
|
|
186
188
|
flwr/server/superlink/state/state_factory.py,sha256=91cSB-KOAFM37z7T098WxTkVeKNaAZ_mTI75snn2_tk,1654
|
|
187
189
|
flwr/server/superlink/state/utils.py,sha256=qhIjBu5_rqm9GLMB6QS5TIRrMDVs85lmY17BqZ1ccLk,2207
|
|
188
190
|
flwr/server/typing.py,sha256=2zSG-KuDAgwFPuzgVjTLDaEqJ8gXXGqFR2RD-qIk730,913
|
|
@@ -202,8 +204,8 @@ flwr/simulation/ray_transport/ray_actor.py,sha256=_wv2eP7qxkCZ-6rMyYWnjLrGPBZRxj
|
|
|
202
204
|
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=oDu4sEPIOu39vrNi-fqDAe10xtNUXMO49bM2RWfRcyw,6738
|
|
203
205
|
flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
|
|
204
206
|
flwr/simulation/run_simulation.py,sha256=HiIH6aa_v56NfKQN5ZBd94NyVfaZNyFs43_kItYsQXU,15685
|
|
205
|
-
flwr_nightly-1.9.0.
|
|
206
|
-
flwr_nightly-1.9.0.
|
|
207
|
-
flwr_nightly-1.9.0.
|
|
208
|
-
flwr_nightly-1.9.0.
|
|
209
|
-
flwr_nightly-1.9.0.
|
|
207
|
+
flwr_nightly-1.9.0.dev20240418.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
208
|
+
flwr_nightly-1.9.0.dev20240418.dist-info/METADATA,sha256=wEQknDEOzu-egZOF5Z6423WK0t7uCFULKtCwHOw1gA0,15260
|
|
209
|
+
flwr_nightly-1.9.0.dev20240418.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
210
|
+
flwr_nightly-1.9.0.dev20240418.dist-info/entry_points.txt,sha256=utu2wybGyYJSTtsB2ktY_gmy-XtMFo9EFZdishX0zR4,320
|
|
211
|
+
flwr_nightly-1.9.0.dev20240418.dist-info/RECORD,,
|
{flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240418.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|