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.
@@ -0,0 +1,64 @@
1
+ import argparse
2
+ import re
3
+
4
+ from cave_cli import __version__
5
+ from cave_cli.utils.constants import CHAR_LINE
6
+ from cave_cli.utils.env import parse_env
7
+ from cave_cli.utils.logger import logger
8
+ from cave_cli.utils.validate import find_app_dir, get_app
9
+
10
+
11
+ def version(args: argparse.Namespace) -> None:
12
+ """
13
+ Usage:
14
+
15
+ - Prints the CLI version and app-specific versions if inside a CAVE app directory
16
+ """
17
+ logger.info(f"CAVE_CLI={__version__}")
18
+ print_app_versions()
19
+
20
+
21
+ def print_app_versions() -> None:
22
+ try:
23
+ app_dir, app_name = get_app()
24
+ except SystemExit:
25
+ return
26
+
27
+ from pathlib import Path
28
+
29
+ logger.header(f"{app_name} versions:")
30
+
31
+ version_file = Path(app_dir) / "VERSION"
32
+ cave_app_version = (
33
+ version_file.read_text().strip()
34
+ if version_file.is_file()
35
+ else "Unknown"
36
+ )
37
+
38
+ req_file = Path(app_dir) / "requirements.txt"
39
+ cave_utils_version = "Unknown"
40
+ if req_file.is_file():
41
+ for line in req_file.read_text().splitlines():
42
+ if "cave_utils" in line:
43
+ parts = line.split("==")
44
+ if len(parts) == 2:
45
+ cave_utils_version = f"v{parts[1].strip()}"
46
+ break
47
+
48
+ env_file = Path(app_dir) / ".env"
49
+ cave_static_version = "Unknown"
50
+ if env_file.is_file():
51
+ env_vars = parse_env(str(env_file))
52
+ static_url = env_vars.get("STATIC_APP_URL", "")
53
+ if "localhost" in static_url:
54
+ cave_static_version = "Local"
55
+ else:
56
+ static_path = env_vars.get("STATIC_APP_URL_PATH", "")
57
+ if static_path:
58
+ match = re.search(r"[0-9]+\.[0-9]+\.[0-9]+", static_path)
59
+ if match:
60
+ cave_static_version = f"v{match.group(0)}"
61
+
62
+ logger.info(f"CAVE_APP={cave_app_version}")
63
+ logger.info(f"CAVE_STATIC={cave_static_version}")
64
+ logger.info(f"CAVE_UTILS={cave_utils_version}")
File without changes
@@ -0,0 +1,235 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+
7
+ def get_cache_dir() -> str:
8
+ """
9
+ Usage:
10
+
11
+ - Returns the platform-appropriate cache directory for cave_cli
12
+
13
+ Returns:
14
+
15
+ - ``path``:
16
+ - Type: str
17
+ - What: Absolute path to the cache directory (created if missing)
18
+
19
+ Notes:
20
+
21
+ - Windows: %LOCALAPPDATA%/cave_cli
22
+ - macOS: ~/Library/Caches/cave_cli
23
+ - Linux: $XDG_CACHE_HOME/cave_cli (fallback ~/.cache/cave_cli)
24
+ """
25
+ if os.name == "nt":
26
+ base_dir = os.environ.get("LOCALAPPDATA", os.path.expanduser("~"))
27
+ elif sys.platform == "darwin":
28
+ base_dir = os.path.expanduser("~/Library/Caches")
29
+ else:
30
+ base_dir = os.environ.get(
31
+ "XDG_CACHE_HOME", os.path.expanduser("~/.cache")
32
+ )
33
+
34
+ path = os.path.join(base_dir, "cave_cli")
35
+ os.makedirs(path, exist_ok=True)
36
+ return path
37
+
38
+
39
+ def cache_path(name: str) -> str:
40
+ return os.path.join(get_cache_dir(), f"{name}.json")
41
+
42
+
43
+ def load_entries(name: str) -> list[dict[str, str]]:
44
+ """
45
+ Usage:
46
+
47
+ - Loads cached entries from a JSON file
48
+
49
+ Requires:
50
+
51
+ - ``name``:
52
+ - Type: str
53
+ - What: The cache name (e.g. "mapbox_tokens", "admin_emails")
54
+
55
+ Returns:
56
+
57
+ - ``entries``:
58
+ - Type: list[dict[str, str]]
59
+ - What: A list of dicts with "label" and "value" keys
60
+ """
61
+ path = cache_path(name)
62
+ if not os.path.isfile(path):
63
+ return []
64
+ try:
65
+ data = json.loads(Path(path).read_text())
66
+ if isinstance(data, list):
67
+ return [
68
+ e
69
+ for e in data
70
+ if isinstance(e, dict) and "label" in e and "value" in e
71
+ ]
72
+ except (json.JSONDecodeError, OSError):
73
+ pass
74
+ return []
75
+
76
+
77
+ def save_entry(name: str, label: str, value: str) -> None:
78
+ """
79
+ Usage:
80
+
81
+ - Appends a new labeled entry to a cache file, deduplicating by value
82
+
83
+ Requires:
84
+
85
+ - ``name``:
86
+ - Type: str
87
+ - What: The cache name
88
+
89
+ - ``label``:
90
+ - Type: str
91
+ - What: A user-provided label for the entry
92
+
93
+ - ``value``:
94
+ - Type: str
95
+ - What: The value to cache
96
+ """
97
+ entries = load_entries(name)
98
+ entries = [e for e in entries if e["value"] != value]
99
+ entries.append({"label": label, "value": value})
100
+ Path(cache_path(name)).write_text(
101
+ json.dumps(entries, indent=2) + "\n"
102
+ )
103
+
104
+
105
+ def _mask_value(value: str) -> str:
106
+ if len(value) <= 4:
107
+ return "****"
108
+ return f"****{value[-4:]}"
109
+
110
+
111
+ def prompt_cached_entry(
112
+ name: str,
113
+ prompt_new: str,
114
+ prompt_label: str = "Label for this entry: ",
115
+ mask: bool = False,
116
+ default: str | None = None,
117
+ ) -> str:
118
+ """
119
+ Usage:
120
+
121
+ - Displays cached entries and lets the user pick one, enter a new
122
+ value, use a default, or skip
123
+
124
+ Requires:
125
+
126
+ - ``name``:
127
+ - Type: str
128
+ - What: The cache name to load/save entries from
129
+
130
+ - ``prompt_new``:
131
+ - Type: str
132
+ - What: Prompt text shown when asking for a new value
133
+
134
+ Optional:
135
+
136
+ - ``prompt_label``:
137
+ - Type: str
138
+ - What: Prompt text shown when asking for a label for the new entry
139
+ - Default: "Label for this entry: "
140
+
141
+ - ``mask``:
142
+ - Type: bool
143
+ - What: Whether to mask values in the display (show last 4 chars)
144
+ - Default: False
145
+
146
+ - ``default``:
147
+ - Type: str | None
148
+ - What: A default value offered as an option
149
+ - Default: None
150
+
151
+ Returns:
152
+
153
+ - ``value``:
154
+ - Type: str
155
+ - What: The selected or entered value, or empty string if skipped
156
+ """
157
+ entries = load_entries(name)
158
+
159
+ if not entries:
160
+ try:
161
+ value = input(
162
+ f"{prompt_new}"
163
+ f"{f' Leave blank for default ({default})' if default else ''}"
164
+ ": "
165
+ )
166
+ except (EOFError, KeyboardInterrupt):
167
+ value = ""
168
+ if not value:
169
+ return default or ""
170
+ try:
171
+ label = input(prompt_label)
172
+ except (EOFError, KeyboardInterrupt):
173
+ label = ""
174
+ if not label:
175
+ label = value if not mask else f"entry-{len(entries) + 1}"
176
+ save_entry(name, label, value)
177
+ return value
178
+
179
+ print(f"\n{prompt_new}")
180
+ print(f"Saved entries:")
181
+ for i, entry in enumerate(entries, 1):
182
+ display = (
183
+ _mask_value(entry["value"]) if mask else entry["value"]
184
+ )
185
+ suffix = " (default)" if i == 1 else ""
186
+ print(f" [{i}] {entry['label']} ({display}){suffix}")
187
+ print(f" [N] Enter a new value")
188
+ if default is not None:
189
+ print(f" [D] Use default ({default})")
190
+ else:
191
+ print(f" [S] Skip")
192
+
193
+ max_idx = len(entries)
194
+ range_str = str(max_idx) if max_idx == 1 else f"1-{max_idx}"
195
+ skip_key = "/D" if default else "/S"
196
+ while True:
197
+ try:
198
+ choice = input(f"Choose [{range_str}/N{skip_key}]: ")
199
+ except (EOFError, KeyboardInterrupt):
200
+ return default or ""
201
+ choice = choice.strip()
202
+
203
+ if not choice:
204
+ return entries[0]["value"]
205
+
206
+ if choice.upper() == "N":
207
+ try:
208
+ value = input(f"{prompt_new}: ")
209
+ except (EOFError, KeyboardInterrupt):
210
+ return default or ""
211
+ if not value:
212
+ return default or ""
213
+ try:
214
+ label = input(prompt_label)
215
+ except (EOFError, KeyboardInterrupt):
216
+ label = ""
217
+ if not label:
218
+ label = (
219
+ value if not mask else f"entry-{len(entries) + 1}"
220
+ )
221
+ save_entry(name, label, value)
222
+ return value
223
+
224
+ if choice.upper() == "S" and default is None:
225
+ return ""
226
+
227
+ if choice.upper() == "D" and default is not None:
228
+ return default
229
+
230
+ if choice.isdigit():
231
+ idx = int(choice)
232
+ if 1 <= idx <= max_idx:
233
+ return entries[idx - 1]["value"]
234
+
235
+ print(f"Invalid choice. Please try again.")
@@ -0,0 +1,43 @@
1
+ import re
2
+ from pathlib import Path
3
+
4
+ HTTPS_URL: str = "https://github.com/MIT-CAVE/cave_app.git"
5
+ MIN_DOCKER_VERSION: str = "23.0.6"
6
+ CHAR_LINE: str = "============================="
7
+
8
+ VALID_NAME_RE: re.Pattern = re.compile(r"^[a-z0-9_-]+$")
9
+ INVALID_NAME_START_RE: re.Pattern = re.compile(r"^[-_]+.*$")
10
+ INVALID_NAME_END_RE: re.Pattern = re.compile(r"^.*[-_]+$")
11
+ INVALID_NAME_HYPHEN_UNDER_RE: re.Pattern = re.compile(r"(-_)+")
12
+ INVALID_NAME_UNDER_HYPHEN_RE: re.Pattern = re.compile(r"(_-)+")
13
+
14
+ IP_PORT_RE: re.Pattern = re.compile(
15
+ r"([0-9]{1,3}\.)+([0-9]{1,3}):[0-9][0-9][0-9][0-9]+"
16
+ )
17
+
18
+ CURRENT_ENV_VARIABLES: tuple[str, ...] = (
19
+ "DATABASE_IMAGE",
20
+ "DATABASE_PASSWORD",
21
+ "DJANGO_ADMIN_EMAIL",
22
+ "DJANGO_ADMIN_FIRST_NAME",
23
+ "DJANGO_ADMIN_LAST_NAME",
24
+ "DJANGO_ADMIN_PASSWORD",
25
+ "DJANGO_ADMIN_USERNAME",
26
+ "SECRET_KEY",
27
+ "STATIC_APP_URL",
28
+ "STATIC_APP_URL_PATH",
29
+ )
30
+
31
+ RETIRED_ENV_VARIABLES: tuple[str, ...] = (
32
+ "DATABASE_HOST",
33
+ "DATABASE_PORT",
34
+ "DATABASE_NAME",
35
+ "DATABASE_USER",
36
+ )
37
+
38
+ VALID_REPOS: tuple[str, ...] = (
39
+ "cave_app",
40
+ "cave_static",
41
+ "cave_cli",
42
+ "cave_utils",
43
+ )