modal 1.0.4.dev12__py3-none-any.whl → 1.0.5__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 (67) hide show
  1. modal/_clustered_functions.pyi +13 -3
  2. modal/_functions.py +84 -46
  3. modal/_partial_function.py +1 -1
  4. modal/_runtime/container_io_manager.pyi +222 -40
  5. modal/_runtime/execution_context.pyi +60 -6
  6. modal/_serialization.py +25 -2
  7. modal/_tunnel.pyi +380 -12
  8. modal/_utils/async_utils.py +1 -1
  9. modal/_utils/blob_utils.py +56 -19
  10. modal/_utils/function_utils.py +33 -7
  11. modal/_utils/grpc_utils.py +11 -4
  12. modal/app.py +5 -5
  13. modal/app.pyi +658 -48
  14. modal/cli/run.py +2 -1
  15. modal/client.pyi +224 -36
  16. modal/cloud_bucket_mount.pyi +192 -4
  17. modal/cls.py +7 -7
  18. modal/cls.pyi +442 -35
  19. modal/container_process.pyi +103 -14
  20. modal/dict.py +4 -4
  21. modal/dict.pyi +453 -51
  22. modal/environments.pyi +41 -9
  23. modal/exception.py +6 -2
  24. modal/experimental/__init__.py +90 -0
  25. modal/experimental/ipython.py +11 -7
  26. modal/file_io.pyi +236 -45
  27. modal/functions.pyi +573 -65
  28. modal/gpu.py +1 -1
  29. modal/image.py +1 -1
  30. modal/image.pyi +1256 -74
  31. modal/io_streams.py +8 -4
  32. modal/io_streams.pyi +348 -38
  33. modal/mount.pyi +261 -31
  34. modal/network_file_system.py +3 -3
  35. modal/network_file_system.pyi +307 -26
  36. modal/object.pyi +48 -9
  37. modal/parallel_map.py +93 -19
  38. modal/parallel_map.pyi +160 -15
  39. modal/partial_function.pyi +255 -14
  40. modal/proxy.py +1 -1
  41. modal/proxy.pyi +28 -3
  42. modal/queue.py +4 -4
  43. modal/queue.pyi +447 -30
  44. modal/runner.pyi +160 -22
  45. modal/sandbox.py +8 -7
  46. modal/sandbox.pyi +310 -50
  47. modal/schedule.py +1 -1
  48. modal/secret.py +2 -2
  49. modal/secret.pyi +164 -15
  50. modal/snapshot.pyi +25 -4
  51. modal/token_flow.pyi +28 -8
  52. modal/volume.py +41 -4
  53. modal/volume.pyi +693 -59
  54. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/METADATA +3 -3
  55. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/RECORD +67 -67
  56. modal_proto/api.proto +56 -0
  57. modal_proto/api_grpc.py +48 -0
  58. modal_proto/api_pb2.py +874 -780
  59. modal_proto/api_pb2.pyi +194 -8
  60. modal_proto/api_pb2_grpc.py +100 -0
  61. modal_proto/api_pb2_grpc.pyi +32 -0
  62. modal_proto/modal_api_grpc.py +3 -0
  63. modal_version/__init__.py +1 -1
  64. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/WHEEL +0 -0
  65. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/entry_points.txt +0 -0
  66. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/licenses/LICENSE +0 -0
  67. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/top_level.txt +0 -0
modal/secret.pyi CHANGED
@@ -5,16 +5,82 @@ import typing
5
5
  import typing_extensions
6
6
 
7
7
  class _Secret(modal._object._Object):
8
+ """Secrets provide a dictionary of environment variables for images.
9
+
10
+ Secrets are a secure way to add credentials and other sensitive information
11
+ to the containers your functions run in. You can create and edit secrets on
12
+ [the dashboard](https://modal.com/secrets), or programmatically from Python code.
13
+
14
+ See [the secrets guide page](https://modal.com/docs/guide/secrets) for more information.
15
+ """
8
16
  @staticmethod
9
- def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> _Secret: ...
17
+ def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> _Secret:
18
+ """Create a secret from a str-str dictionary. Values can also be `None`, which is ignored.
19
+
20
+ Usage:
21
+ ```python
22
+ @app.function(secrets=[modal.Secret.from_dict({"FOO": "bar"})])
23
+ def run():
24
+ print(os.environ["FOO"])
25
+ ```
26
+ """
27
+ ...
28
+
10
29
  @staticmethod
11
- def from_local_environ(env_keys: list[str]) -> _Secret: ...
30
+ def from_local_environ(env_keys: list[str]) -> _Secret:
31
+ """Create secrets from local environment variables automatically."""
32
+ ...
33
+
12
34
  @staticmethod
13
- def from_dotenv(path=None, *, filename=".env") -> _Secret: ...
35
+ def from_dotenv(path=None, *, filename=".env") -> _Secret:
36
+ """Create secrets from a .env file automatically.
37
+
38
+ If no argument is provided, it will use the current working directory as the starting
39
+ point for finding a `.env` file. Note that it does not use the location of the module
40
+ calling `Secret.from_dotenv`.
41
+
42
+ If called with an argument, it will use that as a starting point for finding `.env` files.
43
+ In particular, you can call it like this:
44
+ ```python
45
+ @app.function(secrets=[modal.Secret.from_dotenv(__file__)])
46
+ def run():
47
+ print(os.environ["USERNAME"]) # Assumes USERNAME is defined in your .env file
48
+ ```
49
+
50
+ This will use the location of the script calling `modal.Secret.from_dotenv` as a
51
+ starting point for finding the `.env` file.
52
+
53
+ A file named `.env` is expected by default, but this can be overridden with the `filename`
54
+ keyword argument:
55
+
56
+ ```python
57
+ @app.function(secrets=[modal.Secret.from_dotenv(filename=".env-dev")])
58
+ def run():
59
+ ...
60
+ ```
61
+ """
62
+ ...
63
+
14
64
  @staticmethod
15
65
  def from_name(
16
66
  name: str, *, namespace=1, environment_name: typing.Optional[str] = None, required_keys: list[str] = []
17
- ) -> _Secret: ...
67
+ ) -> _Secret:
68
+ """Reference a Secret by its name.
69
+
70
+ In contrast to most other Modal objects, named Secrets must be provisioned
71
+ from the Dashboard. See other methods for alternate ways of creating a new
72
+ Secret from code.
73
+
74
+ ```python
75
+ secret = modal.Secret.from_name("my-secret")
76
+
77
+ @app.function(secrets=[secret])
78
+ def run():
79
+ ...
80
+ ```
81
+ """
82
+ ...
83
+
18
84
  @staticmethod
19
85
  async def lookup(
20
86
  name: str,
@@ -22,7 +88,10 @@ class _Secret(modal._object._Object):
22
88
  client: typing.Optional[modal.client._Client] = None,
23
89
  environment_name: typing.Optional[str] = None,
24
90
  required_keys: list[str] = [],
25
- ) -> _Secret: ...
91
+ ) -> _Secret:
92
+ """mdmd:hidden"""
93
+ ...
94
+
26
95
  @staticmethod
27
96
  async def create_deployed(
28
97
  deployment_name: str,
@@ -31,20 +100,90 @@ class _Secret(modal._object._Object):
31
100
  client: typing.Optional[modal.client._Client] = None,
32
101
  environment_name: typing.Optional[str] = None,
33
102
  overwrite: bool = False,
34
- ) -> str: ...
103
+ ) -> str:
104
+ """mdmd:hidden"""
105
+ ...
35
106
 
36
107
  class Secret(modal.object.Object):
37
- def __init__(self, *args, **kwargs): ...
108
+ """Secrets provide a dictionary of environment variables for images.
109
+
110
+ Secrets are a secure way to add credentials and other sensitive information
111
+ to the containers your functions run in. You can create and edit secrets on
112
+ [the dashboard](https://modal.com/secrets), or programmatically from Python code.
113
+
114
+ See [the secrets guide page](https://modal.com/docs/guide/secrets) for more information.
115
+ """
116
+ def __init__(self, *args, **kwargs):
117
+ """mdmd:hidden"""
118
+ ...
119
+
38
120
  @staticmethod
39
- def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> Secret: ...
121
+ def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> Secret:
122
+ """Create a secret from a str-str dictionary. Values can also be `None`, which is ignored.
123
+
124
+ Usage:
125
+ ```python
126
+ @app.function(secrets=[modal.Secret.from_dict({"FOO": "bar"})])
127
+ def run():
128
+ print(os.environ["FOO"])
129
+ ```
130
+ """
131
+ ...
132
+
40
133
  @staticmethod
41
- def from_local_environ(env_keys: list[str]) -> Secret: ...
134
+ def from_local_environ(env_keys: list[str]) -> Secret:
135
+ """Create secrets from local environment variables automatically."""
136
+ ...
137
+
42
138
  @staticmethod
43
- def from_dotenv(path=None, *, filename=".env") -> Secret: ...
139
+ def from_dotenv(path=None, *, filename=".env") -> Secret:
140
+ """Create secrets from a .env file automatically.
141
+
142
+ If no argument is provided, it will use the current working directory as the starting
143
+ point for finding a `.env` file. Note that it does not use the location of the module
144
+ calling `Secret.from_dotenv`.
145
+
146
+ If called with an argument, it will use that as a starting point for finding `.env` files.
147
+ In particular, you can call it like this:
148
+ ```python
149
+ @app.function(secrets=[modal.Secret.from_dotenv(__file__)])
150
+ def run():
151
+ print(os.environ["USERNAME"]) # Assumes USERNAME is defined in your .env file
152
+ ```
153
+
154
+ This will use the location of the script calling `modal.Secret.from_dotenv` as a
155
+ starting point for finding the `.env` file.
156
+
157
+ A file named `.env` is expected by default, but this can be overridden with the `filename`
158
+ keyword argument:
159
+
160
+ ```python
161
+ @app.function(secrets=[modal.Secret.from_dotenv(filename=".env-dev")])
162
+ def run():
163
+ ...
164
+ ```
165
+ """
166
+ ...
167
+
44
168
  @staticmethod
45
169
  def from_name(
46
170
  name: str, *, namespace=1, environment_name: typing.Optional[str] = None, required_keys: list[str] = []
47
- ) -> Secret: ...
171
+ ) -> Secret:
172
+ """Reference a Secret by its name.
173
+
174
+ In contrast to most other Modal objects, named Secrets must be provisioned
175
+ from the Dashboard. See other methods for alternate ways of creating a new
176
+ Secret from code.
177
+
178
+ ```python
179
+ secret = modal.Secret.from_name("my-secret")
180
+
181
+ @app.function(secrets=[secret])
182
+ def run():
183
+ ...
184
+ ```
185
+ """
186
+ ...
48
187
 
49
188
  class __lookup_spec(typing_extensions.Protocol):
50
189
  def __call__(
@@ -55,7 +194,10 @@ class Secret(modal.object.Object):
55
194
  client: typing.Optional[modal.client.Client] = None,
56
195
  environment_name: typing.Optional[str] = None,
57
196
  required_keys: list[str] = [],
58
- ) -> Secret: ...
197
+ ) -> Secret:
198
+ """mdmd:hidden"""
199
+ ...
200
+
59
201
  async def aio(
60
202
  self,
61
203
  /,
@@ -64,7 +206,9 @@ class Secret(modal.object.Object):
64
206
  client: typing.Optional[modal.client.Client] = None,
65
207
  environment_name: typing.Optional[str] = None,
66
208
  required_keys: list[str] = [],
67
- ) -> Secret: ...
209
+ ) -> Secret:
210
+ """mdmd:hidden"""
211
+ ...
68
212
 
69
213
  lookup: __lookup_spec
70
214
 
@@ -78,7 +222,10 @@ class Secret(modal.object.Object):
78
222
  client: typing.Optional[modal.client.Client] = None,
79
223
  environment_name: typing.Optional[str] = None,
80
224
  overwrite: bool = False,
81
- ) -> str: ...
225
+ ) -> str:
226
+ """mdmd:hidden"""
227
+ ...
228
+
82
229
  async def aio(
83
230
  self,
84
231
  /,
@@ -88,6 +235,8 @@ class Secret(modal.object.Object):
88
235
  client: typing.Optional[modal.client.Client] = None,
89
236
  environment_name: typing.Optional[str] = None,
90
237
  overwrite: bool = False,
91
- ) -> str: ...
238
+ ) -> str:
239
+ """mdmd:hidden"""
240
+ ...
92
241
 
93
242
  create_deployed: __create_deployed_spec
modal/snapshot.pyi CHANGED
@@ -5,14 +5,35 @@ import typing
5
5
  import typing_extensions
6
6
 
7
7
  class _SandboxSnapshot(modal._object._Object):
8
+ """> Sandbox memory snapshots are in **early preview**.
9
+
10
+ A `SandboxSnapshot` object lets you interact with a stored Sandbox snapshot that was created by calling
11
+ `._experimental_snapshot()` on a Sandbox instance. This includes both the filesystem and memory state of
12
+ the original Sandbox at the time the snapshot was taken.
13
+ """
8
14
  @staticmethod
9
- async def from_id(sandbox_snapshot_id: str, client: typing.Optional[modal.client._Client] = None): ...
15
+ async def from_id(sandbox_snapshot_id: str, client: typing.Optional[modal.client._Client] = None):
16
+ """Construct a `SandboxSnapshot` object from a sandbox snapshot ID."""
17
+ ...
10
18
 
11
19
  class SandboxSnapshot(modal.object.Object):
12
- def __init__(self, *args, **kwargs): ...
20
+ """> Sandbox memory snapshots are in **early preview**.
21
+
22
+ A `SandboxSnapshot` object lets you interact with a stored Sandbox snapshot that was created by calling
23
+ `._experimental_snapshot()` on a Sandbox instance. This includes both the filesystem and memory state of
24
+ the original Sandbox at the time the snapshot was taken.
25
+ """
26
+ def __init__(self, *args, **kwargs):
27
+ """mdmd:hidden"""
28
+ ...
13
29
 
14
30
  class __from_id_spec(typing_extensions.Protocol):
15
- def __call__(self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None): ...
16
- async def aio(self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None): ...
31
+ def __call__(self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None):
32
+ """Construct a `SandboxSnapshot` object from a sandbox snapshot ID."""
33
+ ...
34
+
35
+ async def aio(self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None):
36
+ """Construct a `SandboxSnapshot` object from a sandbox snapshot ID."""
37
+ ...
17
38
 
18
39
  from_id: __from_id_spec
modal/token_flow.pyi CHANGED
@@ -5,13 +5,21 @@ import typing
5
5
  import typing_extensions
6
6
 
7
7
  class _TokenFlow:
8
- def __init__(self, client: modal.client._Client): ...
8
+ def __init__(self, client: modal.client._Client):
9
+ """Initialize self. See help(type(self)) for accurate signature."""
10
+ ...
11
+
9
12
  def start(
10
13
  self, utm_source: typing.Optional[str] = None, next_url: typing.Optional[str] = None
11
- ) -> typing.AsyncContextManager[tuple[str, str, str]]: ...
14
+ ) -> typing.AsyncContextManager[tuple[str, str, str]]:
15
+ """mdmd:hidden"""
16
+ ...
17
+
12
18
  async def finish(
13
19
  self, timeout: float = 40.0, grpc_extra_timeout: float = 5.0
14
- ) -> typing.Optional[modal_proto.api_pb2.TokenFlowWaitResponse]: ...
20
+ ) -> typing.Optional[modal_proto.api_pb2.TokenFlowWaitResponse]:
21
+ """mdmd:hidden"""
22
+ ...
15
23
 
16
24
  SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
17
25
 
@@ -21,20 +29,30 @@ class TokenFlow:
21
29
  class __start_spec(typing_extensions.Protocol[SUPERSELF]):
22
30
  def __call__(
23
31
  self, /, utm_source: typing.Optional[str] = None, next_url: typing.Optional[str] = None
24
- ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[tuple[str, str, str]]: ...
32
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[tuple[str, str, str]]:
33
+ """mdmd:hidden"""
34
+ ...
35
+
25
36
  def aio(
26
37
  self, /, utm_source: typing.Optional[str] = None, next_url: typing.Optional[str] = None
27
- ) -> typing.AsyncContextManager[tuple[str, str, str]]: ...
38
+ ) -> typing.AsyncContextManager[tuple[str, str, str]]:
39
+ """mdmd:hidden"""
40
+ ...
28
41
 
29
42
  start: __start_spec[typing_extensions.Self]
30
43
 
31
44
  class __finish_spec(typing_extensions.Protocol[SUPERSELF]):
32
45
  def __call__(
33
46
  self, /, timeout: float = 40.0, grpc_extra_timeout: float = 5.0
34
- ) -> typing.Optional[modal_proto.api_pb2.TokenFlowWaitResponse]: ...
47
+ ) -> typing.Optional[modal_proto.api_pb2.TokenFlowWaitResponse]:
48
+ """mdmd:hidden"""
49
+ ...
50
+
35
51
  async def aio(
36
52
  self, /, timeout: float = 40.0, grpc_extra_timeout: float = 5.0
37
- ) -> typing.Optional[modal_proto.api_pb2.TokenFlowWaitResponse]: ...
53
+ ) -> typing.Optional[modal_proto.api_pb2.TokenFlowWaitResponse]:
54
+ """mdmd:hidden"""
55
+ ...
38
56
 
39
57
  finish: __finish_spec[typing_extensions.Self]
40
58
 
@@ -55,4 +73,6 @@ async def _set_token(
55
73
  verify: bool = True,
56
74
  server_url: typing.Optional[str] = None,
57
75
  ): ...
58
- def _open_url(url: str) -> bool: ...
76
+ def _open_url(url: str) -> bool:
77
+ """Opens url in web browser, making sure we use a modern one (not Lynx etc)"""
78
+ ...
modal/volume.py CHANGED
@@ -135,6 +135,34 @@ class _Volume(_Object, type_prefix="vo"):
135
135
 
136
136
  _lock: Optional[asyncio.Lock] = None
137
137
  _metadata: "typing.Optional[api_pb2.VolumeMetadata]"
138
+ _read_only: bool = False
139
+
140
+ def read_only(self) -> "_Volume":
141
+ """Configure Volume to mount as read-only.
142
+
143
+ **Example**
144
+
145
+ ```python
146
+ import modal
147
+
148
+ volume = modal.Volume.from_name("my-volume", create_if_missing=True)
149
+
150
+ @app.function(volumes={"/mnt/items": volume.read_only()})
151
+ def f():
152
+ with open("/mnt/items/my-file.txt") as f:
153
+ return f.read()
154
+ ```
155
+
156
+ The Volume is mounted as a read-only volume in a function. Any file system write operation into the
157
+ mounted volume will result in an error.
158
+ """
159
+
160
+ async def _load(new_volume: _Volume, resolver: Resolver, existing_object_id: Optional[str]):
161
+ new_volume._initialize_from_other(self)
162
+ new_volume._read_only = True
163
+
164
+ obj = _Volume._from_loader(_load, "Volume()", hydrate_lazily=True, deps=lambda: [self])
165
+ return obj
138
166
 
139
167
  async def _get_lock(self):
140
168
  # To (mostly*) prevent multiple concurrent operations on the same volume, which can cause problems under
@@ -161,9 +189,9 @@ class _Volume(_Object, type_prefix="vo"):
161
189
  ) -> "_Volume":
162
190
  """Reference a Volume by name, creating if necessary.
163
191
 
164
- In contrast to `modal.Volume.lookup`, this is a lazy method
165
- that defers hydrating the local object with metadata from
166
- Modal servers until the first time is is actually used.
192
+ This is a lazy method that defers hydrating the local
193
+ object with metadata from Modal servers until the first
194
+ time is is actually used.
167
195
 
168
196
  ```python
169
197
  vol = modal.Volume.from_name("my-volume", create_if_missing=True)
@@ -405,7 +433,7 @@ class _Volume(_Object, type_prefix="vo"):
405
433
 
406
434
  Note - this function is primarily intended to be used outside of a Modal App.
407
435
  For more information on downloading files from a Modal Volume, see
408
- [the guide](/docs/guide/volumes).
436
+ [the guide](https://modal.com/docs/guide/volumes).
409
437
 
410
438
  **Example:**
411
439
 
@@ -495,6 +523,9 @@ class _Volume(_Object, type_prefix="vo"):
495
523
  @live_method
496
524
  async def remove_file(self, path: str, recursive: bool = False) -> None:
497
525
  """Remove a file or directory from a volume."""
526
+ if self._read_only:
527
+ raise InvalidError("Read-only Volume can not be written to")
528
+
498
529
  if self._is_v1:
499
530
  req = api_pb2.VolumeRemoveFileRequest(volume_id=self.object_id, path=path, recursive=recursive)
500
531
  await retry_transient_errors(self._client.stub.VolumeRemoveFile, req)
@@ -527,6 +558,9 @@ class _Volume(_Object, type_prefix="vo"):
527
558
  like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
528
559
  the volume mounted as a filesystem, e.g. when running a script on your local computer.
529
560
  """
561
+ if self._read_only:
562
+ raise InvalidError("Read-only Volume can not be written to")
563
+
530
564
  if self._is_v1:
531
565
  if recursive:
532
566
  raise ValueError("`recursive` is not supported for V1 volumes")
@@ -560,6 +594,9 @@ class _Volume(_Object, type_prefix="vo"):
560
594
  batch.put_file(io.BytesIO(b"some data"), "/foobar")
561
595
  ```
562
596
  """
597
+ if self._read_only:
598
+ raise InvalidError("Read-only Volume can not be written to")
599
+
563
600
  return _AbstractVolumeUploadContextManager.resolve(
564
601
  self._metadata.version, self.object_id, self._client, force=force
565
602
  )