cave-cli 3.5.0__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.
cave_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("cave_cli")
cave_cli/cli.py ADDED
@@ -0,0 +1,469 @@
1
+ """
2
+ CAVE CLI entry point.
3
+
4
+ Usage:
5
+ cave <command> [options]
6
+
7
+ Examples:
8
+ cave create my_app
9
+ cave create my_app --version v3.1.0
10
+ cave run
11
+ cave run -it
12
+ cave run 192.168.1.1:8000
13
+ cave reset -y
14
+ cave upgrade --version v3.1.0
15
+ cave sync --url git@github.com:mit-cave/cave_app_aws.git
16
+ cave test
17
+ cave prettify
18
+ cave list
19
+ cave kill
20
+ cave purge my_app
21
+ cave list-versions
22
+ cave lv --all --pattern v3*
23
+ cave update
24
+ cave version
25
+ """
26
+
27
+ import argparse
28
+ import sys
29
+ from importlib.metadata import version
30
+
31
+
32
+ def add_global_args(parser: argparse.ArgumentParser) -> None:
33
+ """Args shared by all commands."""
34
+ parser.add_argument(
35
+ "-v",
36
+ "--verbose",
37
+ action="store_true",
38
+ default=False,
39
+ help="Enable verbose logging output (shorthand for --loglevel DEBUG)",
40
+ )
41
+ parser.add_argument(
42
+ "--loglevel",
43
+ "--ll",
44
+ default="INFO",
45
+ metavar="LEVEL",
46
+ help=(
47
+ "Specify a log level: DEBUG, INFO, WARN, ERROR, SILENT "
48
+ "(default: INFO)"
49
+ ),
50
+ )
51
+ parser.add_argument(
52
+ "-y",
53
+ "--yes",
54
+ action="store_true",
55
+ default=False,
56
+ help="Automatically answer confirmation prompts with yes",
57
+ )
58
+
59
+
60
+ def main():
61
+ __version__ = version("cave_cli")
62
+ parser = argparse.ArgumentParser(
63
+ prog="cave",
64
+ description=(
65
+ f"CAVE CLI ({__version__}): "
66
+ "Create and manage Docker-based CAVE web applications."
67
+ ),
68
+ formatter_class=argparse.RawDescriptionHelpFormatter,
69
+ epilog=__doc__,
70
+ )
71
+
72
+ parser.add_argument(
73
+ "-V",
74
+ "--version",
75
+ action="version",
76
+ version=f"CAVE_CLI={__version__}",
77
+ help="Show the version number and exit",
78
+ )
79
+
80
+ subparsers = parser.add_subparsers(dest="command", metavar="command")
81
+
82
+ # ------------------------------------------------------------------ #
83
+ # create #
84
+ # ------------------------------------------------------------------ #
85
+ p_create = subparsers.add_parser(
86
+ "create",
87
+ help="Create a new CAVE app from the template repository",
88
+ )
89
+ add_global_args(p_create)
90
+ p_create.add_argument(
91
+ "name",
92
+ metavar="app-name",
93
+ help="Name for the new CAVE app",
94
+ )
95
+ p_create.add_argument(
96
+ "--version",
97
+ default=None,
98
+ metavar="VERSION",
99
+ help="CAVE app version (git tag or branch name)",
100
+ )
101
+ p_create.add_argument(
102
+ "--url",
103
+ default=None,
104
+ metavar="URL",
105
+ help="Git URL for the app template repository",
106
+ )
107
+
108
+ # ------------------------------------------------------------------ #
109
+ # run / start #
110
+ # ------------------------------------------------------------------ #
111
+ p_run = subparsers.add_parser(
112
+ "run",
113
+ aliases=["start"],
114
+ help="Build and run the CAVE app in the current directory",
115
+ )
116
+ add_global_args(p_run)
117
+ p_run.add_argument(
118
+ "ip_port",
119
+ nargs="?",
120
+ default=None,
121
+ metavar="ip:port",
122
+ help="IP and port for LAN hosting (e.g. 192.168.1.1:8000)",
123
+ )
124
+ p_run.add_argument(
125
+ "--entrypoint",
126
+ default=None,
127
+ metavar="CMD",
128
+ help="Entrypoint script to run (default: ./utils/run_server.sh)",
129
+ )
130
+ p_run.add_argument(
131
+ "--docker-args",
132
+ default="",
133
+ metavar="ARGS",
134
+ help="Additional arguments to pass to docker run",
135
+ )
136
+ p_run.add_argument(
137
+ "-it",
138
+ "-interactive",
139
+ "--interactive",
140
+ dest="interactive",
141
+ action="store_true",
142
+ default=False,
143
+ help="Run in interactive mode (entrypoint set to bash)",
144
+ )
145
+
146
+ # ------------------------------------------------------------------ #
147
+ # reset #
148
+ # ------------------------------------------------------------------ #
149
+ p_reset = subparsers.add_parser(
150
+ "reset",
151
+ aliases=["reset-db"],
152
+ help=(
153
+ "Remove Docker containers and volumes, "
154
+ "then rebuild from scratch"
155
+ ),
156
+ )
157
+ add_global_args(p_reset)
158
+
159
+ # ------------------------------------------------------------------ #
160
+ # upgrade #
161
+ # ------------------------------------------------------------------ #
162
+ p_upgrade = subparsers.add_parser(
163
+ "upgrade",
164
+ help="Upgrade the CAVE app in the current directory",
165
+ )
166
+ add_global_args(p_upgrade)
167
+ p_upgrade.add_argument(
168
+ "--version",
169
+ default=None,
170
+ metavar="VERSION",
171
+ help="CAVE app version to upgrade to (git tag or branch name)",
172
+ )
173
+ p_upgrade.add_argument(
174
+ "--url",
175
+ default=None,
176
+ metavar="URL",
177
+ help="Git URL for the app template repository",
178
+ )
179
+ p_upgrade.add_argument(
180
+ "--skip-env-upgrade",
181
+ action="store_true",
182
+ default=False,
183
+ help="Skip upgrading the project .env file",
184
+ )
185
+
186
+ # ------------------------------------------------------------------ #
187
+ # sync #
188
+ # ------------------------------------------------------------------ #
189
+ p_sync = subparsers.add_parser(
190
+ "sync",
191
+ help="Merge files from another repository into the CAVE app",
192
+ )
193
+ add_global_args(p_sync)
194
+ p_sync.add_argument(
195
+ "--url",
196
+ required=True,
197
+ metavar="URL",
198
+ help="Git URL of the repository to sync from",
199
+ )
200
+ p_sync.add_argument(
201
+ "--branch",
202
+ default=None,
203
+ metavar="BRANCH",
204
+ help="Branch of the repository to sync from",
205
+ )
206
+ p_sync.add_argument(
207
+ "--include",
208
+ nargs="+",
209
+ default=None,
210
+ metavar="PATTERN",
211
+ help="File patterns to include (overrides excludes)",
212
+ )
213
+ p_sync.add_argument(
214
+ "--exclude",
215
+ nargs="+",
216
+ default=None,
217
+ metavar="PATTERN",
218
+ help="File patterns to exclude",
219
+ )
220
+
221
+ # ------------------------------------------------------------------ #
222
+ # test #
223
+ # ------------------------------------------------------------------ #
224
+ p_test = subparsers.add_parser(
225
+ "test",
226
+ help="Run tests in cave_api/tests/",
227
+ )
228
+ add_global_args(p_test)
229
+ p_test.add_argument(
230
+ "remaining",
231
+ nargs="*",
232
+ help="Additional arguments passed to the test runner",
233
+ )
234
+
235
+ # ------------------------------------------------------------------ #
236
+ # prettify #
237
+ # ------------------------------------------------------------------ #
238
+ p_prettify = subparsers.add_parser(
239
+ "prettify",
240
+ help="Format code with autoflake and black",
241
+ )
242
+ add_global_args(p_prettify)
243
+
244
+ # ------------------------------------------------------------------ #
245
+ # list #
246
+ # ------------------------------------------------------------------ #
247
+ p_list = subparsers.add_parser(
248
+ "list",
249
+ help="List running CAVE apps",
250
+ )
251
+ add_global_args(p_list)
252
+ p_list.add_argument(
253
+ "-a",
254
+ "--all",
255
+ action="store_true",
256
+ default=False,
257
+ help="Show all CAVE app containers with full names",
258
+ )
259
+
260
+ # ------------------------------------------------------------------ #
261
+ # kill #
262
+ # ------------------------------------------------------------------ #
263
+ p_kill = subparsers.add_parser(
264
+ "kill",
265
+ help="Stop Docker containers for a CAVE app",
266
+ )
267
+ add_global_args(p_kill)
268
+ p_kill.add_argument(
269
+ "-a",
270
+ "--all",
271
+ action="store_true",
272
+ default=False,
273
+ help="Kill all running CAVE apps",
274
+ )
275
+
276
+ # ------------------------------------------------------------------ #
277
+ # purge #
278
+ # ------------------------------------------------------------------ #
279
+ p_purge = subparsers.add_parser(
280
+ "purge",
281
+ help="Remove a CAVE app and all its Docker resources",
282
+ )
283
+ add_global_args(p_purge)
284
+ p_purge.add_argument(
285
+ "path",
286
+ metavar="app-path",
287
+ help="Path to the CAVE app directory to purge",
288
+ )
289
+
290
+ # ------------------------------------------------------------------ #
291
+ # list-versions / lv #
292
+ # ------------------------------------------------------------------ #
293
+ p_lv = subparsers.add_parser(
294
+ "list-versions",
295
+ aliases=["lv"],
296
+ help="List available CAVE app versions",
297
+ )
298
+ add_global_args(p_lv)
299
+ p_lv.add_argument(
300
+ "--all",
301
+ action="store_true",
302
+ default=False,
303
+ help="Show all versions (default: 5 most recent per major version)",
304
+ )
305
+ p_lv.add_argument(
306
+ "--pattern",
307
+ default=None,
308
+ metavar="PATTERN",
309
+ help="Glob pattern to filter versions (e.g. v3.*, v3.4.*)",
310
+ )
311
+
312
+ # ------------------------------------------------------------------ #
313
+ # update #
314
+ # ------------------------------------------------------------------ #
315
+ p_update = subparsers.add_parser(
316
+ "update",
317
+ help="Update the CAVE CLI itself",
318
+ )
319
+ add_global_args(p_update)
320
+ p_update.add_argument(
321
+ "--version",
322
+ default=None,
323
+ metavar="VERSION",
324
+ help="Specific CAVE CLI version to install (git tag or branch)",
325
+ )
326
+
327
+ # ------------------------------------------------------------------ #
328
+ # uninstall #
329
+ # ------------------------------------------------------------------ #
330
+ p_uninstall = subparsers.add_parser(
331
+ "uninstall",
332
+ help="Remove the CAVE CLI",
333
+ )
334
+ add_global_args(p_uninstall)
335
+
336
+ # ------------------------------------------------------------------ #
337
+ # version #
338
+ # ------------------------------------------------------------------ #
339
+ p_version = subparsers.add_parser(
340
+ "version",
341
+ help="Print version information",
342
+ )
343
+ add_global_args(p_version)
344
+
345
+ # ------------------------------------------------------------------ #
346
+ # Dispatch #
347
+ # ------------------------------------------------------------------ #
348
+ args = parser.parse_args()
349
+
350
+ if args.command is None:
351
+ parser.print_help()
352
+ sys.exit(0)
353
+
354
+ # Normalize aliases
355
+ _COMMAND_ALIASES: dict[str, str] = {
356
+ "start": "run",
357
+ "reset-db": "reset",
358
+ "lv": "list-versions",
359
+ }
360
+ args.command = _COMMAND_ALIASES.get(args.command, args.command)
361
+
362
+ # Configure logging
363
+ from cave_cli.utils.logger import logger
364
+
365
+ if args.verbose:
366
+ logger.set_level("DEBUG")
367
+ else:
368
+ logger.set_level(args.loglevel)
369
+
370
+ # ------------------------------------------------------------------ #
371
+ # Command dispatch #
372
+ # ------------------------------------------------------------------ #
373
+ if args.command == "create":
374
+ from cave_cli.utils.docker import check_docker
375
+ from cave_cli.commands.create import create
376
+
377
+ check_docker()
378
+ create(args)
379
+
380
+ elif args.command == "run":
381
+ from cave_cli.utils.docker import check_docker
382
+ from cave_cli.commands.run import run
383
+
384
+ check_docker()
385
+ run(args)
386
+
387
+ elif args.command == "reset":
388
+ from cave_cli.utils.docker import check_docker
389
+ from cave_cli.commands.reset import reset
390
+
391
+ check_docker()
392
+ reset(args)
393
+
394
+ elif args.command == "upgrade":
395
+ from cave_cli.utils.docker import check_docker
396
+ from cave_cli.commands.upgrade import upgrade
397
+
398
+ check_docker()
399
+ upgrade(args)
400
+
401
+ elif args.command == "sync":
402
+ from cave_cli.utils.docker import check_docker
403
+ from cave_cli.commands.sync_cmd import sync_cmd
404
+
405
+ check_docker()
406
+ sync_cmd(args)
407
+
408
+ elif args.command == "test":
409
+ from cave_cli.utils.docker import check_docker
410
+ from cave_cli.commands.test import test
411
+
412
+ check_docker()
413
+ test(args)
414
+
415
+ elif args.command == "prettify":
416
+ from cave_cli.utils.docker import check_docker
417
+ from cave_cli.commands.prettify import prettify
418
+
419
+ check_docker()
420
+ prettify(args)
421
+
422
+ elif args.command == "list":
423
+ from cave_cli.utils.docker import check_docker
424
+ from cave_cli.commands.list_cmd import list_cmd
425
+
426
+ check_docker()
427
+ list_cmd(args)
428
+
429
+ elif args.command == "kill":
430
+ from cave_cli.utils.docker import check_docker
431
+ from cave_cli.commands.kill import kill
432
+
433
+ check_docker()
434
+ kill(args)
435
+
436
+ elif args.command == "purge":
437
+ from cave_cli.utils.docker import check_docker
438
+ from cave_cli.commands.purge import purge
439
+
440
+ check_docker()
441
+ purge(args)
442
+
443
+ elif args.command == "list-versions":
444
+ from cave_cli.commands.list_versions import list_versions
445
+
446
+ list_versions(args)
447
+
448
+ elif args.command == "update":
449
+ from cave_cli.commands.update import update
450
+
451
+ update(args)
452
+
453
+ elif args.command == "uninstall":
454
+ from cave_cli.commands.uninstall import uninstall
455
+
456
+ uninstall(args)
457
+
458
+ elif args.command == "version":
459
+ from cave_cli.commands.version import version as version_cmd
460
+
461
+ version_cmd(args)
462
+
463
+ else:
464
+ parser.print_help()
465
+ sys.exit(1)
466
+
467
+
468
+ if __name__ == "__main__":
469
+ main()
File without changes
@@ -0,0 +1,168 @@
1
+ import argparse
2
+ import os
3
+ import shutil
4
+ import stat
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from cave_cli.commands.reset import reset
9
+ from cave_cli.commands.run import run_cave
10
+ from cave_cli.utils.constants import HTTPS_URL
11
+ from cave_cli.utils.docker import build_image, check_docker, generate_secret_key
12
+ from cave_cli.utils.env import create_env_interactive
13
+ from cave_cli.utils.git import add, branch_rename, clone, commit, init
14
+ from cave_cli.utils.logger import logger
15
+ from cave_cli.utils.validate import validate_app_name
16
+
17
+
18
+ def create(args: argparse.Namespace) -> None:
19
+ """
20
+ Usage:
21
+
22
+ - Creates a new CAVE app from the template repository
23
+ """
24
+ app_name = args.name
25
+ error = validate_app_name(app_name)
26
+ if error:
27
+ logger.error(error)
28
+ sys.exit(1)
29
+
30
+ if os.path.isdir(app_name):
31
+ logger.error(
32
+ f"Cannot create app '{app_name}': "
33
+ "This folder already exists in the current directory"
34
+ )
35
+ sys.exit(1)
36
+
37
+ clone_url = getattr(args, "url", None) or HTTPS_URL
38
+ version = getattr(args, "version", None)
39
+
40
+ logger.header("App Creation:")
41
+ logger.info("Downloading the app template...")
42
+
43
+ success = clone(clone_url, app_name, branch=version)
44
+ if not success or not os.path.isdir(app_name):
45
+ logger.error("Clone failed. Ensure you used a valid version.")
46
+ logger.error(
47
+ f"The version must be a tag (or branch) listed at {clone_url}."
48
+ )
49
+ sys.exit(1)
50
+
51
+ logger.info("Done")
52
+
53
+ app_dir = os.path.abspath(app_name)
54
+ remove_licence_info(app_dir)
55
+
56
+ Path(os.path.join(app_dir, ".env")).touch()
57
+
58
+ example_env = os.path.join(app_dir, "example.env")
59
+ env_path = os.path.join(app_dir, ".env")
60
+
61
+ build_image(app_name, app_dir)
62
+ secret_key = generate_secret_key(app_name)
63
+
64
+ create_env_interactive(
65
+ app_name=app_name,
66
+ env_path=env_path,
67
+ template_path=example_env,
68
+ docker_secret_key=secret_key,
69
+ )
70
+
71
+ reset_args = argparse.Namespace(
72
+ yes=True,
73
+ verbose=getattr(args, "verbose", False),
74
+ loglevel=getattr(args, "loglevel", "INFO"),
75
+ )
76
+ reset(reset_args, app_dir=app_dir, app_name=app_name)
77
+
78
+ logger.header("Version Control:")
79
+ logger.info("Configuring git repository...")
80
+ git_dir = os.path.join(app_dir, ".git")
81
+ if os.path.isdir(git_dir):
82
+ rmtree_force(git_dir)
83
+ init(app_dir)
84
+
85
+ gitignore_path = os.path.join(app_dir, ".gitignore")
86
+ if os.path.isfile(gitignore_path):
87
+ content = Path(gitignore_path).read_text()
88
+ content = content.replace(".env", "")
89
+ Path(gitignore_path).write_text(content)
90
+
91
+ if os.path.isfile(gitignore_path):
92
+ content = Path(gitignore_path).read_text()
93
+ if "media" not in content:
94
+ content += "\n# Media\nmedia\n"
95
+ Path(gitignore_path).write_text(content)
96
+
97
+ add(app_dir)
98
+ commit(app_dir, "Initialize CAVE App")
99
+ branch_rename(app_dir, "main")
100
+ logger.info("Done.")
101
+
102
+ logger.info("Generating LLM Docs...")
103
+ docs_args = argparse.Namespace(
104
+ entrypoint="./utils/generate_docs.sh",
105
+ interactive=False,
106
+ it=False,
107
+ docker_args="",
108
+ ip_port=None,
109
+ yes=True,
110
+ verbose=getattr(args, "verbose", False),
111
+ loglevel=getattr(args, "loglevel", "INFO"),
112
+ )
113
+ run_cave(app_dir, app_name, docs_args)
114
+ logger.info("Done.")
115
+
116
+ logger.header("App Creation Status:")
117
+ logger.info(f"App '{app_name}' created successfully!")
118
+ logger.info(
119
+ f"Created variables and additional configuration options "
120
+ f"are available in {app_name}/.env"
121
+ )
122
+
123
+
124
+ def force_remove(func, path, exc):
125
+ os.chmod(path, stat.S_IWRITE)
126
+ func(path)
127
+
128
+
129
+ def rmtree_force(path: str) -> None:
130
+ if sys.version_info >= (3, 12):
131
+ shutil.rmtree(path, onexc=force_remove)
132
+ else:
133
+ shutil.rmtree(path, onerror=force_remove)
134
+
135
+
136
+ def remove_licence_info(app_dir: str) -> None:
137
+ license_path = os.path.join(app_dir, "LICENSE")
138
+ if os.path.isfile(license_path):
139
+ os.remove(license_path)
140
+
141
+ readme_path = os.path.join(app_dir, "README.md")
142
+ if os.path.isfile(readme_path):
143
+ lines = Path(readme_path).read_text().splitlines()
144
+ for i, line in enumerate(lines):
145
+ if line.strip() == "## License Notice":
146
+ lines = lines[:i]
147
+ break
148
+ Path(readme_path).write_text("\n".join(lines) + "\n")
149
+
150
+ notice_path = os.path.join(app_dir, "NOTICE.md")
151
+ if os.path.isfile(notice_path):
152
+ lines = Path(notice_path).read_text().splitlines()
153
+ for i, line in enumerate(lines):
154
+ if line.startswith("Licensed under"):
155
+ lines = lines[:i]
156
+ break
157
+ Path(notice_path).write_text("\n".join(lines) + "\n")
158
+
159
+ setup_path = os.path.join(app_dir, "cave_api", "setup.py")
160
+ if os.path.isfile(setup_path):
161
+ content = Path(setup_path).read_text()
162
+ import re
163
+
164
+ content = re.sub(r'^\s*license="MIT",\s*\n', "", content, flags=re.M)
165
+ content = re.sub(
166
+ r'^\s*"License.*MIT License",\s*\n', "", content, flags=re.M
167
+ )
168
+ Path(setup_path).write_text(content)
@@ -0,0 +1,23 @@
1
+ import argparse
2
+
3
+ from cave_cli.utils.docker import get_running_apps, remove_containers
4
+ from cave_cli.utils.logger import logger
5
+ from cave_cli.utils.validate import get_app
6
+
7
+
8
+ def kill(args: argparse.Namespace) -> None:
9
+ """
10
+ Usage:
11
+
12
+ - Stops Docker containers for CAVE apps
13
+ """
14
+ kill_all = getattr(args, "all", False)
15
+
16
+ if kill_all:
17
+ for app_name in get_running_apps():
18
+ remove_containers(app_name)
19
+ logger.info(f"Killed: {app_name}")
20
+ else:
21
+ _, app_name = get_app()
22
+ remove_containers(app_name)
23
+ logger.info(f"Killed: {app_name}")
@@ -0,0 +1,37 @@
1
+ import argparse
2
+
3
+ from cave_cli.utils.docker import (
4
+ get_container_env,
5
+ get_container_host_port,
6
+ get_all_containers,
7
+ get_running_apps,
8
+ )
9
+ from cave_cli.utils.logger import logger
10
+
11
+
12
+ def list_cmd(args: argparse.Namespace) -> None:
13
+ """
14
+ Usage:
15
+
16
+ - Lists running CAVE apps or all CAVE app containers
17
+ """
18
+ show_all = getattr(args, "all", False)
19
+
20
+ if show_all:
21
+ logger.header("CAVE App Containers (All):")
22
+ for suffix in ("_django", "_db_host", "_redis_host", "_nginx_host"):
23
+ for name in get_all_containers(suffix):
24
+ logger.info(name)
25
+ else:
26
+ logger.header("CAVE Apps (Running):")
27
+ apps = get_running_apps()
28
+ for app_name in apps:
29
+ nginx_containers = get_all_containers("_nginx_host")
30
+ nginx_name = f"{app_name}_nginx_host"
31
+ if nginx_name in nginx_containers:
32
+ ip = get_container_env(nginx_name, "CAVE_IP")
33
+ port = get_container_env(nginx_name, "CAVE_PORT")
34
+ logger.info(f"{app_name} (https://{ip}:{port})")
35
+ else:
36
+ port = get_container_host_port(f"{app_name}_django")
37
+ logger.info(f"{app_name} (http://localhost:{port})")