modal 1.1.5.dev19__py3-none-any.whl → 1.1.5.dev21__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 modal might be problematic. Click here for more details.
- modal/_functions.py +12 -4
- modal/_pty.py +7 -3
- modal/app.py +12 -3
- modal/app.pyi +8 -4
- modal/client.pyi +2 -2
- modal/cls.py +7 -2
- modal/cls.pyi +7 -5
- modal/functions.pyi +6 -2
- modal/image.py +85 -15
- modal/image.pyi +54 -28
- modal/runner.py +2 -2
- modal/sandbox.py +112 -46
- modal/sandbox.pyi +148 -29
- modal/secret.py +1 -1
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/METADATA +1 -1
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/RECORD +21 -21
- modal_version/__init__.py +1 -1
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/top_level.txt +0 -0
modal/_functions.py
CHANGED
|
@@ -6,7 +6,7 @@ import textwrap
|
|
|
6
6
|
import time
|
|
7
7
|
import typing
|
|
8
8
|
import warnings
|
|
9
|
-
from collections.abc import AsyncGenerator, Sequence, Sized
|
|
9
|
+
from collections.abc import AsyncGenerator, Collection, Sequence, Sized
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import PurePosixPath
|
|
12
12
|
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Optional, Union
|
|
@@ -597,7 +597,7 @@ class _FunctionSpec:
|
|
|
597
597
|
|
|
598
598
|
image: Optional[_Image]
|
|
599
599
|
mounts: Sequence[_Mount]
|
|
600
|
-
secrets:
|
|
600
|
+
secrets: Collection[_Secret]
|
|
601
601
|
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem]
|
|
602
602
|
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
|
|
603
603
|
# TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
|
|
@@ -661,7 +661,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
661
661
|
info: FunctionInfo,
|
|
662
662
|
app,
|
|
663
663
|
image: _Image,
|
|
664
|
-
|
|
664
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
665
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
665
666
|
schedule: Optional[Schedule] = None,
|
|
666
667
|
is_generator: bool = False,
|
|
667
668
|
gpu: Union[GPU_T, list[GPU_T]] = None,
|
|
@@ -701,7 +702,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
701
702
|
_experimental_proxy_ip: Optional[str] = None,
|
|
702
703
|
_experimental_custom_scaling_factor: Optional[float] = None,
|
|
703
704
|
) -> "_Function":
|
|
704
|
-
"""mdmd:hidden
|
|
705
|
+
"""mdmd:hidden
|
|
706
|
+
|
|
707
|
+
Note: This is not intended to be public API.
|
|
708
|
+
"""
|
|
705
709
|
# Needed to avoid circular imports
|
|
706
710
|
from ._partial_function import _find_partial_methods_for_user_cls, _PartialFunctionFlags
|
|
707
711
|
|
|
@@ -736,6 +740,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
736
740
|
if is_generator:
|
|
737
741
|
raise InvalidError("Generator functions do not support retries.")
|
|
738
742
|
|
|
743
|
+
secrets = secrets or []
|
|
744
|
+
if env:
|
|
745
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
746
|
+
|
|
739
747
|
function_spec = _FunctionSpec(
|
|
740
748
|
mounts=all_mounts,
|
|
741
749
|
secrets=secrets,
|
modal/_pty.py
CHANGED
|
@@ -7,8 +7,11 @@ from typing import Optional
|
|
|
7
7
|
from modal_proto import api_pb2
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def get_winsz(fd) -> tuple[Optional[int], Optional[int]]:
|
|
10
|
+
def get_winsz(fd=None) -> tuple[Optional[int], Optional[int]]:
|
|
11
11
|
try:
|
|
12
|
+
if fd is None:
|
|
13
|
+
fd = sys.stdin.fileno()
|
|
14
|
+
|
|
12
15
|
import fcntl
|
|
13
16
|
import struct
|
|
14
17
|
import termios
|
|
@@ -40,8 +43,8 @@ def raw_terminal():
|
|
|
40
43
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
41
44
|
|
|
42
45
|
|
|
43
|
-
def get_pty_info(shell: bool) -> api_pb2.PTYInfo:
|
|
44
|
-
rows, cols = get_winsz(
|
|
46
|
+
def get_pty_info(shell: bool, no_terminate_on_idle_stdin: bool = False) -> api_pb2.PTYInfo:
|
|
47
|
+
rows, cols = get_winsz()
|
|
45
48
|
return api_pb2.PTYInfo(
|
|
46
49
|
enabled=True, # TODO(erikbern): deprecated
|
|
47
50
|
winsz_rows=rows,
|
|
@@ -50,4 +53,5 @@ def get_pty_info(shell: bool) -> api_pb2.PTYInfo:
|
|
|
50
53
|
env_colorterm=os.environ.get("COLORTERM"),
|
|
51
54
|
env_term_program=os.environ.get("TERM_PROGRAM"),
|
|
52
55
|
pty_type=api_pb2.PTYInfo.PTY_TYPE_SHELL if shell else api_pb2.PTYInfo.PTY_TYPE_FUNCTION,
|
|
56
|
+
no_terminate_on_idle_stdin=no_terminate_on_idle_stdin,
|
|
53
57
|
)
|
modal/app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import inspect
|
|
3
3
|
import typing
|
|
4
|
-
from collections.abc import AsyncGenerator, Coroutine, Sequence
|
|
4
|
+
from collections.abc import AsyncGenerator, Collection, Coroutine, Sequence
|
|
5
5
|
from pathlib import PurePosixPath
|
|
6
6
|
from textwrap import dedent
|
|
7
7
|
from typing import (
|
|
@@ -616,7 +616,8 @@ class _App:
|
|
|
616
616
|
*,
|
|
617
617
|
image: Optional[_Image] = None, # The image to run as the container for the function
|
|
618
618
|
schedule: Optional[Schedule] = None, # An optional Modal Schedule for the function
|
|
619
|
-
|
|
619
|
+
env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
|
|
620
|
+
secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
|
|
620
621
|
gpu: Union[
|
|
621
622
|
GPU_T, list[GPU_T]
|
|
622
623
|
] = None, # GPU request as string ("any", "T4", ...), object (`modal.GPU.A100()`, ...), or a list of either
|
|
@@ -694,6 +695,9 @@ class _App:
|
|
|
694
695
|
if allow_cross_region_volumes is not None:
|
|
695
696
|
deprecation_warning((2025, 4, 23), "The `allow_cross_region_volumes` parameter no longer has any effect.")
|
|
696
697
|
|
|
698
|
+
secrets = secrets or []
|
|
699
|
+
if env:
|
|
700
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
697
701
|
secrets = [*self._secrets, *secrets]
|
|
698
702
|
|
|
699
703
|
def wrapped(
|
|
@@ -846,7 +850,8 @@ class _App:
|
|
|
846
850
|
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
847
851
|
*,
|
|
848
852
|
image: Optional[_Image] = None, # The image to run as the container for the function
|
|
849
|
-
|
|
853
|
+
env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
|
|
854
|
+
secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
|
|
850
855
|
gpu: Union[
|
|
851
856
|
GPU_T, list[GPU_T]
|
|
852
857
|
] = None, # GPU request as string ("any", "T4", ...), object (`modal.GPU.A100()`, ...), or a list of either
|
|
@@ -920,6 +925,10 @@ class _App:
|
|
|
920
925
|
if allow_cross_region_volumes is not None:
|
|
921
926
|
deprecation_warning((2025, 4, 23), "The `allow_cross_region_volumes` parameter no longer has any effect.")
|
|
922
927
|
|
|
928
|
+
secrets = secrets or []
|
|
929
|
+
if env:
|
|
930
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
931
|
+
|
|
923
932
|
def wrapper(wrapped_cls: Union[CLS_T, _PartialFunction]) -> CLS_T:
|
|
924
933
|
# Check if the decorated object is a class
|
|
925
934
|
if isinstance(wrapped_cls, _PartialFunction):
|
modal/app.pyi
CHANGED
|
@@ -391,7 +391,8 @@ class _App:
|
|
|
391
391
|
*,
|
|
392
392
|
image: typing.Optional[modal.image._Image] = None,
|
|
393
393
|
schedule: typing.Optional[modal.schedule.Schedule] = None,
|
|
394
|
-
|
|
394
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
395
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
395
396
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
|
|
396
397
|
serialized: bool = False,
|
|
397
398
|
network_file_systems: dict[
|
|
@@ -445,7 +446,8 @@ class _App:
|
|
|
445
446
|
_warn_parentheses_missing=None,
|
|
446
447
|
*,
|
|
447
448
|
image: typing.Optional[modal.image._Image] = None,
|
|
448
|
-
|
|
449
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
450
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
449
451
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
|
|
450
452
|
serialized: bool = False,
|
|
451
453
|
network_file_systems: dict[
|
|
@@ -996,7 +998,8 @@ class App:
|
|
|
996
998
|
*,
|
|
997
999
|
image: typing.Optional[modal.image.Image] = None,
|
|
998
1000
|
schedule: typing.Optional[modal.schedule.Schedule] = None,
|
|
999
|
-
|
|
1001
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1002
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1000
1003
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
|
|
1001
1004
|
serialized: bool = False,
|
|
1002
1005
|
network_file_systems: dict[
|
|
@@ -1050,7 +1053,8 @@ class App:
|
|
|
1050
1053
|
_warn_parentheses_missing=None,
|
|
1051
1054
|
*,
|
|
1052
1055
|
image: typing.Optional[modal.image.Image] = None,
|
|
1053
|
-
|
|
1056
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1057
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1054
1058
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
|
|
1055
1059
|
serialized: bool = False,
|
|
1056
1060
|
network_file_systems: dict[
|
modal/client.pyi
CHANGED
|
@@ -33,7 +33,7 @@ class _Client:
|
|
|
33
33
|
server_url: str,
|
|
34
34
|
client_type: int,
|
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.1.5.
|
|
36
|
+
version: str = "1.1.5.dev21",
|
|
37
37
|
):
|
|
38
38
|
"""mdmd:hidden
|
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -164,7 +164,7 @@ class Client:
|
|
|
164
164
|
server_url: str,
|
|
165
165
|
client_type: int,
|
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
|
167
|
-
version: str = "1.1.5.
|
|
167
|
+
version: str = "1.1.5.dev21",
|
|
168
168
|
):
|
|
169
169
|
"""mdmd:hidden
|
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
modal/cls.py
CHANGED
|
@@ -81,7 +81,7 @@ def _get_class_constructor_signature(user_cls: type) -> inspect.Signature:
|
|
|
81
81
|
@dataclasses.dataclass()
|
|
82
82
|
class _ServiceOptions:
|
|
83
83
|
# Note that default values should always be "untruthy" so we can detect when they are not set
|
|
84
|
-
secrets:
|
|
84
|
+
secrets: Collection[_Secret] = ()
|
|
85
85
|
validated_volumes: typing.Sequence[tuple[str, _Volume]] = ()
|
|
86
86
|
resources: Optional[api_pb2.Resources] = None
|
|
87
87
|
retry_policy: Optional[api_pb2.FunctionRetryPolicy] = None
|
|
@@ -686,7 +686,8 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
686
686
|
cpu: Optional[Union[float, tuple[float, float]]] = None,
|
|
687
687
|
memory: Optional[Union[int, tuple[int, int]]] = None,
|
|
688
688
|
gpu: GPU_T = None,
|
|
689
|
-
|
|
689
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
690
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
690
691
|
volumes: dict[Union[str, os.PathLike], _Volume] = {},
|
|
691
692
|
retries: Optional[Union[int, Retries]] = None,
|
|
692
693
|
max_containers: Optional[int] = None, # Limit on the number of containers that can be concurrently running.
|
|
@@ -761,6 +762,10 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
761
762
|
cls = _Cls._from_loader(_load_from_base, rep=f"{self._name}.with_options(...)", is_another_app=True, deps=_deps)
|
|
762
763
|
cls._initialize_from_other(self)
|
|
763
764
|
|
|
765
|
+
secrets = secrets or []
|
|
766
|
+
if env:
|
|
767
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
768
|
+
|
|
764
769
|
new_options = _ServiceOptions(
|
|
765
770
|
secrets=secrets,
|
|
766
771
|
validated_volumes=validate_volumes(volumes),
|
modal/cls.pyi
CHANGED
|
@@ -24,9 +24,9 @@ def _use_annotation_parameters(user_cls: type) -> bool: ...
|
|
|
24
24
|
def _get_class_constructor_signature(user_cls: type) -> inspect.Signature: ...
|
|
25
25
|
|
|
26
26
|
class _ServiceOptions:
|
|
27
|
-
"""_ServiceOptions(secrets: Collection[modal.secret._Secret] = (), validated_volumes: Sequence[tuple[str, modal.volume._Volume]] = (), resources: Optional[modal_proto.api_pb2.Resources] = None, retry_policy: Optional[modal_proto.api_pb2.FunctionRetryPolicy] = None, max_containers: Optional[int] = None, buffer_containers: Optional[int] = None, scaledown_window: Optional[int] = None, timeout_secs: Optional[int] = None, max_concurrent_inputs: Optional[int] = None, target_concurrent_inputs: Optional[int] = None, batch_max_size: Optional[int] = None, batch_wait_ms: Optional[int] = None, scheduler_placement: Optional[modal_proto.api_pb2.SchedulerPlacement] = None, cloud: Optional[str] = None)"""
|
|
27
|
+
"""_ServiceOptions(secrets: collections.abc.Collection[modal.secret._Secret] = (), validated_volumes: Sequence[tuple[str, modal.volume._Volume]] = (), resources: Optional[modal_proto.api_pb2.Resources] = None, retry_policy: Optional[modal_proto.api_pb2.FunctionRetryPolicy] = None, max_containers: Optional[int] = None, buffer_containers: Optional[int] = None, scaledown_window: Optional[int] = None, timeout_secs: Optional[int] = None, max_concurrent_inputs: Optional[int] = None, target_concurrent_inputs: Optional[int] = None, batch_max_size: Optional[int] = None, batch_wait_ms: Optional[int] = None, scheduler_placement: Optional[modal_proto.api_pb2.SchedulerPlacement] = None, cloud: Optional[str] = None)"""
|
|
28
28
|
|
|
29
|
-
secrets:
|
|
29
|
+
secrets: collections.abc.Collection[modal.secret._Secret]
|
|
30
30
|
validated_volumes: typing.Sequence[tuple[str, modal.volume._Volume]]
|
|
31
31
|
resources: typing.Optional[modal_proto.api_pb2.Resources]
|
|
32
32
|
retry_policy: typing.Optional[modal_proto.api_pb2.FunctionRetryPolicy]
|
|
@@ -50,7 +50,7 @@ class _ServiceOptions:
|
|
|
50
50
|
|
|
51
51
|
def __init__(
|
|
52
52
|
self,
|
|
53
|
-
secrets:
|
|
53
|
+
secrets: collections.abc.Collection[modal.secret._Secret] = (),
|
|
54
54
|
validated_volumes: typing.Sequence[tuple[str, modal.volume._Volume]] = (),
|
|
55
55
|
resources: typing.Optional[modal_proto.api_pb2.Resources] = None,
|
|
56
56
|
retry_policy: typing.Optional[modal_proto.api_pb2.FunctionRetryPolicy] = None,
|
|
@@ -396,7 +396,8 @@ class _Cls(modal._object._Object):
|
|
|
396
396
|
cpu: typing.Union[float, tuple[float, float], None] = None,
|
|
397
397
|
memory: typing.Union[int, tuple[int, int], None] = None,
|
|
398
398
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
399
|
-
|
|
399
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
400
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
400
401
|
volumes: dict[typing.Union[str, os.PathLike], modal.volume._Volume] = {},
|
|
401
402
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
|
402
403
|
max_containers: typing.Optional[int] = None,
|
|
@@ -574,7 +575,8 @@ class Cls(modal.object.Object):
|
|
|
574
575
|
cpu: typing.Union[float, tuple[float, float], None] = None,
|
|
575
576
|
memory: typing.Union[int, tuple[int, int], None] = None,
|
|
576
577
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
577
|
-
|
|
578
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
579
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
578
580
|
volumes: dict[typing.Union[str, os.PathLike], modal.volume.Volume] = {},
|
|
579
581
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
|
580
582
|
max_containers: typing.Optional[int] = None,
|
modal/functions.pyi
CHANGED
|
@@ -68,7 +68,8 @@ class Function(
|
|
|
68
68
|
info: modal._utils.function_utils.FunctionInfo,
|
|
69
69
|
app,
|
|
70
70
|
image: modal.image.Image,
|
|
71
|
-
|
|
71
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
72
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
72
73
|
schedule: typing.Optional[modal.schedule.Schedule] = None,
|
|
73
74
|
is_generator: bool = False,
|
|
74
75
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
|
|
@@ -111,7 +112,10 @@ class Function(
|
|
|
111
112
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
112
113
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
113
114
|
) -> Function:
|
|
114
|
-
"""mdmd:hidden
|
|
115
|
+
"""mdmd:hidden
|
|
116
|
+
|
|
117
|
+
Note: This is not intended to be public API.
|
|
118
|
+
"""
|
|
115
119
|
...
|
|
116
120
|
|
|
117
121
|
def _bind_parameters(
|
modal/image.py
CHANGED
|
@@ -7,7 +7,7 @@ import shlex
|
|
|
7
7
|
import sys
|
|
8
8
|
import typing
|
|
9
9
|
import warnings
|
|
10
|
-
from collections.abc import Sequence
|
|
10
|
+
from collections.abc import Collection, Sequence
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
from inspect import isfunction
|
|
13
13
|
from pathlib import Path, PurePosixPath
|
|
@@ -491,7 +491,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
491
491
|
*,
|
|
492
492
|
base_images: Optional[dict[str, "_Image"]] = None,
|
|
493
493
|
dockerfile_function: Optional[Callable[[ImageBuilderVersion], DockerfileSpec]] = None,
|
|
494
|
-
secrets: Optional[
|
|
494
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
495
495
|
gpu_config: Optional[api_pb2.GPUConfig] = None,
|
|
496
496
|
build_function: Optional["modal._functions._Function"] = None,
|
|
497
497
|
build_function_input: Optional[api_pb2.FunctionInput] = None,
|
|
@@ -877,7 +877,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
877
877
|
pre: bool = False, # Passes --pre (allow pre-releases) to pip install
|
|
878
878
|
extra_options: str = "", # Additional options to pass to pip install, e.g. "--no-build-isolation --no-clean"
|
|
879
879
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
880
|
-
|
|
880
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
881
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
881
882
|
gpu: GPU_T = None,
|
|
882
883
|
) -> "_Image":
|
|
883
884
|
"""Install a list of Python packages using pip.
|
|
@@ -924,6 +925,10 @@ class _Image(_Object, type_prefix="im"):
|
|
|
924
925
|
commands = [cmd.strip() for cmd in commands]
|
|
925
926
|
return DockerfileSpec(commands=commands, context_files={})
|
|
926
927
|
|
|
928
|
+
secrets = secrets or []
|
|
929
|
+
if env:
|
|
930
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
931
|
+
|
|
927
932
|
gpu_config = parse_gpu_config(gpu)
|
|
928
933
|
return _Image._from_args(
|
|
929
934
|
base_images={"base": self},
|
|
@@ -943,7 +948,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
943
948
|
pre: bool = False, # Passes --pre (allow pre-releases) to pip install
|
|
944
949
|
extra_options: str = "", # Additional options to pass to pip install, e.g. "--no-build-isolation --no-clean"
|
|
945
950
|
gpu: GPU_T = None,
|
|
946
|
-
|
|
951
|
+
env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
|
|
952
|
+
secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
|
|
947
953
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
948
954
|
) -> "_Image":
|
|
949
955
|
"""
|
|
@@ -977,12 +983,16 @@ class _Image(_Object, type_prefix="im"):
|
|
|
977
983
|
)
|
|
978
984
|
```
|
|
979
985
|
"""
|
|
986
|
+
|
|
980
987
|
if not secrets:
|
|
981
988
|
raise InvalidError(
|
|
982
989
|
"No secrets provided to function. "
|
|
983
990
|
"Installing private packages requires tokens to be passed via modal.Secret objects."
|
|
984
991
|
)
|
|
985
992
|
|
|
993
|
+
if env:
|
|
994
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
995
|
+
|
|
986
996
|
invalid_repos = []
|
|
987
997
|
install_urls = []
|
|
988
998
|
for repo_ref in repositories:
|
|
@@ -1044,11 +1054,16 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1044
1054
|
pre: bool = False, # Passes --pre (allow pre-releases) to pip install
|
|
1045
1055
|
extra_options: str = "", # Additional options to pass to pip install, e.g. "--no-build-isolation --no-clean"
|
|
1046
1056
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
1047
|
-
|
|
1057
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1058
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1048
1059
|
gpu: GPU_T = None,
|
|
1049
1060
|
) -> "_Image":
|
|
1050
1061
|
"""Install a list of Python packages from a local `requirements.txt` file."""
|
|
1051
1062
|
|
|
1063
|
+
secrets = secrets or []
|
|
1064
|
+
if env:
|
|
1065
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1066
|
+
|
|
1052
1067
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
|
1053
1068
|
requirements_txt_path = os.path.expanduser(requirements_txt)
|
|
1054
1069
|
context_files = {"/.requirements.txt": requirements_txt_path}
|
|
@@ -1085,7 +1100,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1085
1100
|
pre: bool = False, # Passes --pre (allow pre-releases) to pip install
|
|
1086
1101
|
extra_options: str = "", # Additional options to pass to pip install, e.g. "--no-build-isolation --no-clean"
|
|
1087
1102
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
1088
|
-
|
|
1103
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1104
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1089
1105
|
gpu: GPU_T = None,
|
|
1090
1106
|
) -> "_Image":
|
|
1091
1107
|
"""Install dependencies specified by a local `pyproject.toml` file.
|
|
@@ -1096,6 +1112,10 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1096
1112
|
all of the packages in each listed section are installed as well.
|
|
1097
1113
|
"""
|
|
1098
1114
|
|
|
1115
|
+
secrets = secrets or []
|
|
1116
|
+
if env:
|
|
1117
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1118
|
+
|
|
1099
1119
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
|
1100
1120
|
# Defer toml import so we don't need it in the container runtime environment
|
|
1101
1121
|
import toml
|
|
@@ -1147,7 +1167,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1147
1167
|
extra_options: str = "", # Additional options to pass to pip install, e.g. "--no-build-isolation"
|
|
1148
1168
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
1149
1169
|
uv_version: Optional[str] = None, # uv version to use
|
|
1150
|
-
|
|
1170
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1171
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1151
1172
|
gpu: GPU_T = None,
|
|
1152
1173
|
) -> "_Image":
|
|
1153
1174
|
"""Install a list of Python packages using uv pip install.
|
|
@@ -1166,6 +1187,11 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1166
1187
|
|
|
1167
1188
|
Added in v1.1.0.
|
|
1168
1189
|
"""
|
|
1190
|
+
|
|
1191
|
+
secrets = secrets or []
|
|
1192
|
+
if env:
|
|
1193
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1194
|
+
|
|
1169
1195
|
pkgs = _flatten_str_args("uv_pip_install", "packages", packages)
|
|
1170
1196
|
|
|
1171
1197
|
if requirements is None or isinstance(requirements, list):
|
|
@@ -1261,7 +1287,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1261
1287
|
poetry_version: Optional[str] = "latest", # Version of poetry to install, or None to skip installation
|
|
1262
1288
|
# If set to True, use old installer. See https://github.com/python-poetry/poetry/issues/3336
|
|
1263
1289
|
old_installer: bool = False,
|
|
1264
|
-
|
|
1290
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1291
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1265
1292
|
gpu: GPU_T = None,
|
|
1266
1293
|
) -> "_Image":
|
|
1267
1294
|
"""Install poetry *dependencies* specified by a local `pyproject.toml` file.
|
|
@@ -1277,6 +1304,10 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1277
1304
|
version, with versions 2024.10 and earlier limiting poetry to 1.x.
|
|
1278
1305
|
"""
|
|
1279
1306
|
|
|
1307
|
+
secrets = secrets or []
|
|
1308
|
+
if env:
|
|
1309
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1310
|
+
|
|
1280
1311
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
|
1281
1312
|
context_files = {"/.pyproject.toml": os.path.expanduser(poetry_pyproject_toml)}
|
|
1282
1313
|
|
|
@@ -1346,7 +1377,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1346
1377
|
frozen: bool = True, # If True, then we run `uv sync --frozen` when a uv.lock file is present
|
|
1347
1378
|
extra_options: str = "", # Extra options to pass to `uv sync`
|
|
1348
1379
|
uv_version: Optional[str] = None, # uv version to use
|
|
1349
|
-
|
|
1380
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1381
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1350
1382
|
gpu: GPU_T = None,
|
|
1351
1383
|
) -> "_Image":
|
|
1352
1384
|
"""Creates a virtual environment with the dependencies in a uv managed project with `uv sync`.
|
|
@@ -1362,6 +1394,10 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1362
1394
|
Added in v1.1.0.
|
|
1363
1395
|
"""
|
|
1364
1396
|
|
|
1397
|
+
secrets = secrets or []
|
|
1398
|
+
if env:
|
|
1399
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1400
|
+
|
|
1365
1401
|
def _normalize_items(items, name) -> list[str]:
|
|
1366
1402
|
if items is None:
|
|
1367
1403
|
return []
|
|
@@ -1494,7 +1530,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1494
1530
|
self,
|
|
1495
1531
|
*dockerfile_commands: Union[str, list[str]],
|
|
1496
1532
|
context_files: dict[str, str] = {},
|
|
1497
|
-
|
|
1533
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1534
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1498
1535
|
gpu: GPU_T = None,
|
|
1499
1536
|
context_mount: Optional[_Mount] = None, # Deprecated: the context is now inferred
|
|
1500
1537
|
context_dir: Optional[Union[Path, str]] = None, # Context for relative COPY commands
|
|
@@ -1552,6 +1589,10 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1552
1589
|
if not cmds:
|
|
1553
1590
|
return self
|
|
1554
1591
|
|
|
1592
|
+
secrets = secrets or []
|
|
1593
|
+
if env:
|
|
1594
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1595
|
+
|
|
1555
1596
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
|
1556
1597
|
return DockerfileSpec(commands=["FROM base", *cmds], context_files=context_files)
|
|
1557
1598
|
|
|
@@ -1595,11 +1636,17 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1595
1636
|
def run_commands(
|
|
1596
1637
|
self,
|
|
1597
1638
|
*commands: Union[str, list[str]],
|
|
1598
|
-
|
|
1639
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1640
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1599
1641
|
gpu: GPU_T = None,
|
|
1600
1642
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
1601
1643
|
) -> "_Image":
|
|
1602
1644
|
"""Extend an image with a list of shell commands to run."""
|
|
1645
|
+
|
|
1646
|
+
secrets = secrets or []
|
|
1647
|
+
if env:
|
|
1648
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1649
|
+
|
|
1603
1650
|
cmds = _flatten_str_args("run_commands", "commands", commands)
|
|
1604
1651
|
if not cmds:
|
|
1605
1652
|
return self
|
|
@@ -1658,10 +1705,16 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1658
1705
|
# A list of Conda channels, eg. ["conda-forge", "nvidia"].
|
|
1659
1706
|
channels: list[str] = [],
|
|
1660
1707
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
1661
|
-
|
|
1708
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1709
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1662
1710
|
gpu: GPU_T = None,
|
|
1663
1711
|
) -> "_Image":
|
|
1664
1712
|
"""Install a list of additional packages using micromamba."""
|
|
1713
|
+
|
|
1714
|
+
secrets = secrets or []
|
|
1715
|
+
if env:
|
|
1716
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
1717
|
+
|
|
1665
1718
|
pkgs = _flatten_str_args("micromamba_install", "packages", packages)
|
|
1666
1719
|
if not pkgs and spec_file is None:
|
|
1667
1720
|
return self
|
|
@@ -1909,7 +1962,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1909
1962
|
context_mount: Optional[_Mount] = None, # Deprecated: the context is now inferred
|
|
1910
1963
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
1911
1964
|
context_dir: Optional[Union[Path, str]] = None, # Context for relative COPY commands
|
|
1912
|
-
|
|
1965
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1966
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1913
1967
|
gpu: GPU_T = None,
|
|
1914
1968
|
add_python: Optional[str] = None,
|
|
1915
1969
|
build_args: dict[str, str] = {},
|
|
@@ -1964,6 +2018,11 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1964
2018
|
)
|
|
1965
2019
|
```
|
|
1966
2020
|
"""
|
|
2021
|
+
|
|
2022
|
+
secrets = secrets or []
|
|
2023
|
+
if env:
|
|
2024
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
2025
|
+
|
|
1967
2026
|
if context_mount is not None:
|
|
1968
2027
|
deprecation_warning(
|
|
1969
2028
|
(2025, 1, 13),
|
|
@@ -2078,7 +2137,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2078
2137
|
self,
|
|
2079
2138
|
*packages: Union[str, list[str]], # A list of packages, e.g. ["ssh", "libpq-dev"]
|
|
2080
2139
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
|
2081
|
-
|
|
2140
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
2141
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
2082
2142
|
gpu: GPU_T = None,
|
|
2083
2143
|
) -> "_Image":
|
|
2084
2144
|
"""Install a list of Debian packages using `apt`.
|
|
@@ -2103,6 +2163,10 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2103
2163
|
]
|
|
2104
2164
|
return DockerfileSpec(commands=commands, context_files={})
|
|
2105
2165
|
|
|
2166
|
+
secrets = secrets or []
|
|
2167
|
+
if env:
|
|
2168
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
2169
|
+
|
|
2106
2170
|
return _Image._from_args(
|
|
2107
2171
|
base_images={"base": self},
|
|
2108
2172
|
dockerfile_function=build_dockerfile,
|
|
@@ -2115,7 +2179,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2115
2179
|
self,
|
|
2116
2180
|
raw_f: Callable[..., Any],
|
|
2117
2181
|
*,
|
|
2118
|
-
|
|
2182
|
+
env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
|
|
2183
|
+
secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
|
|
2119
2184
|
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {}, # Volume mount paths
|
|
2120
2185
|
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {}, # NFS mount paths
|
|
2121
2186
|
gpu: Union[GPU_T, list[GPU_T]] = None, # Requested GPU or or list of acceptable GPUs( e.g. ["A10", "A100"])
|
|
@@ -2157,6 +2222,11 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2157
2222
|
)
|
|
2158
2223
|
```
|
|
2159
2224
|
"""
|
|
2225
|
+
|
|
2226
|
+
secrets = secrets or []
|
|
2227
|
+
if env:
|
|
2228
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
2229
|
+
|
|
2160
2230
|
from ._functions import _Function
|
|
2161
2231
|
|
|
2162
2232
|
if not callable(raw_f):
|