vantage6 4.2.2__py3-none-any.whl → 4.3.0b3__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 vantage6 might be problematic. Click here for more details.

Files changed (49) hide show
  1. tests_cli/test_node_cli.py +1 -1
  2. tests_cli/test_server_cli.py +5 -5
  3. tests_cli/test_wizard.py +8 -9
  4. vantage6/cli/__build__ +1 -1
  5. vantage6/cli/_version.py +1 -1
  6. vantage6/cli/algostore/attach.py +32 -0
  7. vantage6/cli/algostore/new.py +55 -0
  8. vantage6/cli/algostore/start.py +102 -0
  9. vantage6/cli/algostore/stop.py +60 -0
  10. vantage6/cli/cli.py +20 -0
  11. vantage6/cli/common/decorator.py +92 -0
  12. vantage6/cli/common/start.py +232 -0
  13. vantage6/cli/configuration_manager.py +13 -23
  14. vantage6/cli/configuration_wizard.py +134 -60
  15. vantage6/cli/context/__init__.py +86 -0
  16. vantage6/cli/context/algorithm_store.py +130 -0
  17. vantage6/cli/context/base_server.py +89 -0
  18. vantage6/cli/{context.py → context/node.py} +12 -174
  19. vantage6/cli/context/server.py +127 -0
  20. vantage6/cli/dev/create.py +4 -3
  21. vantage6/cli/dev/remove.py +5 -3
  22. vantage6/cli/dev/start.py +2 -1
  23. vantage6/cli/dev/stop.py +2 -1
  24. vantage6/cli/globals.py +24 -0
  25. vantage6/cli/node/common/__init__.py +10 -4
  26. vantage6/cli/node/create_private_key.py +1 -1
  27. vantage6/cli/node/files.py +1 -1
  28. vantage6/cli/node/list.py +1 -1
  29. vantage6/cli/node/new.py +3 -2
  30. vantage6/cli/node/remove.py +1 -1
  31. vantage6/cli/node/set_api_key.py +1 -1
  32. vantage6/cli/node/start.py +7 -6
  33. vantage6/cli/rabbitmq/queue_manager.py +1 -1
  34. vantage6/cli/server/attach.py +3 -3
  35. vantage6/cli/server/common/__init__.py +5 -3
  36. vantage6/cli/server/files.py +1 -1
  37. vantage6/cli/server/import_.py +7 -3
  38. vantage6/cli/server/list.py +9 -5
  39. vantage6/cli/server/new.py +3 -2
  40. vantage6/cli/server/shell.py +3 -3
  41. vantage6/cli/server/start.py +26 -110
  42. vantage6/cli/server/stop.py +3 -4
  43. vantage6/cli/server/version.py +2 -2
  44. {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/METADATA +3 -3
  45. vantage6-4.3.0b3.dist-info/RECORD +68 -0
  46. vantage6-4.2.2.dist-info/RECORD +0 -58
  47. {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/WHEEL +0 -0
  48. {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/entry_points.txt +0 -0
  49. {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,232 @@
1
+ import os
2
+ from threading import Thread
3
+ import time
4
+ import docker
5
+ import enum
6
+ from docker.client import DockerClient
7
+ from docker.models.containers import Container
8
+ from colorama import Fore, Style
9
+ from sqlalchemy.engine.url import make_url
10
+
11
+ from vantage6.common import error, info, warning
12
+ from vantage6.common.context import AppContext
13
+ from vantage6.common.globals import InstanceType, APPNAME, DEFAULT_DOCKER_REGISTRY
14
+ from vantage6.common.docker.addons import check_docker_running, pull_if_newer
15
+ from vantage6.cli.context import AlgorithmStoreContext, ServerContext
16
+ from vantage6.cli.server.common import print_log_worker
17
+ from vantage6.cli.utils import check_config_name_allowed
18
+ from vantage6.cli.globals import ServerGlobals, AlgoStoreGlobals
19
+
20
+
21
+ def check_for_start(ctx: AppContext, type_: InstanceType) -> DockerClient:
22
+ """
23
+ Check if all requirements are met to start the instance.
24
+
25
+ Parameters
26
+ ----------
27
+ ctx : AppContext
28
+ The context object
29
+ type_ : InstanceType
30
+ The type of instance to check for
31
+ """
32
+ # will print an error if not
33
+ check_docker_running()
34
+
35
+ info("Finding Docker daemon.")
36
+ docker_client = docker.from_env()
37
+
38
+ # check if name is allowed for docker volume, else exit
39
+ check_config_name_allowed(ctx.name)
40
+
41
+ # check that this server is not already running
42
+ running_servers = docker_client.containers.list(
43
+ filters={"label": f"{APPNAME}-type={type_}"}
44
+ )
45
+ for server in running_servers:
46
+ if server.name == f"{APPNAME}-{ctx.name}-{ctx.scope}-{type_}":
47
+ error(f"Server {Fore.RED}{ctx.name}{Style.RESET_ALL} " "is already running")
48
+ exit(1)
49
+ return docker_client
50
+
51
+
52
+ def get_image(
53
+ image: str, ctx: AppContext, custom_image_key: str, default_image: str
54
+ ) -> str:
55
+ """
56
+ Get the image name for the given instance type.
57
+
58
+ Parameters
59
+ ----------
60
+ image : str | None
61
+ The image name to use if specified
62
+ ctx : AppContext
63
+ The context object
64
+ custom_image_key : str
65
+ The key to look for in the config file
66
+ default_image : str
67
+ The default image name
68
+
69
+ Returns
70
+ -------
71
+ str
72
+ The image name to use
73
+ """
74
+ # Determine image-name. First we check if the option --image has been used.
75
+ # Then we check if the image has been specified in the config file, and
76
+ # finally we use the default settings from the package.
77
+ if image is None:
78
+ custom_images: dict = ctx.config.get("images")
79
+ if custom_images:
80
+ image = custom_images.get(custom_image_key)
81
+ if not image:
82
+ image = f"{DEFAULT_DOCKER_REGISTRY}/{default_image}"
83
+ return image
84
+
85
+
86
+ def pull_image(docker_client: DockerClient, image: str) -> None:
87
+ """
88
+ Pull the image if it is not already available.
89
+
90
+ Parameters
91
+ ----------
92
+ docker : DockerClient
93
+ The docker client
94
+ image : str
95
+ The image name
96
+ """
97
+ info(f"Pulling latest image '{image}'.")
98
+ try:
99
+ pull_if_newer(docker_client.from_env(), image)
100
+ except Exception as e:
101
+ warning(" ... Getting latest server image failed:")
102
+ warning(f" {e}")
103
+ else:
104
+ info(" ... success!")
105
+
106
+
107
+ def mount_config_file(ctx: AppContext, config_file: str) -> list[docker.types.Mount]:
108
+ """
109
+ Mount the config file in the container.
110
+
111
+ Parameters
112
+ ----------
113
+ ctx : AppContext
114
+ The context object
115
+ config_file : str
116
+ The path to the config file
117
+
118
+ Returns
119
+ -------
120
+ list[docker.types.Mount]
121
+ The mounts to use
122
+ """
123
+ info("Creating mounts")
124
+ return [docker.types.Mount(config_file, str(ctx.config_file), type="bind")]
125
+
126
+
127
+ def mount_source(mount_src: str) -> docker.types.Mount:
128
+ """
129
+ Mount the vantage6 source code in the container.
130
+
131
+ Parameters
132
+ ----------
133
+ mount_src : str
134
+ The path to the source code
135
+
136
+ Returns
137
+ -------
138
+ docker.types.Mount | None
139
+ The mount to use
140
+ """
141
+ if mount_src:
142
+ mount_src = os.path.abspath(mount_src)
143
+ return docker.types.Mount("/vantage6", mount_src, type="bind")
144
+
145
+
146
+ def mount_database(
147
+ ctx: ServerContext | AlgorithmStoreContext, type_: InstanceType
148
+ ) -> tuple[docker.types.Mount, dict]:
149
+ """
150
+ Mount database in the container if it is file-based (e.g. a SQLite DB).
151
+
152
+ Parameters
153
+ ----------
154
+ ctx : AppContext
155
+ The context object
156
+ type_ : InstanceType
157
+ The type of instance to mount the database for
158
+
159
+ Returns
160
+ -------
161
+ docker.types.Mount | None
162
+ The mount to use
163
+ dict | None
164
+ The environment variables to use
165
+ """
166
+ # FIXME: code duplication with cli_server_import()
167
+ # try to mount database
168
+ uri = ctx.config["uri"]
169
+ url = make_url(uri)
170
+ environment_vars = None
171
+ mount = None
172
+
173
+ # If host is None, we're dealing with a file-based DB, like SQLite
174
+ if url.host is None:
175
+ db_path = url.database
176
+
177
+ if not os.path.isabs(db_path):
178
+ # We're dealing with a relative path here -> make it absolute
179
+ db_path = ctx.data_dir / url.database
180
+
181
+ basename = os.path.basename(db_path)
182
+ dirname = os.path.dirname(db_path)
183
+ os.makedirs(dirname, exist_ok=True)
184
+
185
+ # we're mounting the entire folder that contains the database
186
+ mount = docker.types.Mount("/mnt/database/", dirname, type="bind")
187
+
188
+ if type_ == InstanceType.SERVER:
189
+ environment_vars = {
190
+ ServerGlobals.DB_URI_ENV_VAR.value: f"sqlite:////mnt/database/{basename}",
191
+ ServerGlobals.CONFIG_NAME_ENV_VAR.value: ctx.config_file_name,
192
+ }
193
+ elif type_ == InstanceType.ALGORITHM_STORE:
194
+ environment_vars = {
195
+ AlgoStoreGlobals.DB_URI_ENV_VAR.value: f"sqlite:////mnt/database/{basename}",
196
+ AlgoStoreGlobals.CONFIG_NAME_ENV_VAR.value: ctx.config_file_name,
197
+ }
198
+ else:
199
+ warning(
200
+ f"Database could not be transferred, make sure {url.host} "
201
+ "is reachable from the Docker container"
202
+ )
203
+ info("Consider using the docker-compose method to start a server")
204
+
205
+ return mount, environment_vars
206
+
207
+
208
+ def attach_logs(container: Container, type_: InstanceType) -> None:
209
+ """
210
+ Attach container logs to the console if specified.
211
+
212
+ Parameters
213
+ ----------
214
+ container : Container
215
+ The container to attach the logs from
216
+ type_ : InstanceType
217
+ The type of instance to attach the logs for
218
+ """
219
+ if isinstance(type_, enum.Enum):
220
+ type_ = type_.value
221
+ logs = container.attach(stream=True, logs=True, stdout=True)
222
+ Thread(target=print_log_worker, args=(logs,), daemon=True).start()
223
+ while True:
224
+ try:
225
+ time.sleep(1)
226
+ except KeyboardInterrupt:
227
+ info("Closing log file. Keyboard Interrupt.")
228
+ info(
229
+ "Note that your server is still running! Shut it down "
230
+ f"with {Fore.RED}v6 {type_} stop{Style.RESET_ALL}"
231
+ )
232
+ exit(0)
@@ -2,6 +2,17 @@ from schema import And, Or, Use, Optional
2
2
 
3
3
  from vantage6.common.configuration_manager import Configuration, ConfigurationManager
4
4
 
5
+ LOGGING_VALIDATORS = {
6
+ "level": And(
7
+ Use(str), lambda lvl: lvl in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
8
+ ),
9
+ "use_console": Use(bool),
10
+ "backup_count": And(Use(int), lambda n: n > 0),
11
+ "max_size": And(Use(int), lambda b: b > 16),
12
+ "format": Use(str),
13
+ "datefmt": Use(str),
14
+ }
15
+
5
16
 
6
17
  class ServerConfiguration(Configuration):
7
18
  """
@@ -16,18 +27,7 @@ class ServerConfiguration(Configuration):
16
27
  "api_path": Use(str),
17
28
  "uri": Use(str),
18
29
  "allow_drop_all": Use(bool),
19
- "logging": {
20
- "level": And(
21
- Use(str),
22
- lambda lvl: lvl in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
23
- ),
24
- "file": Use(str),
25
- "use_console": Use(bool),
26
- "backup_count": And(Use(int), lambda n: n > 0),
27
- "max_size": And(Use(int), lambda b: b > 16),
28
- "format": Use(str),
29
- "datefmt": Use(str),
30
- },
30
+ "logging": {**LOGGING_VALIDATORS, "file": Use(str)},
31
31
  }
32
32
 
33
33
 
@@ -45,17 +45,7 @@ class NodeConfiguration(Configuration):
45
45
  # TODO: remove `dict` validation from databases
46
46
  "databases": Or([Use(dict)], dict, None),
47
47
  "api_path": Use(str),
48
- "logging": {
49
- "level": And(
50
- Use(str),
51
- lambda lvl: lvl in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
52
- ),
53
- "use_console": Use(bool),
54
- "backup_count": And(Use(int), lambda n: n > 0),
55
- "max_size": And(Use(int), lambda b: b > 16),
56
- "format": Use(str),
57
- "datefmt": Use(str),
58
- },
48
+ "logging": LOGGING_VALIDATORS,
59
49
  "encryption": {"enabled": bool, Optional("private_key"): Use(str)},
60
50
  Optional("node_extra_env"): dict,
61
51
  Optional("node_extra_mounts"): [str],
@@ -3,14 +3,16 @@ import questionary as q
3
3
  from pathlib import Path
4
4
 
5
5
  from vantage6.common import generate_apikey
6
- from vantage6.common.globals import DATABASE_TYPES
6
+ from vantage6.common.globals import DATABASE_TYPES, InstanceType
7
7
  from vantage6.common.client.node_client import NodeClient
8
+ from vantage6.common.context import AppContext
8
9
  from vantage6.common import error, warning
9
- from vantage6.cli.context import NodeContext, ServerContext
10
+ from vantage6.cli.context import select_context_class
10
11
  from vantage6.cli.configuration_manager import (
11
12
  NodeConfigurationManager,
12
13
  ServerConfigurationManager,
13
14
  )
15
+ from vantage6.cli.globals import AlgoStoreGlobals, ServerGlobals
14
16
 
15
17
 
16
18
  def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
@@ -139,29 +141,32 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
139
141
 
140
142
  private_key = "" if not encryption else q.text("Path to private key file:").ask()
141
143
 
142
- config["encryption"] = {
143
- "enabled": encryption is True or encryption == "true",
144
- "private_key": private_key,
145
- }
144
+ config["encryption"] = {"enabled": encryption == "true", "private_key": private_key}
146
145
 
147
146
  return config
148
147
 
149
148
 
150
- def server_configuration_questionaire(instance_name: str) -> dict:
149
+ def _get_common_server_config(
150
+ instance_type: InstanceType, instance_name: str, include_api_path: bool = True
151
+ ) -> dict:
151
152
  """
152
- Questionary to generate a config file for the node instance.
153
+ Part of the questionaire that is common to all server types (vantage6
154
+ server and algorithm store server).
153
155
 
154
156
  Parameters
155
157
  ----------
158
+ instance_type : InstanceType
159
+ Type of server instance.
156
160
  instance_name : str
157
- Name of the node instance.
161
+ Name of the server instance.
162
+ include_api_path : bool
163
+ Whether to include the api path in the questionaire.
158
164
 
159
165
  Returns
160
166
  -------
161
167
  dict
162
- Dictionary with the new server configuration
168
+ Dictionary with new (partial) server configuration
163
169
  """
164
-
165
170
  config = q.prompt(
166
171
  [
167
172
  {
@@ -174,38 +179,99 @@ def server_configuration_questionaire(instance_name: str) -> dict:
174
179
  "type": "text",
175
180
  "name": "port",
176
181
  "message": "Enter port to which the server listens:",
177
- "default": "5000",
178
- },
179
- {
180
- "type": "text",
181
- "name": "api_path",
182
- "message": "Path of the api:",
183
- "default": "/api",
184
- },
185
- {
186
- "type": "text",
187
- "name": "uri",
188
- "message": "Database URI:",
189
- "default": "sqlite:///default.sqlite",
190
- },
191
- {
192
- "type": "select",
193
- "name": "allow_drop_all",
194
- "message": "Allowed to drop all tables: ",
195
- "choices": ["True", "False"],
182
+ "default": (
183
+ # Note that .value is required in YAML to get proper str value
184
+ ServerGlobals.PORT.value
185
+ if instance_type == InstanceType.SERVER
186
+ else AlgoStoreGlobals.PORT.value
187
+ ),
196
188
  },
197
189
  ]
198
190
  )
199
191
 
200
- constant_jwt_secret = q.confirm("Do you want a constant JWT secret?").ask()
201
- if constant_jwt_secret:
202
- config["jwt_secret_key"] = generate_apikey()
192
+ # TODO v5+ remove api_path. It complicates configuration
193
+ if include_api_path:
194
+ config.update(
195
+ q.prompt(
196
+ [
197
+ {
198
+ "type": "text",
199
+ "name": "api_path",
200
+ "message": "Path of the api:",
201
+ "default": "/api",
202
+ }
203
+ ]
204
+ )
205
+ )
206
+
207
+ config.update(
208
+ q.prompt(
209
+ [
210
+ {
211
+ "type": "text",
212
+ "name": "uri",
213
+ "message": "Database URI:",
214
+ "default": "sqlite:///default.sqlite",
215
+ },
216
+ {
217
+ "type": "select",
218
+ "name": "allow_drop_all",
219
+ "message": "Allowed to drop all tables: ",
220
+ "choices": ["True", "False"],
221
+ },
222
+ ]
223
+ )
224
+ )
203
225
 
204
226
  res = q.select(
205
227
  "Which level of logging would you like?",
206
228
  choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "NOTSET"],
207
229
  ).ask()
208
230
 
231
+ config["logging"] = {
232
+ "level": res,
233
+ "file": f"{instance_name}.log",
234
+ "use_console": True,
235
+ "backup_count": 5,
236
+ "max_size": 1024,
237
+ "format": "%(asctime)s - %(name)-14s - %(levelname)-8s - %(message)s",
238
+ "datefmt": "%Y-%m-%d %H:%M:%S",
239
+ "loggers": [
240
+ {"name": "urllib3", "level": "warning"},
241
+ {"name": "socketIO-client", "level": "warning"},
242
+ {"name": "socketio.server", "level": "warning"},
243
+ {"name": "engineio.server", "level": "warning"},
244
+ {"name": "sqlalchemy.engine", "level": "warning"},
245
+ {"name": "requests_oauthlib.oauth2_session", "level": "warning"},
246
+ ],
247
+ }
248
+
249
+ return config
250
+
251
+
252
+ def server_configuration_questionaire(instance_name: str) -> dict:
253
+ """
254
+ Questionary to generate a config file for the server instance.
255
+
256
+ Parameters
257
+ ----------
258
+ instance_name : str
259
+ Name of the server instance.
260
+
261
+ Returns
262
+ -------
263
+ dict
264
+ Dictionary with the new server configuration
265
+ """
266
+
267
+ config = _get_common_server_config(
268
+ InstanceType.SERVER, instance_name, include_api_path=True
269
+ )
270
+
271
+ constant_jwt_secret = q.confirm("Do you want a constant JWT secret?").ask()
272
+ if constant_jwt_secret:
273
+ config["jwt_secret_key"] = generate_apikey()
274
+
209
275
  is_mfa = q.confirm("Do you want to enforce two-factor authentication?").ask()
210
276
  if is_mfa:
211
277
  config["two_factor_auth"] = is_mfa
@@ -260,35 +326,40 @@ def server_configuration_questionaire(instance_name: str) -> dict:
260
326
  "start_with_server": run_rabbit_locally,
261
327
  }
262
328
 
263
- config["logging"] = {
264
- "level": res,
265
- "file": f"{instance_name}.log",
266
- "use_console": True,
267
- "backup_count": 5,
268
- "max_size": 1024,
269
- "format": "%(asctime)s - %(name)-14s - %(levelname)-8s - %(message)s",
270
- "datefmt": "%Y-%m-%d %H:%M:%S",
271
- "loggers": [
272
- {"name": "urllib3", "level": "warning"},
273
- {"name": "socketIO-client", "level": "warning"},
274
- {"name": "socketio.server", "level": "warning"},
275
- {"name": "engineio.server", "level": "warning"},
276
- {"name": "sqlalchemy.engine", "level": "warning"},
277
- {"name": "requests_oauthlib.oauth2_session", "level": "warning"},
278
- ],
279
- }
329
+ return config
330
+
331
+
332
+ def algo_store_configuration_questionaire(instance_name: str) -> dict:
333
+ """
334
+ Questionary to generate a config file for the algorithm store server
335
+ instance.
280
336
 
337
+ Parameters
338
+ ----------
339
+ instance_name : str
340
+ Name of the server instance.
341
+
342
+ Returns
343
+ -------
344
+ dict
345
+ Dictionary with the new server configuration
346
+ """
347
+ config = _get_common_server_config(
348
+ InstanceType.ALGORITHM_STORE, instance_name, include_api_path=False
349
+ )
281
350
  return config
282
351
 
283
352
 
284
- def configuration_wizard(type_: str, instance_name: str, system_folders: bool) -> Path:
353
+ def configuration_wizard(
354
+ type_: InstanceType, instance_name: str, system_folders: bool
355
+ ) -> Path:
285
356
  """
286
357
  Create a configuration file for a node or server instance.
287
358
 
288
359
  Parameters
289
360
  ----------
290
- type_ : str
291
- Type of the instance. Either "node" or "server"
361
+ type_ : InstanceType
362
+ Type of the instance to create a configuration for
292
363
  instance_name : str
293
364
  Name of the instance
294
365
  system_folders : bool
@@ -300,15 +371,18 @@ def configuration_wizard(type_: str, instance_name: str, system_folders: bool) -
300
371
  Path to the configuration file
301
372
  """
302
373
  # for defaults and where to save the config
303
- dirs = NodeContext.instance_folders(type_, instance_name, system_folders)
374
+ dirs = AppContext.instance_folders(type_, instance_name, system_folders)
304
375
 
305
376
  # invoke questionaire to create configuration file
306
- if type_ == "node":
377
+ if type_ == InstanceType.NODE:
307
378
  conf_manager = NodeConfigurationManager
308
379
  config = node_configuration_questionaire(dirs, instance_name)
309
- else:
380
+ elif type_ == InstanceType.SERVER:
310
381
  conf_manager = ServerConfigurationManager
311
382
  config = server_configuration_questionaire(instance_name)
383
+ else:
384
+ conf_manager = ServerConfigurationManager
385
+ config = algo_store_configuration_questionaire(instance_name)
312
386
 
313
387
  # in the case of an environment we need to add it to the current
314
388
  # configuration. In the case of application we can simply overwrite this
@@ -326,15 +400,15 @@ def configuration_wizard(type_: str, instance_name: str, system_folders: bool) -
326
400
  return config_file
327
401
 
328
402
 
329
- def select_configuration_questionaire(type_: str, system_folders: bool) -> str:
403
+ def select_configuration_questionaire(type_: InstanceType, system_folders: bool) -> str:
330
404
  """
331
405
  Ask which configuration the user wants to use. It shows only configurations
332
406
  that are in the default folder.
333
407
 
334
408
  Parameters
335
409
  ----------
336
- type_ : str
337
- Type of the instance. Either "node" or "server"
410
+ type_ : InstanceType
411
+ Type of the instance to create a configuration for
338
412
  system_folders : bool
339
413
  Whether to use the system folders or not
340
414
 
@@ -343,7 +417,7 @@ def select_configuration_questionaire(type_: str, system_folders: bool) -> str:
343
417
  str
344
418
  Name of the configuration
345
419
  """
346
- context = NodeContext if type_ == "node" else ServerContext
420
+ context = select_context_class(type_)
347
421
  configs, _ = context.available_configurations(system_folders)
348
422
 
349
423
  # each collection (file) can contain multiple configs. (e.g. test,
@@ -0,0 +1,86 @@
1
+ """
2
+ The context module in the CLI package contains the Context classes of instances
3
+ started from the CLI, such as nodes and servers. These contexts are related to
4
+ the host system and therefore part of the CLI package.
5
+
6
+ All classes are derived from the abstract AppContext class and provide the
7
+ vantage6 applications with naming conventions, standard file locations, and
8
+ more.
9
+ """
10
+ from colorama import Fore, Style
11
+
12
+ from vantage6.common.globals import InstanceType
13
+ from vantage6.common import error
14
+ from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
15
+ from vantage6.cli.context.node import NodeContext
16
+ from vantage6.cli.context.server import ServerContext
17
+
18
+
19
+ def select_context_class(
20
+ type_: InstanceType,
21
+ ) -> ServerContext | NodeContext | AlgorithmStoreContext:
22
+ """
23
+ Select the context class based on the type of instance.
24
+
25
+ Parameters
26
+ ----------
27
+ type_ : InstanceType
28
+ The type of instance for which the context should be inserted
29
+
30
+ Returns
31
+ -------
32
+ ServerContext | NodeContext | AlgorithmStoreContext
33
+ Specialized subclass of AppContext for the given instance type
34
+
35
+ Raises
36
+ ------
37
+ NotImplementedError
38
+ If the type_ is not implemented
39
+ """
40
+ if type_ == InstanceType.SERVER:
41
+ return ServerContext
42
+ elif type_ == InstanceType.ALGORITHM_STORE:
43
+ return AlgorithmStoreContext
44
+ elif type_ == InstanceType.NODE:
45
+ return NodeContext
46
+ else:
47
+ raise NotImplementedError
48
+
49
+
50
+ def get_context(
51
+ type_: InstanceType, name: str, system_folders: bool
52
+ ) -> ServerContext | NodeContext | AlgorithmStoreContext:
53
+ """
54
+ Load the server context from the configuration file.
55
+
56
+ Parameters
57
+ ----------
58
+ type_ : InstanceType
59
+ The type of instance to get the context for
60
+ name : str
61
+ Name of the instance
62
+ system_folders : bool
63
+ Wether to use system folders or if False, the user folders
64
+
65
+ Returns
66
+ -------
67
+ AppContext
68
+ Specialized subclass context of AppContext for the given instance type
69
+ """
70
+ ctx_class = select_context_class(type_)
71
+ if not ctx_class.config_exists(name, system_folders):
72
+ scope = "system" if system_folders else "user"
73
+ error(
74
+ f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not "
75
+ f"exist in the {Fore.RED}{scope}{Style.RESET_ALL} folders!"
76
+ )
77
+ exit(1)
78
+
79
+ # We do not want to log this here, we do this in the container and not on
80
+ # the host. We only want CLI logging here.
81
+ ctx_class.LOGGING_ENABLED = False
82
+
83
+ # create server context, and initialize db
84
+ ctx = ctx_class(name, system_folders=system_folders)
85
+
86
+ return ctx