modal 0.72.5__py3-none-any.whl → 0.72.48__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 (73) hide show
  1. modal/_container_entrypoint.py +5 -10
  2. modal/_object.py +297 -0
  3. modal/_resolver.py +7 -5
  4. modal/_runtime/container_io_manager.py +0 -11
  5. modal/_runtime/user_code_imports.py +7 -7
  6. modal/_serialization.py +4 -3
  7. modal/_tunnel.py +1 -1
  8. modal/app.py +14 -61
  9. modal/app.pyi +25 -25
  10. modal/cli/app.py +3 -2
  11. modal/cli/container.py +1 -1
  12. modal/cli/import_refs.py +185 -113
  13. modal/cli/launch.py +10 -5
  14. modal/cli/programs/run_jupyter.py +2 -2
  15. modal/cli/programs/vscode.py +3 -3
  16. modal/cli/run.py +134 -68
  17. modal/client.py +1 -0
  18. modal/client.pyi +18 -14
  19. modal/cloud_bucket_mount.py +4 -0
  20. modal/cloud_bucket_mount.pyi +4 -0
  21. modal/cls.py +33 -5
  22. modal/cls.pyi +20 -5
  23. modal/container_process.pyi +8 -6
  24. modal/dict.py +1 -1
  25. modal/dict.pyi +32 -29
  26. modal/environments.py +1 -1
  27. modal/environments.pyi +2 -1
  28. modal/experimental.py +47 -11
  29. modal/experimental.pyi +29 -0
  30. modal/file_io.pyi +30 -28
  31. modal/file_pattern_matcher.py +3 -4
  32. modal/functions.py +31 -23
  33. modal/functions.pyi +57 -50
  34. modal/gpu.py +19 -26
  35. modal/image.py +47 -19
  36. modal/image.pyi +28 -21
  37. modal/io_streams.pyi +14 -12
  38. modal/mount.py +14 -5
  39. modal/mount.pyi +28 -25
  40. modal/network_file_system.py +7 -7
  41. modal/network_file_system.pyi +27 -24
  42. modal/object.py +2 -265
  43. modal/object.pyi +46 -130
  44. modal/parallel_map.py +2 -2
  45. modal/parallel_map.pyi +10 -7
  46. modal/partial_function.py +22 -3
  47. modal/partial_function.pyi +45 -27
  48. modal/proxy.py +1 -1
  49. modal/proxy.pyi +2 -1
  50. modal/queue.py +1 -1
  51. modal/queue.pyi +26 -23
  52. modal/runner.py +14 -3
  53. modal/sandbox.py +11 -7
  54. modal/sandbox.pyi +30 -27
  55. modal/secret.py +1 -1
  56. modal/secret.pyi +2 -1
  57. modal/token_flow.pyi +6 -4
  58. modal/volume.py +1 -1
  59. modal/volume.pyi +36 -33
  60. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/METADATA +2 -2
  61. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/RECORD +73 -71
  62. modal_proto/api.proto +151 -4
  63. modal_proto/api_grpc.py +113 -0
  64. modal_proto/api_pb2.py +998 -795
  65. modal_proto/api_pb2.pyi +430 -11
  66. modal_proto/api_pb2_grpc.py +233 -1
  67. modal_proto/api_pb2_grpc.pyi +75 -3
  68. modal_proto/modal_api_grpc.py +7 -0
  69. modal_version/_version_generated.py +1 -1
  70. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/LICENSE +0 -0
  71. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/WHEEL +0 -0
  72. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/entry_points.txt +0 -0
  73. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/top_level.txt +0 -0
modal/dict.py CHANGED
@@ -7,6 +7,7 @@ from synchronicity.async_wrap import asynccontextmanager
7
7
 
8
8
  from modal_proto import api_pb2
9
9
 
10
+ from ._object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
10
11
  from ._resolver import Resolver
11
12
  from ._serialization import deserialize, serialize
12
13
  from ._utils.async_utils import TaskContext, synchronize_api
@@ -16,7 +17,6 @@ from ._utils.name_utils import check_object_name
16
17
  from .client import _Client
17
18
  from .config import logger
18
19
  from .exception import RequestSizeError
19
- from .object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
20
20
 
21
21
 
22
22
  def _serialize_dict(data):
modal/dict.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections.abc
2
+ import modal._object
2
3
  import modal.client
3
4
  import modal.object
4
5
  import synchronicity.combined_types
@@ -7,7 +8,7 @@ import typing_extensions
7
8
 
8
9
  def _serialize_dict(data): ...
9
10
 
10
- class _Dict(modal.object._Object):
11
+ class _Dict(modal._object._Object):
11
12
  def __init__(self, data={}): ...
12
13
  @classmethod
13
14
  def ephemeral(
@@ -56,6 +57,8 @@ class _Dict(modal.object._Object):
56
57
  def values(self) -> collections.abc.AsyncIterator[typing.Any]: ...
57
58
  def items(self) -> collections.abc.AsyncIterator[tuple[typing.Any, typing.Any]]: ...
58
59
 
60
+ SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
61
+
59
62
  class Dict(modal.object.Object):
60
63
  def __init__(self, data={}): ...
61
64
  @classmethod
@@ -115,86 +118,86 @@ class Dict(modal.object.Object):
115
118
 
116
119
  delete: __delete_spec
117
120
 
118
- class __clear_spec(typing_extensions.Protocol):
121
+ class __clear_spec(typing_extensions.Protocol[SUPERSELF]):
119
122
  def __call__(self) -> None: ...
120
123
  async def aio(self) -> None: ...
121
124
 
122
- clear: __clear_spec
125
+ clear: __clear_spec[typing_extensions.Self]
123
126
 
124
- class __get_spec(typing_extensions.Protocol):
127
+ class __get_spec(typing_extensions.Protocol[SUPERSELF]):
125
128
  def __call__(self, key: typing.Any, default: typing.Optional[typing.Any] = None) -> typing.Any: ...
126
129
  async def aio(self, key: typing.Any, default: typing.Optional[typing.Any] = None) -> typing.Any: ...
127
130
 
128
- get: __get_spec
131
+ get: __get_spec[typing_extensions.Self]
129
132
 
130
- class __contains_spec(typing_extensions.Protocol):
133
+ class __contains_spec(typing_extensions.Protocol[SUPERSELF]):
131
134
  def __call__(self, key: typing.Any) -> bool: ...
132
135
  async def aio(self, key: typing.Any) -> bool: ...
133
136
 
134
- contains: __contains_spec
137
+ contains: __contains_spec[typing_extensions.Self]
135
138
 
136
- class __len_spec(typing_extensions.Protocol):
139
+ class __len_spec(typing_extensions.Protocol[SUPERSELF]):
137
140
  def __call__(self) -> int: ...
138
141
  async def aio(self) -> int: ...
139
142
 
140
- len: __len_spec
143
+ len: __len_spec[typing_extensions.Self]
141
144
 
142
- class ____getitem___spec(typing_extensions.Protocol):
145
+ class ____getitem___spec(typing_extensions.Protocol[SUPERSELF]):
143
146
  def __call__(self, key: typing.Any) -> typing.Any: ...
144
147
  async def aio(self, key: typing.Any) -> typing.Any: ...
145
148
 
146
- __getitem__: ____getitem___spec
149
+ __getitem__: ____getitem___spec[typing_extensions.Self]
147
150
 
148
- class __update_spec(typing_extensions.Protocol):
151
+ class __update_spec(typing_extensions.Protocol[SUPERSELF]):
149
152
  def __call__(self, **kwargs) -> None: ...
150
153
  async def aio(self, **kwargs) -> None: ...
151
154
 
152
- update: __update_spec
155
+ update: __update_spec[typing_extensions.Self]
153
156
 
154
- class __put_spec(typing_extensions.Protocol):
157
+ class __put_spec(typing_extensions.Protocol[SUPERSELF]):
155
158
  def __call__(self, key: typing.Any, value: typing.Any) -> None: ...
156
159
  async def aio(self, key: typing.Any, value: typing.Any) -> None: ...
157
160
 
158
- put: __put_spec
161
+ put: __put_spec[typing_extensions.Self]
159
162
 
160
- class ____setitem___spec(typing_extensions.Protocol):
163
+ class ____setitem___spec(typing_extensions.Protocol[SUPERSELF]):
161
164
  def __call__(self, key: typing.Any, value: typing.Any) -> None: ...
162
165
  async def aio(self, key: typing.Any, value: typing.Any) -> None: ...
163
166
 
164
- __setitem__: ____setitem___spec
167
+ __setitem__: ____setitem___spec[typing_extensions.Self]
165
168
 
166
- class __pop_spec(typing_extensions.Protocol):
169
+ class __pop_spec(typing_extensions.Protocol[SUPERSELF]):
167
170
  def __call__(self, key: typing.Any) -> typing.Any: ...
168
171
  async def aio(self, key: typing.Any) -> typing.Any: ...
169
172
 
170
- pop: __pop_spec
173
+ pop: __pop_spec[typing_extensions.Self]
171
174
 
172
- class ____delitem___spec(typing_extensions.Protocol):
175
+ class ____delitem___spec(typing_extensions.Protocol[SUPERSELF]):
173
176
  def __call__(self, key: typing.Any) -> typing.Any: ...
174
177
  async def aio(self, key: typing.Any) -> typing.Any: ...
175
178
 
176
- __delitem__: ____delitem___spec
179
+ __delitem__: ____delitem___spec[typing_extensions.Self]
177
180
 
178
- class ____contains___spec(typing_extensions.Protocol):
181
+ class ____contains___spec(typing_extensions.Protocol[SUPERSELF]):
179
182
  def __call__(self, key: typing.Any) -> bool: ...
180
183
  async def aio(self, key: typing.Any) -> bool: ...
181
184
 
182
- __contains__: ____contains___spec
185
+ __contains__: ____contains___spec[typing_extensions.Self]
183
186
 
184
- class __keys_spec(typing_extensions.Protocol):
187
+ class __keys_spec(typing_extensions.Protocol[SUPERSELF]):
185
188
  def __call__(self) -> typing.Iterator[typing.Any]: ...
186
189
  def aio(self) -> collections.abc.AsyncIterator[typing.Any]: ...
187
190
 
188
- keys: __keys_spec
191
+ keys: __keys_spec[typing_extensions.Self]
189
192
 
190
- class __values_spec(typing_extensions.Protocol):
193
+ class __values_spec(typing_extensions.Protocol[SUPERSELF]):
191
194
  def __call__(self) -> typing.Iterator[typing.Any]: ...
192
195
  def aio(self) -> collections.abc.AsyncIterator[typing.Any]: ...
193
196
 
194
- values: __values_spec
197
+ values: __values_spec[typing_extensions.Self]
195
198
 
196
- class __items_spec(typing_extensions.Protocol):
199
+ class __items_spec(typing_extensions.Protocol[SUPERSELF]):
197
200
  def __call__(self) -> typing.Iterator[tuple[typing.Any, typing.Any]]: ...
198
201
  def aio(self) -> collections.abc.AsyncIterator[tuple[typing.Any, typing.Any]]: ...
199
202
 
200
- items: __items_spec
203
+ items: __items_spec[typing_extensions.Self]
modal/environments.py CHANGED
@@ -8,6 +8,7 @@ from google.protobuf.wrappers_pb2 import StringValue
8
8
 
9
9
  from modal_proto import api_pb2
10
10
 
11
+ from ._object import _Object
11
12
  from ._resolver import Resolver
12
13
  from ._utils.async_utils import synchronize_api, synchronizer
13
14
  from ._utils.deprecation import renamed_parameter
@@ -15,7 +16,6 @@ from ._utils.grpc_utils import retry_transient_errors
15
16
  from ._utils.name_utils import check_object_name
16
17
  from .client import _Client
17
18
  from .config import config, logger
18
- from .object import _Object
19
19
 
20
20
 
21
21
  @dataclass(frozen=True)
modal/environments.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import google.protobuf.message
2
+ import modal._object
2
3
  import modal.client
3
4
  import modal.object
4
5
  import modal_proto.api_pb2
@@ -16,7 +17,7 @@ class EnvironmentSettings:
16
17
  def __delattr__(self, name): ...
17
18
  def __hash__(self): ...
18
19
 
19
- class _Environment(modal.object._Object):
20
+ class _Environment(modal._object._Object):
20
21
  _settings: EnvironmentSettings
21
22
 
22
23
  def __init__(self): ...
modal/experimental.py CHANGED
@@ -1,16 +1,16 @@
1
1
  # Copyright Modal Labs 2022
2
- from typing import (
3
- Any,
4
- Callable,
5
- )
2
+ from dataclasses import dataclass
3
+ from typing import Any, Callable, Optional
6
4
 
7
- import modal._clustered_functions
8
- from modal.functions import _Function
5
+ from modal_proto import api_pb2
9
6
 
7
+ from ._clustered_functions import ClusterInfo, get_cluster_info as _get_cluster_info
8
+ from ._object import _get_environment_name
10
9
  from ._runtime.container_io_manager import _ContainerIOManager
11
- from .exception import (
12
- InvalidError,
13
- )
10
+ from ._utils.async_utils import synchronizer
11
+ from .client import _Client
12
+ from .exception import InvalidError
13
+ from .functions import _Function
14
14
  from .partial_function import _PartialFunction, _PartialFunctionFlags
15
15
 
16
16
 
@@ -65,5 +65,41 @@ def clustered(size: int, broadcast: bool = True):
65
65
  return wrapper
66
66
 
67
67
 
68
- def get_cluster_info() -> modal._clustered_functions.ClusterInfo:
69
- return modal._clustered_functions.get_cluster_info()
68
+ def get_cluster_info() -> ClusterInfo:
69
+ return _get_cluster_info()
70
+
71
+
72
+ @dataclass
73
+ class AppInfo:
74
+ app_id: str
75
+ name: str
76
+ containers: int
77
+
78
+
79
+ @synchronizer.create_blocking
80
+ async def list_deployed_apps(environment_name: str = "", client: Optional[_Client] = None) -> list[AppInfo]:
81
+ """List deployed Apps along with the number of containers currently running."""
82
+ # This function exists to provide backwards compatibility for some users who had been
83
+ # calling into the private function that previously backed the `modal app list` CLI command.
84
+ # We plan to add more Python API for exposing this sort of information, but we haven't
85
+ # settled on a design we're happy with yet. In the meantime, this function will continue
86
+ # to support existing codebases. It's likely that the final API will be different
87
+ # (e.g. more oriented around the App object). This function should be gracefully deprecated
88
+ # one the new API is released.
89
+ client = client or await _Client.from_env()
90
+
91
+ resp: api_pb2.AppListResponse = await client.stub.AppList(
92
+ api_pb2.AppListRequest(environment_name=_get_environment_name(environment_name))
93
+ )
94
+
95
+ app_infos = []
96
+ for app_stats in resp.apps:
97
+ if app_stats.state == api_pb2.APP_STATE_DEPLOYED:
98
+ app_infos.append(
99
+ AppInfo(
100
+ app_id=app_stats.app_id,
101
+ name=app_stats.description,
102
+ containers=app_stats.n_running_tasks,
103
+ )
104
+ )
105
+ return app_infos
modal/experimental.pyi ADDED
@@ -0,0 +1,29 @@
1
+ import modal._clustered_functions
2
+ import modal.client
3
+ import typing
4
+ import typing_extensions
5
+
6
+ def stop_fetching_inputs(): ...
7
+ def get_local_input_concurrency(): ...
8
+ def set_local_input_concurrency(concurrency: int): ...
9
+ def clustered(size: int, broadcast: bool = True): ...
10
+ def get_cluster_info() -> modal._clustered_functions.ClusterInfo: ...
11
+
12
+ class AppInfo:
13
+ app_id: str
14
+ name: str
15
+ containers: int
16
+
17
+ def __init__(self, app_id: str, name: str, containers: int) -> None: ...
18
+ def __repr__(self): ...
19
+ def __eq__(self, other): ...
20
+
21
+ class __list_deployed_apps_spec(typing_extensions.Protocol):
22
+ def __call__(
23
+ self, environment_name: str = "", client: typing.Optional[modal.client.Client] = None
24
+ ) -> list[AppInfo]: ...
25
+ async def aio(
26
+ self, environment_name: str = "", client: typing.Optional[modal.client.Client] = None
27
+ ) -> list[AppInfo]: ...
28
+
29
+ list_deployed_apps: __list_deployed_apps_spec
modal/file_io.pyi CHANGED
@@ -100,6 +100,8 @@ class __replace_bytes_spec(typing_extensions.Protocol):
100
100
 
101
101
  replace_bytes: __replace_bytes_spec
102
102
 
103
+ SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
104
+
103
105
  T_INNER = typing.TypeVar("T_INNER", covariant=True)
104
106
 
105
107
  class FileIO(typing.Generic[T]):
@@ -112,37 +114,37 @@ class FileIO(typing.Generic[T]):
112
114
  def _validate_mode(self, mode: str) -> None: ...
113
115
  def _handle_error(self, error: modal_proto.api_pb2.SystemErrorMessage) -> None: ...
114
116
 
115
- class ___consume_output_spec(typing_extensions.Protocol):
117
+ class ___consume_output_spec(typing_extensions.Protocol[SUPERSELF]):
116
118
  def __call__(self, exec_id: str) -> typing.Iterator[typing.Optional[bytes]]: ...
117
119
  def aio(self, exec_id: str) -> typing.AsyncIterator[typing.Optional[bytes]]: ...
118
120
 
119
- _consume_output: ___consume_output_spec
121
+ _consume_output: ___consume_output_spec[typing_extensions.Self]
120
122
 
121
- class ___consume_watch_output_spec(typing_extensions.Protocol):
123
+ class ___consume_watch_output_spec(typing_extensions.Protocol[SUPERSELF]):
122
124
  def __call__(self, exec_id: str) -> None: ...
123
125
  async def aio(self, exec_id: str) -> None: ...
124
126
 
125
- _consume_watch_output: ___consume_watch_output_spec
127
+ _consume_watch_output: ___consume_watch_output_spec[typing_extensions.Self]
126
128
 
127
- class ___parse_watch_output_spec(typing_extensions.Protocol):
129
+ class ___parse_watch_output_spec(typing_extensions.Protocol[SUPERSELF]):
128
130
  def __call__(self, event: bytes) -> typing.Optional[FileWatchEvent]: ...
129
131
  async def aio(self, event: bytes) -> typing.Optional[FileWatchEvent]: ...
130
132
 
131
- _parse_watch_output: ___parse_watch_output_spec
133
+ _parse_watch_output: ___parse_watch_output_spec[typing_extensions.Self]
132
134
 
133
- class ___wait_spec(typing_extensions.Protocol):
135
+ class ___wait_spec(typing_extensions.Protocol[SUPERSELF]):
134
136
  def __call__(self, exec_id: str) -> bytes: ...
135
137
  async def aio(self, exec_id: str) -> bytes: ...
136
138
 
137
- _wait: ___wait_spec
139
+ _wait: ___wait_spec[typing_extensions.Self]
138
140
 
139
141
  def _validate_type(self, data: typing.Union[bytes, str]) -> None: ...
140
142
 
141
- class ___open_file_spec(typing_extensions.Protocol):
143
+ class ___open_file_spec(typing_extensions.Protocol[SUPERSELF]):
142
144
  def __call__(self, path: str, mode: str) -> None: ...
143
145
  async def aio(self, path: str, mode: str) -> None: ...
144
146
 
145
- _open_file: ___open_file_spec
147
+ _open_file: ___open_file_spec[typing_extensions.Self]
146
148
 
147
149
  @classmethod
148
150
  def create(
@@ -153,49 +155,49 @@ class FileIO(typing.Generic[T]):
153
155
  task_id: str,
154
156
  ) -> FileIO: ...
155
157
 
156
- class ___make_read_request_spec(typing_extensions.Protocol):
158
+ class ___make_read_request_spec(typing_extensions.Protocol[SUPERSELF]):
157
159
  def __call__(self, n: typing.Optional[int]) -> bytes: ...
158
160
  async def aio(self, n: typing.Optional[int]) -> bytes: ...
159
161
 
160
- _make_read_request: ___make_read_request_spec
162
+ _make_read_request: ___make_read_request_spec[typing_extensions.Self]
161
163
 
162
- class __read_spec(typing_extensions.Protocol[T_INNER]):
164
+ class __read_spec(typing_extensions.Protocol[T_INNER, SUPERSELF]):
163
165
  def __call__(self, n: typing.Optional[int] = None) -> T_INNER: ...
164
166
  async def aio(self, n: typing.Optional[int] = None) -> T_INNER: ...
165
167
 
166
- read: __read_spec[T]
168
+ read: __read_spec[T, typing_extensions.Self]
167
169
 
168
- class __readline_spec(typing_extensions.Protocol[T_INNER]):
170
+ class __readline_spec(typing_extensions.Protocol[T_INNER, SUPERSELF]):
169
171
  def __call__(self) -> T_INNER: ...
170
172
  async def aio(self) -> T_INNER: ...
171
173
 
172
- readline: __readline_spec[T]
174
+ readline: __readline_spec[T, typing_extensions.Self]
173
175
 
174
- class __readlines_spec(typing_extensions.Protocol[T_INNER]):
176
+ class __readlines_spec(typing_extensions.Protocol[T_INNER, SUPERSELF]):
175
177
  def __call__(self) -> typing.Sequence[T_INNER]: ...
176
178
  async def aio(self) -> typing.Sequence[T_INNER]: ...
177
179
 
178
- readlines: __readlines_spec[T]
180
+ readlines: __readlines_spec[T, typing_extensions.Self]
179
181
 
180
- class __write_spec(typing_extensions.Protocol):
182
+ class __write_spec(typing_extensions.Protocol[SUPERSELF]):
181
183
  def __call__(self, data: typing.Union[bytes, str]) -> None: ...
182
184
  async def aio(self, data: typing.Union[bytes, str]) -> None: ...
183
185
 
184
- write: __write_spec
186
+ write: __write_spec[typing_extensions.Self]
185
187
 
186
- class __flush_spec(typing_extensions.Protocol):
188
+ class __flush_spec(typing_extensions.Protocol[SUPERSELF]):
187
189
  def __call__(self) -> None: ...
188
190
  async def aio(self) -> None: ...
189
191
 
190
- flush: __flush_spec
192
+ flush: __flush_spec[typing_extensions.Self]
191
193
 
192
194
  def _get_whence(self, whence: int): ...
193
195
 
194
- class __seek_spec(typing_extensions.Protocol):
196
+ class __seek_spec(typing_extensions.Protocol[SUPERSELF]):
195
197
  def __call__(self, offset: int, whence: int = 0) -> None: ...
196
198
  async def aio(self, offset: int, whence: int = 0) -> None: ...
197
199
 
198
- seek: __seek_spec
200
+ seek: __seek_spec[typing_extensions.Self]
199
201
 
200
202
  @classmethod
201
203
  def ls(cls, path: str, client: modal.client.Client, task_id: str) -> list[str]: ...
@@ -214,17 +216,17 @@ class FileIO(typing.Generic[T]):
214
216
  timeout: typing.Optional[int] = None,
215
217
  ) -> typing.Iterator[FileWatchEvent]: ...
216
218
 
217
- class ___close_spec(typing_extensions.Protocol):
219
+ class ___close_spec(typing_extensions.Protocol[SUPERSELF]):
218
220
  def __call__(self) -> None: ...
219
221
  async def aio(self) -> None: ...
220
222
 
221
- _close: ___close_spec
223
+ _close: ___close_spec[typing_extensions.Self]
222
224
 
223
- class __close_spec(typing_extensions.Protocol):
225
+ class __close_spec(typing_extensions.Protocol[SUPERSELF]):
224
226
  def __call__(self) -> None: ...
225
227
  async def aio(self) -> None: ...
226
228
 
227
- close: __close_spec
229
+ close: __close_spec[typing_extensions.Self]
228
230
 
229
231
  def _check_writable(self) -> None: ...
230
232
  def _check_readable(self) -> None: ...
@@ -112,7 +112,7 @@ class FilePatternMatcher(_AbstractPatternMatcher):
112
112
  self._set_patterns(pattern)
113
113
 
114
114
  @classmethod
115
- def from_file(cls, file_path: Path) -> "FilePatternMatcher":
115
+ def from_file(cls, file_path: Union[str, Path]) -> "FilePatternMatcher":
116
116
  """Initialize a new FilePatternMatcher instance from a file.
117
117
 
118
118
  The patterns in the file will be read lazily when the matcher is first used.
@@ -122,17 +122,16 @@ class FilePatternMatcher(_AbstractPatternMatcher):
122
122
 
123
123
  **Usage:**
124
124
  ```python
125
- from pathlib import Path
126
125
  from modal import FilePatternMatcher
127
126
 
128
- matcher = FilePatternMatcher.from_file(Path("/path/to/ignorefile"))
127
+ matcher = FilePatternMatcher.from_file("/path/to/ignorefile")
129
128
  ```
130
129
 
131
130
  """
132
131
  uninitialized = cls.__new__(cls)
133
132
 
134
133
  def _delayed_init():
135
- uninitialized._set_patterns(file_path.read_text("utf8").splitlines())
134
+ uninitialized._set_patterns(Path(file_path).read_text("utf8").splitlines())
136
135
  uninitialized._delayed_init = None
137
136
 
138
137
  uninitialized._delayed_init = _delayed_init
modal/functions.py CHANGED
@@ -26,6 +26,7 @@ from modal_proto import api_pb2
26
26
  from modal_proto.modal_api_grpc import ModalClientModal
27
27
 
28
28
  from ._location import parse_cloud_provider
29
+ from ._object import _get_environment_name, _Object, live_method, live_method_gen
29
30
  from ._pty import get_pty_info
30
31
  from ._resolver import Resolver
31
32
  from ._resources import convert_fn_config_to_resources_config
@@ -71,7 +72,6 @@ from .gpu import GPU_T, parse_gpu_config
71
72
  from .image import _Image
72
73
  from .mount import _get_client_mount, _Mount, get_auto_mounts
73
74
  from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
74
- from .object import _get_environment_name, _Object, live_method, live_method_gen
75
75
  from .output import _get_output_manager
76
76
  from .parallel_map import (
77
77
  _for_each_async,
@@ -383,12 +383,15 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
383
383
  _serve_mounts: frozenset[_Mount] # set at load time, only by loader
384
384
  _app: Optional["modal.app._App"] = None
385
385
  _obj: Optional["modal.cls._Obj"] = None # only set for InstanceServiceFunctions and bound instance methods
386
- _web_url: Optional[str]
386
+
387
+ _webhook_config: Optional[api_pb2.WebhookConfig] = None # this is set in definition scope, only locally
388
+ _web_url: Optional[str] # this is set on hydration
389
+
387
390
  _function_name: Optional[str]
388
391
  _is_method: bool
389
392
  _spec: Optional[_FunctionSpec] = None
390
393
  _tag: str
391
- _raw_f: Callable[..., Any]
394
+ _raw_f: Optional[Callable[..., Any]] # this is set to None for a "class service [function]"
392
395
  _build_args: dict
393
396
 
394
397
  _is_generator: Optional[bool] = None
@@ -474,7 +477,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
474
477
  _experimental_buffer_containers: Optional[int] = None,
475
478
  _experimental_proxy_ip: Optional[str] = None,
476
479
  _experimental_custom_scaling_factor: Optional[float] = None,
477
- ) -> None:
480
+ ) -> "_Function":
478
481
  """mdmd:hidden"""
479
482
  # Needed to avoid circular imports
480
483
  from .partial_function import _find_partial_methods_for_user_cls, _PartialFunctionFlags
@@ -573,7 +576,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
573
576
  )
574
577
  image = _Image._from_args(
575
578
  base_images={"base": image},
576
- build_function=snapshot_function,
579
+ build_function=snapshot_function, # type: ignore # TODO: separate functions.py and _functions.py
577
580
  force_build=image.force_build or pf.force_build,
578
581
  )
579
582
 
@@ -785,7 +788,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
785
788
  task_idle_timeout_secs=container_idle_timeout or 0,
786
789
  concurrency_limit=concurrency_limit or 0,
787
790
  pty_info=pty_info,
788
- cloud_provider=cloud_provider,
791
+ cloud_provider=cloud_provider, # Deprecated at some point
792
+ cloud_provider_str=cloud.upper() if cloud else "", # Supersedes cloud_provider
789
793
  warm_pool_size=keep_warm or 0,
790
794
  runtime=config.get("function_runtime"),
791
795
  runtime_debug=config.get("function_runtime_debug"),
@@ -911,6 +915,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
911
915
  obj._cluster_size = cluster_size
912
916
  obj._is_method = False
913
917
  obj._spec = function_spec # needed for modal shell
918
+ obj._webhook_config = webhook_config # only set locally
914
919
 
915
920
  # Used to check whether we should rebuild a modal.Image which uses `run_function`.
916
921
  gpus: list[GPU_T] = gpu if isinstance(gpu, list) else [gpu]
@@ -962,7 +967,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
962
967
  f"The {identity} has not been hydrated with the metadata it needs to run on Modal{reason}."
963
968
  )
964
969
 
965
- assert parent._client.stub
970
+ assert parent._client and parent._client.stub
966
971
 
967
972
  if can_use_parent:
968
973
  # We can end up here if parent wasn't hydrated when class was instantiated, but has been since.
@@ -983,9 +988,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
983
988
  else:
984
989
  serialized_params = serialize((args, kwargs))
985
990
  environment_name = _get_environment_name(None, resolver)
986
- assert parent is not None
991
+ assert parent is not None and parent.is_hydrated
987
992
  req = api_pb2.FunctionBindParamsRequest(
988
- function_id=parent._object_id,
993
+ function_id=parent.object_id,
989
994
  serialized_params=serialized_params,
990
995
  function_options=options,
991
996
  environment_name=environment_name
@@ -1032,11 +1037,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1032
1037
  """
1033
1038
  )
1034
1039
  )
1035
- assert self._client and self._client.stub
1036
1040
  request = api_pb2.FunctionUpdateSchedulingParamsRequest(
1037
- function_id=self._object_id, warm_pool_size_override=warm_pool_size
1041
+ function_id=self.object_id, warm_pool_size_override=warm_pool_size
1038
1042
  )
1039
- await retry_transient_errors(self._client.stub.FunctionUpdateSchedulingParams, request)
1043
+ await retry_transient_errors(self.client.stub.FunctionUpdateSchedulingParams, request)
1040
1044
 
1041
1045
  @classmethod
1042
1046
  @renamed_parameter((2024, 12, 18), "tag", "name")
@@ -1138,11 +1142,15 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1138
1142
  assert self._spec
1139
1143
  return self._spec
1140
1144
 
1145
+ def _is_web_endpoint(self) -> bool:
1146
+ # only defined in definition scope/locally, and not for class methods at the moment
1147
+ return bool(self._webhook_config and self._webhook_config.type != api_pb2.WEBHOOK_TYPE_UNSPECIFIED)
1148
+
1141
1149
  def get_build_def(self) -> str:
1142
1150
  """mdmd:hidden"""
1143
1151
  # Plaintext source and arg definition for the function, so it's part of the image
1144
1152
  # hash. We can't use the cloudpickle hash because it's not very stable.
1145
- assert hasattr(self, "_raw_f") and hasattr(self, "_build_args")
1153
+ assert hasattr(self, "_raw_f") and hasattr(self, "_build_args") and self._raw_f is not None
1146
1154
  return f"{inspect.getsource(self._raw_f)}\n{repr(self._build_args)}"
1147
1155
 
1148
1156
  # Live handle methods
@@ -1207,12 +1215,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1207
1215
  async def is_generator(self) -> bool:
1208
1216
  """mdmd:hidden"""
1209
1217
  # hacky: kind of like @live_method, but not hydrating if we have the value already from local source
1218
+ # TODO(michael) use a common / lightweight method for handling unhydrated metadata properties
1210
1219
  if self._is_generator is not None:
1211
1220
  # this is set if the function or class is local
1212
1221
  return self._is_generator
1213
1222
 
1214
1223
  # not set - this is a from_name lookup - hydrate
1215
- await self.resolve()
1224
+ await self.hydrate()
1216
1225
  assert self._is_generator is not None # should be set now
1217
1226
  return self._is_generator
1218
1227
 
@@ -1248,7 +1257,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1248
1257
  _map_invocation(
1249
1258
  self, # type: ignore
1250
1259
  input_queue,
1251
- self._client,
1260
+ self.client,
1252
1261
  order_outputs,
1253
1262
  return_exceptions,
1254
1263
  count_update_callback,
@@ -1266,7 +1275,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1266
1275
  self,
1267
1276
  args,
1268
1277
  kwargs,
1269
- client=self._client,
1278
+ client=self.client,
1270
1279
  function_call_invocation_type=function_call_invocation_type,
1271
1280
  )
1272
1281
 
@@ -1276,7 +1285,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1276
1285
  self, args, kwargs, function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType"
1277
1286
  ) -> _Invocation:
1278
1287
  return await _Invocation.create(
1279
- self, args, kwargs, client=self._client, function_call_invocation_type=function_call_invocation_type
1288
+ self, args, kwargs, client=self.client, function_call_invocation_type=function_call_invocation_type
1280
1289
  )
1281
1290
 
1282
1291
  @warn_if_generator_is_not_consumed()
@@ -1287,7 +1296,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1287
1296
  self,
1288
1297
  args,
1289
1298
  kwargs,
1290
- client=self._client,
1299
+ client=self.client,
1291
1300
  function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY,
1292
1301
  )
1293
1302
  async for res in invocation.run_generator():
@@ -1303,7 +1312,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1303
1312
  self,
1304
1313
  args,
1305
1314
  kwargs,
1306
- client=self._client,
1315
+ client=self.client,
1307
1316
  function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_ASYNC_LEGACY,
1308
1317
  )
1309
1318
 
@@ -1452,14 +1461,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1452
1461
 
1453
1462
  def get_raw_f(self) -> Callable[..., Any]:
1454
1463
  """Return the inner Python object wrapped by this Modal Function."""
1464
+ assert self._raw_f is not None
1455
1465
  return self._raw_f
1456
1466
 
1457
1467
  @live_method
1458
1468
  async def get_current_stats(self) -> FunctionStats:
1459
1469
  """Return a `FunctionStats` object describing the current function's queue and runner counts."""
1460
- assert self._client.stub
1461
1470
  resp = await retry_transient_errors(
1462
- self._client.stub.FunctionGetCurrentStats,
1471
+ self.client.stub.FunctionGetCurrentStats,
1463
1472
  api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
1464
1473
  total_timeout=10.0,
1465
1474
  )
@@ -1491,8 +1500,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1491
1500
  _is_generator: bool = False
1492
1501
 
1493
1502
  def _invocation(self):
1494
- assert self._client.stub
1495
- return _Invocation(self._client.stub, self.object_id, self._client)
1503
+ return _Invocation(self.client.stub, self.object_id, self.client)
1496
1504
 
1497
1505
  async def get(self, timeout: Optional[float] = None) -> ReturnType:
1498
1506
  """Get the result of the function call.