truss 0.10.11rc1__py3-none-any.whl → 0.10.12__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 truss might be problematic. Click here for more details.
- truss/cli/train/common.py +19 -0
- truss/cli/train/core.py +136 -0
- truss/cli/train/metrics_watcher.py +8 -9
- truss/cli/train_commands.py +73 -2
- truss/cli/utils/common.py +14 -1
- truss/remote/baseten/api.py +7 -0
- truss/remote/baseten/custom_types.py +28 -0
- truss/templates/server.Dockerfile.jinja +1 -1
- truss/tests/cli/train/test_cache_view.py +712 -0
- {truss-0.10.11rc1.dist-info → truss-0.10.12.dist-info}/METADATA +2 -2
- {truss-0.10.11rc1.dist-info → truss-0.10.12.dist-info}/RECORD +15 -14
- truss_train/deployment.py +1 -0
- {truss-0.10.11rc1.dist-info → truss-0.10.12.dist-info}/WHEEL +0 -0
- {truss-0.10.11rc1.dist-info → truss-0.10.12.dist-info}/entry_points.txt +0 -0
- {truss-0.10.11rc1.dist-info → truss-0.10.12.dist-info}/licenses/LICENSE +0 -0
truss/cli/train/common.py
CHANGED
|
@@ -5,6 +5,12 @@ import rich_click as click
|
|
|
5
5
|
from truss.cli.utils.output import console
|
|
6
6
|
from truss.remote.baseten import BasetenRemote
|
|
7
7
|
|
|
8
|
+
# Byte size constants
|
|
9
|
+
BYTES_PER_KB = 1000
|
|
10
|
+
BYTES_PER_MB = BYTES_PER_KB * 1000
|
|
11
|
+
BYTES_PER_GB = BYTES_PER_MB * 1000
|
|
12
|
+
BYTES_PER_TB = BYTES_PER_GB * 1000
|
|
13
|
+
|
|
8
14
|
|
|
9
15
|
def get_most_recent_job(
|
|
10
16
|
remote_provider: BasetenRemote, project_id: Optional[str], job_id: Optional[str]
|
|
@@ -27,3 +33,16 @@ def get_most_recent_job(
|
|
|
27
33
|
project_id = cast(str, job["training_project"]["id"])
|
|
28
34
|
job_id = cast(str, job["id"])
|
|
29
35
|
return project_id, job_id
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def format_bytes_to_human_readable(bytes: int) -> str:
|
|
39
|
+
if bytes > BYTES_PER_TB:
|
|
40
|
+
return f"{bytes / BYTES_PER_TB:.2f} TB"
|
|
41
|
+
if bytes > BYTES_PER_GB:
|
|
42
|
+
return f"{bytes / BYTES_PER_GB:.2f} GB"
|
|
43
|
+
elif bytes > BYTES_PER_MB:
|
|
44
|
+
return f"{bytes / BYTES_PER_MB:.2f} MB"
|
|
45
|
+
elif bytes > BYTES_PER_KB:
|
|
46
|
+
return f"{bytes / BYTES_PER_KB:.2f} KB"
|
|
47
|
+
else:
|
|
48
|
+
return f"{bytes} B"
|
truss/cli/train/core.py
CHANGED
|
@@ -20,6 +20,14 @@ from truss.remote.baseten.remote import BasetenRemote
|
|
|
20
20
|
from truss_train import loader
|
|
21
21
|
from truss_train.definitions import DeployCheckpointsConfig
|
|
22
22
|
|
|
23
|
+
SORT_BY_FILEPATH = "filepath"
|
|
24
|
+
SORT_BY_SIZE = "size"
|
|
25
|
+
SORT_BY_MODIFIED = "modified"
|
|
26
|
+
SORT_BY_TYPE = "type"
|
|
27
|
+
SORT_BY_PERMISSIONS = "permissions"
|
|
28
|
+
SORT_ORDER_ASC = "asc"
|
|
29
|
+
SORT_ORDER_DESC = "desc"
|
|
30
|
+
|
|
23
31
|
ACTIVE_JOB_STATUSES = [
|
|
24
32
|
"TRAINING_JOB_RUNNING",
|
|
25
33
|
"TRAINING_JOB_CREATED",
|
|
@@ -401,3 +409,131 @@ def download_checkpoint_artifacts(
|
|
|
401
409
|
|
|
402
410
|
def status_page_url(remote_url: str, training_job_id: str) -> str:
|
|
403
411
|
return f"{remote_url}/training/jobs/{training_job_id}"
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def fetch_project_by_name_or_id(
|
|
415
|
+
remote_provider: BasetenRemote, project_identifier: str
|
|
416
|
+
) -> dict:
|
|
417
|
+
"""Fetch a training project by name or ID.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
remote_provider: The remote provider instance
|
|
421
|
+
project_identifier: Either a project ID or project name
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
The project object as a dictionary
|
|
425
|
+
|
|
426
|
+
Raises:
|
|
427
|
+
click.ClickException: If the project is not found
|
|
428
|
+
"""
|
|
429
|
+
try:
|
|
430
|
+
projects = remote_provider.api.list_training_projects()
|
|
431
|
+
projects_by_name = {project.get("name"): project for project in projects}
|
|
432
|
+
projects_by_id = {project.get("id"): project for project in projects}
|
|
433
|
+
if project_identifier in projects_by_id:
|
|
434
|
+
return projects_by_id[project_identifier]
|
|
435
|
+
if project_identifier in projects_by_name:
|
|
436
|
+
return projects_by_name[project_identifier]
|
|
437
|
+
valid_project_ids_and_names = ", ".join(
|
|
438
|
+
[f"{project.get('id')} ({project.get('name')})" for project in projects]
|
|
439
|
+
)
|
|
440
|
+
raise click.ClickException(
|
|
441
|
+
f"Project '{project_identifier}' not found. Valid project IDs and names: {valid_project_ids_and_names}"
|
|
442
|
+
)
|
|
443
|
+
except click.ClickException:
|
|
444
|
+
raise
|
|
445
|
+
except Exception as e:
|
|
446
|
+
raise click.ClickException(f"Error fetching project: {str(e)}")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def view_cache_summary(
|
|
450
|
+
remote_provider: BasetenRemote,
|
|
451
|
+
project_id: str,
|
|
452
|
+
sort_by: str = SORT_BY_FILEPATH,
|
|
453
|
+
order: str = SORT_ORDER_ASC,
|
|
454
|
+
):
|
|
455
|
+
"""View cache summary for a training project."""
|
|
456
|
+
try:
|
|
457
|
+
cache_data = remote_provider.api.get_cache_summary(project_id)
|
|
458
|
+
|
|
459
|
+
if not cache_data:
|
|
460
|
+
console.print("No cache summary found for this project.", style="yellow")
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
table = rich.table.Table(title=f"Cache summary for project: {project_id}")
|
|
464
|
+
table.add_column("File Path", style="cyan")
|
|
465
|
+
table.add_column("Size", style="green")
|
|
466
|
+
table.add_column("Modified", style="yellow")
|
|
467
|
+
table.add_column("Type")
|
|
468
|
+
table.add_column("Permissions", style="magenta")
|
|
469
|
+
|
|
470
|
+
files = cache_data.get("file_summaries", [])
|
|
471
|
+
if not files:
|
|
472
|
+
console.print("No files found in cache.", style="yellow")
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
reverse = order == SORT_ORDER_DESC
|
|
476
|
+
|
|
477
|
+
if sort_by == SORT_BY_FILEPATH:
|
|
478
|
+
files.sort(key=lambda x: x.get("path", ""), reverse=reverse)
|
|
479
|
+
elif sort_by == SORT_BY_SIZE:
|
|
480
|
+
files.sort(key=lambda x: x.get("size_bytes", 0), reverse=reverse)
|
|
481
|
+
elif sort_by == SORT_BY_MODIFIED:
|
|
482
|
+
files.sort(key=lambda x: x.get("modified", ""), reverse=reverse)
|
|
483
|
+
elif sort_by == SORT_BY_TYPE:
|
|
484
|
+
files.sort(key=lambda x: x.get("file_type", ""), reverse=reverse)
|
|
485
|
+
elif sort_by == SORT_BY_PERMISSIONS:
|
|
486
|
+
files.sort(key=lambda x: x.get("permissions", ""), reverse=reverse)
|
|
487
|
+
|
|
488
|
+
total_size = 0
|
|
489
|
+
for file_info in files:
|
|
490
|
+
total_size += file_info.get("size_bytes", 0)
|
|
491
|
+
|
|
492
|
+
total_size_str = common.format_bytes_to_human_readable(total_size)
|
|
493
|
+
|
|
494
|
+
console.print(
|
|
495
|
+
f"📅 Cache captured at: {cache_data.get('timestamp', 'Unknown')}",
|
|
496
|
+
style="bold blue",
|
|
497
|
+
)
|
|
498
|
+
console.print(
|
|
499
|
+
f"📁 Project ID: {cache_data.get('project_id', 'Unknown')}",
|
|
500
|
+
style="bold blue",
|
|
501
|
+
)
|
|
502
|
+
console.print()
|
|
503
|
+
console.print(f"📊 Total files: {len(files)}", style="bold green")
|
|
504
|
+
console.print(f"💾 Total size: {total_size_str}", style="bold green")
|
|
505
|
+
console.print()
|
|
506
|
+
|
|
507
|
+
for file_info in files:
|
|
508
|
+
size_bytes = file_info.get("size_bytes", 0)
|
|
509
|
+
|
|
510
|
+
size_str = cli_common.format_bytes_to_human_readable(int(size_bytes))
|
|
511
|
+
|
|
512
|
+
modified_str = cli_common.format_localized_time(
|
|
513
|
+
file_info.get("modified", "Unknown")
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
table.add_row(
|
|
517
|
+
file_info.get("path", "Unknown"),
|
|
518
|
+
size_str,
|
|
519
|
+
modified_str,
|
|
520
|
+
file_info.get("file_type", "Unknown"),
|
|
521
|
+
file_info.get("permissions", "Unknown"),
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
console.print(table)
|
|
525
|
+
|
|
526
|
+
except Exception as e:
|
|
527
|
+
console.print(f"Error fetching cache summary: {str(e)}", style="red")
|
|
528
|
+
raise
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def view_cache_summary_by_project(
|
|
532
|
+
remote_provider: BasetenRemote,
|
|
533
|
+
project_identifier: str,
|
|
534
|
+
sort_by: str = SORT_BY_FILEPATH,
|
|
535
|
+
order: str = SORT_ORDER_ASC,
|
|
536
|
+
):
|
|
537
|
+
"""View cache summary for a training project by ID or name."""
|
|
538
|
+
project = fetch_project_by_name_or_id(remote_provider, project_identifier)
|
|
539
|
+
view_cache_summary(remote_provider, project["id"], sort_by, order)
|
|
@@ -35,18 +35,17 @@ class MetricsWatcher(TrainingPollerMixin):
|
|
|
35
35
|
|
|
36
36
|
def _format_bytes(self, bytes_val: float) -> Tuple[str, str]:
|
|
37
37
|
"""Convert bytes to human readable format"""
|
|
38
|
+
default_color = "green"
|
|
38
39
|
color_map = {"MB": "green", "GB": "cyan", "TB": "magenta"}
|
|
39
|
-
unit = "
|
|
40
|
-
if bytes_val >
|
|
40
|
+
unit = "B"
|
|
41
|
+
if bytes_val > 1000 * 1000 * 1000 * 1000:
|
|
41
42
|
unit = "TB"
|
|
42
|
-
elif bytes_val >
|
|
43
|
+
elif bytes_val > 1000 * 1000 * 1000:
|
|
43
44
|
unit = "GB"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return f"{bytes_val / (1024 * 1024 * 1024):.2f} GB", color_map[unit]
|
|
49
|
-
return f"{bytes_val:.2f} bytes", color_map[unit]
|
|
45
|
+
elif bytes_val > 1000 * 1000:
|
|
46
|
+
unit = "MB"
|
|
47
|
+
color = color_map.get(unit, default_color)
|
|
48
|
+
return (common.format_bytes_to_human_readable(int(bytes_val)), color)
|
|
50
49
|
|
|
51
50
|
def _format_storage_utilization(self, utilization: float) -> Tuple[str, str]:
|
|
52
51
|
percent = round(utilization * 100, 4)
|
truss/cli/train_commands.py
CHANGED
|
@@ -11,11 +11,21 @@ from truss.cli.logs import utils as cli_log_utils
|
|
|
11
11
|
from truss.cli.logs.training_log_watcher import TrainingLogWatcher
|
|
12
12
|
from truss.cli.train import common as train_common
|
|
13
13
|
from truss.cli.train import core
|
|
14
|
+
from truss.cli.train.core import (
|
|
15
|
+
SORT_BY_FILEPATH,
|
|
16
|
+
SORT_BY_MODIFIED,
|
|
17
|
+
SORT_BY_PERMISSIONS,
|
|
18
|
+
SORT_BY_SIZE,
|
|
19
|
+
SORT_BY_TYPE,
|
|
20
|
+
SORT_ORDER_ASC,
|
|
21
|
+
SORT_ORDER_DESC,
|
|
22
|
+
)
|
|
14
23
|
from truss.cli.utils import common
|
|
15
24
|
from truss.cli.utils.output import console, error_console
|
|
16
25
|
from truss.remote.baseten.core import get_training_job_logs_with_pagination
|
|
17
26
|
from truss.remote.baseten.remote import BasetenRemote
|
|
18
27
|
from truss.remote.remote_factory import RemoteFactory
|
|
28
|
+
from truss_train import TrainingJob
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
@click.group()
|
|
@@ -27,15 +37,30 @@ truss_cli.add_command(train)
|
|
|
27
37
|
|
|
28
38
|
|
|
29
39
|
def _print_training_job_success_message(
|
|
30
|
-
job_id: str,
|
|
40
|
+
job_id: str,
|
|
41
|
+
project_name: str,
|
|
42
|
+
job_object: TrainingJob,
|
|
43
|
+
remote_provider: BasetenRemote,
|
|
31
44
|
) -> None:
|
|
32
45
|
"""Print success message and helpful commands for a training job."""
|
|
33
46
|
console.print("✨ Training job successfully created!", style="green")
|
|
47
|
+
should_print_cache_summary = (
|
|
48
|
+
job_object.runtime.enable_cache
|
|
49
|
+
or job_object.runtime.cache_config
|
|
50
|
+
and job_object.runtime.cache_config.enabled
|
|
51
|
+
)
|
|
52
|
+
cache_summary_snippet = ""
|
|
53
|
+
if should_print_cache_summary:
|
|
54
|
+
cache_summary_snippet = (
|
|
55
|
+
f"📁 View cache summary via "
|
|
56
|
+
f"[cyan]'truss train cache summarize --project {project_name}'[/cyan]\n"
|
|
57
|
+
)
|
|
34
58
|
console.print(
|
|
35
59
|
f"🪵 View logs for your job via "
|
|
36
60
|
f"[cyan]'truss train logs --job-id {job_id} --tail'[/cyan]\n"
|
|
37
61
|
f"🔍 View metrics for your job via "
|
|
38
62
|
f"[cyan]'truss train metrics --job-id {job_id}'[/cyan]\n"
|
|
63
|
+
f"{cache_summary_snippet}"
|
|
39
64
|
f"🌐 Status page: {common.format_link(core.status_page_url(remote_provider.remote_url, job_id))}"
|
|
40
65
|
)
|
|
41
66
|
|
|
@@ -44,6 +69,7 @@ def _handle_post_create_logic(
|
|
|
44
69
|
job_resp: dict, remote_provider: BasetenRemote, tail: bool
|
|
45
70
|
) -> None:
|
|
46
71
|
project_id, job_id = job_resp["training_project"]["id"], job_resp["id"]
|
|
72
|
+
project_name = job_resp["training_project"]["name"]
|
|
47
73
|
|
|
48
74
|
if job_resp.get("current_status", None) == "TRAINING_JOB_QUEUED":
|
|
49
75
|
console.print(
|
|
@@ -51,7 +77,9 @@ def _handle_post_create_logic(
|
|
|
51
77
|
style="green",
|
|
52
78
|
)
|
|
53
79
|
else:
|
|
54
|
-
_print_training_job_success_message(
|
|
80
|
+
_print_training_job_success_message(
|
|
81
|
+
job_id, project_name, job_resp["job_object"], remote_provider
|
|
82
|
+
)
|
|
55
83
|
|
|
56
84
|
if tail:
|
|
57
85
|
watcher = TrainingLogWatcher(remote_provider.api, project_id, job_id)
|
|
@@ -351,3 +379,46 @@ def download_checkpoint_artifacts(job_id: Optional[str], remote: Optional[str])
|
|
|
351
379
|
except Exception as e:
|
|
352
380
|
error_console.print(f"Failed to download checkpoint artifacts data: {str(e)}")
|
|
353
381
|
sys.exit(1)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@train.group(name="cache")
|
|
385
|
+
def cache():
|
|
386
|
+
"""Cache-related subcommands for truss train"""
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@cache.command(name="summarize")
|
|
390
|
+
@click.option(
|
|
391
|
+
"--project", type=str, required=True, help="Project ID or name to view cache for."
|
|
392
|
+
)
|
|
393
|
+
@click.option("--remote", type=str, required=False, help="Remote to use")
|
|
394
|
+
@click.option(
|
|
395
|
+
"--sort",
|
|
396
|
+
type=click.Choice(
|
|
397
|
+
[
|
|
398
|
+
SORT_BY_FILEPATH,
|
|
399
|
+
SORT_BY_SIZE,
|
|
400
|
+
SORT_BY_MODIFIED,
|
|
401
|
+
SORT_BY_TYPE,
|
|
402
|
+
SORT_BY_PERMISSIONS,
|
|
403
|
+
]
|
|
404
|
+
),
|
|
405
|
+
default=SORT_BY_FILEPATH,
|
|
406
|
+
help="Sort files by filepath, size, modified date, file type, or permissions.",
|
|
407
|
+
)
|
|
408
|
+
@click.option(
|
|
409
|
+
"--order",
|
|
410
|
+
type=click.Choice([SORT_ORDER_ASC, SORT_ORDER_DESC]),
|
|
411
|
+
default=SORT_ORDER_ASC,
|
|
412
|
+
help="Sort order: ascending or descending.",
|
|
413
|
+
)
|
|
414
|
+
@common.common_options()
|
|
415
|
+
def view_cache_summary(project: str, remote: Optional[str], sort: str, order: str):
|
|
416
|
+
"""View cache summary for a training project"""
|
|
417
|
+
if not remote:
|
|
418
|
+
remote = remote_cli.inquire_remote_name()
|
|
419
|
+
|
|
420
|
+
remote_provider: BasetenRemote = cast(
|
|
421
|
+
BasetenRemote, RemoteFactory.create(remote=remote)
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
train_cli.view_cache_summary_by_project(remote_provider, project, sort, order)
|
truss/cli/utils/common.py
CHANGED
|
@@ -186,4 +186,17 @@ def format_localized_time(iso_timestamp: str) -> str:
|
|
|
186
186
|
iso_timestamp = iso_timestamp.replace("Z", "+00:00")
|
|
187
187
|
utc_time = datetime.datetime.fromisoformat(iso_timestamp)
|
|
188
188
|
local_time = utc_time.astimezone()
|
|
189
|
-
return local_time.strftime("%Y-%m-%d %H:%M")
|
|
189
|
+
return local_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def format_bytes_to_human_readable(bytes: int) -> str:
|
|
193
|
+
if bytes > 1000 * 1000 * 1000 * 1000:
|
|
194
|
+
return f"{bytes / (1000 * 1000 * 1000 * 1000):.2f} TB"
|
|
195
|
+
if bytes > 1000 * 1000 * 1000:
|
|
196
|
+
return f"{bytes / (1000 * 1000 * 1000):.2f} GB"
|
|
197
|
+
elif bytes > 1000 * 1000:
|
|
198
|
+
return f"{bytes / (1000 * 1000):.2f} MB"
|
|
199
|
+
elif bytes > 1000:
|
|
200
|
+
return f"{bytes / 1000:.2f} KB"
|
|
201
|
+
else:
|
|
202
|
+
return f"{bytes} B"
|
truss/remote/baseten/api.py
CHANGED
|
@@ -669,6 +669,13 @@ class BasetenApi:
|
|
|
669
669
|
# NB(nikhil): reverse order so latest logs are at the end
|
|
670
670
|
return resp_json["logs"][::-1]
|
|
671
671
|
|
|
672
|
+
def get_cache_summary(self, project_id: str):
|
|
673
|
+
"""Get cache summary for a training project."""
|
|
674
|
+
resp_json = self._rest_api_client.get(
|
|
675
|
+
f"v1/training_projects/{project_id}/cache/summary"
|
|
676
|
+
)
|
|
677
|
+
return resp_json
|
|
678
|
+
|
|
672
679
|
def _fetch_log_batch(
|
|
673
680
|
self, project_id: str, job_id: str, query_params: Dict[str, Any]
|
|
674
681
|
) -> List[Any]:
|
|
@@ -120,3 +120,31 @@ class TrussUserEnv(pydantic.BaseModel):
|
|
|
120
120
|
class BlobType(Enum):
|
|
121
121
|
MODEL = "model"
|
|
122
122
|
TRAIN = "train"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class FileSummary(pydantic.BaseModel):
|
|
126
|
+
"""Information about a file in the cache."""
|
|
127
|
+
|
|
128
|
+
path: str = pydantic.Field(description="Relative path of the file in the cache")
|
|
129
|
+
size_bytes: int = pydantic.Field(description="Size of the file in bytes")
|
|
130
|
+
modified: str = pydantic.Field(description="Last modification time of the file")
|
|
131
|
+
file_type: Optional[str] = pydantic.Field(
|
|
132
|
+
default=None,
|
|
133
|
+
description="Type of the file (e.g., 'file', 'directory', 'symlink')",
|
|
134
|
+
)
|
|
135
|
+
permissions: Optional[str] = pydantic.Field(
|
|
136
|
+
default=None,
|
|
137
|
+
description="File permissions in Unix symbolic format (e.g., 'drwxr-xr-x', '-rw-r--r--')",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class GetCacheSummaryResponseV1(pydantic.BaseModel):
|
|
142
|
+
"""Response for getting cache summary."""
|
|
143
|
+
|
|
144
|
+
timestamp: str = pydantic.Field(
|
|
145
|
+
description="Timestamp when the cache summary was captured"
|
|
146
|
+
)
|
|
147
|
+
project_id: str = pydantic.Field(description="Project ID associated with the cache")
|
|
148
|
+
file_summaries: list[FileSummary] = pydantic.Field(
|
|
149
|
+
description="List of files in the cache"
|
|
150
|
+
)
|
|
@@ -70,7 +70,7 @@ COPY ./{{ config.data_dir }} /app/data
|
|
|
70
70
|
|
|
71
71
|
{%- if model_cache_v2 %}
|
|
72
72
|
# v0.0.9, keep synced with server_requirements.txt
|
|
73
|
-
RUN curl -sSL --fail --retry 5 --retry-delay 2 -o /usr/local/bin/truss-transfer-cli https://github.com/basetenlabs/truss/releases/download/v0.10.
|
|
73
|
+
RUN curl -sSL --fail --retry 5 --retry-delay 2 -o /usr/local/bin/truss-transfer-cli https://github.com/basetenlabs/truss/releases/download/v0.10.11rc1/truss-transfer-cli-v0.10.11rc1-linux-x86_64-unknown-linux-musl
|
|
74
74
|
RUN chmod +x /usr/local/bin/truss-transfer-cli
|
|
75
75
|
RUN mkdir /static-bptr
|
|
76
76
|
RUN echo "hash {{model_cache_hash}}"
|
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from truss.cli.train.core import (
|
|
7
|
+
SORT_BY_FILEPATH,
|
|
8
|
+
SORT_BY_MODIFIED,
|
|
9
|
+
SORT_BY_PERMISSIONS,
|
|
10
|
+
SORT_BY_SIZE,
|
|
11
|
+
SORT_BY_TYPE,
|
|
12
|
+
SORT_ORDER_ASC,
|
|
13
|
+
SORT_ORDER_DESC,
|
|
14
|
+
view_cache_summary,
|
|
15
|
+
view_cache_summary_by_project,
|
|
16
|
+
)
|
|
17
|
+
from truss.remote.baseten.remote import BasetenRemote
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_view_cache_summary_success(capsys):
|
|
21
|
+
"""Test successful cache summary viewing."""
|
|
22
|
+
mock_api = Mock()
|
|
23
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
24
|
+
mock_remote.api = mock_api
|
|
25
|
+
|
|
26
|
+
mock_api.get_cache_summary.return_value = {
|
|
27
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
28
|
+
"project_id": "proj123",
|
|
29
|
+
"file_summaries": [
|
|
30
|
+
{
|
|
31
|
+
"path": "model/weights.bin",
|
|
32
|
+
"size_bytes": 1024 * 1024 * 100,
|
|
33
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
34
|
+
"file_type": "file",
|
|
35
|
+
"permissions": "-rw-r--r--",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"path": "config.json",
|
|
39
|
+
"size_bytes": 1024,
|
|
40
|
+
"modified": "2024-01-01T09:00:00Z",
|
|
41
|
+
"file_type": "file",
|
|
42
|
+
"permissions": "-rw-r--r--",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "model/",
|
|
46
|
+
"size_bytes": 0,
|
|
47
|
+
"modified": "2024-01-01T08:00:00Z",
|
|
48
|
+
"file_type": "directory",
|
|
49
|
+
"permissions": "drwxr-xr-x",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
}
|
|
53
|
+
mock_api.list_training_projects.return_value = [
|
|
54
|
+
{"id": "proj123", "name": "test-project"}
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_FILEPATH, SORT_ORDER_ASC)
|
|
58
|
+
|
|
59
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
60
|
+
|
|
61
|
+
captured = capsys.readouterr()
|
|
62
|
+
assert "Cache summary for project: proj123" in captured.out
|
|
63
|
+
assert "model/weights.bin" in captured.out
|
|
64
|
+
assert "config.json" in captured.out
|
|
65
|
+
assert "104.86 MB" in captured.out
|
|
66
|
+
assert "1.02 KB" in captured.out
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_view_cache_summary_no_cache(capsys):
|
|
70
|
+
"""Test when no cache summary is found."""
|
|
71
|
+
mock_api = Mock()
|
|
72
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
73
|
+
mock_remote.api = mock_api
|
|
74
|
+
|
|
75
|
+
mock_api.get_cache_summary.return_value = {}
|
|
76
|
+
|
|
77
|
+
mock_api.list_training_projects.return_value = [
|
|
78
|
+
{"id": "proj123", "name": "test-project"}
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_FILEPATH, SORT_ORDER_ASC)
|
|
82
|
+
|
|
83
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
84
|
+
|
|
85
|
+
captured = capsys.readouterr()
|
|
86
|
+
assert "No cache summary found for this project." in captured.out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_view_cache_summary_empty_files(capsys):
|
|
90
|
+
"""Test when cache summary exists but has no files."""
|
|
91
|
+
mock_api = Mock()
|
|
92
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
93
|
+
mock_remote.api = mock_api
|
|
94
|
+
|
|
95
|
+
mock_api.list_training_projects.return_value = [
|
|
96
|
+
{"id": "proj123", "name": "test-project"}
|
|
97
|
+
]
|
|
98
|
+
mock_api.get_cache_summary.return_value = {
|
|
99
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
100
|
+
"project_id": "proj123",
|
|
101
|
+
"file_summaries": [],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_FILEPATH, SORT_ORDER_ASC)
|
|
105
|
+
|
|
106
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
107
|
+
|
|
108
|
+
captured = capsys.readouterr()
|
|
109
|
+
assert "No files found in cache." in captured.out
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_view_cache_summary_api_error(capsys):
|
|
113
|
+
"""Test when API call fails."""
|
|
114
|
+
mock_api = Mock()
|
|
115
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
116
|
+
mock_remote.api = mock_api
|
|
117
|
+
|
|
118
|
+
mock_api.list_training_projects.return_value = [
|
|
119
|
+
{"id": "proj123", "name": "test-project"}
|
|
120
|
+
]
|
|
121
|
+
mock_api.get_cache_summary.side_effect = Exception("API Error")
|
|
122
|
+
|
|
123
|
+
with pytest.raises(Exception, match="API Error"):
|
|
124
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_FILEPATH, SORT_ORDER_ASC)
|
|
125
|
+
|
|
126
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
127
|
+
|
|
128
|
+
captured = capsys.readouterr()
|
|
129
|
+
assert "Error fetching cache summary: API Error" in captured.out
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_view_cache_summary_sort_by_size_asc(capsys):
|
|
133
|
+
"""Test sorting by size in ascending order."""
|
|
134
|
+
mock_api = Mock()
|
|
135
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
136
|
+
mock_remote.api = mock_api
|
|
137
|
+
|
|
138
|
+
mock_api.list_training_projects.return_value = [
|
|
139
|
+
{"id": "proj123", "name": "test-project"}
|
|
140
|
+
]
|
|
141
|
+
mock_api.get_cache_summary.return_value = {
|
|
142
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
143
|
+
"project_id": "proj123",
|
|
144
|
+
"file_summaries": [
|
|
145
|
+
{
|
|
146
|
+
"path": "large_file.bin",
|
|
147
|
+
"size_bytes": 1024 * 1024 * 100,
|
|
148
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
149
|
+
"file_type": "file",
|
|
150
|
+
"permissions": "-rw-r--r--",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"path": "small_file.txt",
|
|
154
|
+
"size_bytes": 1024,
|
|
155
|
+
"modified": "2024-01-01T09:00:00Z",
|
|
156
|
+
"file_type": "file",
|
|
157
|
+
"permissions": "-rw-r--r--",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"path": "medium_file.dat",
|
|
161
|
+
"size_bytes": 1024 * 1024,
|
|
162
|
+
"modified": "2024-01-01T11:00:00Z",
|
|
163
|
+
"file_type": "file",
|
|
164
|
+
"permissions": "-rw-r--r--",
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_SIZE, SORT_ORDER_ASC)
|
|
170
|
+
|
|
171
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
172
|
+
|
|
173
|
+
captured = capsys.readouterr()
|
|
174
|
+
output_lines = captured.out.split("\n")
|
|
175
|
+
|
|
176
|
+
table_start = -1
|
|
177
|
+
for i, line in enumerate(output_lines):
|
|
178
|
+
if "small_file.txt" in line:
|
|
179
|
+
table_start = i
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
small_file_line = None
|
|
183
|
+
medium_file_line = None
|
|
184
|
+
large_file_line = None
|
|
185
|
+
|
|
186
|
+
for line in output_lines[table_start:]:
|
|
187
|
+
if "small_file.txt" in line:
|
|
188
|
+
small_file_line = line
|
|
189
|
+
elif "medium_file.dat" in line:
|
|
190
|
+
medium_file_line = line
|
|
191
|
+
elif "large_file.bin" in line:
|
|
192
|
+
large_file_line = line
|
|
193
|
+
|
|
194
|
+
assert small_file_line is not None
|
|
195
|
+
assert medium_file_line is not None
|
|
196
|
+
assert large_file_line is not None
|
|
197
|
+
|
|
198
|
+
small_pos = captured.out.find("small_file.txt")
|
|
199
|
+
medium_pos = captured.out.find("medium_file.dat")
|
|
200
|
+
large_pos = captured.out.find("large_file.bin")
|
|
201
|
+
|
|
202
|
+
assert small_pos < medium_pos < large_pos
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_view_cache_summary_sort_by_size_desc(capsys):
|
|
206
|
+
"""Test sorting by size in descending order."""
|
|
207
|
+
mock_api = Mock()
|
|
208
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
209
|
+
mock_remote.api = mock_api
|
|
210
|
+
|
|
211
|
+
mock_api.list_training_projects.return_value = [
|
|
212
|
+
{"id": "proj123", "name": "test-project"}
|
|
213
|
+
]
|
|
214
|
+
mock_api.get_cache_summary.return_value = {
|
|
215
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
216
|
+
"project_id": "proj123",
|
|
217
|
+
"file_summaries": [
|
|
218
|
+
{
|
|
219
|
+
"path": "large_file.bin",
|
|
220
|
+
"size_bytes": 1024 * 1024 * 100,
|
|
221
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
222
|
+
"file_type": "file",
|
|
223
|
+
"permissions": "-rw-r--r--",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"path": "small_file.txt",
|
|
227
|
+
"size_bytes": 1024,
|
|
228
|
+
"modified": "2024-01-01T09:00:00Z",
|
|
229
|
+
"file_type": "file",
|
|
230
|
+
"permissions": "-rw-r--r--",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"path": "medium_file.dat",
|
|
234
|
+
"size_bytes": 1024 * 1024,
|
|
235
|
+
"modified": "2024-01-01T11:00:00Z",
|
|
236
|
+
"file_type": "file",
|
|
237
|
+
"permissions": "-rw-r--r--",
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_SIZE, SORT_ORDER_DESC)
|
|
243
|
+
|
|
244
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
245
|
+
|
|
246
|
+
captured = capsys.readouterr()
|
|
247
|
+
|
|
248
|
+
small_pos = captured.out.find("small_file.txt")
|
|
249
|
+
medium_pos = captured.out.find("medium_file.dat")
|
|
250
|
+
large_pos = captured.out.find("large_file.bin")
|
|
251
|
+
|
|
252
|
+
assert large_pos < medium_pos < small_pos
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_view_cache_summary_sort_by_modified_asc(capsys):
|
|
256
|
+
"""Test sorting by modified date in ascending order."""
|
|
257
|
+
mock_api = Mock()
|
|
258
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
259
|
+
mock_remote.api = mock_api
|
|
260
|
+
|
|
261
|
+
mock_api.list_training_projects.return_value = [
|
|
262
|
+
{"id": "proj123", "name": "test-project"}
|
|
263
|
+
]
|
|
264
|
+
mock_api.get_cache_summary.return_value = {
|
|
265
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
266
|
+
"project_id": "proj123",
|
|
267
|
+
"file_summaries": [
|
|
268
|
+
{
|
|
269
|
+
"path": "old_file.txt",
|
|
270
|
+
"size_bytes": 1024,
|
|
271
|
+
"modified": "2024-01-01T08:00:00Z",
|
|
272
|
+
"file_type": "file",
|
|
273
|
+
"permissions": "-rw-r--r--",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"path": "new_file.txt",
|
|
277
|
+
"size_bytes": 1024,
|
|
278
|
+
"modified": "2024-01-01T12:00:00Z",
|
|
279
|
+
"file_type": "file",
|
|
280
|
+
"permissions": "-rw-r--r--",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"path": "middle_file.txt",
|
|
284
|
+
"size_bytes": 1024,
|
|
285
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
286
|
+
"file_type": "file",
|
|
287
|
+
"permissions": "-rw-r--r--",
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_MODIFIED, SORT_ORDER_ASC)
|
|
293
|
+
|
|
294
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
295
|
+
|
|
296
|
+
captured = capsys.readouterr()
|
|
297
|
+
|
|
298
|
+
old_pos = captured.out.find("old_file.txt")
|
|
299
|
+
middle_pos = captured.out.find("middle_file.txt")
|
|
300
|
+
new_pos = captured.out.find("new_file.txt")
|
|
301
|
+
|
|
302
|
+
assert old_pos < middle_pos < new_pos
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def test_view_cache_summary_sort_by_filepath_desc(capsys):
|
|
306
|
+
"""Test sorting by filepath in descending order."""
|
|
307
|
+
mock_api = Mock()
|
|
308
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
309
|
+
mock_remote.api = mock_api
|
|
310
|
+
|
|
311
|
+
mock_api.get_cache_summary.return_value = {
|
|
312
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
313
|
+
"project_id": "proj123",
|
|
314
|
+
"file_summaries": [
|
|
315
|
+
{
|
|
316
|
+
"path": "a_file.txt",
|
|
317
|
+
"size_bytes": 1024,
|
|
318
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
319
|
+
"file_type": "file",
|
|
320
|
+
"permissions": "-rw-r--r--",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"path": "z_file.txt",
|
|
324
|
+
"size_bytes": 1024,
|
|
325
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
326
|
+
"file_type": "file",
|
|
327
|
+
"permissions": "-rw-r--r--",
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"path": "m_file.txt",
|
|
331
|
+
"size_bytes": 1024,
|
|
332
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
333
|
+
"file_type": "file",
|
|
334
|
+
"permissions": "-rw-r--r--",
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_FILEPATH, SORT_ORDER_DESC)
|
|
340
|
+
|
|
341
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
342
|
+
|
|
343
|
+
captured = capsys.readouterr()
|
|
344
|
+
|
|
345
|
+
a_pos = captured.out.find("a_file.txt")
|
|
346
|
+
m_pos = captured.out.find("m_file.txt")
|
|
347
|
+
z_pos = captured.out.find("z_file.txt")
|
|
348
|
+
|
|
349
|
+
assert z_pos < m_pos < a_pos
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def test_view_cache_summary_by_project_name_success(capsys):
|
|
353
|
+
"""Test successful cache summary viewing by project name."""
|
|
354
|
+
mock_api = Mock()
|
|
355
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
356
|
+
mock_remote.api = mock_api
|
|
357
|
+
|
|
358
|
+
# Mock the get_cache_summary response for successful project ID lookup
|
|
359
|
+
mock_api.get_cache_summary.return_value = {
|
|
360
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
361
|
+
"project_id": "proj123",
|
|
362
|
+
"file_summaries": [
|
|
363
|
+
{
|
|
364
|
+
"path": "model/weights.bin",
|
|
365
|
+
"size_bytes": 1024 * 1024 * 100,
|
|
366
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
367
|
+
"file_type": "file",
|
|
368
|
+
"permissions": "-rw-r--r--",
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# Mock the list_training_projects response
|
|
374
|
+
mock_api.list_training_projects.return_value = [
|
|
375
|
+
{"id": "proj123", "name": "test-project"},
|
|
376
|
+
{"id": "proj456", "name": "another-project"},
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
view_cache_summary_by_project(
|
|
380
|
+
mock_remote, "test-project", SORT_BY_FILEPATH, SORT_ORDER_ASC
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
assert mock_api.get_cache_summary.call_count == 1
|
|
384
|
+
assert mock_api.list_training_projects.call_count == 1
|
|
385
|
+
|
|
386
|
+
captured = capsys.readouterr()
|
|
387
|
+
assert "Cache summary for project: proj123" in captured.out
|
|
388
|
+
assert "model/weights.bin" in captured.out
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def test_view_cache_summary_by_project_name_not_found(capsys):
|
|
392
|
+
"""Test when project name is not found."""
|
|
393
|
+
mock_api = Mock()
|
|
394
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
395
|
+
mock_remote.api = mock_api
|
|
396
|
+
|
|
397
|
+
mock_api.list_training_projects.return_value = [
|
|
398
|
+
{"id": "proj123", "name": "test-project"},
|
|
399
|
+
{"id": "proj456", "name": "another-project"},
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
with pytest.raises(
|
|
403
|
+
click.ClickException, match="Project 'nonexistent-project' not found"
|
|
404
|
+
):
|
|
405
|
+
view_cache_summary_by_project(
|
|
406
|
+
mock_remote, "nonexistent-project", SORT_BY_FILEPATH, SORT_ORDER_ASC
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
mock_api.list_training_projects.assert_called_once()
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def test_view_cache_summary_by_project_id_direct(capsys):
|
|
413
|
+
"""Test that project ID is used directly."""
|
|
414
|
+
mock_api = Mock()
|
|
415
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
416
|
+
mock_remote.api = mock_api
|
|
417
|
+
|
|
418
|
+
mock_api.get_cache_summary.return_value = {
|
|
419
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
420
|
+
"project_id": "proj123",
|
|
421
|
+
"file_summaries": [
|
|
422
|
+
{
|
|
423
|
+
"path": "model/weights.bin",
|
|
424
|
+
"size_bytes": 1024 * 1024 * 100,
|
|
425
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
426
|
+
"file_type": "file",
|
|
427
|
+
"permissions": "-rw-r--r--",
|
|
428
|
+
}
|
|
429
|
+
],
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
mock_api.list_training_projects.return_value = [
|
|
433
|
+
{"id": "proj123", "name": "test-project"},
|
|
434
|
+
{"id": "proj456", "name": "another-project"},
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
project_id = "proj123"
|
|
438
|
+
view_cache_summary_by_project(
|
|
439
|
+
mock_remote, project_id, SORT_BY_FILEPATH, SORT_ORDER_ASC
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
assert mock_api.get_cache_summary.call_count == 1
|
|
443
|
+
assert mock_api.list_training_projects.call_count == 1
|
|
444
|
+
|
|
445
|
+
captured = capsys.readouterr()
|
|
446
|
+
assert "Cache summary for project: proj123" in captured.out
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def test_view_cache_summary_by_project_other_error():
|
|
450
|
+
"""Test that other errors (not 404) are re-raised."""
|
|
451
|
+
mock_api = Mock()
|
|
452
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
453
|
+
mock_remote.api = mock_api
|
|
454
|
+
|
|
455
|
+
mock_api.list_training_projects.return_value = [
|
|
456
|
+
{"id": "proj123", "name": "some-project"}
|
|
457
|
+
]
|
|
458
|
+
mock_api.get_cache_summary.side_effect = Exception("Network error")
|
|
459
|
+
|
|
460
|
+
with pytest.raises(Exception, match="Network error"):
|
|
461
|
+
view_cache_summary_by_project(
|
|
462
|
+
mock_remote, "some-project", SORT_BY_FILEPATH, SORT_ORDER_ASC
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_view_cache_summary_by_project_list_error(capsys):
|
|
469
|
+
"""Test when listing projects fails after 404."""
|
|
470
|
+
mock_api = Mock()
|
|
471
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
472
|
+
mock_remote.api = mock_api
|
|
473
|
+
|
|
474
|
+
mock_api.list_training_projects.side_effect = Exception("API error")
|
|
475
|
+
|
|
476
|
+
with pytest.raises(click.ClickException, match="Error fetching project: API error"):
|
|
477
|
+
view_cache_summary_by_project(
|
|
478
|
+
mock_remote, "nonexistent-project", SORT_BY_FILEPATH, SORT_ORDER_ASC
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
mock_api.list_training_projects.assert_called_once()
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def test_view_cache_summary_sort_by_type_asc(capsys):
|
|
485
|
+
"""Test sorting by file type in ascending order."""
|
|
486
|
+
mock_api = Mock()
|
|
487
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
488
|
+
mock_remote.api = mock_api
|
|
489
|
+
|
|
490
|
+
mock_api.list_training_projects.return_value = [
|
|
491
|
+
{"id": "proj123", "name": "test-project"}
|
|
492
|
+
]
|
|
493
|
+
mock_api.get_cache_summary.return_value = {
|
|
494
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
495
|
+
"project_id": "proj123",
|
|
496
|
+
"file_summaries": [
|
|
497
|
+
{
|
|
498
|
+
"path": "config.json",
|
|
499
|
+
"size_bytes": 1024,
|
|
500
|
+
"modified": "2024-01-01T09:00:00Z",
|
|
501
|
+
"file_type": "file",
|
|
502
|
+
"permissions": "-rw-r--r--",
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"path": "model/",
|
|
506
|
+
"size_bytes": 0,
|
|
507
|
+
"modified": "2024-01-01T08:00:00Z",
|
|
508
|
+
"file_type": "directory",
|
|
509
|
+
"permissions": "drwxr-xr-x",
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"path": "data.txt",
|
|
513
|
+
"size_bytes": 2048,
|
|
514
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
515
|
+
"file_type": "file",
|
|
516
|
+
"permissions": "-rw-r--r--",
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_TYPE, SORT_ORDER_ASC)
|
|
522
|
+
|
|
523
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
524
|
+
|
|
525
|
+
captured = capsys.readouterr()
|
|
526
|
+
output_lines = captured.out.split("\n")
|
|
527
|
+
|
|
528
|
+
table_start = -1
|
|
529
|
+
for i, line in enumerate(output_lines):
|
|
530
|
+
if "config.json" in line or "data.txt" in line or "model/" in line:
|
|
531
|
+
table_start = i
|
|
532
|
+
break
|
|
533
|
+
|
|
534
|
+
directory_line = None
|
|
535
|
+
file_lines = []
|
|
536
|
+
|
|
537
|
+
for line in output_lines[table_start:]:
|
|
538
|
+
if "model/" in line:
|
|
539
|
+
directory_line = line
|
|
540
|
+
elif "config.json" in line or "data.txt" in line:
|
|
541
|
+
file_lines.append(line)
|
|
542
|
+
|
|
543
|
+
assert directory_line is not None
|
|
544
|
+
assert len(file_lines) == 2
|
|
545
|
+
|
|
546
|
+
directory_pos = captured.out.find("model/")
|
|
547
|
+
config_pos = captured.out.find("config.json")
|
|
548
|
+
data_pos = captured.out.find("data.txt")
|
|
549
|
+
|
|
550
|
+
assert directory_pos < config_pos
|
|
551
|
+
assert directory_pos < data_pos
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def test_view_cache_summary_sort_by_type_desc(capsys):
|
|
555
|
+
"""Test sorting by file type in descending order."""
|
|
556
|
+
mock_api = Mock()
|
|
557
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
558
|
+
mock_remote.api = mock_api
|
|
559
|
+
|
|
560
|
+
mock_api.list_training_projects.return_value = [
|
|
561
|
+
{"id": "proj123", "name": "test-project"}
|
|
562
|
+
]
|
|
563
|
+
mock_api.get_cache_summary.return_value = {
|
|
564
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
565
|
+
"project_id": "proj123",
|
|
566
|
+
"file_summaries": [
|
|
567
|
+
{
|
|
568
|
+
"path": "config.json",
|
|
569
|
+
"size_bytes": 1024,
|
|
570
|
+
"modified": "2024-01-01T09:00:00Z",
|
|
571
|
+
"file_type": "file",
|
|
572
|
+
"permissions": "-rw-r--r--",
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"path": "model/",
|
|
576
|
+
"size_bytes": 0,
|
|
577
|
+
"modified": "2024-01-01T08:00:00Z",
|
|
578
|
+
"file_type": "directory",
|
|
579
|
+
"permissions": "drwxr-xr-x",
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
"path": "data.txt",
|
|
583
|
+
"size_bytes": 2048,
|
|
584
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
585
|
+
"file_type": "file",
|
|
586
|
+
"permissions": "-rw-r--r--",
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_TYPE, SORT_ORDER_DESC)
|
|
592
|
+
|
|
593
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
594
|
+
|
|
595
|
+
captured = capsys.readouterr()
|
|
596
|
+
|
|
597
|
+
config_pos = captured.out.find("config.json")
|
|
598
|
+
data_pos = captured.out.find("data.txt")
|
|
599
|
+
directory_pos = captured.out.find("model/")
|
|
600
|
+
|
|
601
|
+
assert config_pos < directory_pos
|
|
602
|
+
assert data_pos < directory_pos
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def test_view_cache_summary_sort_by_permissions_asc(capsys):
|
|
606
|
+
"""Test sorting by permissions in ascending order."""
|
|
607
|
+
mock_api = Mock()
|
|
608
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
609
|
+
mock_remote.api = mock_api
|
|
610
|
+
|
|
611
|
+
mock_api.list_training_projects.return_value = [
|
|
612
|
+
{"id": "proj123", "name": "test-project"}
|
|
613
|
+
]
|
|
614
|
+
mock_api.get_cache_summary.return_value = {
|
|
615
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
616
|
+
"project_id": "proj123",
|
|
617
|
+
"file_summaries": [
|
|
618
|
+
{
|
|
619
|
+
"path": "config.json",
|
|
620
|
+
"size_bytes": 1024,
|
|
621
|
+
"modified": "2024-01-01T09:00:00Z",
|
|
622
|
+
"file_type": "file",
|
|
623
|
+
"permissions": "-rw-r--r--",
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
"path": "model/",
|
|
627
|
+
"size_bytes": 0,
|
|
628
|
+
"modified": "2024-01-01T08:00:00Z",
|
|
629
|
+
"file_type": "directory",
|
|
630
|
+
"permissions": "drwxr-xr-x",
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
"path": "script.sh",
|
|
634
|
+
"size_bytes": 2048,
|
|
635
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
636
|
+
"file_type": "file",
|
|
637
|
+
"permissions": "-rwxr-xr-x",
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_PERMISSIONS, SORT_ORDER_ASC)
|
|
643
|
+
|
|
644
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
645
|
+
|
|
646
|
+
captured = capsys.readouterr()
|
|
647
|
+
|
|
648
|
+
config_pos = captured.out.find("config.json")
|
|
649
|
+
script_pos = captured.out.find("script.sh")
|
|
650
|
+
directory_pos = captured.out.find("model/")
|
|
651
|
+
|
|
652
|
+
assert config_pos != -1, "Config file not found in output"
|
|
653
|
+
assert script_pos != -1, "Script file not found in output"
|
|
654
|
+
assert directory_pos != -1, "Directory not found in output"
|
|
655
|
+
|
|
656
|
+
assert config_pos < script_pos
|
|
657
|
+
assert script_pos < directory_pos
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def test_view_cache_summary_sort_by_permissions_desc(capsys):
|
|
661
|
+
"""Test sorting by permissions in descending order."""
|
|
662
|
+
mock_api = Mock()
|
|
663
|
+
mock_remote = Mock(spec=BasetenRemote)
|
|
664
|
+
mock_remote.api = mock_api
|
|
665
|
+
|
|
666
|
+
mock_api.list_training_projects.return_value = [
|
|
667
|
+
{"id": "proj123", "name": "test-project"}
|
|
668
|
+
]
|
|
669
|
+
mock_api.get_cache_summary.return_value = {
|
|
670
|
+
"timestamp": "2024-01-01T12:00:00Z",
|
|
671
|
+
"project_id": "proj123",
|
|
672
|
+
"file_summaries": [
|
|
673
|
+
{
|
|
674
|
+
"path": "config.json",
|
|
675
|
+
"size_bytes": 1024,
|
|
676
|
+
"modified": "2024-01-01T09:00:00Z",
|
|
677
|
+
"file_type": "file",
|
|
678
|
+
"permissions": "-rw-r--r--",
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
"path": "model/",
|
|
682
|
+
"size_bytes": 0,
|
|
683
|
+
"modified": "2024-01-01T08:00:00Z",
|
|
684
|
+
"file_type": "directory",
|
|
685
|
+
"permissions": "drwxr-xr-x",
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
"path": "script.sh",
|
|
689
|
+
"size_bytes": 2048,
|
|
690
|
+
"modified": "2024-01-01T10:00:00Z",
|
|
691
|
+
"file_type": "file",
|
|
692
|
+
"permissions": "-rwxr-xr-x",
|
|
693
|
+
},
|
|
694
|
+
],
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
view_cache_summary(mock_remote, "proj123", SORT_BY_PERMISSIONS, SORT_ORDER_DESC)
|
|
698
|
+
|
|
699
|
+
mock_api.get_cache_summary.assert_called_once_with("proj123")
|
|
700
|
+
|
|
701
|
+
captured = capsys.readouterr()
|
|
702
|
+
|
|
703
|
+
directory_pos = captured.out.find("model/")
|
|
704
|
+
script_pos = captured.out.find("script.sh")
|
|
705
|
+
config_pos = captured.out.find("config.json")
|
|
706
|
+
|
|
707
|
+
assert directory_pos != -1, "Directory not found in output"
|
|
708
|
+
assert script_pos != -1, "Script file not found in output"
|
|
709
|
+
assert config_pos != -1, "Config file not found in output"
|
|
710
|
+
|
|
711
|
+
assert directory_pos < script_pos
|
|
712
|
+
assert script_pos < config_pos
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: truss
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.12
|
|
4
4
|
Summary: A seamless bridge from model development to model delivery
|
|
5
5
|
Project-URL: Repository, https://github.com/basetenlabs/truss
|
|
6
6
|
Project-URL: Homepage, https://truss.baseten.co
|
|
@@ -37,7 +37,7 @@ Requires-Dist: rich<14,>=13.4.2
|
|
|
37
37
|
Requires-Dist: ruff>=0.4.8
|
|
38
38
|
Requires-Dist: tenacity>=8.0.1
|
|
39
39
|
Requires-Dist: tomlkit>=0.13.2
|
|
40
|
-
Requires-Dist: truss-transfer==0.0.
|
|
40
|
+
Requires-Dist: truss-transfer==0.0.30
|
|
41
41
|
Requires-Dist: watchfiles<0.20,>=0.19.0
|
|
42
42
|
Description-Content-Type: text/markdown
|
|
43
43
|
|
|
@@ -11,16 +11,16 @@ truss/base/truss_spec.py,sha256=jFVF79CXoEEspl2kXBAPyi-rwISReIGTdobGpaIhwJw,5979
|
|
|
11
11
|
truss/cli/chains_commands.py,sha256=y6pdIAGCcKOPG9bPuCXPfSA0onQm5x-tT_3blSBfPYg,16971
|
|
12
12
|
truss/cli/cli.py,sha256=PaMkuwXZflkU7sa1tEoT_Zmy-iBkEZs1m4IVqcieaeo,30367
|
|
13
13
|
truss/cli/remote_cli.py,sha256=G_xCKRXzgkCmkiZJhUFfsv5YSVgde1jLA5LPQitpZgI,1905
|
|
14
|
-
truss/cli/train_commands.py,sha256=
|
|
14
|
+
truss/cli/train_commands.py,sha256=eFVx8Lba4ILYA4FCjRjMmABH-uUR_i7h4Io1mn7reZM,14423
|
|
15
15
|
truss/cli/logs/base_watcher.py,sha256=KKyd7lIrdaEeDVt8EtjMioSPGVpLyOcF0ewyzE_GGdQ,2785
|
|
16
16
|
truss/cli/logs/model_log_watcher.py,sha256=NACcP-wkcaroYa2Cb9BZC7Yr0554WZa_FSM2LXOf4A8,1263
|
|
17
17
|
truss/cli/logs/training_log_watcher.py,sha256=r6HRqrLnz-PiKTUXiDYYxg4ZnP8vYcXlEX1YmgHhzlo,1173
|
|
18
18
|
truss/cli/logs/utils.py,sha256=z-U_FG4BUzdZLbE3BnXb4DZQ0zt3LSZ3PiQpLaDuc3o,1031
|
|
19
|
-
truss/cli/train/common.py,sha256=
|
|
20
|
-
truss/cli/train/core.py,sha256=
|
|
19
|
+
truss/cli/train/common.py,sha256=xTR41U5FeSndXfNBBHF9wF5XwZH1sOIVFlv-XHjsKIU,1547
|
|
20
|
+
truss/cli/train/core.py,sha256=gfFRqxCxGsanbMNgJBJ0WFqVxZm4SwOEM2iyLS3K8Ns,18687
|
|
21
21
|
truss/cli/train/deploy_from_checkpoint_config.yml,sha256=mktaVrfhN8Kjx1UveC4xr-gTW-kjwbHvq6bx_LpO-Wg,371
|
|
22
22
|
truss/cli/train/deploy_from_checkpoint_config_whisper.yml,sha256=6GbOorYC8ml0UyOUvuBpFO_fuYtYE646JqsalR-D4oY,406
|
|
23
|
-
truss/cli/train/metrics_watcher.py,sha256=
|
|
23
|
+
truss/cli/train/metrics_watcher.py,sha256=smz-zrEsBj_-wJHI0pAZ-EAPrvfCWzq1eQjGiFNM-Mk,12755
|
|
24
24
|
truss/cli/train/poller.py,sha256=TGRzELxsicga0bEXewSX1ujw6lfPmDnHd6nr8zvOFO8,3550
|
|
25
25
|
truss/cli/train/types.py,sha256=alGtr4Q71GeB65PpGMhsoKygw4k_ncR6MKIP1ioP8rI,951
|
|
26
26
|
truss/cli/train/deploy_checkpoints/__init__.py,sha256=wL-M2yu8PxO2tFvjwshXAfPnB-5TlvsBp2v_bdzimRU,99
|
|
@@ -29,7 +29,7 @@ truss/cli/train/deploy_checkpoints/deploy_checkpoints_helpers.py,sha256=6x5nS_Hn
|
|
|
29
29
|
truss/cli/train/deploy_checkpoints/deploy_full_checkpoints.py,sha256=FYRG5KTMlxEMZS-RA_m2gp1wuqWbSpqt2RhdQfLibhA,3968
|
|
30
30
|
truss/cli/train/deploy_checkpoints/deploy_lora_checkpoints.py,sha256=P91dIAzuhl2GlzmrWwCcYI7uCMT1Lm7C79JQHM_exN4,4442
|
|
31
31
|
truss/cli/train/deploy_checkpoints/deploy_whisper_checkpoints.py,sha256=NSo2kEn-CxawGvUhn-xE81noxoTJ0cCv8X9fkrMxAsM,2617
|
|
32
|
-
truss/cli/utils/common.py,sha256=
|
|
32
|
+
truss/cli/utils/common.py,sha256=ink9ZE0MsOv6PCFK_Ra5k1aHm281TXTnMpnLjf2PtUM,6585
|
|
33
33
|
truss/cli/utils/output.py,sha256=GNjU85ZAMp5BI6Yij5wYXcaAvpm_kmHV0nHNmdkMxb0,646
|
|
34
34
|
truss/cli/utils/self_upgrade.py,sha256=eTJZA4Wc8uUp4Qh6viRQp6bZm--wnQp7KWe5KRRpPtg,5427
|
|
35
35
|
truss/contexts/docker_build_setup.py,sha256=cF4ExZgtYvrWxvyCAaUZUvV_DB_7__MqVomUDpalvKo,3925
|
|
@@ -52,10 +52,10 @@ truss/patch/truss_dir_patch_applier.py,sha256=ALnaVnu96g0kF2UmGuBFTua3lrXpwAy4sG
|
|
|
52
52
|
truss/remote/remote_factory.py,sha256=-0gLh_yIyNDgD48Q6sR8Yo5dOMQg84lrHRvn_XR0n4s,3585
|
|
53
53
|
truss/remote/truss_remote.py,sha256=TEe6h6by5-JLy7PMFsDN2QxIY5FmdIYN3bKvHHl02xM,8440
|
|
54
54
|
truss/remote/baseten/__init__.py,sha256=XNqJW1zyp143XQc6-7XVwsUA_Q_ZJv_ausn1_Ohtw9Y,176
|
|
55
|
-
truss/remote/baseten/api.py,sha256=
|
|
55
|
+
truss/remote/baseten/api.py,sha256=lJOt2i3tu0ZeCh4B_-hpfpjcZKgTHVnkxraooK7TUHw,24699
|
|
56
56
|
truss/remote/baseten/auth.py,sha256=tI7s6cI2EZgzpMIzrdbILHyGwiHDnmoKf_JBhJXT55E,776
|
|
57
57
|
truss/remote/baseten/core.py,sha256=uxtmBI9RAVHu1glIEJb5Q4ccJYLeZM1Cp5Svb9W68Yw,21965
|
|
58
|
-
truss/remote/baseten/custom_types.py,sha256=
|
|
58
|
+
truss/remote/baseten/custom_types.py,sha256=gUG7EkTeXzqcqznbKxz1SGTtHavNKGm1UoTFiln3LmQ,4309
|
|
59
59
|
truss/remote/baseten/error.py,sha256=3TNTwwPqZnr4NRd9Sl6SfLUQR2fz9l6akDPpOntTpzA,578
|
|
60
60
|
truss/remote/baseten/remote.py,sha256=Se8AES5mk8jxa8S9fN2DSG7wnsaV7ftRjJ4Uwc_w_S0,22544
|
|
61
61
|
truss/remote/baseten/rest_client.py,sha256=_t3CWsWARt2u0C0fDsF4rtvkkHe-lH7KXoPxWXAkKd4,1185
|
|
@@ -71,7 +71,7 @@ truss/templates/cache.Dockerfile.jinja,sha256=LhsVP9F3BATKQGkgya_YT4v6ABTUkpy-Jb
|
|
|
71
71
|
truss/templates/cache_requirements.txt,sha256=xoPoJ-OVnf1z6oq_RVM3vCr3ionByyqMLj7wGs61nUs,87
|
|
72
72
|
truss/templates/copy_cache_files.Dockerfile.jinja,sha256=arHldnuclt7vUFHyRz6vus5NGMDkIofm-1RU37A0xZM,98
|
|
73
73
|
truss/templates/docker_server_requirements.txt,sha256=PyhOPKAmKW1N2vLvTfLMwsEtuGpoRrbWuNo7tT6v2Mc,18
|
|
74
|
-
truss/templates/server.Dockerfile.jinja,sha256=
|
|
74
|
+
truss/templates/server.Dockerfile.jinja,sha256=TK4P5y8SPV7Mfy0dX8_u10SiGP5PdGcKj5fKrKk575A,5996
|
|
75
75
|
truss/templates/control/requirements.txt,sha256=Kk0tYID7trPk5gwX38Wrt2-YGWZAXFJCJRcqJ8ZzCjc,251
|
|
76
76
|
truss/templates/control/control/application.py,sha256=jYeta6hWe1SkfLL3W4IDmdYjg3ZuKqI_UagWYs5RB_E,3793
|
|
77
77
|
truss/templates/control/control/endpoints.py,sha256=FM-sgao7I3gMoUTasM3Xq_g2LDoJQe75JxIoaQxzeNo,10031
|
|
@@ -139,6 +139,7 @@ truss/tests/test_truss_gatherer.py,sha256=bn288OEkC49YY0mhly4cAl410ktZPfElNdWwZy
|
|
|
139
139
|
truss/tests/test_truss_handle.py,sha256=-xz9VXkecXDTslmQZ-dmUmQLnvD0uumRqHS2uvGlMBA,30750
|
|
140
140
|
truss/tests/test_util.py,sha256=hs1bNMkXKEdoPRx4Nw-NAEdoibR92OubZuADGmbiYsQ,1344
|
|
141
141
|
truss/tests/cli/test_cli.py,sha256=yfbVS5u1hnAmmA8mJ539vj3lhH-JVGUvC4Q_Mbort44,787
|
|
142
|
+
truss/tests/cli/train/test_cache_view.py,sha256=aVRCh3atRpFbJqyYgq7N-vAW0DiKMftQ7ajUqO2ClOg,22606
|
|
142
143
|
truss/tests/cli/train/test_deploy_checkpoints.py,sha256=wQZ3DPLPAyXE3iaQiyHJTBO15v_gXN44eDk1StYkKmM,44764
|
|
143
144
|
truss/tests/cli/train/test_train_cli_core.py,sha256=T1Xa6-NRk2nTJGX6sXaA8x4qCwL3Ini72PBI2gW7rYM,7879
|
|
144
145
|
truss/tests/cli/train/resources/test_deploy_from_checkpoint_config.yml,sha256=GF7r9l0KaeXiUYCPSBpeMPd2QG6PeWWyI12NdbqLOgc,1930
|
|
@@ -360,11 +361,11 @@ truss_chains/remote_chainlet/stub.py,sha256=Y2gDUzMY9WRaQNHIz-o4dfLUfFyYV9dUhIRQ
|
|
|
360
361
|
truss_chains/remote_chainlet/utils.py,sha256=O_5P-VAUvg0cegEW1uKCOf5EBwD8rEGYVoGMivOmc7k,22374
|
|
361
362
|
truss_train/__init__.py,sha256=7hE6j6-u6UGzCGaNp3CsCN0kAVjBus1Ekups-Bk0fi4,837
|
|
362
363
|
truss_train/definitions.py,sha256=V985HhY4rdXL10DZxpFEpze9ScxzWErMht4WwaPknGU,6789
|
|
363
|
-
truss_train/deployment.py,sha256=
|
|
364
|
+
truss_train/deployment.py,sha256=lWWANSuzBWu2M4oK4qD7n-oVR1JKdmw2Pn5BJQHg-Ck,3074
|
|
364
365
|
truss_train/loader.py,sha256=0o66EjBaHc2YY4syxxHVR4ordJWs13lNXnKjKq2wq0U,1630
|
|
365
366
|
truss_train/public_api.py,sha256=9N_NstiUlmBuLUwH_fNG_1x7OhGCytZLNvqKXBlStrM,1220
|
|
366
|
-
truss-0.10.
|
|
367
|
-
truss-0.10.
|
|
368
|
-
truss-0.10.
|
|
369
|
-
truss-0.10.
|
|
370
|
-
truss-0.10.
|
|
367
|
+
truss-0.10.12.dist-info/METADATA,sha256=D39-4SrfGGxR5CjGVA9z4w8f9yB4JODhNM1sowDnnmw,6670
|
|
368
|
+
truss-0.10.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
369
|
+
truss-0.10.12.dist-info/entry_points.txt,sha256=-MwKfHHQHQ6j0HqIgvxrz3CehCmczDLTD-OsRHnjjuU,130
|
|
370
|
+
truss-0.10.12.dist-info/licenses/LICENSE,sha256=FTqGzu85i-uw1Gi8E_o0oD60bH9yQ_XIGtZbA1QUYiw,1064
|
|
371
|
+
truss-0.10.12.dist-info/RECORD,,
|
truss_train/deployment.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|