modal 0.73.114__py3-none-any.whl → 0.73.116__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.
- modal/_serialization.py +23 -2
- modal/_utils/function_utils.py +7 -14
- modal/client.pyi +2 -2
- modal/cls.py +14 -8
- modal/cls.pyi +2 -0
- modal/image.py +2 -0
- {modal-0.73.114.dist-info → modal-0.73.116.dist-info}/METADATA +1 -1
- {modal-0.73.114.dist-info → modal-0.73.116.dist-info}/RECORD +13 -13
- modal_version/_version_generated.py +1 -1
- {modal-0.73.114.dist-info → modal-0.73.116.dist-info}/LICENSE +0 -0
- {modal-0.73.114.dist-info → modal-0.73.116.dist-info}/WHEEL +0 -0
- {modal-0.73.114.dist-info → modal-0.73.116.dist-info}/entry_points.txt +0 -0
- {modal-0.73.114.dist-info → modal-0.73.116.dist-info}/top_level.txt +0 -0
modal/_serialization.py
CHANGED
@@ -389,6 +389,12 @@ def check_valid_cls_constructor_arg(key, obj):
|
|
389
389
|
)
|
390
390
|
|
391
391
|
|
392
|
+
def assert_bytes(obj: Any):
|
393
|
+
if not isinstance(obj, bytes):
|
394
|
+
raise TypeError(f"Expected bytes, got {type(obj)}")
|
395
|
+
return obj
|
396
|
+
|
397
|
+
|
392
398
|
@dataclass
|
393
399
|
class ParamTypeInfo:
|
394
400
|
default_field: str
|
@@ -396,16 +402,27 @@ class ParamTypeInfo:
|
|
396
402
|
converter: typing.Callable[[str], typing.Any]
|
397
403
|
|
398
404
|
|
399
|
-
|
405
|
+
PYTHON_TO_PROTO_TYPE: dict[type, "api_pb2.ParameterType.ValueType"] = {
|
406
|
+
# python type -> protobuf type enum
|
407
|
+
str: api_pb2.PARAM_TYPE_STRING,
|
408
|
+
int: api_pb2.PARAM_TYPE_INT,
|
409
|
+
bytes: api_pb2.PARAM_TYPE_BYTES,
|
410
|
+
}
|
411
|
+
|
412
|
+
PROTO_TYPE_INFO = {
|
413
|
+
# Protobuf type enum -> encode/decode helper metadata
|
400
414
|
api_pb2.PARAM_TYPE_STRING: ParamTypeInfo(default_field="string_default", proto_field="string_value", converter=str),
|
401
415
|
api_pb2.PARAM_TYPE_INT: ParamTypeInfo(default_field="int_default", proto_field="int_value", converter=int),
|
416
|
+
api_pb2.PARAM_TYPE_BYTES: ParamTypeInfo(
|
417
|
+
default_field="bytes_default", proto_field="bytes_value", converter=assert_bytes
|
418
|
+
),
|
402
419
|
}
|
403
420
|
|
404
421
|
|
405
422
|
def serialize_proto_params(python_params: dict[str, Any], schema: typing.Sequence[api_pb2.ClassParameterSpec]) -> bytes:
|
406
423
|
proto_params: list[api_pb2.ClassParameterValue] = []
|
407
424
|
for schema_param in schema:
|
408
|
-
type_info =
|
425
|
+
type_info = PROTO_TYPE_INFO.get(schema_param.type)
|
409
426
|
if not type_info:
|
410
427
|
raise ValueError(f"Unsupported parameter type: {schema_param.type}")
|
411
428
|
proto_param = api_pb2.ClassParameterValue(
|
@@ -429,6 +446,8 @@ def serialize_proto_params(python_params: dict[str, Any], schema: typing.Sequenc
|
|
429
446
|
|
430
447
|
|
431
448
|
def deserialize_proto_params(serialized_params: bytes, schema: list[api_pb2.ClassParameterSpec]) -> dict[str, Any]:
|
449
|
+
# TODO: this currently requires the schema to decode a payload, but we should make the validation
|
450
|
+
# distinct from the deserialization
|
432
451
|
proto_struct = api_pb2.ClassParameterSet()
|
433
452
|
proto_struct.ParseFromString(serialized_params)
|
434
453
|
value_by_name = {p.name: p for p in proto_struct.parameters}
|
@@ -449,6 +468,8 @@ def deserialize_proto_params(serialized_params: bytes, schema: list[api_pb2.Clas
|
|
449
468
|
python_value = param_value.string_value
|
450
469
|
elif schema_param.type == api_pb2.PARAM_TYPE_INT:
|
451
470
|
python_value = param_value.int_value
|
471
|
+
elif schema_param.type == api_pb2.PARAM_TYPE_BYTES:
|
472
|
+
python_value = param_value.bytes_value
|
452
473
|
else:
|
453
474
|
# TODO(elias): based on `parameters` declared types, we could add support for
|
454
475
|
# custom non proto types encoded as bytes in the proto, e.g. PARAM_TYPE_PYTHON_PICKLE
|
modal/_utils/function_utils.py
CHANGED
@@ -15,7 +15,7 @@ from synchronicity.exceptions import UserCodeException
|
|
15
15
|
import modal_proto
|
16
16
|
from modal_proto import api_pb2
|
17
17
|
|
18
|
-
from .._serialization import deserialize, deserialize_data_format, serialize
|
18
|
+
from .._serialization import PROTO_TYPE_INFO, PYTHON_TO_PROTO_TYPE, deserialize, deserialize_data_format, serialize
|
19
19
|
from .._traceback import append_modal_tb
|
20
20
|
from ..config import config, logger
|
21
21
|
from ..exception import (
|
@@ -38,13 +38,6 @@ class FunctionInfoType(Enum):
|
|
38
38
|
NOTEBOOK = "notebook"
|
39
39
|
|
40
40
|
|
41
|
-
# TODO(elias): Add support for quoted/str annotations
|
42
|
-
CLASS_PARAM_TYPE_MAP: dict[type, tuple["api_pb2.ParameterType.ValueType", str]] = {
|
43
|
-
str: (api_pb2.PARAM_TYPE_STRING, "string_default"),
|
44
|
-
int: (api_pb2.PARAM_TYPE_INT, "int_default"),
|
45
|
-
}
|
46
|
-
|
47
|
-
|
48
41
|
class LocalFunctionError(InvalidError):
|
49
42
|
"""Raised if a function declared in a non-global scope is used in an impermissible way"""
|
50
43
|
|
@@ -284,7 +277,7 @@ class FunctionInfo:
|
|
284
277
|
return api_pb2.ClassParameterInfo()
|
285
278
|
|
286
279
|
# TODO(elias): Resolve circular dependencies... maybe we'll need some cls_utils module
|
287
|
-
from modal.cls import _get_class_constructor_signature, _use_annotation_parameters
|
280
|
+
from modal.cls import _get_class_constructor_signature, _use_annotation_parameters, _validate_parameter_type
|
288
281
|
|
289
282
|
if not _use_annotation_parameters(self.user_cls):
|
290
283
|
return api_pb2.ClassParameterInfo(format=api_pb2.ClassParameterInfo.PARAM_SERIALIZATION_FORMAT_PICKLE)
|
@@ -296,12 +289,12 @@ class FunctionInfo:
|
|
296
289
|
signature = _get_class_constructor_signature(self.user_cls)
|
297
290
|
for param in signature.parameters.values():
|
298
291
|
has_default = param.default is not param.empty
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
class_param_spec = api_pb2.ClassParameterSpec(name=param.name, has_default=has_default, type=
|
292
|
+
_validate_parameter_type(self.user_cls.__name__, param.name, param.annotation)
|
293
|
+
proto_type = PYTHON_TO_PROTO_TYPE[param.annotation]
|
294
|
+
proto_type_info = PROTO_TYPE_INFO[proto_type]
|
295
|
+
class_param_spec = api_pb2.ClassParameterSpec(name=param.name, has_default=has_default, type=proto_type)
|
303
296
|
if has_default:
|
304
|
-
setattr(class_param_spec, default_field, param.default)
|
297
|
+
setattr(class_param_spec, proto_type_info.default_field, param.default)
|
305
298
|
modal_parameters.append(class_param_spec)
|
306
299
|
|
307
300
|
return api_pb2.ClassParameterInfo(
|
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 = "0.73.
|
34
|
+
version: str = "0.73.116",
|
35
35
|
): ...
|
36
36
|
def is_closed(self) -> bool: ...
|
37
37
|
@property
|
@@ -93,7 +93,7 @@ class Client:
|
|
93
93
|
server_url: str,
|
94
94
|
client_type: int,
|
95
95
|
credentials: typing.Optional[tuple[str, str]],
|
96
|
-
version: str = "0.73.
|
96
|
+
version: str = "0.73.116",
|
97
97
|
): ...
|
98
98
|
def is_closed(self) -> bool: ...
|
99
99
|
@property
|
modal/cls.py
CHANGED
@@ -9,7 +9,6 @@ from typing import Any, Callable, Optional, TypeVar, Union
|
|
9
9
|
from google.protobuf.message import Message
|
10
10
|
from grpclib import GRPCError, Status
|
11
11
|
|
12
|
-
from modal._utils.function_utils import CLASS_PARAM_TYPE_MAP, FunctionInfo
|
13
12
|
from modal_proto import api_pb2
|
14
13
|
|
15
14
|
from ._functions import _Function, _parse_retries
|
@@ -22,7 +21,7 @@ from ._partial_function import (
|
|
22
21
|
)
|
23
22
|
from ._resolver import Resolver
|
24
23
|
from ._resources import convert_fn_config_to_resources_config
|
25
|
-
from ._serialization import check_valid_cls_constructor_arg
|
24
|
+
from ._serialization import PYTHON_TO_PROTO_TYPE, check_valid_cls_constructor_arg
|
26
25
|
from ._traceback import print_server_warnings
|
27
26
|
from ._utils.async_utils import synchronize_api, synchronizer
|
28
27
|
from ._utils.deprecation import deprecation_warning, renamed_parameter, warn_on_renamed_autoscaler_settings
|
@@ -133,6 +132,8 @@ def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name:
|
|
133
132
|
|
134
133
|
if cls._is_local():
|
135
134
|
partial_function = cls._method_partials[method_name]
|
135
|
+
from modal._utils.function_utils import FunctionInfo
|
136
|
+
|
136
137
|
fun._info = FunctionInfo(
|
137
138
|
# ugly - needed for .local() TODO (elias): Clean up!
|
138
139
|
partial_function.raw_f,
|
@@ -361,6 +362,15 @@ class _Obj:
|
|
361
362
|
Obj = synchronize_api(_Obj)
|
362
363
|
|
363
364
|
|
365
|
+
def _validate_parameter_type(cls_name: str, parameter_name: str, parameter_type: type):
|
366
|
+
if parameter_type not in PYTHON_TO_PROTO_TYPE:
|
367
|
+
type_name = getattr(parameter_type, "__name__", repr(parameter_type))
|
368
|
+
supported = ", ".join(parameter_type.__name__ for parameter_type in PYTHON_TO_PROTO_TYPE.keys())
|
369
|
+
raise InvalidError(
|
370
|
+
f"{cls_name}.{parameter_name}: {type_name} is not a supported parameter type. Use one of: {supported}"
|
371
|
+
)
|
372
|
+
|
373
|
+
|
364
374
|
class _Cls(_Object, type_prefix="cs"):
|
365
375
|
"""
|
366
376
|
Cls adds method pooling and [lifecycle hook](/docs/guide/lifecycle-functions) behavior
|
@@ -461,12 +471,8 @@ class _Cls(_Object, type_prefix="cs"):
|
|
461
471
|
|
462
472
|
annotated_params = {k: t for k, t in annotations.items() if k in params}
|
463
473
|
for k, t in annotated_params.items():
|
464
|
-
if t not in
|
465
|
-
|
466
|
-
supported = ", ".join(t.__name__ for t in CLASS_PARAM_TYPE_MAP.keys())
|
467
|
-
raise InvalidError(
|
468
|
-
f"{user_cls.__name__}.{k}: {t_name} is not a supported parameter type. Use one of: {supported}"
|
469
|
-
)
|
474
|
+
if t not in PYTHON_TO_PROTO_TYPE:
|
475
|
+
_validate_parameter_type(user_cls.__name__, k, t)
|
470
476
|
|
471
477
|
@staticmethod
|
472
478
|
def from_local(user_cls, app: "modal.app._App", class_service_function: _Function) -> "_Cls":
|
modal/cls.pyi
CHANGED
@@ -109,6 +109,8 @@ class Obj:
|
|
109
109
|
async def _aenter(self): ...
|
110
110
|
def __getattr__(self, k): ...
|
111
111
|
|
112
|
+
def _validate_parameter_type(cls_name: str, parameter_name: str, parameter_type: type): ...
|
113
|
+
|
112
114
|
class _Cls(modal._object._Object):
|
113
115
|
_class_service_function: typing.Optional[modal._functions._Function]
|
114
116
|
_options: typing.Optional[_ServiceOptions]
|
modal/image.py
CHANGED
@@ -861,6 +861,8 @@ class _Image(_Object, type_prefix="im"):
|
|
861
861
|
|
862
862
|
*Added in v0.67.28*: This method replaces the deprecated `modal.Mount.from_local_python_packages` pattern.
|
863
863
|
"""
|
864
|
+
if not all(isinstance(module, str) for module in modules):
|
865
|
+
raise InvalidError("Local Python modules must be specified as strings.")
|
864
866
|
mount = _Mount._from_local_python_packages(*modules, ignore=ignore)
|
865
867
|
img = self._add_mount_layer_or_copy(mount, copy=copy)
|
866
868
|
img._added_python_source_set |= set(modules)
|
@@ -13,7 +13,7 @@ modal/_proxy_tunnel.py,sha256=gnKyCfmVB7x2d1A6c-JDysNIP3kEFxmXzhcXhPrzPn0,1906
|
|
13
13
|
modal/_pty.py,sha256=JZfPDDpzqICZqtyPI_oMJf_9w-p_lLNuzHhwhodUXio,1329
|
14
14
|
modal/_resolver.py,sha256=RtoXoYzSllPlFu0D1vel_FWiEmDO7RyToiC2bxeN8ZY,6917
|
15
15
|
modal/_resources.py,sha256=5qmcirXUI8dSH926nwkUaeX9H25mqYu9mXD_KuT79-o,1733
|
16
|
-
modal/_serialization.py,sha256=
|
16
|
+
modal/_serialization.py,sha256=xqBzCNvzDofh3DSmXFbK3nFvpz6gi4JDEnsLhcW21uI,20554
|
17
17
|
modal/_traceback.py,sha256=IZQzB3fVlUfMHOSyKUgw0H6qv4yHnpyq-XVCNZKfUdA,5023
|
18
18
|
modal/_tunnel.py,sha256=zTBxBiuH1O22tS1OliAJdIsSmaZS8PlnifS_6S5z-mk,6320
|
19
19
|
modal/_tunnel.pyi,sha256=JmmDYAy9F1FpgJ_hWx0xkom2nTOFQjn4mTPYlU3PFo4,1245
|
@@ -22,11 +22,11 @@ modal/app.py,sha256=ojhuLZuNZAQ1OsbDH0k6G4pm1W7bOIvZfXbaKlvQ-Ao,45622
|
|
22
22
|
modal/app.pyi,sha256=tZFbcsu20SuvfB2puxCyuXLFNJ9bQulzag55rVpgZmc,26827
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
24
24
|
modal/client.py,sha256=j9D3hNis1lfhnz9lVFGgJgowbH3PaGUzNKgHPWYG778,15372
|
25
|
-
modal/client.pyi,sha256=
|
25
|
+
modal/client.pyi,sha256=U8G30XH2gEmB8-0JSMqRSu_BKopRwvnPHXnNWfpSWtk,7661
|
26
26
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
27
27
|
modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
|
28
|
-
modal/cls.py,sha256=
|
29
|
-
modal/cls.pyi,sha256=
|
28
|
+
modal/cls.py,sha256=jjhzmnGdmLaBTJzVfUM81B-DWU2Mm2Rdw-m2LCGYbE0,31955
|
29
|
+
modal/cls.pyi,sha256=odrnYkqtQRiVwrAc6Ly23_zMYojIDYPo2bwMeElz-Ms,10395
|
30
30
|
modal/config.py,sha256=Zx7YsllgIJzMRKeIkaGSLLtMFV4kTUvGxpptnmqlP1U,11623
|
31
31
|
modal/container_process.py,sha256=WTqLn01dJPVkPpwR_0w_JH96ceN5mV4TGtiu1ZR2RRA,6108
|
32
32
|
modal/container_process.pyi,sha256=Hf0J5JyDdCCXBJSKx6gvkPOo0XrztCm78xzxamtzUjQ,2828
|
@@ -43,7 +43,7 @@ modal/file_pattern_matcher.py,sha256=trosX-Bp7dOubudN1bLLhRAoidWy1TcoaR4Pv8CedWw
|
|
43
43
|
modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
|
44
44
|
modal/functions.pyi,sha256=D-PDJfSbwqMDXdq7Bxu2ErZRENo-tRgu_zPoB-jl0OU,14377
|
45
45
|
modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
|
46
|
-
modal/image.py,sha256=
|
46
|
+
modal/image.py,sha256=6hDizLZvRIjRy2cMWGT06Kj1uqW_L5B9GR6x3AKr4v4,92341
|
47
47
|
modal/image.pyi,sha256=DQ4DLOCPr6_yV7z4LS0bTY0rOyvQP9-dQOrzaW7pPG8,25260
|
48
48
|
modal/io_streams.py,sha256=h5O2LmbRoT9l777z3TQhCAm-JF1r7avZ2ykXlejztDs,15163
|
49
49
|
modal/io_streams.pyi,sha256=bJ7ZLmSmJ0nKoa6r4FJpbqvzdUVa0lEe0Fa-MMpMezU,5071
|
@@ -98,7 +98,7 @@ modal/_utils/blob_utils.py,sha256=RB1G6T7eC1Poe-O45qYLaxwCr2jkM-Q6Nexk1J3wk_w,14
|
|
98
98
|
modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
|
99
99
|
modal/_utils/deprecation.py,sha256=EXP1beU4pmEqEzWMLw6E3kUfNfpmNA_VOp6i0EHi93g,4856
|
100
100
|
modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
|
101
|
-
modal/_utils/function_utils.py,sha256=
|
101
|
+
modal/_utils/function_utils.py,sha256=Un9WXZCmLb0itbDDOn4q6hF6n1r5ayhxPDb6oMCkucE,27190
|
102
102
|
modal/_utils/git_utils.py,sha256=qtUU6JAttF55ZxYq51y55OR58B0tDPZsZWK5dJe6W5g,3182
|
103
103
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
104
104
|
modal/_utils/grpc_utils.py,sha256=wmMydVKN9YbugTwUXuOuzxbpzYvxkTDaFRxlBtIDE_0,8526
|
@@ -170,10 +170,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
170
170
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
171
171
|
modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
|
172
172
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
173
|
-
modal_version/_version_generated.py,sha256=
|
174
|
-
modal-0.73.
|
175
|
-
modal-0.73.
|
176
|
-
modal-0.73.
|
177
|
-
modal-0.73.
|
178
|
-
modal-0.73.
|
179
|
-
modal-0.73.
|
173
|
+
modal_version/_version_generated.py,sha256=Dt0kKCL30ILeQYfIk21dDhOoQb1oTOnpgKttcNXZovw,150
|
174
|
+
modal-0.73.116.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
175
|
+
modal-0.73.116.dist-info/METADATA,sha256=vzU2LT0158plcQnfBuJbbg23vAPW1qNT7PL8Xhy5Ohc,2453
|
176
|
+
modal-0.73.116.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
177
|
+
modal-0.73.116.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
178
|
+
modal-0.73.116.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
179
|
+
modal-0.73.116.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|