modal 1.2.7.dev12__tar.gz → 1.2.7.dev13__tar.gz
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-1.2.7.dev12 → modal-1.2.7.dev13}/PKG-INFO +1 -1
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_functions.py +12 -6
- modal-1.2.7.dev13/modal/_ipython.py +21 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_object.py +16 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/async_utils.py +310 -2
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/grpc_testing.py +6 -3
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/app.py +5 -4
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/client.py +3 -2
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/client.pyi +4 -2
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/config.py +1 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/exception.py +4 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/functions.pyi +8 -28
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/image.py +10 -5
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/image.pyi +16 -17
- modal-1.2.7.dev13/modal/snapshot.py +56 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/snapshot.pyi +18 -10
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/volume.py +13 -4
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/volume.pyi +8 -6
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_version/__init__.py +1 -1
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/pyproject.toml +1 -0
- modal-1.2.7.dev12/modal/_ipython.py +0 -11
- modal-1.2.7.dev12/modal/snapshot.py +0 -45
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/LICENSE +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/README.md +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/__main__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_billing.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_clustered_functions.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_grpc_client.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_load_context.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_location.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_output.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_partial_function.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_pty.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_resolver.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_resources.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_serialization.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_traceback.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_tunnel.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_tunnel.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_type_manager.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/logger.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_watcher.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/app.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/billing.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/README.md +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/base-images.json +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/call_graph.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/_download.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/_traceback.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/app.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/cluster.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/config.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/container.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/dict.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/entry_point.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/environment.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/import_refs.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/launch.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/profile.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/queues.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/run.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/secret.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/shell.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/token.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/utils.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/volume.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cls.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cls.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/container_process.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/container_process.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/dict.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/dict.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/environments.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/environments.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/flash.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/ipython.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/file_io.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/file_io.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/functions.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/gpu.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/io_streams.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/io_streams.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/mount.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/mount.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/network_file_system.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/network_file_system.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/object.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/object.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/output.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/parallel_map.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/parallel_map.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/partial_function.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/partial_function.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/proxy.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/proxy.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/py.typed +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/queue.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/queue.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/retries.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/runner.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/runner.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/running_app.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/sandbox.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/sandbox.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/schedule.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/scheduler_placement.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/secret.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/secret.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/serving.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/serving.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/stream_type.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/token_flow.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/token_flow.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/__init__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api.proto +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/py.typed +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_version/__main__.py +0 -0
- {modal-1.2.7.dev12 → modal-1.2.7.dev13}/setup.cfg +0 -0
|
@@ -38,6 +38,7 @@ from ._utils.async_utils import (
|
|
|
38
38
|
aclosing,
|
|
39
39
|
async_merge,
|
|
40
40
|
callable_to_agen,
|
|
41
|
+
deprecate_aio_usage,
|
|
41
42
|
synchronizer,
|
|
42
43
|
warn_if_generator_is_not_consumed,
|
|
43
44
|
)
|
|
@@ -96,6 +97,7 @@ from .volume import _Volume
|
|
|
96
97
|
if TYPE_CHECKING:
|
|
97
98
|
import modal.app
|
|
98
99
|
import modal.cls
|
|
100
|
+
import modal.functions
|
|
99
101
|
|
|
100
102
|
MAX_INTERNAL_FAILURE_COUNT = 8
|
|
101
103
|
TERMINAL_STATUSES = (
|
|
@@ -2025,8 +2027,11 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2025
2027
|
assert self._client and self._client.stub
|
|
2026
2028
|
await self._client.stub.FunctionCallCancel(request)
|
|
2027
2029
|
|
|
2028
|
-
@
|
|
2029
|
-
|
|
2030
|
+
@deprecate_aio_usage((2025, 11, 14), "FunctionCall.from_id")
|
|
2031
|
+
@classmethod
|
|
2032
|
+
def from_id(
|
|
2033
|
+
cls, function_call_id: str, client: Optional["modal.client.Client"] = None
|
|
2034
|
+
) -> "modal.functions.FunctionCall[Any]":
|
|
2030
2035
|
"""Instantiate a FunctionCall object from an existing ID.
|
|
2031
2036
|
|
|
2032
2037
|
Examples:
|
|
@@ -2037,7 +2042,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2037
2042
|
fc_id = fc.object_id
|
|
2038
2043
|
|
|
2039
2044
|
# Later, use the ID to re-instantiate the FunctionCall object
|
|
2040
|
-
fc =
|
|
2045
|
+
fc = FunctionCall.from_id(fc_id)
|
|
2041
2046
|
result = fc.get()
|
|
2042
2047
|
```
|
|
2043
2048
|
|
|
@@ -2045,6 +2050,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2045
2050
|
if you no longer have access to the original object returned from `Function.spawn`.
|
|
2046
2051
|
|
|
2047
2052
|
"""
|
|
2053
|
+
_client = typing.cast(_Client, synchronizer._translate_in(client))
|
|
2048
2054
|
|
|
2049
2055
|
async def _load(
|
|
2050
2056
|
self: _FunctionCall, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
@@ -2053,10 +2059,10 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2053
2059
|
self._hydrate(function_call_id, load_context.client, None)
|
|
2054
2060
|
|
|
2055
2061
|
rep = f"FunctionCall.from_id({function_call_id!r})"
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext(client=client)
|
|
2062
|
+
impl_instance = _FunctionCall._from_loader(
|
|
2063
|
+
_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext(client=_client)
|
|
2059
2064
|
)
|
|
2065
|
+
return typing.cast("modal.functions.FunctionCall[Any]", synchronizer._translate_out(impl_instance))
|
|
2060
2066
|
|
|
2061
2067
|
@staticmethod
|
|
2062
2068
|
async def gather(*function_calls: "_FunctionCall[T]") -> typing.Sequence[T]:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright Modal Labs 2022
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def is_interactive_ipython():
|
|
6
|
+
"""
|
|
7
|
+
Detect if we're running in an interactive IPython session.
|
|
8
|
+
|
|
9
|
+
Returns True for IPython shells (including Jupyter notebooks), False otherwise.
|
|
10
|
+
"""
|
|
11
|
+
try:
|
|
12
|
+
# Check if IPython is available and get the current instance
|
|
13
|
+
ipython = sys.modules.get("IPython")
|
|
14
|
+
if ipython is None:
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
# Try to get the active IPython instance
|
|
18
|
+
shell = ipython.get_ipython()
|
|
19
|
+
return shell is not None
|
|
20
|
+
except Exception:
|
|
21
|
+
return False
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
+
import contextlib
|
|
2
3
|
import typing
|
|
3
4
|
import uuid
|
|
4
5
|
from collections.abc import Awaitable, Hashable, Sequence
|
|
@@ -350,3 +351,18 @@ def live_method_gen(method):
|
|
|
350
351
|
yield item
|
|
351
352
|
|
|
352
353
|
return wrapped
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def live_method_contextmanager(method):
|
|
357
|
+
# make sure a wrapped function returning an async context manager
|
|
358
|
+
# will not require both an `await func.aio()` and `async with`
|
|
359
|
+
# which would have been the case if it was wrapped in live_method
|
|
360
|
+
|
|
361
|
+
@wraps(method)
|
|
362
|
+
@contextlib.asynccontextmanager
|
|
363
|
+
async def wrapped(self, *args, **kwargs):
|
|
364
|
+
await self.hydrate()
|
|
365
|
+
async with method(self, *args, **kwargs) as ctx:
|
|
366
|
+
yield ctx
|
|
367
|
+
|
|
368
|
+
return wrapped
|
|
@@ -5,8 +5,10 @@ import contextlib
|
|
|
5
5
|
import functools
|
|
6
6
|
import inspect
|
|
7
7
|
import itertools
|
|
8
|
+
import os
|
|
8
9
|
import sys
|
|
9
10
|
import time
|
|
11
|
+
import types
|
|
10
12
|
import typing
|
|
11
13
|
import warnings
|
|
12
14
|
from collections.abc import AsyncGenerator, AsyncIterable, Awaitable, Iterable, Iterator
|
|
@@ -24,10 +26,14 @@ from typing import (
|
|
|
24
26
|
|
|
25
27
|
import synchronicity
|
|
26
28
|
from synchronicity.async_utils import Runner
|
|
29
|
+
from synchronicity.combined_types import MethodWithAio
|
|
27
30
|
from synchronicity.exceptions import NestedEventLoops
|
|
28
31
|
from typing_extensions import ParamSpec, assert_type
|
|
29
32
|
|
|
30
|
-
from
|
|
33
|
+
from modal._ipython import is_interactive_ipython
|
|
34
|
+
from modal._utils.deprecation import deprecation_warning
|
|
35
|
+
|
|
36
|
+
from ..exception import AsyncUsageWarning, InvalidError
|
|
31
37
|
from .logger import logger
|
|
32
38
|
|
|
33
39
|
T = TypeVar("T")
|
|
@@ -38,7 +44,285 @@ if sys.platform == "win32":
|
|
|
38
44
|
# quick workaround for deadlocks on shutdown - need to investigate further
|
|
39
45
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
|
|
48
|
+
def rewrite_sync_to_async(code_line: str, original_func: Callable) -> tuple[bool, str]:
|
|
49
|
+
"""
|
|
50
|
+
Rewrite a blocking call to use async/await syntax.
|
|
51
|
+
|
|
52
|
+
Handles four patterns:
|
|
53
|
+
1. __aiter__: for x in obj -> async for x in obj
|
|
54
|
+
2. __aenter__: with obj as x -> async with obj as x
|
|
55
|
+
3. Async generators in for loops: for x in obj.method(...) -> async for x in obj.method(...)
|
|
56
|
+
4. Regular methods: obj.method() -> await obj.method.aio()
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
code_line: The line of code containing the blocking call
|
|
60
|
+
original_func: The original function object being called
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A tuple of (success, rewritten_code):
|
|
64
|
+
- success: True if the pattern was found and rewritten, False if falling back to generic
|
|
65
|
+
- rewritten_code: The rewritten code or a generic suggestion
|
|
66
|
+
"""
|
|
67
|
+
import re
|
|
68
|
+
|
|
69
|
+
func_name = original_func.__name__ # type: ignore
|
|
70
|
+
|
|
71
|
+
# Check if this is an async generator function
|
|
72
|
+
is_async_gen = inspect.isasyncgenfunction(original_func)
|
|
73
|
+
|
|
74
|
+
# Handle __aiter__ pattern: for x in obj -> async for x in obj
|
|
75
|
+
if func_name == "__aiter__" and code_line.startswith("for "):
|
|
76
|
+
suggestion = code_line.replace("for ", "async for ", 1)
|
|
77
|
+
return (True, suggestion)
|
|
78
|
+
|
|
79
|
+
# Handle __aenter__ pattern: with obj as x -> async with obj as x
|
|
80
|
+
if func_name == "__aenter__" and code_line.startswith("with "):
|
|
81
|
+
suggestion = code_line.replace("with ", "async with ", 1)
|
|
82
|
+
return (True, suggestion)
|
|
83
|
+
|
|
84
|
+
# Handle __setitem__ pattern: dct['key'] = value -> suggest alternative
|
|
85
|
+
if func_name == "__setitem__":
|
|
86
|
+
# Try to extract the object and key from the bracket syntax
|
|
87
|
+
setitem_match = re.match(r"(\w+)\[([^\]]+)\]\s*=\s*(.+)", code_line.strip())
|
|
88
|
+
if setitem_match:
|
|
89
|
+
obj, key, value = setitem_match.groups()
|
|
90
|
+
suggestion = (
|
|
91
|
+
f"You can't use `{obj}[{key}] = {value}` syntax asynchronously - "
|
|
92
|
+
f"there may be an alternative api, e.g. {obj}.put.aio({key}, {value})"
|
|
93
|
+
)
|
|
94
|
+
return (False, suggestion)
|
|
95
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
96
|
+
|
|
97
|
+
# Handle __getitem__ pattern: dct['key'] -> suggest alternative
|
|
98
|
+
if func_name == "__getitem__":
|
|
99
|
+
# Try to extract the object and key from the bracket syntax
|
|
100
|
+
getitem_match = re.match(r"(\w+)\[([^\]]+)\]$", code_line.strip())
|
|
101
|
+
if getitem_match:
|
|
102
|
+
obj, key = getitem_match.groups()
|
|
103
|
+
suggestion = (
|
|
104
|
+
f"You can't use `{obj}[{key}]` syntax asynchronously - "
|
|
105
|
+
f"there may be an alternative api, e.g. {obj}.get.aio({key})"
|
|
106
|
+
)
|
|
107
|
+
return (False, suggestion)
|
|
108
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
109
|
+
|
|
110
|
+
# Handle async generator methods in for loops: for x in obj.method(...) -> async for x in obj.method(...)
|
|
111
|
+
if is_async_gen and code_line.strip().startswith("for "):
|
|
112
|
+
# Pattern: for <var> in <expr>.<method>(<args>):
|
|
113
|
+
for_pattern = rf"(for\s+\w+\s+in\s+.*\.){re.escape(func_name)}(\s*\()"
|
|
114
|
+
for_match = re.search(for_pattern, code_line)
|
|
115
|
+
|
|
116
|
+
if for_match:
|
|
117
|
+
# Just replace "for" with "async for" - no .aio() needed for async generators
|
|
118
|
+
suggestion = code_line.replace("for ", "async for ", 1)
|
|
119
|
+
return (True, suggestion)
|
|
120
|
+
|
|
121
|
+
# Handle regular method calls and property access
|
|
122
|
+
# First check if it's a property access (no parentheses after the name)
|
|
123
|
+
property_pattern = rf"\.{re.escape(func_name)}(?!\s*\()"
|
|
124
|
+
property_match = re.search(property_pattern, code_line)
|
|
125
|
+
|
|
126
|
+
if property_match:
|
|
127
|
+
# This is a property access, rewrite to use await without .aio()
|
|
128
|
+
# Find the start of the expression (skip statement keywords and assignments)
|
|
129
|
+
statement_start = 0
|
|
130
|
+
prefix_match = re.match(r"^(\s*(?:\w+\s*=|return|yield|raise)\s+)", code_line)
|
|
131
|
+
if prefix_match:
|
|
132
|
+
statement_start = len(prefix_match.group(1))
|
|
133
|
+
|
|
134
|
+
before_expr = code_line[:statement_start]
|
|
135
|
+
after_prefix = code_line[statement_start:]
|
|
136
|
+
|
|
137
|
+
# Just add await before the expression for properties
|
|
138
|
+
suggestion = before_expr + "await " + after_prefix.lstrip()
|
|
139
|
+
return (True, suggestion)
|
|
140
|
+
|
|
141
|
+
# Try to find a method call (with parentheses)
|
|
142
|
+
method_pattern = rf"\.{re.escape(func_name)}\s*\("
|
|
143
|
+
method_match = re.search(method_pattern, code_line)
|
|
144
|
+
|
|
145
|
+
if not method_match:
|
|
146
|
+
# Can't find the function call or property
|
|
147
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
148
|
+
|
|
149
|
+
# Safety check: don't attempt rewrite for complex expressions
|
|
150
|
+
unsafe_keywords = ["if", "elif", "while", "and", "or", "not", "in", "is", "for"]
|
|
151
|
+
|
|
152
|
+
# Check if line contains control flow keywords (might be too complex)
|
|
153
|
+
for keyword in unsafe_keywords:
|
|
154
|
+
if re.search(rf"\b{keyword}\b", code_line):
|
|
155
|
+
# Fall back to generic suggestion for complex expressions
|
|
156
|
+
return (False, f"await ...{func_name}.aio(...)")
|
|
157
|
+
|
|
158
|
+
# Find the start of the object expression that leads to the method call
|
|
159
|
+
# We need to find where the object/chain starts, e.g., in "2 * foo.bar.method()" we want "foo"
|
|
160
|
+
# Work backwards from the method match to find the start of the identifier chain
|
|
161
|
+
method_start = method_match.start()
|
|
162
|
+
|
|
163
|
+
# Find the start of the identifier chain (the object being called)
|
|
164
|
+
# Walk backwards to find identifiers and dots that form the chain
|
|
165
|
+
expr_start = method_start
|
|
166
|
+
i = method_start - 1
|
|
167
|
+
while i >= 0:
|
|
168
|
+
c = code_line[i]
|
|
169
|
+
if c.isalnum() or c == "_" or c == ".":
|
|
170
|
+
expr_start = i
|
|
171
|
+
i -= 1
|
|
172
|
+
elif c.isspace():
|
|
173
|
+
# Skip whitespace within the chain (though unusual)
|
|
174
|
+
i -= 1
|
|
175
|
+
else:
|
|
176
|
+
# Found a non-identifier character, stop
|
|
177
|
+
break
|
|
178
|
+
|
|
179
|
+
# Now expr_start points to the start of the object chain (e.g., "foo" in "foo.method()")
|
|
180
|
+
# But we need to check if the identifier we found is actually a keyword like return/yield/raise
|
|
181
|
+
# In that case, skip over it and find the actual object
|
|
182
|
+
before_obj = code_line[:expr_start]
|
|
183
|
+
obj_and_rest = code_line[expr_start:]
|
|
184
|
+
|
|
185
|
+
# Check if what we found starts with a statement keyword
|
|
186
|
+
keyword_match = re.match(r"^(return|yield|raise)\s+", obj_and_rest)
|
|
187
|
+
if keyword_match:
|
|
188
|
+
# The "object" we found is actually a keyword, adjust to skip it
|
|
189
|
+
keyword_len = len(keyword_match.group(0))
|
|
190
|
+
before_obj = code_line[: expr_start + keyword_len]
|
|
191
|
+
obj_and_rest = code_line[expr_start + keyword_len :]
|
|
192
|
+
|
|
193
|
+
# Add .aio() after the method name and await before the object
|
|
194
|
+
rewritten_expr = re.sub(rf"(\.{re.escape(func_name)})\s*\(", r"\1.aio(", obj_and_rest, count=1)
|
|
195
|
+
suggestion = before_obj + "await " + rewritten_expr
|
|
196
|
+
|
|
197
|
+
return (True, suggestion)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class _CallFrame:
|
|
202
|
+
"""Simple dataclass to hold call frame information."""
|
|
203
|
+
|
|
204
|
+
filename: str
|
|
205
|
+
lineno: int
|
|
206
|
+
line: Optional[str]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _extract_user_call_frame():
|
|
210
|
+
"""
|
|
211
|
+
Extract the call frame from user code by filtering out frames from synchronicity and asyncio.
|
|
212
|
+
|
|
213
|
+
Returns a _CallFrame with the filename, line number, and source line, or None if not found.
|
|
214
|
+
"""
|
|
215
|
+
import linecache
|
|
216
|
+
import os
|
|
217
|
+
|
|
218
|
+
# Get the current call stack
|
|
219
|
+
stack = inspect.stack()
|
|
220
|
+
|
|
221
|
+
# Get the absolute path of this module to filter it out
|
|
222
|
+
this_file = os.path.abspath(__file__)
|
|
223
|
+
|
|
224
|
+
# Filter out frames from synchronicity, asyncio, and this module
|
|
225
|
+
for frame_info in stack:
|
|
226
|
+
filename = frame_info.filename
|
|
227
|
+
# Skip frames from synchronicity, asyncio packages, and this module
|
|
228
|
+
# Use path separators to ensure we're matching packages, not just filenames containing these words
|
|
229
|
+
if (
|
|
230
|
+
os.path.sep + "synchronicity" + os.path.sep in filename
|
|
231
|
+
or os.path.sep + "asyncio" + os.path.sep in filename
|
|
232
|
+
or os.path.abspath(filename) == this_file
|
|
233
|
+
):
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# Found a user frame
|
|
237
|
+
line = linecache.getline(filename, frame_info.lineno)
|
|
238
|
+
return _CallFrame(filename=filename, lineno=frame_info.lineno, line=line if line else None)
|
|
239
|
+
|
|
240
|
+
# Fallback if we can't find a suitable frame
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _blocking_in_async_warning(original_func: types.FunctionType):
|
|
245
|
+
if is_interactive_ipython():
|
|
246
|
+
# in notebooks or interactive sessions where sync usage is expected
|
|
247
|
+
# even if it's actually running in an event loop
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
import warnings
|
|
251
|
+
|
|
252
|
+
# Skip warnings for __aexit__ and __anext__ - the __aenter__ and __aiter__ warnings are sufficient
|
|
253
|
+
if original_func:
|
|
254
|
+
func_name = getattr(original_func, "__name__", str(original_func))
|
|
255
|
+
if func_name in ("__aexit__", "__anext__"):
|
|
256
|
+
# These dunders would typically already have caused a warning on the __aenter__ or __aiter__ respectively
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
# Extract the call frame from the stack
|
|
260
|
+
call_frame = _extract_user_call_frame()
|
|
261
|
+
|
|
262
|
+
# Build detailed warning message with location and function first
|
|
263
|
+
message_parts = [
|
|
264
|
+
"A blocking Modal interface is being used in an async context.",
|
|
265
|
+
"\n\nThis may cause performance issues or bugs.",
|
|
266
|
+
" Consider rewriting to use Modal's async interfaces:",
|
|
267
|
+
"\nhttps://modal.com/docs/guide/async",
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
# Generate intelligent suggestion based on the context
|
|
271
|
+
suggestion = None
|
|
272
|
+
code_line = None
|
|
273
|
+
|
|
274
|
+
if original_func and call_frame and call_frame.line:
|
|
275
|
+
code_line = call_frame.line.strip()
|
|
276
|
+
# Use the unified rewrite function for all patterns
|
|
277
|
+
_, suggestion = rewrite_sync_to_async(code_line, original_func)
|
|
278
|
+
|
|
279
|
+
# Add suggestion in "change X to Y" format
|
|
280
|
+
if suggestion and code_line:
|
|
281
|
+
# this is a bit ugly, but the warnings formatter will show the offending source line
|
|
282
|
+
# on the last line regardless what we do, so we add this to not make it look out of place
|
|
283
|
+
message_parts.append(f"\n\nSuggested rewrite:\n {suggestion}\n\nOriginal line:")
|
|
284
|
+
|
|
285
|
+
# Use warn_explicit to provide precise location information from the call frame
|
|
286
|
+
if call_frame:
|
|
287
|
+
# Extract module name from filename, or use a default
|
|
288
|
+
module_name = os.path.splitext(os.path.basename(call_frame.filename))[0]
|
|
289
|
+
|
|
290
|
+
warnings.warn_explicit(
|
|
291
|
+
"".join(message_parts),
|
|
292
|
+
AsyncUsageWarning,
|
|
293
|
+
filename=call_frame.filename,
|
|
294
|
+
lineno=call_frame.lineno,
|
|
295
|
+
module=module_name,
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
# Fallback to regular warn if no frame information available
|
|
299
|
+
warnings.warn("".join(message_parts), AsyncUsageWarning)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _safe_blocking_in_async_warning(original_func: types.FunctionType):
|
|
303
|
+
"""
|
|
304
|
+
Safety wrapper around _blocking_in_async_warning to ensure it never raises exceptions.
|
|
305
|
+
|
|
306
|
+
This is non-critical functionality (just a warning), so we don't want it to break user code.
|
|
307
|
+
However, if the warning has been configured to be treated as an error (via filterwarnings),
|
|
308
|
+
we should let that propagate.
|
|
309
|
+
"""
|
|
310
|
+
from ..config import config
|
|
311
|
+
|
|
312
|
+
if not config.get("async_warnings"):
|
|
313
|
+
return
|
|
314
|
+
try:
|
|
315
|
+
_blocking_in_async_warning(original_func)
|
|
316
|
+
except AsyncUsageWarning:
|
|
317
|
+
# Re-raise the warning if it's been configured as an error
|
|
318
|
+
raise
|
|
319
|
+
except Exception:
|
|
320
|
+
# Silently ignore any other errors in the warning system
|
|
321
|
+
# We don't want the warning mechanism itself to cause problems
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
synchronizer = synchronicity.Synchronizer(blocking_in_async_callback=_safe_blocking_in_async_warning)
|
|
42
326
|
|
|
43
327
|
|
|
44
328
|
def synchronize_api(obj, target_module=None):
|
|
@@ -389,6 +673,7 @@ class _WarnIfGeneratorIsNotConsumed:
|
|
|
389
673
|
self.function_name = function_name
|
|
390
674
|
self.iterated = False
|
|
391
675
|
self.warned = False
|
|
676
|
+
self.__wrapped__ = gen
|
|
392
677
|
|
|
393
678
|
def __aiter__(self):
|
|
394
679
|
self.iterated = True
|
|
@@ -897,3 +1182,26 @@ async def async_chain(*generators: AsyncGenerator[T, None]) -> AsyncGenerator[T,
|
|
|
897
1182
|
logger.exception(f"Error closing async generator: {e}")
|
|
898
1183
|
if first_exception is not None:
|
|
899
1184
|
raise first_exception
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
def deprecate_aio_usage(deprecation_date: tuple[int, int, int], readable_sync_call: str):
|
|
1188
|
+
# Note: Currently only works on methods, not top level functions
|
|
1189
|
+
def deco(sync_implementation):
|
|
1190
|
+
if isinstance(sync_implementation, classmethod):
|
|
1191
|
+
sync_implementation = sync_implementation.__func__
|
|
1192
|
+
is_classmethod = True
|
|
1193
|
+
else:
|
|
1194
|
+
is_classmethod = False
|
|
1195
|
+
|
|
1196
|
+
async def _async_proxy(*args, **kwargs):
|
|
1197
|
+
deprecation_warning(
|
|
1198
|
+
deprecation_date,
|
|
1199
|
+
f"""The async constructor {readable_sync_call}.aio(...) will be deprecated in a future version of Modal.
|
|
1200
|
+
Please use {readable_sync_call}(...) instead (it doesn't perform any IO, and is safe in async contexts)
|
|
1201
|
+
""",
|
|
1202
|
+
)
|
|
1203
|
+
return sync_implementation(*args, **kwargs)
|
|
1204
|
+
|
|
1205
|
+
return MethodWithAio(sync_implementation, _async_proxy, synchronizer, is_classmethod=is_classmethod)
|
|
1206
|
+
|
|
1207
|
+
return deco
|
|
@@ -45,8 +45,11 @@ def patch_mock_servicer(cls):
|
|
|
45
45
|
Also patches all unimplemented abstract methods in a mock servicer with default error implementations.
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
def fallback(name: str):
|
|
49
|
+
async def _fallback(self, stream) -> None:
|
|
50
|
+
raise GRPCError(Status.UNIMPLEMENTED, f"{name} not implemented in mock servicer " + repr(cls))
|
|
51
|
+
|
|
52
|
+
return _fallback
|
|
50
53
|
|
|
51
54
|
@contextlib.contextmanager
|
|
52
55
|
def intercept(servicer):
|
|
@@ -85,7 +88,7 @@ def patch_mock_servicer(cls):
|
|
|
85
88
|
for name in dir(cls):
|
|
86
89
|
method = getattr(cls, name)
|
|
87
90
|
if getattr(method, "__isabstractmethod__", False):
|
|
88
|
-
setattr(cls, name, patch_grpc_method(name, fallback))
|
|
91
|
+
setattr(cls, name, patch_grpc_method(name, fallback(name)))
|
|
89
92
|
elif name[0].isupper() and inspect.isfunction(method):
|
|
90
93
|
setattr(cls, name, patch_grpc_method(name, method))
|
|
91
94
|
|
|
@@ -21,7 +21,7 @@ from synchronicity.async_wrap import asynccontextmanager
|
|
|
21
21
|
from modal_proto import api_pb2
|
|
22
22
|
|
|
23
23
|
from ._functions import _Function
|
|
24
|
-
from ._ipython import
|
|
24
|
+
from ._ipython import is_interactive_ipython
|
|
25
25
|
from ._load_context import LoadContext
|
|
26
26
|
from ._object import _get_environment_name, _Object
|
|
27
27
|
from ._partial_function import (
|
|
@@ -509,9 +509,10 @@ class _App:
|
|
|
509
509
|
if old_function is function:
|
|
510
510
|
return # already added the same exact instance, ignore
|
|
511
511
|
|
|
512
|
-
# In
|
|
513
|
-
#
|
|
514
|
-
|
|
512
|
+
# In a notebook or interactive REPL it would be relatively normal to rerun a cell that
|
|
513
|
+
# registers a function multiple times (i.e. as you iterate on the Function definition),
|
|
514
|
+
# and we don't want to warn about a collision in that case.
|
|
515
|
+
if not is_interactive_ipython():
|
|
515
516
|
logger.warning(
|
|
516
517
|
f"Warning: function name '{function.tag}' collision!"
|
|
517
518
|
" Overriding existing function "
|
|
@@ -71,14 +71,15 @@ class _Client:
|
|
|
71
71
|
_client_from_env_lock: ClassVar[Optional[asyncio.Lock]] = None
|
|
72
72
|
_cancellation_context: TaskContext
|
|
73
73
|
_cancellation_context_event_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
74
|
-
_stub: Optional[modal_api_grpc.ModalClientModal]
|
|
74
|
+
_stub: Optional[modal_api_grpc.ModalClientModal] = None
|
|
75
75
|
_auth_token_manager: Optional[_AuthTokenManager] = None
|
|
76
76
|
_snapshotted: bool = False
|
|
77
|
+
client_type: "api_pb2.ClientType.ValueType"
|
|
77
78
|
|
|
78
79
|
def __init__(
|
|
79
80
|
self,
|
|
80
81
|
server_url: str,
|
|
81
|
-
client_type:
|
|
82
|
+
client_type: "api_pb2.ClientType.ValueType",
|
|
82
83
|
credentials: Optional[tuple[str, str]],
|
|
83
84
|
version: str = __version__,
|
|
84
85
|
):
|
|
@@ -26,13 +26,14 @@ class _Client:
|
|
|
26
26
|
_stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
|
|
27
27
|
_auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
|
|
28
28
|
_snapshotted: bool
|
|
29
|
+
client_type: int
|
|
29
30
|
|
|
30
31
|
def __init__(
|
|
31
32
|
self,
|
|
32
33
|
server_url: str,
|
|
33
34
|
client_type: int,
|
|
34
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
35
|
-
version: str = "1.2.7.
|
|
36
|
+
version: str = "1.2.7.dev13",
|
|
36
37
|
):
|
|
37
38
|
"""mdmd:hidden
|
|
38
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -155,13 +156,14 @@ class Client:
|
|
|
155
156
|
_stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
|
|
156
157
|
_auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
|
|
157
158
|
_snapshotted: bool
|
|
159
|
+
client_type: int
|
|
158
160
|
|
|
159
161
|
def __init__(
|
|
160
162
|
self,
|
|
161
163
|
server_url: str,
|
|
162
164
|
client_type: int,
|
|
163
165
|
credentials: typing.Optional[tuple[str, str]],
|
|
164
|
-
version: str = "1.2.7.
|
|
166
|
+
version: str = "1.2.7.dev13",
|
|
165
167
|
):
|
|
166
168
|
"""mdmd:hidden
|
|
167
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -263,6 +263,7 @@ _SETTINGS = {
|
|
|
263
263
|
),
|
|
264
264
|
"dev_suffix": _Setting("", transform=_enforce_suffix_rules),
|
|
265
265
|
"max_throttle_wait": _Setting(None, transform=lambda x: int(x) if x else None),
|
|
266
|
+
"async_warnings": _Setting(False, transform=_to_boolean), # Feature flag for async API warnings
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
|
|
@@ -252,6 +252,10 @@ class ServerWarning(UserWarning):
|
|
|
252
252
|
"""Warning originating from the Modal server and re-issued in client code."""
|
|
253
253
|
|
|
254
254
|
|
|
255
|
+
class AsyncUsageWarning(UserWarning):
|
|
256
|
+
"""Warning emitted when a blocking Modal interface is used in an async context."""
|
|
257
|
+
|
|
258
|
+
|
|
255
259
|
class InternalFailure(Error):
|
|
256
260
|
"""Retriable internal error."""
|
|
257
261
|
|
|
@@ -407,7 +407,7 @@ class Function(
|
|
|
407
407
|
|
|
408
408
|
_call_generator: ___call_generator_spec
|
|
409
409
|
|
|
410
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
410
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
411
411
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
412
412
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
413
413
|
...
|
|
@@ -416,7 +416,7 @@ class Function(
|
|
|
416
416
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
417
417
|
...
|
|
418
418
|
|
|
419
|
-
remote: __remote_spec[modal._functions.
|
|
419
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
420
420
|
|
|
421
421
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
422
422
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -443,7 +443,7 @@ class Function(
|
|
|
443
443
|
"""
|
|
444
444
|
...
|
|
445
445
|
|
|
446
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
446
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
447
447
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
448
448
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
449
449
|
|
|
@@ -466,7 +466,7 @@ class Function(
|
|
|
466
466
|
"""
|
|
467
467
|
...
|
|
468
468
|
|
|
469
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
469
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
470
470
|
|
|
471
471
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
472
472
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -474,7 +474,7 @@ class Function(
|
|
|
474
474
|
|
|
475
475
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
476
476
|
|
|
477
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
477
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
478
478
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
479
479
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
480
480
|
|
|
@@ -495,7 +495,7 @@ class Function(
|
|
|
495
495
|
"""
|
|
496
496
|
...
|
|
497
497
|
|
|
498
|
-
spawn: __spawn_spec[modal._functions.
|
|
498
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
499
499
|
|
|
500
500
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
501
501
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -864,7 +864,7 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
|
|
|
864
864
|
fc_id = fc.object_id
|
|
865
865
|
|
|
866
866
|
# Later, use the ID to re-instantiate the FunctionCall object
|
|
867
|
-
fc =
|
|
867
|
+
fc = FunctionCall.from_id(fc_id)
|
|
868
868
|
result = fc.get()
|
|
869
869
|
```
|
|
870
870
|
|
|
@@ -873,27 +873,7 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
|
|
|
873
873
|
"""
|
|
874
874
|
...
|
|
875
875
|
|
|
876
|
-
async def aio(
|
|
877
|
-
self, /, function_call_id: str, client: typing.Optional[modal.client.Client] = None
|
|
878
|
-
) -> FunctionCall[typing.Any]:
|
|
879
|
-
"""Instantiate a FunctionCall object from an existing ID.
|
|
880
|
-
|
|
881
|
-
Examples:
|
|
882
|
-
|
|
883
|
-
```python notest
|
|
884
|
-
# Spawn a FunctionCall and keep track of its object ID
|
|
885
|
-
fc = my_func.spawn()
|
|
886
|
-
fc_id = fc.object_id
|
|
887
|
-
|
|
888
|
-
# Later, use the ID to re-instantiate the FunctionCall object
|
|
889
|
-
fc = _FunctionCall.from_id(fc_id)
|
|
890
|
-
result = fc.get()
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
Note that it's only necessary to re-instantiate the `FunctionCall` with this method
|
|
894
|
-
if you no longer have access to the original object returned from `Function.spawn`.
|
|
895
|
-
"""
|
|
896
|
-
...
|
|
876
|
+
async def aio(self, /, function_call_id: str, client: typing.Optional[modal.client.Client] = None): ...
|
|
897
877
|
|
|
898
878
|
from_id: typing.ClassVar[__from_id_spec]
|
|
899
879
|
|