flwr-nightly 1.9.0.dev20240417__py3-none-any.whl → 1.9.0.dev20240419__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/config_utils.py +33 -0
- flwr/cli/new/new.py +9 -4
- flwr/cli/new/templates/app/.gitignore.tpl +160 -0
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +56 -0
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +18 -0
- flwr/client/__init__.py +2 -0
- flwr/client/app.py +7 -53
- flwr/client/grpc_client/connection.py +2 -1
- flwr/client/grpc_rere_client/connection.py +16 -2
- flwr/client/rest_client/connection.py +87 -168
- flwr/client/supernode/__init__.py +22 -0
- flwr/client/supernode/app.py +107 -0
- flwr/common/record/recordset.py +67 -28
- flwr/common/telemetry.py +4 -0
- flwr/server/app.py +5 -5
- flwr/server/driver/abc_driver.py +140 -0
- flwr/server/superlink/driver/driver_servicer.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +4 -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.dev20240419.dist-info}/METADATA +1 -1
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240419.dist-info}/RECORD +26 -22
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240419.dist-info}/entry_points.txt +1 -0
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240419.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240419.dist-info}/WHEEL +0 -0
flwr/cli/config_utils.py
CHANGED
|
@@ -15,13 +15,46 @@
|
|
|
15
15
|
"""Utility to validate the `pyproject.toml` file."""
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
|
+
from pathlib import Path
|
|
18
19
|
from typing import Any, Dict, List, Optional, Tuple
|
|
19
20
|
|
|
20
21
|
import tomli
|
|
22
|
+
import typer
|
|
21
23
|
|
|
22
24
|
from flwr.common import object_ref
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
def validate_project_dir(project_dir: Path) -> Optional[Dict[str, Any]]:
|
|
28
|
+
"""Check if a Flower App directory is valid."""
|
|
29
|
+
config = load(str(project_dir / "pyproject.toml"))
|
|
30
|
+
if config is None:
|
|
31
|
+
typer.secho(
|
|
32
|
+
"❌ Project configuration could not be loaded. "
|
|
33
|
+
"`pyproject.toml` does not exist.",
|
|
34
|
+
fg=typer.colors.RED,
|
|
35
|
+
bold=True,
|
|
36
|
+
)
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
if not validate(config):
|
|
40
|
+
typer.secho(
|
|
41
|
+
"❌ Project configuration is invalid.",
|
|
42
|
+
fg=typer.colors.RED,
|
|
43
|
+
bold=True,
|
|
44
|
+
)
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
if "publisher" not in config["flower"]:
|
|
48
|
+
typer.secho(
|
|
49
|
+
"❌ Project configuration is missing required `publisher` field.",
|
|
50
|
+
fg=typer.colors.RED,
|
|
51
|
+
bold=True,
|
|
52
|
+
)
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
return config
|
|
56
|
+
|
|
57
|
+
|
|
25
58
|
def load_and_validate_with_defaults(
|
|
26
59
|
path: Optional[str] = None,
|
|
27
60
|
) -> Tuple[Optional[Dict[str, Any]], List[str], List[str]]:
|
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/
|
|
@@ -1 +1,57 @@
|
|
|
1
1
|
"""$project_name: A Flower / TensorFlow app."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import tensorflow as tf
|
|
6
|
+
from flwr.client import ClientApp, NumPyClient
|
|
7
|
+
from flwr_datasets import FederatedDataset
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
|
11
|
+
|
|
12
|
+
# Define Flower client
|
|
13
|
+
class FlowerClient(NumPyClient):
|
|
14
|
+
def __init__(self, model, train_data, test_data):
|
|
15
|
+
self.model = model
|
|
16
|
+
self.x_train, self.y_train = train_data
|
|
17
|
+
self.x_test, self.y_test = test_data
|
|
18
|
+
|
|
19
|
+
def get_parameters(self, config):
|
|
20
|
+
return self.model.get_weights()
|
|
21
|
+
|
|
22
|
+
def fit(self, parameters, config):
|
|
23
|
+
self.model.set_weights(parameters)
|
|
24
|
+
self.model.fit(self.x_train, self.y_train, epochs=1, batch_size=32, verbose=0)
|
|
25
|
+
return self.model.get_weights(), len(self.x_train), {}
|
|
26
|
+
|
|
27
|
+
def evaluate(self, parameters, config):
|
|
28
|
+
self.model.set_weights(parameters)
|
|
29
|
+
loss, accuracy = self.model.evaluate(self.x_test, self.y_test, verbose=0)
|
|
30
|
+
return loss, len(self.x_test), {"accuracy": accuracy}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
fds = FederatedDataset(dataset="cifar10", partitioners={"train": 2})
|
|
34
|
+
|
|
35
|
+
def client_fn(cid: str):
|
|
36
|
+
"""Create and return an instance of Flower `Client`."""
|
|
37
|
+
|
|
38
|
+
# Load model and data (MobileNetV2, CIFAR-10)
|
|
39
|
+
model = tf.keras.applications.MobileNetV2((32, 32, 3), classes=10, weights=None)
|
|
40
|
+
model.compile("adam", "sparse_categorical_crossentropy", metrics=["accuracy"])
|
|
41
|
+
|
|
42
|
+
# Download and partition dataset
|
|
43
|
+
partition = fds.load_partition(int(cid), "train")
|
|
44
|
+
partition.set_format("numpy")
|
|
45
|
+
|
|
46
|
+
# Divide data on each node: 80% train, 20% test
|
|
47
|
+
partition = partition.train_test_split(test_size=0.2, seed=42)
|
|
48
|
+
train_data = partition["train"]["img"] / 255.0, partition["train"]["label"]
|
|
49
|
+
test_data = partition["test"]["img"] / 255.0, partition["test"]["label"]
|
|
50
|
+
|
|
51
|
+
return FlowerClient(model, train_data, test_data).to_client()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Flower ClientApp
|
|
55
|
+
app = ClientApp(
|
|
56
|
+
client_fn=client_fn,
|
|
57
|
+
)
|
|
@@ -1 +1,19 @@
|
|
|
1
1
|
"""$project_name: A Flower / TensorFlow app."""
|
|
2
|
+
|
|
3
|
+
from flwr.server import ServerApp, ServerConfig
|
|
4
|
+
from flwr.server.strategy import FedAvg
|
|
5
|
+
|
|
6
|
+
# Define config
|
|
7
|
+
config = ServerConfig(num_rounds=3)
|
|
8
|
+
|
|
9
|
+
strategy = FedAvg(
|
|
10
|
+
fraction_fit=1.0,
|
|
11
|
+
fraction_evaluate=1.0,
|
|
12
|
+
min_available_clients=2,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Flower ServerApp
|
|
16
|
+
app = ServerApp(
|
|
17
|
+
config=config,
|
|
18
|
+
strategy=strategy,
|
|
19
|
+
)
|
flwr/client/__init__.py
CHANGED
|
@@ -21,6 +21,7 @@ from .app import start_numpy_client as start_numpy_client
|
|
|
21
21
|
from .client import Client as Client
|
|
22
22
|
from .client_app import ClientApp as ClientApp
|
|
23
23
|
from .numpy_client import NumPyClient as NumPyClient
|
|
24
|
+
from .supernode import run_supernode as run_supernode
|
|
24
25
|
from .typing import ClientFn as ClientFn
|
|
25
26
|
|
|
26
27
|
__all__ = [
|
|
@@ -29,6 +30,7 @@ __all__ = [
|
|
|
29
30
|
"ClientFn",
|
|
30
31
|
"NumPyClient",
|
|
31
32
|
"run_client_app",
|
|
33
|
+
"run_supernode",
|
|
32
34
|
"start_client",
|
|
33
35
|
"start_numpy_client",
|
|
34
36
|
]
|
flwr/client/app.py
CHANGED
|
@@ -47,14 +47,15 @@ from .grpc_rere_client.connection import grpc_request_response
|
|
|
47
47
|
from .message_handler.message_handler import handle_control_message
|
|
48
48
|
from .node_state import NodeState
|
|
49
49
|
from .numpy_client import NumPyClient
|
|
50
|
+
from .supernode.app import parse_args_run_client_app
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def run_client_app() -> None:
|
|
53
54
|
"""Run Flower client app."""
|
|
54
|
-
event(EventType.RUN_CLIENT_APP_ENTER)
|
|
55
|
-
|
|
56
55
|
log(INFO, "Long-running Flower client starting")
|
|
57
56
|
|
|
57
|
+
event(EventType.RUN_CLIENT_APP_ENTER)
|
|
58
|
+
|
|
58
59
|
args = _parse_args_run_client_app().parse_args()
|
|
59
60
|
|
|
60
61
|
# Obtain certificates
|
|
@@ -131,56 +132,7 @@ def _parse_args_run_client_app() -> argparse.ArgumentParser:
|
|
|
131
132
|
description="Start a Flower client app",
|
|
132
133
|
)
|
|
133
134
|
|
|
134
|
-
parser
|
|
135
|
-
"client-app",
|
|
136
|
-
help="For example: `client:app` or `project.package.module:wrapper.app`",
|
|
137
|
-
)
|
|
138
|
-
parser.add_argument(
|
|
139
|
-
"--insecure",
|
|
140
|
-
action="store_true",
|
|
141
|
-
help="Run the client without HTTPS. By default, the client runs with "
|
|
142
|
-
"HTTPS enabled. Use this flag only if you understand the risks.",
|
|
143
|
-
)
|
|
144
|
-
parser.add_argument(
|
|
145
|
-
"--rest",
|
|
146
|
-
action="store_true",
|
|
147
|
-
help="Use REST as a transport layer for the client.",
|
|
148
|
-
)
|
|
149
|
-
parser.add_argument(
|
|
150
|
-
"--root-certificates",
|
|
151
|
-
metavar="ROOT_CERT",
|
|
152
|
-
type=str,
|
|
153
|
-
help="Specifies the path to the PEM-encoded root certificate file for "
|
|
154
|
-
"establishing secure HTTPS connections.",
|
|
155
|
-
)
|
|
156
|
-
parser.add_argument(
|
|
157
|
-
"--server",
|
|
158
|
-
default="0.0.0.0:9092",
|
|
159
|
-
help="Server address",
|
|
160
|
-
)
|
|
161
|
-
parser.add_argument(
|
|
162
|
-
"--max-retries",
|
|
163
|
-
type=int,
|
|
164
|
-
default=None,
|
|
165
|
-
help="The maximum number of times the client will try to connect to the"
|
|
166
|
-
"server before giving up in case of a connection error. By default,"
|
|
167
|
-
"it is set to None, meaning there is no limit to the number of tries.",
|
|
168
|
-
)
|
|
169
|
-
parser.add_argument(
|
|
170
|
-
"--max-wait-time",
|
|
171
|
-
type=float,
|
|
172
|
-
default=None,
|
|
173
|
-
help="The maximum duration before the client stops trying to"
|
|
174
|
-
"connect to the server in case of connection error. By default, it"
|
|
175
|
-
"is set to None, meaning there is no limit to the total time.",
|
|
176
|
-
)
|
|
177
|
-
parser.add_argument(
|
|
178
|
-
"--dir",
|
|
179
|
-
default="",
|
|
180
|
-
help="Add specified directory to the PYTHONPATH and load Flower "
|
|
181
|
-
"app from there."
|
|
182
|
-
" Default: current working directory.",
|
|
183
|
-
)
|
|
135
|
+
parse_args_run_client_app(parser=parser)
|
|
184
136
|
|
|
185
137
|
return parser
|
|
186
138
|
|
|
@@ -442,7 +394,8 @@ def _start_client_internal(
|
|
|
442
394
|
grpc_max_message_length,
|
|
443
395
|
root_certificates,
|
|
444
396
|
) as conn:
|
|
445
|
-
|
|
397
|
+
# pylint: disable-next=W0612
|
|
398
|
+
receive, send, create_node, delete_node, get_run = conn
|
|
446
399
|
|
|
447
400
|
# Register node
|
|
448
401
|
if create_node is not None:
|
|
@@ -660,6 +613,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[
|
|
|
660
613
|
Callable[[Message], None],
|
|
661
614
|
Optional[Callable[[], None]],
|
|
662
615
|
Optional[Callable[[], None]],
|
|
616
|
+
Optional[Callable[[int], Tuple[str, str]]],
|
|
663
617
|
]
|
|
664
618
|
],
|
|
665
619
|
],
|
|
@@ -68,6 +68,7 @@ def grpc_connection( # pylint: disable=R0915
|
|
|
68
68
|
Callable[[Message], None],
|
|
69
69
|
Optional[Callable[[], None]],
|
|
70
70
|
Optional[Callable[[], None]],
|
|
71
|
+
Optional[Callable[[int], Tuple[str, str]]],
|
|
71
72
|
]
|
|
72
73
|
]:
|
|
73
74
|
"""Establish a gRPC connection to a gRPC server.
|
|
@@ -224,7 +225,7 @@ def grpc_connection( # pylint: disable=R0915
|
|
|
224
225
|
|
|
225
226
|
try:
|
|
226
227
|
# Yield methods
|
|
227
|
-
yield (receive, send, None, None)
|
|
228
|
+
yield (receive, send, None, None, None)
|
|
228
229
|
finally:
|
|
229
230
|
# Make sure to have a final
|
|
230
231
|
channel.close()
|
|
@@ -41,6 +41,8 @@ from flwr.common.serde import message_from_taskins, message_to_taskres
|
|
|
41
41
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
42
42
|
CreateNodeRequest,
|
|
43
43
|
DeleteNodeRequest,
|
|
44
|
+
GetRunRequest,
|
|
45
|
+
GetRunResponse,
|
|
44
46
|
PingRequest,
|
|
45
47
|
PingResponse,
|
|
46
48
|
PullTaskInsRequest,
|
|
@@ -69,6 +71,7 @@ def grpc_request_response( # pylint: disable=R0914, R0915
|
|
|
69
71
|
Callable[[Message], None],
|
|
70
72
|
Optional[Callable[[], None]],
|
|
71
73
|
Optional[Callable[[], None]],
|
|
74
|
+
Optional[Callable[[int], Tuple[str, str]]],
|
|
72
75
|
]
|
|
73
76
|
]:
|
|
74
77
|
"""Primitives for request/response-based interaction with a server.
|
|
@@ -122,7 +125,7 @@ def grpc_request_response( # pylint: disable=R0914, R0915
|
|
|
122
125
|
ping_stop_event = threading.Event()
|
|
123
126
|
|
|
124
127
|
###########################################################################
|
|
125
|
-
# ping/create_node/delete_node/receive/send functions
|
|
128
|
+
# ping/create_node/delete_node/receive/send/get_run functions
|
|
126
129
|
###########################################################################
|
|
127
130
|
|
|
128
131
|
def ping() -> None:
|
|
@@ -241,8 +244,19 @@ def grpc_request_response( # pylint: disable=R0914, R0915
|
|
|
241
244
|
# Cleanup
|
|
242
245
|
metadata = None
|
|
243
246
|
|
|
247
|
+
def get_run(run_id: int) -> Tuple[str, str]:
|
|
248
|
+
# Call FleetAPI
|
|
249
|
+
get_run_request = GetRunRequest(run_id=run_id)
|
|
250
|
+
get_run_response: GetRunResponse = retry_invoker.invoke(
|
|
251
|
+
stub.GetRun,
|
|
252
|
+
request=get_run_request,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Return fab_id and fab_version
|
|
256
|
+
return get_run_response.run.fab_id, get_run_response.run.fab_version
|
|
257
|
+
|
|
244
258
|
try:
|
|
245
259
|
# Yield methods
|
|
246
|
-
yield (receive, send, create_node, delete_node)
|
|
260
|
+
yield (receive, send, create_node, delete_node, get_run)
|
|
247
261
|
except Exception as exc: # pylint: disable=broad-except
|
|
248
262
|
log(ERROR, exc)
|