deepctl-shared-utils 0.1.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.
- deepctl_shared_utils/__init__.py +13 -0
- deepctl_shared_utils/models.py +19 -0
- deepctl_shared_utils/py.typed +1 -0
- deepctl_shared_utils/validation.py +543 -0
- deepctl_shared_utils-0.1.0.dist-info/METADATA +23 -0
- deepctl_shared_utils-0.1.0.dist-info/RECORD +8 -0
- deepctl_shared_utils-0.1.0.dist-info/WHEEL +5 -0
- deepctl_shared_utils-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Shared utilities for deepctl."""
|
|
2
|
+
|
|
3
|
+
from .models import FileInfo
|
|
4
|
+
from .validation import validate_audio_file, validate_date_format, validate_url
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"FileInfo",
|
|
8
|
+
"validate_audio_file",
|
|
9
|
+
"validate_date_format",
|
|
10
|
+
"validate_url",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Models for shared utilities."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileInfo(BaseModel):
|
|
7
|
+
"""File information for validation results."""
|
|
8
|
+
|
|
9
|
+
path: str
|
|
10
|
+
name: str | None = None
|
|
11
|
+
extension: str | None = None
|
|
12
|
+
size_bytes: int | None = None
|
|
13
|
+
size_mb: float | None = None
|
|
14
|
+
modified: float | None = None
|
|
15
|
+
readable: bool | None = None
|
|
16
|
+
exists: bool = False
|
|
17
|
+
is_file: bool | None = None
|
|
18
|
+
is_audio: bool | None = None
|
|
19
|
+
error: str | None = None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file indicates that this package supports type hints
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
"""Input validation utilities for deepctl."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from .models import FileInfo
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
# Supported audio file extensions
|
|
16
|
+
SUPPORTED_AUDIO_EXTENSIONS = {
|
|
17
|
+
".mp3",
|
|
18
|
+
".wav",
|
|
19
|
+
".flac",
|
|
20
|
+
".m4a",
|
|
21
|
+
".aac",
|
|
22
|
+
".ogg",
|
|
23
|
+
".wma",
|
|
24
|
+
".opus",
|
|
25
|
+
".amr",
|
|
26
|
+
".3gp",
|
|
27
|
+
".mp4",
|
|
28
|
+
".mov",
|
|
29
|
+
".avi",
|
|
30
|
+
".mkv",
|
|
31
|
+
".webm",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Common audio MIME types
|
|
35
|
+
AUDIO_MIME_TYPES = {
|
|
36
|
+
"audio/mpeg",
|
|
37
|
+
"audio/wav",
|
|
38
|
+
"audio/flac",
|
|
39
|
+
"audio/m4a",
|
|
40
|
+
"audio/aac",
|
|
41
|
+
"audio/ogg",
|
|
42
|
+
"audio/opus",
|
|
43
|
+
"audio/amr",
|
|
44
|
+
"audio/3gp",
|
|
45
|
+
"audio/mp4",
|
|
46
|
+
"video/mp4",
|
|
47
|
+
"video/quicktime",
|
|
48
|
+
"video/x-msvideo",
|
|
49
|
+
"video/x-matroska",
|
|
50
|
+
"video/webm",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def validate_audio_file(file_path: str | Path) -> bool:
|
|
55
|
+
"""Validate that a file exists and appears to be an audio file.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
file_path: Path to the file
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if file is valid audio file, False otherwise
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
path = Path(file_path)
|
|
65
|
+
|
|
66
|
+
# Check if file exists
|
|
67
|
+
if not path.exists():
|
|
68
|
+
console.print(f"[red]Error:[/red] File not found: {file_path}")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
# Check if it's a file (not directory)
|
|
72
|
+
if not path.is_file():
|
|
73
|
+
console.print(f"[red]Error:[/red] Path is not a file: {file_path}")
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
# Check file extension
|
|
77
|
+
extension = path.suffix.lower()
|
|
78
|
+
if extension not in SUPPORTED_AUDIO_EXTENSIONS:
|
|
79
|
+
console.print(
|
|
80
|
+
f"[yellow]Warning:[/yellow] File extension '{extension}' "
|
|
81
|
+
f"is not in supported list"
|
|
82
|
+
)
|
|
83
|
+
console.print(
|
|
84
|
+
f"[dim]Supported extensions: "
|
|
85
|
+
f"{', '.join(sorted(SUPPORTED_AUDIO_EXTENSIONS))}[/dim]"
|
|
86
|
+
)
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# Check if file is readable
|
|
90
|
+
if not os.access(path, os.R_OK):
|
|
91
|
+
console.print(
|
|
92
|
+
f"[red]Error:[/red] File is not readable: {file_path}"
|
|
93
|
+
)
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
# Check file size (warn if very large)
|
|
97
|
+
size_mb = path.stat().st_size / (1024 * 1024)
|
|
98
|
+
if size_mb > 500: # 500 MB
|
|
99
|
+
console.print(
|
|
100
|
+
f"[yellow]Warning:[/yellow] Large file detected "
|
|
101
|
+
f"({size_mb:.1f} MB)"
|
|
102
|
+
)
|
|
103
|
+
console.print("[dim]Large files may take longer to process[/dim]")
|
|
104
|
+
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
console.print(f"[red]Error validating file:[/red] {e}")
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def validate_url(url: str, check_accessibility: bool = True) -> bool:
|
|
113
|
+
"""Validate URL format and optionally check accessibility.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
url: URL to validate
|
|
117
|
+
check_accessibility: Whether to check if URL is accessible
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
True if URL is valid, False otherwise
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
# Parse URL
|
|
124
|
+
parsed = urlparse(url)
|
|
125
|
+
|
|
126
|
+
# Check basic URL structure
|
|
127
|
+
if not parsed.scheme or not parsed.netloc:
|
|
128
|
+
console.print(f"[red]Error:[/red] Invalid URL format: {url}")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
# Check scheme
|
|
132
|
+
if parsed.scheme not in ("http", "https"):
|
|
133
|
+
console.print(
|
|
134
|
+
f"[red]Error:[/red] Only HTTP/HTTPS URLs are supported: {url}"
|
|
135
|
+
)
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
# Check if URL looks like an audio file
|
|
139
|
+
path = parsed.path.lower()
|
|
140
|
+
if path:
|
|
141
|
+
extension = Path(path).suffix
|
|
142
|
+
if extension and extension not in SUPPORTED_AUDIO_EXTENSIONS:
|
|
143
|
+
console.print(
|
|
144
|
+
f"[yellow]Warning:[/yellow] URL doesn't appear to be an "
|
|
145
|
+
f"audio file: {url}"
|
|
146
|
+
)
|
|
147
|
+
console.print(
|
|
148
|
+
f"[dim]Expected extensions: "
|
|
149
|
+
f"{', '.join(sorted(SUPPORTED_AUDIO_EXTENSIONS))}[/dim]"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Check accessibility if requested
|
|
153
|
+
if check_accessibility:
|
|
154
|
+
return _check_url_accessibility(url)
|
|
155
|
+
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
console.print(f"[red]Error validating URL:[/red] {e}")
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _check_url_accessibility(url: str) -> bool:
|
|
164
|
+
"""Check if URL is accessible.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
url: URL to check
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
True if accessible, False otherwise
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
console.print("[dim]Checking URL accessibility...[/dim]")
|
|
174
|
+
|
|
175
|
+
with httpx.Client(timeout=10.0) as client:
|
|
176
|
+
# Use HEAD request to check without downloading
|
|
177
|
+
response = client.head(url, follow_redirects=True)
|
|
178
|
+
|
|
179
|
+
if response.status_code == 200:
|
|
180
|
+
# Check content type if available
|
|
181
|
+
content_type = response.headers.get("content-type", "").lower()
|
|
182
|
+
if content_type and not any(
|
|
183
|
+
mime in content_type for mime in AUDIO_MIME_TYPES
|
|
184
|
+
):
|
|
185
|
+
console.print(
|
|
186
|
+
f"[yellow]Warning:[/yellow] Content type may not be "
|
|
187
|
+
f"audio: {content_type}"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Check content length if available
|
|
191
|
+
content_length = response.headers.get("content-length")
|
|
192
|
+
if content_length:
|
|
193
|
+
size_mb = int(content_length) / (1024 * 1024)
|
|
194
|
+
if size_mb > 500: # 500 MB
|
|
195
|
+
console.print(
|
|
196
|
+
f"[yellow]Warning:[/yellow] Large file detected "
|
|
197
|
+
f"({size_mb:.1f} MB)"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return True
|
|
201
|
+
else:
|
|
202
|
+
console.print(
|
|
203
|
+
f"[red]Error:[/red] URL not accessible "
|
|
204
|
+
f"(HTTP {response.status_code}): {url}"
|
|
205
|
+
)
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
except httpx.TimeoutException:
|
|
209
|
+
console.print(f"[red]Error:[/red] URL request timed out: {url}")
|
|
210
|
+
return False
|
|
211
|
+
except httpx.RequestError as e:
|
|
212
|
+
console.print(f"[red]Error:[/red] Failed to access URL: {e}")
|
|
213
|
+
return False
|
|
214
|
+
except Exception as e:
|
|
215
|
+
console.print(f"[red]Error checking URL accessibility:[/red] {e}")
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def validate_api_key(api_key: str) -> bool:
|
|
220
|
+
"""Validate API key format.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
api_key: API key to validate
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if format is valid, False otherwise
|
|
227
|
+
"""
|
|
228
|
+
if not api_key:
|
|
229
|
+
console.print("[red]Error:[/red] API key cannot be empty")
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
# Check basic format
|
|
233
|
+
if not api_key.startswith(("sk-", "pk-")):
|
|
234
|
+
console.print(
|
|
235
|
+
"[yellow]Warning:[/yellow] API key doesn't match expected "
|
|
236
|
+
"format (should start with 'sk-' or 'pk-')"
|
|
237
|
+
)
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
# Check length (Deepgram API keys are typically longer)
|
|
241
|
+
if len(api_key) < 20:
|
|
242
|
+
console.print("[yellow]Warning:[/yellow] API key seems too short")
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
# Check for invalid characters
|
|
246
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", api_key):
|
|
247
|
+
console.print("[red]Error:[/red] API key contains invalid characters")
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
return True
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def validate_project_id(project_id: str) -> bool:
|
|
254
|
+
"""Validate project ID format.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
project_id: Project ID to validate
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if format is valid, False otherwise
|
|
261
|
+
"""
|
|
262
|
+
if not project_id:
|
|
263
|
+
console.print("[red]Error:[/red] Project ID cannot be empty")
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
# Check if it's a valid UUID format
|
|
267
|
+
uuid_pattern = (
|
|
268
|
+
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
269
|
+
)
|
|
270
|
+
if not re.match(uuid_pattern, project_id, re.IGNORECASE):
|
|
271
|
+
console.print(
|
|
272
|
+
"[yellow]Warning:[/yellow] Project ID doesn't match expected "
|
|
273
|
+
"UUID format"
|
|
274
|
+
)
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
return True
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def validate_language_code(language: str) -> bool:
|
|
281
|
+
"""Validate language code format.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
language: Language code to validate
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
True if format is valid, False otherwise
|
|
288
|
+
"""
|
|
289
|
+
if not language:
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
# Common language codes supported by Deepgram
|
|
293
|
+
supported_languages = {
|
|
294
|
+
"en-US",
|
|
295
|
+
"en-GB",
|
|
296
|
+
"en-AU",
|
|
297
|
+
"en-NZ",
|
|
298
|
+
"en-IN",
|
|
299
|
+
"es-ES",
|
|
300
|
+
"es-US",
|
|
301
|
+
"es-419",
|
|
302
|
+
"fr-FR",
|
|
303
|
+
"fr-CA",
|
|
304
|
+
"de-DE",
|
|
305
|
+
"de-CH",
|
|
306
|
+
"it-IT",
|
|
307
|
+
"pt-BR",
|
|
308
|
+
"pt-PT",
|
|
309
|
+
"ru-RU",
|
|
310
|
+
"tr-TR",
|
|
311
|
+
"hi-IN",
|
|
312
|
+
"ja-JP",
|
|
313
|
+
"ko-KR",
|
|
314
|
+
"zh-CN",
|
|
315
|
+
"zh-TW",
|
|
316
|
+
"nl-NL",
|
|
317
|
+
"pl-PL",
|
|
318
|
+
"sv-SE",
|
|
319
|
+
"no-NO",
|
|
320
|
+
"da-DK",
|
|
321
|
+
"fi-FI",
|
|
322
|
+
"uk-UA",
|
|
323
|
+
"el-GR",
|
|
324
|
+
"cs-CZ",
|
|
325
|
+
"hu-HU",
|
|
326
|
+
"ro-RO",
|
|
327
|
+
"sk-SK",
|
|
328
|
+
"sl-SI",
|
|
329
|
+
"bg-BG",
|
|
330
|
+
"hr-HR",
|
|
331
|
+
"et-EE",
|
|
332
|
+
"lv-LV",
|
|
333
|
+
"lt-LT",
|
|
334
|
+
"mt-MT",
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if language not in supported_languages:
|
|
338
|
+
console.print(
|
|
339
|
+
f"[yellow]Warning:[/yellow] Language '{language}' may not be "
|
|
340
|
+
f"supported"
|
|
341
|
+
)
|
|
342
|
+
console.print(
|
|
343
|
+
"[dim]Common supported languages: en-US, es-ES, fr-FR, de-DE, "
|
|
344
|
+
"it-IT, pt-BR, ja-JP, ko-KR, zh-CN[/dim]"
|
|
345
|
+
)
|
|
346
|
+
return False
|
|
347
|
+
|
|
348
|
+
return True
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def validate_model_name(model: str) -> bool:
|
|
352
|
+
"""Validate model name.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
model: Model name to validate
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
True if format is valid, False otherwise
|
|
359
|
+
"""
|
|
360
|
+
if not model:
|
|
361
|
+
return False
|
|
362
|
+
|
|
363
|
+
# Common Deepgram models
|
|
364
|
+
supported_models = {
|
|
365
|
+
"nova-2",
|
|
366
|
+
"nova",
|
|
367
|
+
"enhanced",
|
|
368
|
+
"base",
|
|
369
|
+
"meeting",
|
|
370
|
+
"phonecall",
|
|
371
|
+
"voicemail",
|
|
372
|
+
"finance",
|
|
373
|
+
"conversationalai",
|
|
374
|
+
"video",
|
|
375
|
+
"nova-2-general",
|
|
376
|
+
"nova-2-meeting",
|
|
377
|
+
"nova-2-phonecall",
|
|
378
|
+
"nova-2-voicemail",
|
|
379
|
+
"nova-2-finance",
|
|
380
|
+
"nova-2-conversationalai",
|
|
381
|
+
"nova-2-video",
|
|
382
|
+
"whisper-tiny",
|
|
383
|
+
"whisper-base",
|
|
384
|
+
"whisper-small",
|
|
385
|
+
"whisper-medium",
|
|
386
|
+
"whisper-large",
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if model not in supported_models:
|
|
390
|
+
console.print(
|
|
391
|
+
f"[yellow]Warning:[/yellow] Model '{model}' may not be supported"
|
|
392
|
+
)
|
|
393
|
+
console.print(
|
|
394
|
+
"[dim]Common supported models: nova-2, nova, enhanced, base, "
|
|
395
|
+
"meeting, phonecall[/dim]"
|
|
396
|
+
)
|
|
397
|
+
return False
|
|
398
|
+
|
|
399
|
+
return True
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def validate_date_format(date_str: str) -> bool:
|
|
403
|
+
"""Validate date format (ISO 8601).
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
date_str: Date string to validate
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
True if format is valid, False otherwise
|
|
410
|
+
"""
|
|
411
|
+
if not date_str:
|
|
412
|
+
return False
|
|
413
|
+
|
|
414
|
+
# ISO 8601 date format patterns
|
|
415
|
+
patterns = [
|
|
416
|
+
r"^\d{4}-\d{2}-\d{2}$", # YYYY-MM-DD
|
|
417
|
+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$", # YYYY-MM-DDTHH:MM:SS
|
|
418
|
+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", # YYYY-MM-DDTHH:MM:SSZ
|
|
419
|
+
# YYYY-MM-DDTHH:MM:SS+TZ
|
|
420
|
+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$",
|
|
421
|
+
]
|
|
422
|
+
|
|
423
|
+
for pattern in patterns:
|
|
424
|
+
if re.match(pattern, date_str):
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
console.print(f"[red]Error:[/red] Invalid date format: {date_str}")
|
|
428
|
+
console.print(
|
|
429
|
+
"[dim]Expected formats: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or "
|
|
430
|
+
"YYYY-MM-DDTHH:MM:SSZ[/dim]"
|
|
431
|
+
)
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def validate_file_permissions(file_path: str | Path) -> bool:
|
|
436
|
+
"""Validate file permissions.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
file_path: Path to check
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
True if permissions are valid, False otherwise
|
|
443
|
+
"""
|
|
444
|
+
try:
|
|
445
|
+
path = Path(file_path)
|
|
446
|
+
|
|
447
|
+
# Check read permission
|
|
448
|
+
if not os.access(path, os.R_OK):
|
|
449
|
+
console.print(
|
|
450
|
+
f"[red]Error:[/red] No read permission for file: {file_path}"
|
|
451
|
+
)
|
|
452
|
+
return False
|
|
453
|
+
|
|
454
|
+
# Check if file is not empty
|
|
455
|
+
if path.stat().st_size == 0:
|
|
456
|
+
console.print(f"[red]Error:[/red] File is empty: {file_path}")
|
|
457
|
+
return False
|
|
458
|
+
|
|
459
|
+
return True
|
|
460
|
+
|
|
461
|
+
except Exception as e:
|
|
462
|
+
console.print(f"[red]Error checking file permissions:[/red] {e}")
|
|
463
|
+
return False
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def sanitize_filename(filename: str) -> str:
|
|
467
|
+
"""Sanitize filename for cross-platform compatibility.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
filename: Original filename
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
Sanitized filename
|
|
474
|
+
"""
|
|
475
|
+
# Remove invalid characters
|
|
476
|
+
sanitized = re.sub(r'[<>:"/\\|?*]', "_", filename)
|
|
477
|
+
|
|
478
|
+
# Remove leading/trailing whitespace and dots
|
|
479
|
+
sanitized = sanitized.strip(" .")
|
|
480
|
+
|
|
481
|
+
# Ensure filename is not empty
|
|
482
|
+
if not sanitized:
|
|
483
|
+
sanitized = "unnamed_file"
|
|
484
|
+
|
|
485
|
+
# Truncate if too long
|
|
486
|
+
if len(sanitized) > 255:
|
|
487
|
+
sanitized = sanitized[:255]
|
|
488
|
+
|
|
489
|
+
return sanitized
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def validate_output_format(format_type: str) -> bool:
|
|
493
|
+
"""Validate output format.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
format_type: Format to validate
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
True if format is valid, False otherwise
|
|
500
|
+
"""
|
|
501
|
+
supported_formats = {"json", "yaml", "table", "csv"}
|
|
502
|
+
|
|
503
|
+
if format_type not in supported_formats:
|
|
504
|
+
console.print(
|
|
505
|
+
f"[red]Error:[/red] Unsupported output format: {format_type}"
|
|
506
|
+
)
|
|
507
|
+
console.print(
|
|
508
|
+
f"[dim]Supported formats: "
|
|
509
|
+
f"{', '.join(sorted(supported_formats))}[/dim]"
|
|
510
|
+
)
|
|
511
|
+
return False
|
|
512
|
+
|
|
513
|
+
return True
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def get_file_info(file_path: str | Path) -> FileInfo:
|
|
517
|
+
"""Get detailed information about a file.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
file_path: Path to the file
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
FileInfo model with file information
|
|
524
|
+
"""
|
|
525
|
+
try:
|
|
526
|
+
path = Path(file_path)
|
|
527
|
+
stat = path.stat()
|
|
528
|
+
|
|
529
|
+
return FileInfo(
|
|
530
|
+
path=str(path.absolute()),
|
|
531
|
+
name=path.name,
|
|
532
|
+
extension=path.suffix.lower(),
|
|
533
|
+
size_bytes=stat.st_size,
|
|
534
|
+
size_mb=stat.st_size / (1024 * 1024),
|
|
535
|
+
modified=stat.st_mtime,
|
|
536
|
+
readable=os.access(path, os.R_OK),
|
|
537
|
+
exists=path.exists(),
|
|
538
|
+
is_file=path.is_file(),
|
|
539
|
+
is_audio=path.suffix.lower() in SUPPORTED_AUDIO_EXTENSIONS,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
except Exception as e:
|
|
543
|
+
return FileInfo(path=str(file_path), error=str(e), exists=False)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-shared-utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared utilities for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,utilities,validation,helpers
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: pydantic>=2.0.0
|
|
18
|
+
Requires-Dist: click>=8.0.0
|
|
19
|
+
Requires-Dist: httpx>=0.24.0
|
|
20
|
+
|
|
21
|
+
# deepctl-shared-utils
|
|
22
|
+
|
|
23
|
+
Shared utilities for deepctl. This package is an internal component of deepctl and is not intended for standalone use.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
deepctl_shared_utils/__init__.py,sha256=Q54OjCHV7ndxwJ3CBUBt257sgQL7AYogrzA7ZnkGaEs,275
|
|
2
|
+
deepctl_shared_utils/models.py,sha256=Sg9_Zh-xthUdxbESBy40bpM4nVl4zsyJOGF1sYaToXk,476
|
|
3
|
+
deepctl_shared_utils/py.typed,sha256=6f0ps5pRLTsVV1khUGtuRMcPzRHmQ_f3rM3_42XKqVU,60
|
|
4
|
+
deepctl_shared_utils/validation.py,sha256=uUGJpA6W4A1Eo8HvudpD7r0r8rTLlbxwD5yzMFGx2i8,14742
|
|
5
|
+
deepctl_shared_utils-0.1.0.dist-info/METADATA,sha256=U9tfbHO48u4yE-XhjrpCtqQ0uD6e-FgvsVFtVquaZC4,853
|
|
6
|
+
deepctl_shared_utils-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
deepctl_shared_utils-0.1.0.dist-info/top_level.txt,sha256=6lKjgFbNbA5Lniqel9HNhViYXKPoICnh3Q3_54Qm9Zk,21
|
|
8
|
+
deepctl_shared_utils-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepctl_shared_utils
|