vantage6 5.0.0a21__py3-none-any.whl → 5.0.0a26__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 (39) hide show
  1. tests_cli/test_client_script.py +23 -0
  2. vantage6/cli/__build__ +1 -1
  3. vantage6/cli/algorithm/generate_algorithm_json.py +529 -0
  4. vantage6/cli/cli.py +25 -0
  5. vantage6/cli/common/start.py +220 -9
  6. vantage6/cli/common/stop.py +90 -0
  7. vantage6/cli/common/utils.py +8 -7
  8. vantage6/cli/config.py +260 -0
  9. vantage6/cli/configuration_manager.py +3 -11
  10. vantage6/cli/configuration_wizard.py +60 -101
  11. vantage6/cli/context/node.py +34 -45
  12. vantage6/cli/context/server.py +26 -0
  13. vantage6/cli/dev/create.py +78 -17
  14. vantage6/cli/dev/data/km_dataset.csv +2401 -0
  15. vantage6/cli/dev/remove.py +99 -98
  16. vantage6/cli/globals.py +20 -0
  17. vantage6/cli/node/new.py +4 -3
  18. vantage6/cli/node/remove.py +4 -2
  19. vantage6/cli/node/start.py +17 -20
  20. vantage6/cli/prometheus/monitoring_manager.py +146 -0
  21. vantage6/cli/prometheus/prometheus.yml +5 -0
  22. vantage6/cli/server/new.py +25 -6
  23. vantage6/cli/server/start.py +42 -212
  24. vantage6/cli/server/stop.py +35 -105
  25. vantage6/cli/template/algo_store_config.j2 +0 -1
  26. vantage6/cli/template/node_config.j2 +1 -1
  27. vantage6/cli/template/server_import_config.j2 +0 -2
  28. vantage6/cli/test/algo_test_scripts/algo_test_arguments.py +29 -0
  29. vantage6/cli/test/algo_test_scripts/algo_test_script.py +91 -0
  30. vantage6/cli/test/client_script.py +151 -0
  31. vantage6/cli/test/common/diagnostic_runner.py +2 -2
  32. vantage6/cli/use/context.py +46 -0
  33. vantage6/cli/use/namespace.py +55 -0
  34. vantage6/cli/utils.py +70 -4
  35. {vantage6-5.0.0a21.dist-info → vantage6-5.0.0a26.dist-info}/METADATA +5 -8
  36. {vantage6-5.0.0a21.dist-info → vantage6-5.0.0a26.dist-info}/RECORD +39 -27
  37. {vantage6-5.0.0a21.dist-info → vantage6-5.0.0a26.dist-info}/WHEEL +0 -0
  38. {vantage6-5.0.0a21.dist-info → vantage6-5.0.0a26.dist-info}/entry_points.txt +0 -0
  39. {vantage6-5.0.0a21.dist-info → vantage6-5.0.0a26.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,23 @@
1
+ # from click import UsageError
2
+ # from vantage6.cli.test.client_script import cli_test_client_script
3
+
4
+ # import click
5
+ # import unittest
6
+
7
+
8
+ # class TestScriptTest(unittest.TestCase):
9
+ # def test_script_incorrect_usage(self):
10
+ # ctx = click.Context(cli_test_client_script)
11
+
12
+ # with self.assertRaises(UsageError):
13
+ # ctx.invoke(
14
+ # cli_test_client_script,
15
+ # script="path/to/script.py",
16
+ # task_arguments="{'my_arg': 1}",
17
+ # )
18
+
19
+ # with self.assertRaises(UsageError):
20
+ # ctx.invoke(
21
+ # cli_test_client_script,
22
+ # task_arguments="not_a_json",
23
+ # )
vantage6/cli/__build__ CHANGED
@@ -1 +1 @@
1
- 21
1
+ 26
@@ -0,0 +1,529 @@
1
+ import os
2
+ import sys
3
+ import importlib
4
+ import inspect
5
+ import json
6
+
7
+ from enum import Enum
8
+ from inspect import getmembers, isfunction, ismodule, signature
9
+ from pathlib import Path
10
+ from types import ModuleType, UnionType
11
+ from typing import Any, OrderedDict
12
+
13
+ import click
14
+ import questionary as q
15
+ import pandas as pd
16
+
17
+ from vantage6.algorithm.client import AlgorithmClient
18
+ from vantage6.algorithm.tools import DecoratorStepType
19
+ from vantage6.common import error, info, warning
20
+ from vantage6.common.enum import AlgorithmArgumentType, AlgorithmStepType
21
+ from vantage6.common.algorithm_function import (
22
+ get_vantage6_decorator_type,
23
+ is_vantage6_algorithm_func,
24
+ )
25
+ from vantage6.algorithm.preprocessing.algorithm_json_data import (
26
+ PREPROCESSING_FUNCTIONS_JSON_DATA,
27
+ )
28
+
29
+
30
+ class MergePreference:
31
+ """Singleton class to manage global merge preference state"""
32
+
33
+ _instance = None
34
+ _prefer_existing = None
35
+
36
+ def __new__(cls):
37
+ if cls._instance is None:
38
+ cls._instance = super(MergePreference, cls).__new__(cls)
39
+ return cls._instance
40
+
41
+ @classmethod
42
+ def get_preference(cls) -> bool | None:
43
+ """Get the current merge preference"""
44
+ return cls._prefer_existing
45
+
46
+ @classmethod
47
+ def set_preference(cls, prefer_existing: bool) -> None:
48
+ """Set the merge preference globally"""
49
+ cls._prefer_existing = prefer_existing
50
+
51
+ @classmethod
52
+ def reset(cls) -> None:
53
+ """Reset the preference to None"""
54
+ cls._prefer_existing = None
55
+
56
+
57
+ class FunctionArgumentType(Enum):
58
+ """Type of the function argument"""
59
+
60
+ PARAMETER = "parameter"
61
+ DATAFRAME = "dataframe"
62
+
63
+
64
+ class Function:
65
+ """Class to handle a function and its JSON representation"""
66
+
67
+ def __init__(self, func: callable):
68
+ self.func = func
69
+ self.name = func.__name__
70
+ self.signature = signature(func)
71
+ self.docstring = func.__doc__
72
+ self.json = None
73
+ self.step_type = None
74
+
75
+ def prepare_json(self) -> None:
76
+ """Convert the function to a JSON format"""
77
+ self.step_type = self._get_step_type()
78
+ function_json = {
79
+ "name": self.name,
80
+ "display_name": self._pretty_print_name(self.name),
81
+ "standalone": True,
82
+ "description": self._extract_headline_of_docstring(),
83
+ "step_type": self.step_type,
84
+ "ui_visualizations": [],
85
+ "arguments": [],
86
+ "databases": [],
87
+ }
88
+
89
+ parameters = OrderedDict(self.signature.parameters)
90
+
91
+ # if the function is a data extraction function, the first argument is a dict
92
+ # with database connection details. This argument should not be added to the
93
+ # function json. Instead, a database should be added to the function json.
94
+ if self.step_type == AlgorithmStepType.DATA_EXTRACTION.value:
95
+ function_json["databases"].append(
96
+ {
97
+ "name": "Database",
98
+ "description": "Database to extract data from",
99
+ }
100
+ )
101
+ # remove database connection details from the signature
102
+ parameters.popitem(last=False)
103
+
104
+ # add the arguments to the function json
105
+ for name, param in parameters.items():
106
+ arg_json, arg_type = self._get_argument_json(name, param)
107
+ if arg_json is None:
108
+ continue
109
+ elif arg_type == FunctionArgumentType.DATAFRAME:
110
+ function_json["databases"].append(arg_json)
111
+ else:
112
+ function_json["arguments"].append(arg_json)
113
+ self.json = function_json
114
+
115
+ def merge_with_template_json_data(self) -> None:
116
+ """
117
+ Merge the function jsons with the json data from the algorithm_json_data module
118
+ """
119
+ # Only merge the function jsons with template json data if it is an
120
+ # infrastructure-defined function
121
+ if (
122
+ not self.func.__module__.startswith("vantage6.algorithm.")
123
+ or not self.json["name"] in PREPROCESSING_FUNCTIONS_JSON_DATA
124
+ ):
125
+ return
126
+
127
+ # get the template json data for the function
128
+ template_json = PREPROCESSING_FUNCTIONS_JSON_DATA[self.json["name"]]
129
+ # merge the dicts, with the template dict taking precedence
130
+ for argument in self.json["arguments"]:
131
+ if argument["name"] in template_json["arguments"]:
132
+ argument.update(template_json["arguments"][argument["name"]])
133
+ # Add any frontend arguments specified in the template json
134
+ if "frontend_arguments" in template_json:
135
+ for frontend_argument in template_json["frontend_arguments"]:
136
+ self._add_frontend_argument(template_json, frontend_argument)
137
+
138
+ def merge_with_existing_json(self, existing_json: dict) -> None:
139
+ """Merge the function json with the existing json data"""
140
+ self._merge_dicts(self.json, existing_json)
141
+
142
+ def _merge_dicts(self, target: dict, source: dict) -> None:
143
+ """
144
+ Recursively merge source dict into target dict, with source taking precedence
145
+ """
146
+ for key, value in source.items():
147
+ if key in target:
148
+ if isinstance(value, dict) and isinstance(target[key], dict):
149
+ # Recursively merge nested dictionaries
150
+ self._merge_dicts(target[key], value)
151
+ else:
152
+ self._replace_target_with_source(target, key, value)
153
+ else:
154
+ # Add new key-value pair from source to target
155
+ self._replace_target_with_source(target, key, value)
156
+
157
+ def _replace_target_with_source(self, target: dict, key: str, value: Any) -> None:
158
+ """Replace the value in target with the one from source"""
159
+ if target[key] == value:
160
+ return
161
+
162
+ prefer_existing = MergePreference.get_preference()
163
+ if prefer_existing:
164
+ target[key] = value
165
+ elif prefer_existing is None:
166
+ info(
167
+ f"Different values for the same key '{key}' in function '{self.name}' "
168
+ "were found."
169
+ )
170
+ info(f"Value from function itself: {target[key]}")
171
+ info(f"Value from algorithm.json: {value}")
172
+ result = q.select(
173
+ "Please select the value to keep:",
174
+ choices=[
175
+ "function itself",
176
+ "algorithm.json",
177
+ "function itself (also for all other conflicts)",
178
+ "algorithm.json (also for all other conflicts)",
179
+ ],
180
+ ).unsafe_ask()
181
+ if result == "algorithm.json":
182
+ target[key] = value
183
+ elif result == "function itself":
184
+ pass # do nothing
185
+ elif result == "function itself (also for all other conflicts)":
186
+ MergePreference.set_preference(False)
187
+ elif result == "algorithm.json (also for all other conflicts)":
188
+ MergePreference.set_preference(True)
189
+ target[key] = value
190
+
191
+ def _get_argument_json(
192
+ self, name: str, param: inspect.Parameter
193
+ ) -> tuple[dict | None, FunctionArgumentType | None]:
194
+ """Get the argument JSON"""
195
+
196
+ if param.annotation is None:
197
+ error(f"Function {self.name} has no annotation for argument {name}")
198
+ info(f"Please add a type annotation to the argument {name}")
199
+ info(f"For example, for string arguments: 'def {self.name}({name}: str)'")
200
+ exit(1)
201
+
202
+ if param.annotation is AlgorithmClient:
203
+ # Algorithm client arguments do not have to be provided by the user
204
+ return None, None
205
+ elif param.annotation is pd.DataFrame:
206
+ # this is an argument that requires the user to supply a dataframe. That
207
+ # only requires a name and description.
208
+ return {
209
+ "name": name if name != "df" else "Data to use",
210
+ "description": self._extract_parameter_description(name),
211
+ }, FunctionArgumentType.DATAFRAME
212
+ else:
213
+ # This is a regular function parameter
214
+ arg_json = {
215
+ "name": name,
216
+ "display_name": self._pretty_print_name(name),
217
+ "description": self._extract_parameter_description(name),
218
+ "type": self.get_argument_type(param, name),
219
+ "required": param.default == inspect.Parameter.empty,
220
+ "has_default_value": param.default != inspect.Parameter.empty,
221
+ "is_frontend_only": False,
222
+ }
223
+ if param.default != inspect.Parameter.empty:
224
+ arg_json["default"] = param.default
225
+ return arg_json, FunctionArgumentType.PARAMETER
226
+
227
+ def _add_frontend_argument(
228
+ self, template_json: dict, frontend_argument: str
229
+ ) -> None:
230
+ """Add a frontend argument to the function json"""
231
+ frontend_argument_json: dict = template_json["frontend_arguments"][
232
+ frontend_argument
233
+ ]
234
+ before_arg_name = frontend_argument_json.pop("before_argument")
235
+
236
+ try:
237
+ before_arg_idx = next(
238
+ idx
239
+ for idx, arg in enumerate(self.json["arguments"])
240
+ if arg["name"] == before_arg_name
241
+ )
242
+ self.json["arguments"].insert(before_arg_idx, frontend_argument_json)
243
+ except StopIteration:
244
+ warning(
245
+ f"Could not find argument {before_arg_name} in function "
246
+ f"{self.json['name']}. Frontend argument {frontend_argument} "
247
+ "will not be added."
248
+ )
249
+
250
+ def get_argument_type(self, param: inspect.Parameter, name: str) -> str:
251
+ """Get the type of the argument"""
252
+ if isinstance(param.annotation, UnionType):
253
+ # Arguments with default values may have type 'str | None'. If that is the
254
+ # case, we want to use the type of the first element in the union.
255
+ if len(param.annotation.__args__) > 2:
256
+ # if there are more than 2 elements in the union, don't handle
257
+ warning(
258
+ f"Unsupported argument type: {param.annotation} for argument {name}"
259
+ f" in function {self.name}"
260
+ )
261
+ return None
262
+ elif len(param.annotation.__args__) == 2:
263
+ # if there are two, we want to use the first one if the second is None
264
+ if param.annotation.__args__[1] is type(None):
265
+ type_ = param.annotation.__args__[0]
266
+ else:
267
+ warning(
268
+ f"Unsupported argument type: {param.annotation} for argument "
269
+ f"{name} in function {self.name}"
270
+ )
271
+ return None
272
+ else:
273
+ # normally, unions have 2+ elements. If there is only one, use that
274
+ type_ = param.annotation.__args__[0]
275
+ else:
276
+ type_ = param.annotation
277
+
278
+ if type_ == str:
279
+ return AlgorithmArgumentType.STRING.value
280
+ elif type_ == dict:
281
+ return AlgorithmArgumentType.JSON.value
282
+ elif type_ == int:
283
+ return AlgorithmArgumentType.INTEGER.value
284
+ elif type_ == float:
285
+ return AlgorithmArgumentType.FLOAT.value
286
+ elif type_ == bool:
287
+ return AlgorithmArgumentType.BOOLEAN.value
288
+ elif type_ == list:
289
+ return AlgorithmArgumentType.STRINGS.value
290
+ elif type_ == list[str]:
291
+ return AlgorithmArgumentType.STRINGS.value
292
+ elif type_ == list[int]:
293
+ return AlgorithmArgumentType.INTEGERS.value
294
+ elif type_ == list[float]:
295
+ return AlgorithmArgumentType.FLOATS.value
296
+ else:
297
+ warning(
298
+ f"Unsupported argument type: {param.annotation} for argument {name} "
299
+ f"in function {self.name}"
300
+ )
301
+ return None
302
+
303
+ def _pretty_print_name(self, name: str) -> str:
304
+ """Pretty print the name of the function"""
305
+ pretty = name.replace("_", " ")
306
+ if len(pretty):
307
+ pretty = pretty[0].upper() + pretty[1:]
308
+ return pretty
309
+
310
+ def _extract_headline_of_docstring(self) -> str:
311
+ """Extract the headline of the docstring"""
312
+ if not self.docstring:
313
+ return ""
314
+
315
+ # Split by double newlines to get the first paragraph
316
+ paragraphs = self.docstring.split("\n\n")
317
+ first_paragraph = paragraphs[0]
318
+
319
+ # Split by single newlines and join the lines with spaces
320
+ lines = first_paragraph.split("\n")
321
+ header = " ".join(line.strip() for line in lines if line.strip() != "")
322
+ return header
323
+
324
+ def _get_step_type(self) -> str:
325
+ """Get the step type of the function"""
326
+ decorator_type = get_vantage6_decorator_type(self.func)
327
+ if decorator_type == DecoratorStepType.FEDERATED:
328
+ return AlgorithmStepType.FEDERATED_COMPUTE.value
329
+ elif decorator_type == DecoratorStepType.CENTRAL:
330
+ return AlgorithmStepType.CENTRAL_COMPUTE.value
331
+ elif decorator_type == DecoratorStepType.PREPROCESSING:
332
+ return AlgorithmStepType.PREPROCESSING.value
333
+ elif decorator_type == DecoratorStepType.DATA_EXTRACTION:
334
+ return AlgorithmStepType.DATA_EXTRACTION.value
335
+ else:
336
+ warning(
337
+ f"Unsupported decorator type: {decorator_type} for function "
338
+ f"{self.name}"
339
+ )
340
+ return None
341
+
342
+ def _extract_parameter_description(self, name: str) -> str:
343
+ """Extract the description of the parameter"""
344
+ if not self.docstring:
345
+ return ""
346
+
347
+ # Try both patterns: "{name}:" and "{name} :"
348
+ patterns = [f"{name}:", f"{name} :"]
349
+
350
+ for pattern in patterns:
351
+ if pattern in self.docstring:
352
+ return self.docstring.split(pattern)[1].split("\n")[1].strip()
353
+
354
+ return ""
355
+
356
+
357
+ @click.command()
358
+ @click.option(
359
+ "--algo-function-file",
360
+ default=None,
361
+ type=str,
362
+ help="Path to the file containing or importing the algorithm functions",
363
+ )
364
+ @click.option(
365
+ "--current-json",
366
+ default=None,
367
+ type=str,
368
+ help="Path to the current algorithm.json file",
369
+ )
370
+ @click.option(
371
+ "--output-file",
372
+ default="new-algorithm.json",
373
+ type=str,
374
+ help="Path to the output file",
375
+ )
376
+ def cli_algorithm_generate_algorithm_json(
377
+ algo_function_file: str, current_json: str, output_file: str
378
+ ) -> dict:
379
+ """
380
+ Generate an updated algorithm.json file to submit to the algorithm store.
381
+
382
+ You should provide the path to the file where the algorithm functions are
383
+ defined.
384
+
385
+ Note that if you do asterisk ('from x import *') imports, all functions from the
386
+ imported module will be added to the algorithm.json file.
387
+ """
388
+ algo_function_file = _get_algo_function_file_location(algo_function_file)
389
+
390
+ current_json = _get_current_json_location(current_json)
391
+
392
+ # read the current algorithm.json file
393
+ with open(current_json, "r", encoding="utf-8") as f:
394
+ current_json_data = json.load(f)
395
+
396
+ # get the functions from the file
397
+ info(f"Importing functions from {algo_function_file}...")
398
+ functions = _get_functions_from_file(algo_function_file)
399
+ function_objs = [Function(f) for f in functions]
400
+
401
+ info("Converting functions to JSON...")
402
+ for function in function_objs:
403
+ function.prepare_json()
404
+ function.merge_with_template_json_data()
405
+
406
+ # merge the function jsons with the existing json data
407
+ current_json_func = [
408
+ f for f in current_json_data["functions"] if f["name"] == function.name
409
+ ]
410
+ if current_json_func:
411
+ function.merge_with_existing_json(current_json_func[0])
412
+
413
+ # write the new algorithm.json file
414
+ info(f"Writing new algorithm.json file to {output_file}...")
415
+ current_json_data["functions"] = [f.json for f in function_objs]
416
+ with open(output_file, "w", encoding="utf-8") as f:
417
+ json.dump(current_json_data, f, indent=2)
418
+
419
+ info(f"New algorithm.json file written to {output_file}")
420
+
421
+ warning("-" * 80)
422
+ warning("Always check the generated algorithm.json file before submitting it to ")
423
+ warning("the algorithm store!")
424
+ warning("-" * 80)
425
+
426
+
427
+ def _get_functions_from_file(file_path: str) -> None:
428
+ """Get the functions from the file
429
+
430
+ Parameters
431
+ ----------
432
+ file_path : str
433
+ Path to the file containing or importing the algorithm functions
434
+ """
435
+ # Convert path to absolute path
436
+ file_path = str(Path(file_path).resolve())
437
+
438
+ # Get the package root directory (two levels up from the file)
439
+ package_root = str(Path(file_path).parent.parent)
440
+ if package_root not in sys.path:
441
+ sys.path.insert(0, package_root)
442
+
443
+ # Get the module name from the file path, including the package name
444
+ package_name = Path(file_path).parent.name
445
+ module_name = f"{package_name}.{Path(file_path).stem}"
446
+
447
+ # Import the module
448
+ try:
449
+ module = importlib.import_module(module_name)
450
+ except ImportError as e:
451
+ raise ImportError(f"Could not import module {module_name}: {str(e)}") from e
452
+
453
+ def get_members_from_module(module: ModuleType) -> list:
454
+ """Get the functions from the module"""
455
+ return [
456
+ member for name, member in getmembers(module) if not name.startswith("_")
457
+ ]
458
+
459
+ # get the functions from the algorithm module
460
+ import_members = get_members_from_module(module)
461
+ import_functions = [
462
+ m for m in import_members if isfunction(m) and is_vantage6_algorithm_func(m)
463
+ ]
464
+ import_modules = [m for m in import_members if ismodule(m)]
465
+
466
+ # add the functions from the imported modules (only 1 level deep). This is so that
467
+ # if you do e.g. 'from vantage6.algorithm.preprocessing import *', all functions
468
+ # from within those modules are also imported.
469
+ for import_module in import_modules:
470
+ second_level_import_members = get_members_from_module(import_module)
471
+ import_functions.extend(
472
+ [
473
+ m
474
+ for m in second_level_import_members
475
+ if isfunction(m) and is_vantage6_algorithm_func(m)
476
+ ]
477
+ )
478
+
479
+ return import_functions
480
+
481
+
482
+ def _get_algo_function_file_location(algo_function_file: str | None) -> None:
483
+ """Get user input for the algorithm creation
484
+
485
+ Parameters
486
+ ----------
487
+ algo_function_file : str
488
+ Path to the file containing or importingthe algorithm functions
489
+ """
490
+ if not algo_function_file:
491
+ default_dir = str(Path(os.getcwd()) / "__init__.py")
492
+ algo_function_file = q.text(
493
+ "Path to the file containing or importing the algorithm functions:",
494
+ default=default_dir,
495
+ ).unsafe_ask()
496
+
497
+ # Convert to absolute path using pathlib
498
+ algo_function_file = str(Path(algo_function_file).resolve())
499
+
500
+ # check if the file exists
501
+ if not Path(algo_function_file).exists():
502
+ raise FileNotFoundError(f"File {algo_function_file} does not exist")
503
+
504
+ return algo_function_file
505
+
506
+
507
+ def _get_current_json_location(current_json: str) -> None:
508
+ """Get user input for the current algorithm.json file
509
+
510
+ Parameters
511
+ ----------
512
+ current_json : str
513
+ Path to the current algorithm.json file
514
+ """
515
+ if not current_json:
516
+ default_dir = str(Path(os.getcwd()) / "algorithm_store.json")
517
+ current_json = q.text(
518
+ "Path to the current algorithm.json file:",
519
+ default=default_dir,
520
+ ).unsafe_ask()
521
+
522
+ # Convert to absolute path using pathlib
523
+ current_json = str(Path(current_json).resolve())
524
+
525
+ # check if the file exists
526
+ if not Path(current_json).exists():
527
+ raise FileNotFoundError(f"File {current_json} does not exist")
528
+
529
+ return current_json
vantage6/cli/cli.py CHANGED
@@ -24,6 +24,11 @@ from vantage6.cli.node.stop import cli_node_stop
24
24
  from vantage6.cli.node.version import cli_node_version
25
25
  from vantage6.cli.algorithm.create import cli_algorithm_create
26
26
  from vantage6.cli.algorithm.update import cli_algorithm_update
27
+ from vantage6.cli.algorithm.generate_algorithm_json import (
28
+ cli_algorithm_generate_algorithm_json,
29
+ )
30
+
31
+ # from vantage6.cli.test.client_script import cli_test_client_script
27
32
  from vantage6.cli.test.feature_tester import cli_test_features
28
33
 
29
34
  # from vantage6.cli.test.integration_test import cli_test_integration
@@ -34,6 +39,8 @@ from vantage6.cli.algostore.stop import cli_algo_store_stop
34
39
  from vantage6.cli.algostore.files import cli_algo_store_files
35
40
  from vantage6.cli.algostore.list import cli_algo_store_configuration_list
36
41
  from vantage6.cli.algostore.remove import cli_algo_store_remove
42
+ from vantage6.cli.use.context import cli_use_context
43
+ from vantage6.cli.use.namespace import cli_use_namespace
37
44
 
38
45
 
39
46
  # Define the server group
@@ -104,6 +111,9 @@ def cli_algorithm() -> None:
104
111
  # Define the commands for the algorithm group
105
112
  cli_algorithm.add_command(cli_algorithm_create, name="create")
106
113
  cli_algorithm.add_command(cli_algorithm_update, name="update")
114
+ cli_algorithm.add_command(
115
+ cli_algorithm_generate_algorithm_json, name="generate-algorithm-json"
116
+ )
107
117
 
108
118
 
109
119
  # Define the test group
@@ -117,6 +127,7 @@ def cli_test() -> None:
117
127
  # Define the commands for the test group
118
128
  cli_test.add_command(cli_test_features, name="feature-test")
119
129
  # cli_test.add_command(cli_test_integration, name="integration-test")
130
+ # cli_test.add_command(cli_test_client_script, name="client-script")
120
131
 
121
132
 
122
133
  # Define the algorithm-store group
@@ -137,6 +148,19 @@ cli_algo_store.add_command(cli_algo_store_configuration_list, name="list")
137
148
  cli_algo_store.add_command(cli_algo_store_remove, name="remove")
138
149
 
139
150
 
151
+ # Add the use group
152
+ @click.group(name="use")
153
+ def cli_use() -> None:
154
+ """
155
+ Manage Kubernetes context and namespace.
156
+ """
157
+
158
+
159
+ # Define the commands for the use group
160
+ cli_use.add_command(cli_use_context, name="context")
161
+ cli_use.add_command(cli_use_namespace, name="namespace")
162
+
163
+
140
164
  # Define the overall group
141
165
  @click.group(name="cli", context_settings={"show_default": True})
142
166
  def cli_complete() -> None:
@@ -155,3 +179,4 @@ cli_complete.add_command(cli_dev)
155
179
  cli_complete.add_command(cli_algorithm)
156
180
  cli_complete.add_command(cli_test)
157
181
  cli_complete.add_command(cli_algo_store)
182
+ cli_complete.add_command(cli_use)