truss 0.11.6rc102__py3-none-any.whl → 0.11.24rc2__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.
Files changed (88) hide show
  1. truss/api/__init__.py +5 -2
  2. truss/base/constants.py +1 -0
  3. truss/base/trt_llm_config.py +14 -3
  4. truss/base/truss_config.py +19 -4
  5. truss/cli/chains_commands.py +49 -1
  6. truss/cli/cli.py +38 -7
  7. truss/cli/logs/base_watcher.py +31 -12
  8. truss/cli/logs/model_log_watcher.py +24 -1
  9. truss/cli/remote_cli.py +29 -0
  10. truss/cli/resolvers/chain_team_resolver.py +82 -0
  11. truss/cli/resolvers/model_team_resolver.py +90 -0
  12. truss/cli/resolvers/training_project_team_resolver.py +81 -0
  13. truss/cli/train/cache.py +332 -0
  14. truss/cli/train/core.py +57 -163
  15. truss/cli/train/deploy_checkpoints/__init__.py +2 -2
  16. truss/cli/train/deploy_checkpoints/deploy_checkpoints.py +236 -103
  17. truss/cli/train/deploy_checkpoints/deploy_checkpoints_helpers.py +1 -52
  18. truss/cli/train/deploy_checkpoints/deploy_full_checkpoints.py +1 -86
  19. truss/cli/train/deploy_checkpoints/deploy_lora_checkpoints.py +1 -85
  20. truss/cli/train/deploy_checkpoints/deploy_whisper_checkpoints.py +1 -56
  21. truss/cli/train/types.py +18 -9
  22. truss/cli/train_commands.py +180 -35
  23. truss/cli/utils/common.py +40 -3
  24. truss/contexts/image_builder/serving_image_builder.py +17 -4
  25. truss/remote/baseten/api.py +215 -9
  26. truss/remote/baseten/core.py +63 -7
  27. truss/remote/baseten/custom_types.py +1 -0
  28. truss/remote/baseten/remote.py +42 -2
  29. truss/remote/baseten/service.py +0 -7
  30. truss/remote/baseten/utils/transfer.py +5 -2
  31. truss/templates/base.Dockerfile.jinja +8 -4
  32. truss/templates/control/control/application.py +51 -26
  33. truss/templates/control/control/endpoints.py +1 -5
  34. truss/templates/control/control/helpers/inference_server_process_controller.py +10 -4
  35. truss/templates/control/control/helpers/truss_patch/model_container_patch_applier.py +33 -18
  36. truss/templates/control/control/server.py +1 -1
  37. truss/templates/control/requirements.txt +1 -2
  38. truss/templates/docker_server/proxy.conf.jinja +13 -0
  39. truss/templates/docker_server/supervisord.conf.jinja +2 -1
  40. truss/templates/no_build.Dockerfile.jinja +1 -0
  41. truss/templates/server/requirements.txt +2 -3
  42. truss/templates/server/truss_server.py +2 -5
  43. truss/templates/server.Dockerfile.jinja +12 -12
  44. truss/templates/shared/lazy_data_resolver.py +214 -2
  45. truss/templates/shared/util.py +6 -5
  46. truss/tests/cli/chains/test_chains_team_parameter.py +443 -0
  47. truss/tests/cli/test_chains_cli.py +144 -0
  48. truss/tests/cli/test_cli.py +134 -1
  49. truss/tests/cli/test_cli_utils_common.py +11 -0
  50. truss/tests/cli/test_model_team_resolver.py +279 -0
  51. truss/tests/cli/train/test_cache_view.py +240 -3
  52. truss/tests/cli/train/test_deploy_checkpoints.py +2 -846
  53. truss/tests/cli/train/test_train_cli_core.py +2 -2
  54. truss/tests/cli/train/test_train_team_parameter.py +395 -0
  55. truss/tests/conftest.py +187 -0
  56. truss/tests/contexts/image_builder/test_serving_image_builder.py +10 -5
  57. truss/tests/remote/baseten/test_api.py +122 -3
  58. truss/tests/remote/baseten/test_chain_upload.py +294 -0
  59. truss/tests/remote/baseten/test_core.py +86 -0
  60. truss/tests/remote/baseten/test_remote.py +216 -288
  61. truss/tests/remote/baseten/test_service.py +56 -0
  62. truss/tests/templates/control/control/conftest.py +20 -0
  63. truss/tests/templates/control/control/test_endpoints.py +4 -0
  64. truss/tests/templates/control/control/test_server.py +8 -24
  65. truss/tests/templates/control/control/test_server_integration.py +4 -2
  66. truss/tests/test_config.py +21 -12
  67. truss/tests/test_data/server.Dockerfile +3 -1
  68. truss/tests/test_data/test_build_commands_truss/__init__.py +0 -0
  69. truss/tests/test_data/test_build_commands_truss/config.yaml +14 -0
  70. truss/tests/test_data/test_build_commands_truss/model/model.py +12 -0
  71. truss/tests/test_data/test_build_commands_truss/packages/constants/constants.py +1 -0
  72. truss/tests/test_data/test_truss_server_model_cache_v1/config.yaml +1 -0
  73. truss/tests/test_model_inference.py +13 -0
  74. truss/tests/util/test_env_vars.py +8 -3
  75. truss/util/__init__.py +0 -0
  76. truss/util/env_vars.py +19 -8
  77. truss/util/error_utils.py +37 -0
  78. {truss-0.11.6rc102.dist-info → truss-0.11.24rc2.dist-info}/METADATA +2 -2
  79. {truss-0.11.6rc102.dist-info → truss-0.11.24rc2.dist-info}/RECORD +88 -70
  80. {truss-0.11.6rc102.dist-info → truss-0.11.24rc2.dist-info}/WHEEL +1 -1
  81. truss_chains/deployment/deployment_client.py +16 -4
  82. truss_chains/private_types.py +18 -0
  83. truss_chains/public_api.py +3 -0
  84. truss_train/definitions.py +6 -4
  85. truss_train/deployment.py +43 -21
  86. truss_train/public_api.py +4 -2
  87. {truss-0.11.6rc102.dist-info → truss-0.11.24rc2.dist-info}/entry_points.txt +0 -0
  88. {truss-0.11.6rc102.dist-info → truss-0.11.24rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,30 +1,15 @@
1
1
  import os
2
2
  import socket
3
- import sys
4
3
  from contextlib import contextmanager
5
- from pathlib import Path
6
4
  from typing import Dict, List
7
5
  from unittest.mock import AsyncMock, patch
8
6
 
9
7
  import httpx
10
8
  import pytest
11
9
 
12
- from truss.truss_handle.patch.custom_types import PatchRequest
13
-
14
- # Needed to simulate the set up on the model docker container
15
- sys.path.append(
16
- str(
17
- Path(__file__).parent.parent.parent.parent.parent
18
- / "templates"
19
- / "control"
20
- / "control"
21
- )
22
- )
10
+ from truss.tests.templates.control.control.conftest import setup_control_imports
23
11
 
24
- sys.path.append(str(Path(__file__).parent.parent.parent.parent.parent / "templates"))
25
- sys.path.append(
26
- str(Path(__file__).parent.parent.parent.parent.parent / "templates" / "shared")
27
- )
12
+ setup_control_imports()
28
13
 
29
14
  from truss.templates.control.control.application import create_app # noqa
30
15
  from truss.templates.control.control.helpers.custom_types import ( # noqa
@@ -34,6 +19,7 @@ from truss.templates.control.control.helpers.custom_types import ( # noqa
34
19
  PatchType,
35
20
  PythonRequirementPatch,
36
21
  )
22
+ from truss.truss_handle.patch.custom_types import PatchRequest
37
23
 
38
24
 
39
25
  @pytest.fixture
@@ -67,7 +53,7 @@ def app(truss_container_fs, truss_original_hash, ports):
67
53
  "control_server_port": ports["control_server_port"],
68
54
  "inference_server_port": ports["inference_server_port"],
69
55
  "oversee_inference_server": False,
70
- "pip_path": "pip",
56
+ "uv_path": "uv",
71
57
  }
72
58
  )
73
59
  inference_server_controller = control_app.state.inference_server_controller
@@ -273,14 +259,12 @@ async def test_retries(client, app):
273
259
  ]
274
260
  )
275
261
 
276
- with (
277
- patch("endpoints.INFERENCE_SERVER_START_WAIT_SECS", new=4),
278
- pytest.raises(httpx.RemoteProtocolError),
279
- ):
280
- await client.get("/v1/models/model")
262
+ with patch("endpoints.INFERENCE_SERVER_START_WAIT_SECS", new=4):
263
+ resp = await client.get("/v1/models/model")
281
264
 
282
- # We should have made 5 attempts
265
+ # We should have made 5 attempts, and then surfaced a 500
283
266
  assert app.state.proxy_client.send.call_count == 5
267
+ assert resp.status_code == 500
284
268
 
285
269
 
286
270
  async def _verify_apply_patch_success(client, patch: Patch):
@@ -35,10 +35,12 @@ def _start_truss_server(
35
35
  f"http://localhost:{patch_ping_server_port}"
36
36
  )
37
37
  sys.stdout = open(stdout_capture_file_path, "w")
38
+
39
+ # NB(nikhil): Insert paths at the beginning to ensure we search there first.
38
40
  app_path = truss_control_container_fs / "app"
39
- sys.path.append(str(app_path))
41
+ sys.path.insert(0, str(app_path))
40
42
  control_path = truss_control_container_fs / "control" / "control"
41
- sys.path.append(str(control_path))
43
+ sys.path.insert(0, str(control_path))
42
44
 
43
45
  from server import ControlServer
44
46
 
@@ -23,6 +23,7 @@ from truss.base.truss_config import (
23
23
  HTTPOptions,
24
24
  ModelCache,
25
25
  ModelRepo,
26
+ ModelRepoCacheInternal,
26
27
  Resources,
27
28
  Runtime,
28
29
  TransportKind,
@@ -292,7 +293,10 @@ def test_cache_internal_with_models(default_config):
292
293
  config = TrussConfig(
293
294
  python_version="py39",
294
295
  cache_internal=CacheInternal(
295
- [ModelRepo(repo_id="test/model"), ModelRepo(repo_id="test/model2")]
296
+ [
297
+ ModelRepoCacheInternal(repo_id="test/model"),
298
+ ModelRepoCacheInternal(repo_id="test/model2"),
299
+ ]
296
300
  ),
297
301
  )
298
302
  new_config = default_config
@@ -305,11 +309,12 @@ def test_cache_internal_with_models(default_config):
305
309
 
306
310
  def test_huggingface_cache_single_model_default_revision(default_config):
307
311
  config = TrussConfig(
308
- python_version="py39", model_cache=ModelCache([ModelRepo(repo_id="test/model")])
312
+ python_version="py39",
313
+ model_cache=ModelCache([ModelRepo(repo_id="test/model", use_volume=False)]),
309
314
  )
310
315
 
311
316
  new_config = default_config
312
- new_config["model_cache"] = [{"repo_id": "test/model"}]
317
+ new_config["model_cache"] = [{"repo_id": "test/model", "use_volume": False}]
313
318
 
314
319
  assert new_config == config.to_dict(verbose=False)
315
320
  assert config.to_dict(verbose=True)["model_cache"][0].get("revision") is None
@@ -319,7 +324,9 @@ def test_huggingface_cache_single_model_non_default_revision_v1():
319
324
  config = TrussConfig(
320
325
  python_version="py39",
321
326
  requirements=[],
322
- model_cache=ModelCache([ModelRepo(repo_id="test/model", revision="not-main")]),
327
+ model_cache=ModelCache(
328
+ [ModelRepo(repo_id="test/model", revision="not-main", use_volume=False)]
329
+ ),
323
330
  )
324
331
 
325
332
  assert config.to_dict(verbose=False)["model_cache"][0].get("revision") == "not-main"
@@ -330,16 +337,16 @@ def test_huggingface_cache_multiple_models_default_revision(default_config):
330
337
  python_version="py39",
331
338
  model_cache=ModelCache(
332
339
  [
333
- ModelRepo(repo_id="test/model1", revision="main"),
334
- ModelRepo(repo_id="test/model2"),
340
+ ModelRepo(repo_id="test/model1", revision="main", use_volume=False),
341
+ ModelRepo(repo_id="test/model2", use_volume=False),
335
342
  ]
336
343
  ),
337
344
  )
338
345
 
339
346
  new_config = default_config
340
347
  new_config["model_cache"] = [
341
- {"repo_id": "test/model1", "revision": "main"},
342
- {"repo_id": "test/model2"},
348
+ {"repo_id": "test/model1", "revision": "main", "use_volume": False},
349
+ {"repo_id": "test/model2", "use_volume": False},
343
350
  ]
344
351
 
345
352
  assert new_config == config.to_dict(verbose=False)
@@ -355,16 +362,18 @@ def test_huggingface_cache_multiple_models_mixed_revision(default_config):
355
362
  python_version="py39",
356
363
  model_cache=ModelCache(
357
364
  [
358
- ModelRepo(repo_id="test/model1"),
359
- ModelRepo(repo_id="test/model2", revision="not-main2"),
365
+ ModelRepo(repo_id="test/model1", use_volume=False),
366
+ ModelRepo(
367
+ repo_id="test/model2", revision="not-main2", use_volume=False
368
+ ),
360
369
  ]
361
370
  ),
362
371
  )
363
372
 
364
373
  new_config = default_config
365
374
  new_config["model_cache"] = [
366
- {"repo_id": "test/model1"},
367
- {"repo_id": "test/model2", "revision": "not-main2"},
375
+ {"repo_id": "test/model1", "use_volume": False},
376
+ {"repo_id": "test/model2", "revision": "not-main2", "use_volume": False},
368
377
  ]
369
378
 
370
379
  assert new_config == config.to_dict(verbose=False)
@@ -17,7 +17,8 @@ RUN /usr/local/bin/python3 -c "import sys; \
17
17
  || { echo "ERROR: Supplied base image does not have 3.8 <= python <= 3.13"; exit 1; }
18
18
  RUN if ! command -v uv >/dev/null 2>&1; then \
19
19
  command -v curl >/dev/null 2>&1 || (apt update && apt install -y curl) && \
20
- curl -LsSf --retry 5 --retry-delay 5 https://astral.sh/uv/0.7.19/install.sh | sh; \
20
+ curl -LsSf --retry 5 --retry-delay 5 https://astral.sh/uv/0.8.22/install.sh | sh && \
21
+ test -x ${HOME}/.local/bin/uv; \
21
22
  fi
22
23
  ENV PATH=${PATH}:${HOME}/.local/bin
23
24
  ENV PYTHONUNBUFFERED="True"
@@ -35,6 +36,7 @@ COPY --chown= ./data ${APP_HOME}/data
35
36
  COPY --chown= ./server ${APP_HOME}
36
37
  COPY --chown= ./config.yaml ${APP_HOME}/config.yaml
37
38
  COPY --chown= ./model ${APP_HOME}/model
39
+ RUN mkdir -p /packages
38
40
  COPY --chown= ./packages /packages
39
41
  ENV INFERENCE_SERVER_PORT="8080"
40
42
  ENV SERVER_START_CMD="/usr/local/bin/python3 /app/main.py"
@@ -0,0 +1,14 @@
1
+ environment_variables: {}
2
+ external_package_dirs: []
3
+ model_metadata: {}
4
+ model_name: Test Build Commands
5
+ python_version: py39
6
+ resources:
7
+ accelerator: null
8
+ cpu: '1'
9
+ memory: 2Gi
10
+ use_gpu: false
11
+ secrets: {}
12
+ system_packages: []
13
+ build_commands:
14
+ - sed -i 's/TEST_FIRST_VALUE/TEST_SECOND_VALUE/g' /packages/constants/constants.py
@@ -0,0 +1,12 @@
1
+ from constants.constants import TEST_KEY
2
+
3
+
4
+ class Model:
5
+ def __init__(self, **kwargs):
6
+ pass
7
+
8
+ def load(self):
9
+ pass
10
+
11
+ def predict(self, input) -> str:
12
+ return TEST_KEY
@@ -0,0 +1 @@
1
+ TEST_KEY = "TEST_FIRST_VALUE"
@@ -8,6 +8,7 @@ requirements:
8
8
  - torch
9
9
  model_cache:
10
10
  - repo_id: julien-c/EsperBERTo-small
11
+ use_volume: false
11
12
  ignore_patterns:
12
13
  - "*.bin"
13
14
  - "*.msgpack"
@@ -2099,3 +2099,16 @@ async def test_websocket_ping_timeout_behavior(caplog):
2099
2099
 
2100
2100
  # We wait 3 seconds, so there should be ~3 PING/PONGS
2101
2101
  assert 2 <= caplog.text.count("PING") <= 4
2102
+
2103
+
2104
+ @pytest.mark.integration
2105
+ def test_build_commands_on_model_files(test_data_path):
2106
+ with ensure_kill_all():
2107
+ truss_dir = test_data_path / "test_build_commands_truss"
2108
+ tr = TrussHandle(truss_dir)
2109
+ container, urls = tr.docker_run_for_test()
2110
+ time.sleep(3) # Sleeping to allow the load to finish
2111
+
2112
+ response = requests.post(urls.predict_url, json={})
2113
+ assert response.status_code == 200
2114
+ assert response.json() == "TEST_SECOND_VALUE"
@@ -1,14 +1,19 @@
1
1
  import os
2
2
 
3
- from truss.util.env_vars import override_env_vars
3
+ from truss.util.env_vars import modify_env_vars
4
4
 
5
5
 
6
- def test_override_env_vars():
6
+ def test_modify_env_vars():
7
7
  os.environ["API_KEY"] = "original_key"
8
+ os.environ["AWS_CONFIG_FILE"] = "original_config_file"
8
9
 
9
- with override_env_vars({"API_KEY": "new_key", "DEBUG": "true"}):
10
+ with modify_env_vars(
11
+ overrides={"API_KEY": "new_key", "DEBUG": "true"}, deletions={"AWS_CONFIG_FILE"}
12
+ ):
10
13
  assert os.environ["API_KEY"] == "new_key"
11
14
  assert os.environ["DEBUG"] == "true"
15
+ assert "AWS_CONFIG_FILE" not in os.environ
12
16
 
13
17
  assert os.environ["API_KEY"] == "original_key"
14
18
  assert "DEBUG" not in os.environ
19
+ assert os.environ["AWS_CONFIG_FILE"] == "original_config_file"
truss/util/__init__.py ADDED
File without changes
truss/util/env_vars.py CHANGED
@@ -1,32 +1,43 @@
1
1
  import os
2
- from typing import Dict, Optional
2
+ from typing import Dict, Optional, Set
3
3
 
4
4
 
5
- class override_env_vars:
5
+ class modify_env_vars:
6
6
  """A context manager for temporarily overwriting environment variables.
7
7
 
8
8
  Usage:
9
- with override_env_vars({'API_KEY': 'test_key', 'DEBUG': 'true'}):
9
+ with modify_env_vars(overrides={'API_KEY': 'test_key', 'DEBUG': 'true'}, deletions={'AWS_CONFIG_FILE'}):
10
10
  # Environment variables are modified here
11
11
  ...
12
12
  # Original environment is restored here
13
13
  """
14
14
 
15
- def __init__(self, env_vars: Dict[str, str]):
15
+ def __init__(
16
+ self,
17
+ overrides: Optional[Dict[str, str]] = None,
18
+ deletions: Optional[Set[str]] = None,
19
+ ):
16
20
  """
17
21
  Args:
18
- env_vars: Dictionary of environment variables to set
22
+ overrides: Dictionary of environment variables to set
23
+ deletions: Set of environment variables to delete
19
24
  """
20
- self.env_vars = env_vars
25
+ self.overrides: Dict[str, str] = overrides or dict()
26
+ self.deletions: Set[str] = deletions or set()
21
27
  self.original_vars: Dict[str, Optional[str]] = {}
22
28
 
23
29
  def __enter__(self):
24
- for key in self.env_vars:
30
+ all_keys = set(self.overrides.keys()) | self.deletions
31
+ for key in all_keys:
25
32
  self.original_vars[key] = os.environ.get(key)
26
33
 
27
- for key, value in self.env_vars.items():
34
+ for key, value in self.overrides.items():
28
35
  os.environ[key] = value
29
36
 
37
+ for key in self.deletions:
38
+ if key in os.environ:
39
+ del os.environ[key]
40
+
30
41
  return self
31
42
 
32
43
  def __exit__(self, exc_type, exc_val, exc_tb):
@@ -0,0 +1,37 @@
1
+ import logging
2
+ from contextlib import contextmanager
3
+ from typing import Generator
4
+
5
+ from botocore.exceptions import ClientError, NoCredentialsError
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ @contextmanager
11
+ def handle_client_error(
12
+ operation_description: str = "AWS operation",
13
+ ) -> Generator[None, None, None]:
14
+ """
15
+ Context manager to handle common boto3 errors and convert them to RuntimeError.
16
+
17
+ Args:
18
+ operation_description: Description of the operation being performed for error messages
19
+
20
+ Raises:
21
+ RuntimeError: For NoCredentialsError, ClientError, and other exceptions
22
+ """
23
+ try:
24
+ yield
25
+ except NoCredentialsError as nce:
26
+ raise RuntimeError(
27
+ f"No AWS credentials found for {operation_description}\nOriginal exception: {str(nce)}"
28
+ )
29
+ except ClientError as ce:
30
+ raise RuntimeError(
31
+ f"AWS client error when {operation_description} (check your credentials): {str(ce)}"
32
+ )
33
+
34
+ except Exception as exc:
35
+ raise RuntimeError(
36
+ f"Unexpected error `{exc}` during `{operation_description}`\nOriginal exception: {str(exc)}"
37
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: truss
3
- Version: 0.11.6rc102
3
+ Version: 0.11.24rc2
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.35,>=0.0.30
40
+ Requires-Dist: truss-transfer<0.0.40,>=0.0.37
41
41
  Requires-Dist: watchfiles<0.20,>=0.19.0
42
42
  Description-Content-Type: text/markdown
43
43