modal 1.0.6.dev4__py3-none-any.whl → 1.0.6.dev6__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 +14 -7
- modal/_utils/deprecation.py +19 -0
- modal/cli/utils.py +1 -3
- modal/client.pyi +2 -2
- modal/cls.py +9 -5
- modal/cls.pyi +15 -5
- modal/dict.py +6 -6
- modal/dict.pyi +5 -5
- modal/file_pattern_matcher.py +31 -0
- modal/functions.pyi +4 -4
- modal/mount.py +24 -3
- modal/mount.pyi +16 -3
- modal/network_file_system.py +10 -7
- modal/network_file_system.pyi +8 -8
- modal/queue.py +8 -5
- modal/queue.pyi +5 -5
- modal/runner.py +5 -6
- modal/runner.pyi +4 -4
- modal/secret.py +13 -7
- modal/secret.pyi +8 -8
- modal/volume.py +7 -7
- modal/volume.pyi +8 -8
- {modal-1.0.6.dev4.dist-info → modal-1.0.6.dev6.dist-info}/METADATA +1 -1
- {modal-1.0.6.dev4.dist-info → modal-1.0.6.dev6.dist-info}/RECORD +33 -33
- modal_docs/mdmd/mdmd.py +6 -3
- modal_proto/api.proto +10 -10
- modal_proto/api_pb2.py +910 -910
- modal_proto/api_pb2.pyi +10 -40
- modal_version/__init__.py +1 -1
- {modal-1.0.6.dev4.dist-info → modal-1.0.6.dev6.dist-info}/WHEEL +0 -0
- {modal-1.0.6.dev4.dist-info → modal-1.0.6.dev6.dist-info}/entry_points.txt +0 -0
- {modal-1.0.6.dev4.dist-info → modal-1.0.6.dev6.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.6.dev4.dist-info → modal-1.0.6.dev6.dist-info}/top_level.txt +0 -0
modal/_functions.py
CHANGED
|
@@ -40,7 +40,7 @@ from ._utils.async_utils import (
|
|
|
40
40
|
synchronizer,
|
|
41
41
|
warn_if_generator_is_not_consumed,
|
|
42
42
|
)
|
|
43
|
-
from ._utils.deprecation import deprecation_warning
|
|
43
|
+
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
44
44
|
from ._utils.function_utils import (
|
|
45
45
|
ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
46
46
|
OUTPUTS_TIMEOUT,
|
|
@@ -1241,7 +1241,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1241
1241
|
await self.update_autoscaler(min_containers=warm_pool_size)
|
|
1242
1242
|
|
|
1243
1243
|
@classmethod
|
|
1244
|
-
def _from_name(
|
|
1244
|
+
def _from_name(
|
|
1245
|
+
cls,
|
|
1246
|
+
app_name: str,
|
|
1247
|
+
name: str,
|
|
1248
|
+
namespace=None, # mdmd:line-hidden
|
|
1249
|
+
environment_name: Optional[str] = None,
|
|
1250
|
+
):
|
|
1245
1251
|
# internal function lookup implementation that allows lookup of class "service functions"
|
|
1246
1252
|
# in addition to non-class functions
|
|
1247
1253
|
async def _load_remote(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
|
@@ -1249,7 +1255,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1249
1255
|
request = api_pb2.FunctionGetRequest(
|
|
1250
1256
|
app_name=app_name,
|
|
1251
1257
|
object_tag=name,
|
|
1252
|
-
namespace=namespace,
|
|
1253
1258
|
environment_name=_get_environment_name(environment_name, resolver) or "",
|
|
1254
1259
|
)
|
|
1255
1260
|
try:
|
|
@@ -1276,7 +1281,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1276
1281
|
app_name: str,
|
|
1277
1282
|
name: str,
|
|
1278
1283
|
*,
|
|
1279
|
-
namespace=
|
|
1284
|
+
namespace=None, # mdmd:line-hidden
|
|
1280
1285
|
environment_name: Optional[str] = None,
|
|
1281
1286
|
) -> "_Function":
|
|
1282
1287
|
"""Reference a Function from a deployed App by its name.
|
|
@@ -1300,13 +1305,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1300
1305
|
f"instance.{method_name}.remote(...)\n",
|
|
1301
1306
|
)
|
|
1302
1307
|
|
|
1303
|
-
|
|
1308
|
+
warn_if_passing_namespace(namespace, "modal.Function.from_name")
|
|
1309
|
+
return cls._from_name(app_name, name, environment_name=environment_name)
|
|
1304
1310
|
|
|
1305
1311
|
@staticmethod
|
|
1306
1312
|
async def lookup(
|
|
1307
1313
|
app_name: str,
|
|
1308
1314
|
name: str,
|
|
1309
|
-
namespace=
|
|
1315
|
+
namespace=None, # mdmd:line-hidden
|
|
1310
1316
|
client: Optional[_Client] = None,
|
|
1311
1317
|
environment_name: Optional[str] = None,
|
|
1312
1318
|
) -> "_Function":
|
|
@@ -1328,7 +1334,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1328
1334
|
" It can be replaced with `modal.Function.from_name`."
|
|
1329
1335
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
1330
1336
|
)
|
|
1331
|
-
|
|
1337
|
+
warn_if_passing_namespace(namespace, "modal.Function.lookup")
|
|
1338
|
+
obj = _Function.from_name(app_name, name, environment_name=environment_name)
|
|
1332
1339
|
if client is None:
|
|
1333
1340
|
client = await _Client.from_env()
|
|
1334
1341
|
resolver = Resolver(client=client)
|
modal/_utils/deprecation.py
CHANGED
|
@@ -122,3 +122,22 @@ def warn_on_renamed_autoscaler_settings(func: Callable[P, R]) -> Callable[P, R]:
|
|
|
122
122
|
return func(*args, **kwargs)
|
|
123
123
|
|
|
124
124
|
return wrapper
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def warn_if_passing_namespace(
|
|
128
|
+
namespace: Any,
|
|
129
|
+
resource_name: str,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Issue deprecation warning for namespace parameter if non-None value is passed.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
namespace: The namespace parameter value (may be None or actual value)
|
|
135
|
+
resource_name: Name of the resource type for the warning message
|
|
136
|
+
"""
|
|
137
|
+
if namespace is not None:
|
|
138
|
+
deprecation_warning(
|
|
139
|
+
(2025, 6, 30),
|
|
140
|
+
f"The `namespace` parameter for `{resource_name}` is deprecated and will be"
|
|
141
|
+
" removed in a future release. It is no longer needed, so can be removed"
|
|
142
|
+
" from your code.",
|
|
143
|
+
)
|
modal/cli/utils.py
CHANGED
|
@@ -48,9 +48,7 @@ async def get_app_id_from_name(name: str, env: Optional[str], client: Optional[_
|
|
|
48
48
|
if client is None:
|
|
49
49
|
client = await _Client.from_env()
|
|
50
50
|
env_name = ensure_env(env)
|
|
51
|
-
request = api_pb2.AppGetByDeploymentNameRequest(
|
|
52
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE, name=name, environment_name=env_name
|
|
53
|
-
)
|
|
51
|
+
request = api_pb2.AppGetByDeploymentNameRequest(name=name, environment_name=env_name)
|
|
54
52
|
try:
|
|
55
53
|
resp = await client.stub.AppGetByDeploymentName(request)
|
|
56
54
|
except GRPCError as exc:
|
modal/client.pyi
CHANGED
|
@@ -31,7 +31,7 @@ class _Client:
|
|
|
31
31
|
server_url: str,
|
|
32
32
|
client_type: int,
|
|
33
33
|
credentials: typing.Optional[tuple[str, str]],
|
|
34
|
-
version: str = "1.0.6.
|
|
34
|
+
version: str = "1.0.6.dev6",
|
|
35
35
|
):
|
|
36
36
|
"""mdmd:hidden
|
|
37
37
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -160,7 +160,7 @@ class Client:
|
|
|
160
160
|
server_url: str,
|
|
161
161
|
client_type: int,
|
|
162
162
|
credentials: typing.Optional[tuple[str, str]],
|
|
163
|
-
version: str = "1.0.6.
|
|
163
|
+
version: str = "1.0.6.dev6",
|
|
164
164
|
):
|
|
165
165
|
"""mdmd:hidden
|
|
166
166
|
The Modal client object is not intended to be instantiated directly by users.
|
modal/cls.py
CHANGED
|
@@ -25,7 +25,11 @@ from ._serialization import check_valid_cls_constructor_arg
|
|
|
25
25
|
from ._traceback import print_server_warnings
|
|
26
26
|
from ._type_manager import parameter_serde_registry
|
|
27
27
|
from ._utils.async_utils import synchronize_api, synchronizer
|
|
28
|
-
from ._utils.deprecation import
|
|
28
|
+
from ._utils.deprecation import (
|
|
29
|
+
deprecation_warning,
|
|
30
|
+
warn_if_passing_namespace,
|
|
31
|
+
warn_on_renamed_autoscaler_settings,
|
|
32
|
+
)
|
|
29
33
|
from ._utils.grpc_utils import retry_transient_errors
|
|
30
34
|
from ._utils.mount_utils import validate_volumes
|
|
31
35
|
from .client import _Client
|
|
@@ -612,7 +616,7 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
612
616
|
app_name: str,
|
|
613
617
|
name: str,
|
|
614
618
|
*,
|
|
615
|
-
namespace=
|
|
619
|
+
namespace: Any = None, # mdmd:line-hidden
|
|
616
620
|
environment_name: Optional[str] = None,
|
|
617
621
|
) -> "_Cls":
|
|
618
622
|
"""Reference a Cls from a deployed App by its name.
|
|
@@ -625,13 +629,13 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
625
629
|
Model = modal.Cls.from_name("other-app", "Model")
|
|
626
630
|
```
|
|
627
631
|
"""
|
|
632
|
+
warn_if_passing_namespace(namespace, "modal.Cls.from_name")
|
|
628
633
|
_environment_name = environment_name or config.get("environment")
|
|
629
634
|
|
|
630
635
|
async def _load_remote(self: _Cls, resolver: Resolver, existing_object_id: Optional[str]):
|
|
631
636
|
request = api_pb2.ClassGetRequest(
|
|
632
637
|
app_name=app_name,
|
|
633
638
|
object_tag=name,
|
|
634
|
-
namespace=namespace,
|
|
635
639
|
environment_name=_environment_name,
|
|
636
640
|
only_class_function=True,
|
|
637
641
|
)
|
|
@@ -823,7 +827,7 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
823
827
|
async def lookup(
|
|
824
828
|
app_name: str,
|
|
825
829
|
name: str,
|
|
826
|
-
namespace=
|
|
830
|
+
namespace=None, # mdmd:line-hidden
|
|
827
831
|
client: Optional[_Client] = None,
|
|
828
832
|
environment_name: Optional[str] = None,
|
|
829
833
|
) -> "_Cls":
|
|
@@ -847,10 +851,10 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
847
851
|
" It can be replaced with `modal.Cls.from_name`."
|
|
848
852
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
849
853
|
)
|
|
854
|
+
warn_if_passing_namespace(namespace, "modal.Cls.lookup")
|
|
850
855
|
obj = _Cls.from_name(
|
|
851
856
|
app_name,
|
|
852
857
|
name,
|
|
853
|
-
namespace=namespace,
|
|
854
858
|
environment_name=environment_name,
|
|
855
859
|
)
|
|
856
860
|
if client is None:
|
modal/cls.pyi
CHANGED
|
@@ -363,7 +363,12 @@ class _Cls(modal._object._Object):
|
|
|
363
363
|
|
|
364
364
|
@classmethod
|
|
365
365
|
def from_name(
|
|
366
|
-
cls: type[_Cls],
|
|
366
|
+
cls: type[_Cls],
|
|
367
|
+
app_name: str,
|
|
368
|
+
name: str,
|
|
369
|
+
*,
|
|
370
|
+
namespace: typing.Any = None,
|
|
371
|
+
environment_name: typing.Optional[str] = None,
|
|
367
372
|
) -> _Cls:
|
|
368
373
|
"""Reference a Cls from a deployed App by its name.
|
|
369
374
|
|
|
@@ -453,7 +458,7 @@ class _Cls(modal._object._Object):
|
|
|
453
458
|
async def lookup(
|
|
454
459
|
app_name: str,
|
|
455
460
|
name: str,
|
|
456
|
-
namespace=
|
|
461
|
+
namespace=None,
|
|
457
462
|
client: typing.Optional[modal.client._Client] = None,
|
|
458
463
|
environment_name: typing.Optional[str] = None,
|
|
459
464
|
) -> _Cls:
|
|
@@ -522,7 +527,12 @@ class Cls(modal.object.Object):
|
|
|
522
527
|
|
|
523
528
|
@classmethod
|
|
524
529
|
def from_name(
|
|
525
|
-
cls: type[Cls],
|
|
530
|
+
cls: type[Cls],
|
|
531
|
+
app_name: str,
|
|
532
|
+
name: str,
|
|
533
|
+
*,
|
|
534
|
+
namespace: typing.Any = None,
|
|
535
|
+
environment_name: typing.Optional[str] = None,
|
|
526
536
|
) -> Cls:
|
|
527
537
|
"""Reference a Cls from a deployed App by its name.
|
|
528
538
|
|
|
@@ -614,7 +624,7 @@ class Cls(modal.object.Object):
|
|
|
614
624
|
/,
|
|
615
625
|
app_name: str,
|
|
616
626
|
name: str,
|
|
617
|
-
namespace=
|
|
627
|
+
namespace=None,
|
|
618
628
|
client: typing.Optional[modal.client.Client] = None,
|
|
619
629
|
environment_name: typing.Optional[str] = None,
|
|
620
630
|
) -> Cls:
|
|
@@ -639,7 +649,7 @@ class Cls(modal.object.Object):
|
|
|
639
649
|
/,
|
|
640
650
|
app_name: str,
|
|
641
651
|
name: str,
|
|
642
|
-
namespace=
|
|
652
|
+
namespace=None,
|
|
643
653
|
client: typing.Optional[modal.client.Client] = None,
|
|
644
654
|
environment_name: typing.Optional[str] = None,
|
|
645
655
|
) -> Cls:
|
modal/dict.py
CHANGED
|
@@ -11,7 +11,7 @@ from ._object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _O
|
|
|
11
11
|
from ._resolver import Resolver
|
|
12
12
|
from ._serialization import deserialize, serialize
|
|
13
13
|
from ._utils.async_utils import TaskContext, synchronize_api
|
|
14
|
-
from ._utils.deprecation import deprecation_warning
|
|
14
|
+
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
15
15
|
from ._utils.grpc_utils import retry_transient_errors
|
|
16
16
|
from ._utils.name_utils import check_object_name
|
|
17
17
|
from .client import _Client
|
|
@@ -117,9 +117,9 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
117
117
|
@staticmethod
|
|
118
118
|
def from_name(
|
|
119
119
|
name: str,
|
|
120
|
-
data: Optional[dict] = None, # DEPRECATED
|
|
120
|
+
data: Optional[dict] = None, # DEPRECATED, mdmd:line-hidden
|
|
121
121
|
*,
|
|
122
|
-
namespace=
|
|
122
|
+
namespace=None, # mdmd:line-hidden
|
|
123
123
|
environment_name: Optional[str] = None,
|
|
124
124
|
create_if_missing: bool = False,
|
|
125
125
|
) -> "_Dict":
|
|
@@ -135,6 +135,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
135
135
|
```
|
|
136
136
|
"""
|
|
137
137
|
check_object_name(name, "Dict")
|
|
138
|
+
warn_if_passing_namespace(namespace, "modal.Dict.from_name")
|
|
138
139
|
|
|
139
140
|
if data:
|
|
140
141
|
deprecation_warning(
|
|
@@ -146,7 +147,6 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
146
147
|
serialized = _serialize_dict(data if data is not None else {})
|
|
147
148
|
req = api_pb2.DictGetOrCreateRequest(
|
|
148
149
|
deployment_name=name,
|
|
149
|
-
namespace=namespace,
|
|
150
150
|
environment_name=_get_environment_name(environment_name, resolver),
|
|
151
151
|
object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
|
|
152
152
|
data=serialized,
|
|
@@ -161,7 +161,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
161
161
|
async def lookup(
|
|
162
162
|
name: str,
|
|
163
163
|
data: Optional[dict] = None,
|
|
164
|
-
namespace=
|
|
164
|
+
namespace=None, # mdmd:line-hidden
|
|
165
165
|
client: Optional[_Client] = None,
|
|
166
166
|
environment_name: Optional[str] = None,
|
|
167
167
|
create_if_missing: bool = False,
|
|
@@ -185,10 +185,10 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
185
185
|
" It can be replaced with `modal.Dict.from_name`."
|
|
186
186
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
187
187
|
)
|
|
188
|
+
warn_if_passing_namespace(namespace, "modal.Dict.lookup")
|
|
188
189
|
obj = _Dict.from_name(
|
|
189
190
|
name,
|
|
190
191
|
data=data,
|
|
191
|
-
namespace=namespace,
|
|
192
192
|
environment_name=environment_name,
|
|
193
193
|
create_if_missing=create_if_missing,
|
|
194
194
|
)
|
modal/dict.pyi
CHANGED
|
@@ -83,7 +83,7 @@ class _Dict(modal._object._Object):
|
|
|
83
83
|
name: str,
|
|
84
84
|
data: typing.Optional[dict] = None,
|
|
85
85
|
*,
|
|
86
|
-
namespace=
|
|
86
|
+
namespace=None,
|
|
87
87
|
environment_name: typing.Optional[str] = None,
|
|
88
88
|
create_if_missing: bool = False,
|
|
89
89
|
) -> _Dict:
|
|
@@ -104,7 +104,7 @@ class _Dict(modal._object._Object):
|
|
|
104
104
|
async def lookup(
|
|
105
105
|
name: str,
|
|
106
106
|
data: typing.Optional[dict] = None,
|
|
107
|
-
namespace=
|
|
107
|
+
namespace=None,
|
|
108
108
|
client: typing.Optional[modal.client._Client] = None,
|
|
109
109
|
environment_name: typing.Optional[str] = None,
|
|
110
110
|
create_if_missing: bool = False,
|
|
@@ -298,7 +298,7 @@ class Dict(modal.object.Object):
|
|
|
298
298
|
name: str,
|
|
299
299
|
data: typing.Optional[dict] = None,
|
|
300
300
|
*,
|
|
301
|
-
namespace=
|
|
301
|
+
namespace=None,
|
|
302
302
|
environment_name: typing.Optional[str] = None,
|
|
303
303
|
create_if_missing: bool = False,
|
|
304
304
|
) -> Dict:
|
|
@@ -321,7 +321,7 @@ class Dict(modal.object.Object):
|
|
|
321
321
|
/,
|
|
322
322
|
name: str,
|
|
323
323
|
data: typing.Optional[dict] = None,
|
|
324
|
-
namespace=
|
|
324
|
+
namespace=None,
|
|
325
325
|
client: typing.Optional[modal.client.Client] = None,
|
|
326
326
|
environment_name: typing.Optional[str] = None,
|
|
327
327
|
create_if_missing: bool = False,
|
|
@@ -346,7 +346,7 @@ class Dict(modal.object.Object):
|
|
|
346
346
|
/,
|
|
347
347
|
name: str,
|
|
348
348
|
data: typing.Optional[dict] = None,
|
|
349
|
-
namespace=
|
|
349
|
+
namespace=None,
|
|
350
350
|
client: typing.Optional[modal.client.Client] = None,
|
|
351
351
|
environment_name: typing.Optional[str] = None,
|
|
352
352
|
create_if_missing: bool = False,
|
modal/file_pattern_matcher.py
CHANGED
|
@@ -46,6 +46,18 @@ class _AbstractPatternMatcher:
|
|
|
46
46
|
|
|
47
47
|
return super().__repr__()
|
|
48
48
|
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def can_prune_directories(self) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Returns True if this pattern matcher allows safe early directory pruning.
|
|
53
|
+
|
|
54
|
+
Directory pruning is safe when matching directories can be skipped entirely
|
|
55
|
+
without missing any files that should be included.
|
|
56
|
+
|
|
57
|
+
An example where pruning is not safe is for inverted patterns, like "!**/*.py".
|
|
58
|
+
"""
|
|
59
|
+
...
|
|
60
|
+
|
|
49
61
|
@abstractmethod
|
|
50
62
|
def __call__(self, path: Path) -> bool: ...
|
|
51
63
|
|
|
@@ -54,6 +66,15 @@ class _CustomPatternMatcher(_AbstractPatternMatcher):
|
|
|
54
66
|
def __init__(self, predicate: Callable[[Path], bool]):
|
|
55
67
|
self._predicate = predicate
|
|
56
68
|
|
|
69
|
+
def can_prune_directories(self) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
Custom pattern matchers (like negated matchers) cannot safely prune directories.
|
|
72
|
+
|
|
73
|
+
Since these are arbitrary predicates, we cannot determine if a directory
|
|
74
|
+
can be safely skipped without evaluating all files within it.
|
|
75
|
+
"""
|
|
76
|
+
return False
|
|
77
|
+
|
|
57
78
|
def __call__(self, path: Path) -> bool:
|
|
58
79
|
return self._predicate(path)
|
|
59
80
|
|
|
@@ -173,6 +194,16 @@ class FilePatternMatcher(_AbstractPatternMatcher):
|
|
|
173
194
|
|
|
174
195
|
return matched
|
|
175
196
|
|
|
197
|
+
def can_prune_directories(self) -> bool:
|
|
198
|
+
"""
|
|
199
|
+
Returns True if this pattern matcher allows safe early directory pruning.
|
|
200
|
+
|
|
201
|
+
Directory pruning is safe when matching directories can be skipped entirely
|
|
202
|
+
without missing any files that should be included. This is for example not
|
|
203
|
+
safe when we have inverted/negated ignore patterns (e.g. "!**/*.py").
|
|
204
|
+
"""
|
|
205
|
+
return not any(pattern.exclusion for pattern in self.patterns)
|
|
206
|
+
|
|
176
207
|
def __call__(self, file_path: Path) -> bool:
|
|
177
208
|
if self._delayed_init:
|
|
178
209
|
self._delayed_init()
|
modal/functions.pyi
CHANGED
|
@@ -240,10 +240,10 @@ class Function(
|
|
|
240
240
|
keep_warm: __keep_warm_spec[typing_extensions.Self]
|
|
241
241
|
|
|
242
242
|
@classmethod
|
|
243
|
-
def _from_name(cls, app_name: str, name: str, namespace, environment_name: typing.Optional[str]): ...
|
|
243
|
+
def _from_name(cls, app_name: str, name: str, namespace=None, environment_name: typing.Optional[str] = None): ...
|
|
244
244
|
@classmethod
|
|
245
245
|
def from_name(
|
|
246
|
-
cls: type[Function], app_name: str, name: str, *, namespace=
|
|
246
|
+
cls: type[Function], app_name: str, name: str, *, namespace=None, environment_name: typing.Optional[str] = None
|
|
247
247
|
) -> Function:
|
|
248
248
|
"""Reference a Function from a deployed App by its name.
|
|
249
249
|
|
|
@@ -263,7 +263,7 @@ class Function(
|
|
|
263
263
|
/,
|
|
264
264
|
app_name: str,
|
|
265
265
|
name: str,
|
|
266
|
-
namespace=
|
|
266
|
+
namespace=None,
|
|
267
267
|
client: typing.Optional[modal.client.Client] = None,
|
|
268
268
|
environment_name: typing.Optional[str] = None,
|
|
269
269
|
) -> Function:
|
|
@@ -286,7 +286,7 @@ class Function(
|
|
|
286
286
|
/,
|
|
287
287
|
app_name: str,
|
|
288
288
|
name: str,
|
|
289
|
-
namespace=
|
|
289
|
+
namespace=None,
|
|
290
290
|
client: typing.Optional[modal.client.Client] = None,
|
|
291
291
|
environment_name: typing.Optional[str] = None,
|
|
292
292
|
) -> Function:
|
modal/mount.py
CHANGED
|
@@ -8,7 +8,7 @@ import re
|
|
|
8
8
|
import time
|
|
9
9
|
import typing
|
|
10
10
|
import warnings
|
|
11
|
-
from collections.abc import AsyncGenerator
|
|
11
|
+
from collections.abc import AsyncGenerator, Generator
|
|
12
12
|
from pathlib import Path, PurePosixPath
|
|
13
13
|
from typing import Callable, Optional, Sequence, Union
|
|
14
14
|
|
|
@@ -133,12 +133,27 @@ class _MountFile(_MountEntry):
|
|
|
133
133
|
class _MountDir(_MountEntry):
|
|
134
134
|
local_dir: Path
|
|
135
135
|
remote_path: PurePosixPath
|
|
136
|
-
ignore: Callable[[Path], bool]
|
|
136
|
+
ignore: Union[Callable[[Path], bool], modal.file_pattern_matcher._AbstractPatternMatcher]
|
|
137
137
|
recursive: bool
|
|
138
138
|
|
|
139
139
|
def description(self):
|
|
140
140
|
return str(self.local_dir.expanduser().absolute())
|
|
141
141
|
|
|
142
|
+
def _walk_and_prune(self, top_dir: Path) -> Generator[str, None, None]:
|
|
143
|
+
"""Walk directories and prune ignored directories early."""
|
|
144
|
+
for root, dirs, files in os.walk(top_dir, topdown=True):
|
|
145
|
+
# with topdown=True, os.walk allows modifying the dirs list in-place, and will only
|
|
146
|
+
# recurse into dirs that are not ignored.
|
|
147
|
+
dirs[:] = [d for d in dirs if not self.ignore(Path(os.path.join(root, d)).relative_to(top_dir))]
|
|
148
|
+
for file in files:
|
|
149
|
+
yield os.path.join(root, file)
|
|
150
|
+
|
|
151
|
+
def _walk_all(self, top_dir: Path) -> Generator[str, None, None]:
|
|
152
|
+
"""Walk all directories without early pruning - safe for complex/inverted ignore patterns."""
|
|
153
|
+
for root, _, files in os.walk(top_dir):
|
|
154
|
+
for file in files:
|
|
155
|
+
yield os.path.join(root, file)
|
|
156
|
+
|
|
142
157
|
def get_files_to_upload(self):
|
|
143
158
|
# we can't use .resolve() eagerly here since that could end up "renaming" symlinked files
|
|
144
159
|
# see test_mount_directory_with_symlinked_file
|
|
@@ -153,7 +168,13 @@ class _MountDir(_MountEntry):
|
|
|
153
168
|
raise NotADirectoryError(msg)
|
|
154
169
|
|
|
155
170
|
if self.recursive:
|
|
156
|
-
|
|
171
|
+
if (
|
|
172
|
+
isinstance(self.ignore, modal.file_pattern_matcher._AbstractPatternMatcher)
|
|
173
|
+
and self.ignore.can_prune_directories()
|
|
174
|
+
):
|
|
175
|
+
gen = self._walk_and_prune(local_dir)
|
|
176
|
+
else:
|
|
177
|
+
gen = self._walk_all(local_dir)
|
|
157
178
|
else:
|
|
158
179
|
gen = (dir_entry.path for dir_entry in os.scandir(local_dir) if dir_entry.is_file())
|
|
159
180
|
|
modal/mount.pyi
CHANGED
|
@@ -4,6 +4,7 @@ import modal._object
|
|
|
4
4
|
import modal._resolver
|
|
5
5
|
import modal._utils.blob_utils
|
|
6
6
|
import modal.client
|
|
7
|
+
import modal.file_pattern_matcher
|
|
7
8
|
import modal.object
|
|
8
9
|
import modal_proto.api_pb2
|
|
9
10
|
import pathlib
|
|
@@ -49,14 +50,24 @@ class _MountFile(_MountEntry):
|
|
|
49
50
|
...
|
|
50
51
|
|
|
51
52
|
class _MountDir(_MountEntry):
|
|
52
|
-
"""_MountDir(local_dir: pathlib.Path, remote_path: pathlib.PurePosixPath, ignore: Callable[[pathlib.Path], bool], recursive: bool)"""
|
|
53
|
+
"""_MountDir(local_dir: pathlib.Path, remote_path: pathlib.PurePosixPath, ignore: Union[Callable[[pathlib.Path], bool], modal.file_pattern_matcher._AbstractPatternMatcher], recursive: bool)"""
|
|
53
54
|
|
|
54
55
|
local_dir: pathlib.Path
|
|
55
56
|
remote_path: pathlib.PurePosixPath
|
|
56
|
-
ignore:
|
|
57
|
+
ignore: typing.Union[
|
|
58
|
+
collections.abc.Callable[[pathlib.Path], bool], modal.file_pattern_matcher._AbstractPatternMatcher
|
|
59
|
+
]
|
|
57
60
|
recursive: bool
|
|
58
61
|
|
|
59
62
|
def description(self): ...
|
|
63
|
+
def _walk_and_prune(self, top_dir: pathlib.Path) -> collections.abc.Generator[str, None, None]:
|
|
64
|
+
"""Walk directories and prune ignored directories early."""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def _walk_all(self, top_dir: pathlib.Path) -> collections.abc.Generator[str, None, None]:
|
|
68
|
+
"""Walk all directories without early pruning - safe for complex/inverted ignore patterns."""
|
|
69
|
+
...
|
|
70
|
+
|
|
60
71
|
def get_files_to_upload(self): ...
|
|
61
72
|
def watch_entry(self): ...
|
|
62
73
|
def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
|
|
@@ -64,7 +75,9 @@ class _MountDir(_MountEntry):
|
|
|
64
75
|
self,
|
|
65
76
|
local_dir: pathlib.Path,
|
|
66
77
|
remote_path: pathlib.PurePosixPath,
|
|
67
|
-
ignore:
|
|
78
|
+
ignore: typing.Union[
|
|
79
|
+
collections.abc.Callable[[pathlib.Path], bool], modal.file_pattern_matcher._AbstractPatternMatcher
|
|
80
|
+
],
|
|
68
81
|
recursive: bool,
|
|
69
82
|
) -> None:
|
|
70
83
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
modal/network_file_system.py
CHANGED
|
@@ -22,7 +22,7 @@ from ._object import (
|
|
|
22
22
|
from ._resolver import Resolver
|
|
23
23
|
from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
|
|
24
24
|
from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
|
|
25
|
-
from ._utils.deprecation import deprecation_warning
|
|
25
|
+
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
26
26
|
from ._utils.grpc_utils import retry_transient_errors
|
|
27
27
|
from ._utils.hash_utils import get_sha256_hex
|
|
28
28
|
from ._utils.name_utils import check_object_name
|
|
@@ -92,7 +92,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
92
92
|
def from_name(
|
|
93
93
|
name: str,
|
|
94
94
|
*,
|
|
95
|
-
namespace=
|
|
95
|
+
namespace=None, # mdmd:line-hidden
|
|
96
96
|
environment_name: Optional[str] = None,
|
|
97
97
|
create_if_missing: bool = False,
|
|
98
98
|
) -> "_NetworkFileSystem":
|
|
@@ -111,11 +111,11 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
111
111
|
```
|
|
112
112
|
"""
|
|
113
113
|
check_object_name(name, "NetworkFileSystem")
|
|
114
|
+
warn_if_passing_namespace(namespace, "modal.NetworkFileSystem.from_name")
|
|
114
115
|
|
|
115
116
|
async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
|
|
116
117
|
req = api_pb2.SharedVolumeGetOrCreateRequest(
|
|
117
118
|
deployment_name=name,
|
|
118
|
-
namespace=namespace,
|
|
119
119
|
environment_name=_get_environment_name(environment_name, resolver),
|
|
120
120
|
object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
|
|
121
121
|
)
|
|
@@ -167,7 +167,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
167
167
|
@staticmethod
|
|
168
168
|
async def lookup(
|
|
169
169
|
name: str,
|
|
170
|
-
namespace=
|
|
170
|
+
namespace=None, # mdmd:line-hidden
|
|
171
171
|
client: Optional[_Client] = None,
|
|
172
172
|
environment_name: Optional[str] = None,
|
|
173
173
|
create_if_missing: bool = False,
|
|
@@ -191,8 +191,11 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
191
191
|
" It can be replaced with `modal.NetworkFileSystem.from_name`."
|
|
192
192
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
193
193
|
)
|
|
194
|
+
warn_if_passing_namespace(namespace, "modal.NetworkFileSystem.lookup")
|
|
194
195
|
obj = _NetworkFileSystem.from_name(
|
|
195
|
-
name,
|
|
196
|
+
name,
|
|
197
|
+
environment_name=environment_name,
|
|
198
|
+
create_if_missing=create_if_missing,
|
|
196
199
|
)
|
|
197
200
|
if client is None:
|
|
198
201
|
client = await _Client.from_env()
|
|
@@ -203,17 +206,17 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
203
206
|
@staticmethod
|
|
204
207
|
async def create_deployed(
|
|
205
208
|
deployment_name: str,
|
|
206
|
-
namespace=
|
|
209
|
+
namespace=None, # mdmd:line-hidden
|
|
207
210
|
client: Optional[_Client] = None,
|
|
208
211
|
environment_name: Optional[str] = None,
|
|
209
212
|
) -> str:
|
|
210
213
|
"""mdmd:hidden"""
|
|
211
214
|
check_object_name(deployment_name, "NetworkFileSystem")
|
|
215
|
+
warn_if_passing_namespace(namespace, "modal.NetworkFileSystem.create_deployed")
|
|
212
216
|
if client is None:
|
|
213
217
|
client = await _Client.from_env()
|
|
214
218
|
request = api_pb2.SharedVolumeGetOrCreateRequest(
|
|
215
219
|
deployment_name=deployment_name,
|
|
216
|
-
namespace=namespace,
|
|
217
220
|
environment_name=_get_environment_name(environment_name),
|
|
218
221
|
object_creation_type=api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS,
|
|
219
222
|
)
|
modal/network_file_system.pyi
CHANGED
|
@@ -52,7 +52,7 @@ class _NetworkFileSystem(modal._object._Object):
|
|
|
52
52
|
"""
|
|
53
53
|
@staticmethod
|
|
54
54
|
def from_name(
|
|
55
|
-
name: str, *, namespace=
|
|
55
|
+
name: str, *, namespace=None, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
|
|
56
56
|
) -> _NetworkFileSystem:
|
|
57
57
|
"""Reference a NetworkFileSystem by its name, creating if necessary.
|
|
58
58
|
|
|
@@ -95,7 +95,7 @@ class _NetworkFileSystem(modal._object._Object):
|
|
|
95
95
|
@staticmethod
|
|
96
96
|
async def lookup(
|
|
97
97
|
name: str,
|
|
98
|
-
namespace=
|
|
98
|
+
namespace=None,
|
|
99
99
|
client: typing.Optional[modal.client._Client] = None,
|
|
100
100
|
environment_name: typing.Optional[str] = None,
|
|
101
101
|
create_if_missing: bool = False,
|
|
@@ -118,7 +118,7 @@ class _NetworkFileSystem(modal._object._Object):
|
|
|
118
118
|
@staticmethod
|
|
119
119
|
async def create_deployed(
|
|
120
120
|
deployment_name: str,
|
|
121
|
-
namespace=
|
|
121
|
+
namespace=None,
|
|
122
122
|
client: typing.Optional[modal.client._Client] = None,
|
|
123
123
|
environment_name: typing.Optional[str] = None,
|
|
124
124
|
) -> str:
|
|
@@ -229,7 +229,7 @@ class NetworkFileSystem(modal.object.Object):
|
|
|
229
229
|
|
|
230
230
|
@staticmethod
|
|
231
231
|
def from_name(
|
|
232
|
-
name: str, *, namespace=
|
|
232
|
+
name: str, *, namespace=None, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
|
|
233
233
|
) -> NetworkFileSystem:
|
|
234
234
|
"""Reference a NetworkFileSystem by its name, creating if necessary.
|
|
235
235
|
|
|
@@ -274,7 +274,7 @@ class NetworkFileSystem(modal.object.Object):
|
|
|
274
274
|
self,
|
|
275
275
|
/,
|
|
276
276
|
name: str,
|
|
277
|
-
namespace=
|
|
277
|
+
namespace=None,
|
|
278
278
|
client: typing.Optional[modal.client.Client] = None,
|
|
279
279
|
environment_name: typing.Optional[str] = None,
|
|
280
280
|
create_if_missing: bool = False,
|
|
@@ -298,7 +298,7 @@ class NetworkFileSystem(modal.object.Object):
|
|
|
298
298
|
self,
|
|
299
299
|
/,
|
|
300
300
|
name: str,
|
|
301
|
-
namespace=
|
|
301
|
+
namespace=None,
|
|
302
302
|
client: typing.Optional[modal.client.Client] = None,
|
|
303
303
|
environment_name: typing.Optional[str] = None,
|
|
304
304
|
create_if_missing: bool = False,
|
|
@@ -325,7 +325,7 @@ class NetworkFileSystem(modal.object.Object):
|
|
|
325
325
|
self,
|
|
326
326
|
/,
|
|
327
327
|
deployment_name: str,
|
|
328
|
-
namespace=
|
|
328
|
+
namespace=None,
|
|
329
329
|
client: typing.Optional[modal.client.Client] = None,
|
|
330
330
|
environment_name: typing.Optional[str] = None,
|
|
331
331
|
) -> str:
|
|
@@ -336,7 +336,7 @@ class NetworkFileSystem(modal.object.Object):
|
|
|
336
336
|
self,
|
|
337
337
|
/,
|
|
338
338
|
deployment_name: str,
|
|
339
|
-
namespace=
|
|
339
|
+
namespace=None,
|
|
340
340
|
client: typing.Optional[modal.client.Client] = None,
|
|
341
341
|
environment_name: typing.Optional[str] = None,
|
|
342
342
|
) -> str:
|