modAL 1.2.1__tar.gz → 1.2.1.dev0__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.1 → modal-1.2.1.dev0}/PKG-INFO +5 -5
- {modal-1.2.1 → modal-1.2.1.dev0}/README.md +4 -4
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_container_entrypoint.py +1 -4
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_partial_function.py +3 -28
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/function_utils.py +0 -4
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/app.py +54 -93
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/app.pyi +18 -48
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/_download.py +3 -19
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/cluster.py +2 -4
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/container.py +2 -4
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/entry_point.py +0 -1
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/launch.py +2 -1
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/run.py +0 -6
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/volume.py +1 -7
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/client.pyi +10 -2
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cls.py +12 -5
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/config.py +0 -14
- modal-1.2.1.dev0/modal/container_process.py +196 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/container_process.pyi +32 -95
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/exception.py +0 -4
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/flash.py +47 -21
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/flash.pyi +20 -6
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/functions.pyi +6 -6
- modal-1.2.1.dev0/modal/io_streams.py +471 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/io_streams.pyi +95 -220
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/partial_function.pyi +1 -4
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/runner.py +36 -39
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/runner.pyi +24 -40
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/sandbox.py +11 -130
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/sandbox.pyi +9 -145
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/volume.py +3 -23
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/volume.pyi +0 -30
- {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/PKG-INFO +5 -5
- {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/SOURCES.txt +0 -1
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api.proto +26 -2
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_grpc.py +32 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2.py +367 -327
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2.pyi +69 -6
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2_grpc.py +67 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2_grpc.pyi +22 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/modal_api_grpc.py +2 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router.proto +4 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2.pyi +4 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_version/__init__.py +1 -1
- modal-1.2.1/modal/_utils/task_command_router_client.py +0 -537
- modal-1.2.1/modal/container_process.py +0 -476
- modal-1.2.1/modal/io_streams.py +0 -804
- {modal-1.2.1 → modal-1.2.1.dev0}/LICENSE +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/__main__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_billing.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_functions.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_ipython.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_location.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_object.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_output.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_pty.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_resolver.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_resources.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_serialization.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_traceback.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_tunnel.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_type_manager.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/_watcher.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/billing.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/README.md +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/call_graph.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/app.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/config.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/dict.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/environment.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/profile.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/queues.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/secret.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/token.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/utils.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/client.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/cls.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/dict.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/dict.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/environments.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/environments.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/file_io.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/file_io.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/functions.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/gpu.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/image.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/image.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/mount.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/mount.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/network_file_system.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/object.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/object.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/output.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/parallel_map.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/partial_function.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/proxy.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/proxy.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/py.typed +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/queue.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/queue.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/retries.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/running_app.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/schedule.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/secret.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/secret.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/serving.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/serving.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/snapshot.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/stream_type.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/token_flow.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/py.typed +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/modal_version/__main__.py +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/pyproject.toml +0 -0
- {modal-1.2.1 → modal-1.2.1.dev0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.2.1
|
|
3
|
+
Version: 1.2.1.dev0
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -32,14 +32,14 @@ Requires-Dist: watchfiles
|
|
|
32
32
|
Requires-Dist: typing_extensions~=4.6
|
|
33
33
|
Dynamic: license-file
|
|
34
34
|
|
|
35
|
-
# Modal Python
|
|
35
|
+
# Modal Python Library
|
|
36
36
|
|
|
37
37
|
[](https://pypi.org/project/modal/)
|
|
38
38
|
[](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
|
|
39
39
|
[](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
|
|
40
40
|
[](https://modal.com/slack)
|
|
41
41
|
|
|
42
|
-
The [Modal](https://modal.com/) Python
|
|
42
|
+
The [Modal](https://modal.com/) Python library provides convenient, on-demand
|
|
43
43
|
access to serverless cloud compute from Python scripts on your local computer.
|
|
44
44
|
|
|
45
45
|
## Documentation
|
|
@@ -53,10 +53,10 @@ a [user guide](https://modal.com/docs/guide), and the detailed
|
|
|
53
53
|
|
|
54
54
|
**This library requires Python 3.9 – 3.13.**
|
|
55
55
|
|
|
56
|
-
Install the package with `
|
|
56
|
+
Install the package with `pip`:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
59
|
+
pip install modal
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
You can create a Modal account (or link your existing one) directly on the
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Modal Python
|
|
1
|
+
# Modal Python Library
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/modal/)
|
|
4
4
|
[](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
|
|
5
5
|
[](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
|
|
6
6
|
[](https://modal.com/slack)
|
|
7
7
|
|
|
8
|
-
The [Modal](https://modal.com/) Python
|
|
8
|
+
The [Modal](https://modal.com/) Python library provides convenient, on-demand
|
|
9
9
|
access to serverless cloud compute from Python scripts on your local computer.
|
|
10
10
|
|
|
11
11
|
## Documentation
|
|
@@ -19,10 +19,10 @@ a [user guide](https://modal.com/docs/guide), and the detailed
|
|
|
19
19
|
|
|
20
20
|
**This library requires Python 3.9 – 3.13.**
|
|
21
21
|
|
|
22
|
-
Install the package with `
|
|
22
|
+
Install the package with `pip`:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
|
|
25
|
+
pip install modal
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
You can create a Modal account (or link your existing one) directly on the
|
|
@@ -450,10 +450,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
450
450
|
f"Function has {len(service.service_deps)} dependencies"
|
|
451
451
|
f" but container got {len(dep_object_ids)} object ids.\n"
|
|
452
452
|
f"Code deps: {service.service_deps}\n"
|
|
453
|
-
f"Object ids: {dep_object_ids}
|
|
454
|
-
"\n"
|
|
455
|
-
"This can happen if you are defining Modal objects under a conditional statement "
|
|
456
|
-
"that evaluates differently in the local and remote environments."
|
|
453
|
+
f"Object ids: {dep_object_ids}"
|
|
457
454
|
)
|
|
458
455
|
for object_id, obj in zip(dep_object_ids, service.service_deps):
|
|
459
456
|
metadata: Message = container_app.object_handle_metadata[object_id]
|
|
@@ -19,7 +19,7 @@ from ._functions import _Function
|
|
|
19
19
|
from ._utils.async_utils import synchronizer
|
|
20
20
|
from ._utils.deprecation import deprecation_warning
|
|
21
21
|
from ._utils.function_utils import callable_has_non_self_params
|
|
22
|
-
from .config import
|
|
22
|
+
from .config import logger
|
|
23
23
|
from .exception import InvalidError
|
|
24
24
|
|
|
25
25
|
MAX_MAX_BATCH_SIZE = 1000
|
|
@@ -93,26 +93,6 @@ NullaryFuncOrMethod = Union[Callable[[], Any], Callable[[Any], Any]]
|
|
|
93
93
|
NullaryMethod = Callable[[Any], Any]
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def verify_concurrent_params(params: _PartialFunctionParams, is_flash: bool = False) -> None:
|
|
97
|
-
def _verify_concurrent_params_with_flash_settings(params: _PartialFunctionParams) -> None:
|
|
98
|
-
if params.max_concurrent_inputs is not None:
|
|
99
|
-
raise TypeError(
|
|
100
|
-
"@modal.concurrent(max_inputs=...) is not yet supported for Flash functions. "
|
|
101
|
-
"Use `@modal.concurrent(target_inputs=...)` instead."
|
|
102
|
-
)
|
|
103
|
-
if params.target_concurrent_inputs is None:
|
|
104
|
-
raise TypeError("`@modal.concurrent()` missing required argument: `target_inputs`.")
|
|
105
|
-
|
|
106
|
-
def _verify_concurrent_params(params: _PartialFunctionParams) -> None:
|
|
107
|
-
if params.max_concurrent_inputs is None:
|
|
108
|
-
raise TypeError("`@modal.concurrent()` missing required argument: `max_inputs`.")
|
|
109
|
-
|
|
110
|
-
if is_flash:
|
|
111
|
-
_verify_concurrent_params_with_flash_settings(params)
|
|
112
|
-
else:
|
|
113
|
-
_verify_concurrent_params(params)
|
|
114
|
-
|
|
115
|
-
|
|
116
96
|
class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
117
97
|
"""Object produced by a decorator in the `modal` namespace
|
|
118
98
|
|
|
@@ -398,7 +378,6 @@ def _fastapi_endpoint(
|
|
|
398
378
|
method=method,
|
|
399
379
|
web_endpoint_docs=docs,
|
|
400
380
|
requested_suffix=label or "",
|
|
401
|
-
ephemeral_suffix=config.get("dev_suffix"),
|
|
402
381
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
403
382
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
404
383
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -467,7 +446,6 @@ def _web_endpoint(
|
|
|
467
446
|
method=method,
|
|
468
447
|
web_endpoint_docs=docs,
|
|
469
448
|
requested_suffix=label or "",
|
|
470
|
-
ephemeral_suffix=config.get("dev_suffix"),
|
|
471
449
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
472
450
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
473
451
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -527,7 +505,6 @@ def _asgi_app(
|
|
|
527
505
|
webhook_config = api_pb2.WebhookConfig(
|
|
528
506
|
type=api_pb2.WEBHOOK_TYPE_ASGI_APP,
|
|
529
507
|
requested_suffix=label or "",
|
|
530
|
-
ephemeral_suffix=config.get("dev_suffix"),
|
|
531
508
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
532
509
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
533
510
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -585,7 +562,6 @@ def _wsgi_app(
|
|
|
585
562
|
webhook_config = api_pb2.WebhookConfig(
|
|
586
563
|
type=api_pb2.WEBHOOK_TYPE_WSGI_APP,
|
|
587
564
|
requested_suffix=label or "",
|
|
588
|
-
ephemeral_suffix=config.get("dev_suffix"),
|
|
589
565
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
590
566
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
591
567
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -647,7 +623,6 @@ def _web_server(
|
|
|
647
623
|
webhook_config = api_pb2.WebhookConfig(
|
|
648
624
|
type=api_pb2.WEBHOOK_TYPE_WEB_SERVER,
|
|
649
625
|
requested_suffix=label or "",
|
|
650
|
-
ephemeral_suffix=config.get("dev_suffix"),
|
|
651
626
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
652
627
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
653
628
|
web_server_port=port,
|
|
@@ -785,7 +760,7 @@ def _batched(
|
|
|
785
760
|
def _concurrent(
|
|
786
761
|
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
787
762
|
*,
|
|
788
|
-
max_inputs:
|
|
763
|
+
max_inputs: int, # Hard limit on each container's input concurrency
|
|
789
764
|
target_inputs: Optional[int] = None, # Input concurrency that Modal's autoscaler should target
|
|
790
765
|
) -> Callable[
|
|
791
766
|
[Union[Callable[P, ReturnType], _PartialFunction[P, ReturnType, ReturnType]]],
|
|
@@ -837,7 +812,7 @@ def _concurrent(
|
|
|
837
812
|
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.concurrent()`."
|
|
838
813
|
)
|
|
839
814
|
|
|
840
|
-
if
|
|
815
|
+
if target_inputs and target_inputs > max_inputs:
|
|
841
816
|
raise InvalidError("`target_inputs` parameter cannot be greater than `max_inputs`.")
|
|
842
817
|
|
|
843
818
|
flags = _PartialFunctionFlags.CONCURRENT
|
|
@@ -75,10 +75,6 @@ def is_global_object(object_qual_name: str):
|
|
|
75
75
|
return "<locals>" not in object_qual_name.split(".")
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
def is_flash_object(experimental_options: Optional[dict[str, Any]]) -> bool:
|
|
79
|
-
return experimental_options.get("flash", False) if experimental_options else False
|
|
80
|
-
|
|
81
|
-
|
|
82
78
|
def is_method_fn(object_qual_name: str):
|
|
83
79
|
# methods have names like Cls.foo.
|
|
84
80
|
if "<locals>" in object_qual_name:
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import inspect
|
|
3
3
|
import typing
|
|
4
4
|
from collections.abc import AsyncGenerator, Collection, Coroutine, Mapping, Sequence
|
|
5
|
-
from dataclasses import dataclass
|
|
6
5
|
from pathlib import PurePosixPath
|
|
7
6
|
from textwrap import dedent
|
|
8
7
|
from typing import (
|
|
@@ -27,14 +26,13 @@ from ._partial_function import (
|
|
|
27
26
|
_find_partial_methods_for_user_cls,
|
|
28
27
|
_PartialFunction,
|
|
29
28
|
_PartialFunctionFlags,
|
|
30
|
-
verify_concurrent_params,
|
|
31
29
|
)
|
|
32
30
|
from ._utils.async_utils import synchronize_api
|
|
33
31
|
from ._utils.deprecation import (
|
|
34
32
|
deprecation_warning,
|
|
35
33
|
warn_on_renamed_autoscaler_settings,
|
|
36
34
|
)
|
|
37
|
-
from ._utils.function_utils import FunctionInfo,
|
|
35
|
+
from ._utils.function_utils import FunctionInfo, is_global_object, is_method_fn
|
|
38
36
|
from ._utils.grpc_utils import retry_transient_errors
|
|
39
37
|
from ._utils.mount_utils import validate_volumes
|
|
40
38
|
from ._utils.name_utils import check_object_name, check_tag_dict
|
|
@@ -116,22 +114,6 @@ class _FunctionDecoratorType:
|
|
|
116
114
|
def __call__(self, func): ...
|
|
117
115
|
|
|
118
116
|
|
|
119
|
-
@dataclass()
|
|
120
|
-
class _LocalAppState:
|
|
121
|
-
"""All state for apps that's part of the local/definition state"""
|
|
122
|
-
|
|
123
|
-
functions: dict[str, _Function]
|
|
124
|
-
classes: dict[str, _Cls]
|
|
125
|
-
image_default: Optional[_Image]
|
|
126
|
-
web_endpoints: list[str] # Used by the CLI
|
|
127
|
-
local_entrypoints: dict[str, _LocalEntrypoint]
|
|
128
|
-
tags: dict[str, str]
|
|
129
|
-
|
|
130
|
-
include_source_default: bool
|
|
131
|
-
secrets_default: Sequence[_Secret]
|
|
132
|
-
volumes_default: dict[Union[str, PurePosixPath], _Volume]
|
|
133
|
-
|
|
134
|
-
|
|
135
117
|
class _App:
|
|
136
118
|
"""A Modal App is a group of functions and classes that are deployed together.
|
|
137
119
|
|
|
@@ -169,21 +151,23 @@ class _App:
|
|
|
169
151
|
|
|
170
152
|
_name: Optional[str]
|
|
171
153
|
_description: Optional[str]
|
|
154
|
+
_tags: dict[str, str]
|
|
155
|
+
|
|
156
|
+
_functions: dict[str, _Function]
|
|
157
|
+
_classes: dict[str, _Cls]
|
|
172
158
|
|
|
173
|
-
|
|
159
|
+
_image: Optional[_Image]
|
|
160
|
+
_secrets: Sequence[_Secret]
|
|
161
|
+
_volumes: dict[Union[str, PurePosixPath], _Volume]
|
|
162
|
+
_web_endpoints: list[str] # Used by the CLI
|
|
163
|
+
_local_entrypoints: dict[str, _LocalEntrypoint]
|
|
174
164
|
|
|
175
165
|
# Running apps only (container apps or running local)
|
|
176
166
|
_app_id: Optional[str] # Kept after app finishes
|
|
177
167
|
_running_app: Optional[RunningApp] # Various app info
|
|
178
168
|
_client: Optional[_Client]
|
|
179
169
|
|
|
180
|
-
|
|
181
|
-
def _local_state(self) -> _LocalAppState:
|
|
182
|
-
"""For internal use only. Do not use this property directly."""
|
|
183
|
-
|
|
184
|
-
if self._local_state_attr is None:
|
|
185
|
-
raise AttributeError("Local state is not initialized - app is not locally available")
|
|
186
|
-
return self._local_state_attr
|
|
170
|
+
_include_source_default: Optional[bool] = None
|
|
187
171
|
|
|
188
172
|
def __init__(
|
|
189
173
|
self,
|
|
@@ -212,6 +196,8 @@ class _App:
|
|
|
212
196
|
|
|
213
197
|
self._name = name
|
|
214
198
|
self._description = name
|
|
199
|
+
self._tags = tags or {}
|
|
200
|
+
self._include_source_default = include_source
|
|
215
201
|
|
|
216
202
|
check_sequence(secrets, _Secret, "`secrets=` has to be a list or tuple of `modal.Secret` objects")
|
|
217
203
|
validate_volumes(volumes)
|
|
@@ -219,24 +205,16 @@ class _App:
|
|
|
219
205
|
if image is not None and not isinstance(image, _Image):
|
|
220
206
|
raise InvalidError("`image=` has to be a `modal.Image` object")
|
|
221
207
|
|
|
222
|
-
self.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
web_endpoints=[],
|
|
230
|
-
local_entrypoints={},
|
|
231
|
-
tags=tags or {},
|
|
232
|
-
)
|
|
208
|
+
self._functions = {}
|
|
209
|
+
self._classes = {}
|
|
210
|
+
self._image = image
|
|
211
|
+
self._secrets = secrets
|
|
212
|
+
self._volumes = volumes
|
|
213
|
+
self._local_entrypoints = {}
|
|
214
|
+
self._web_endpoints = []
|
|
233
215
|
|
|
234
|
-
# Running apps only
|
|
235
216
|
self._app_id = None
|
|
236
217
|
self._running_app = None # Set inside container, OR during the time an app is running locally
|
|
237
|
-
|
|
238
|
-
# Client is special - needed to be set just before the app is "hydrated" or running at the latest
|
|
239
|
-
# Guaranteed to be set for running apps, but also needed to actually *hydrate* the app and make it running
|
|
240
218
|
self._client = None
|
|
241
219
|
|
|
242
220
|
# Register this app. This is used to look up the app in the container, when we can't get it from the function
|
|
@@ -305,8 +283,7 @@ class _App:
|
|
|
305
283
|
|
|
306
284
|
response = await retry_transient_errors(client.stub.AppGetOrCreate, request)
|
|
307
285
|
|
|
308
|
-
app = _App(name)
|
|
309
|
-
app._local_state_attr = None # this is not a locally defined App, so no local state
|
|
286
|
+
app = _App(name)
|
|
310
287
|
app._app_id = response.app_id
|
|
311
288
|
app._client = client
|
|
312
289
|
app._running_app = RunningApp(response.app_id, interactive=False)
|
|
@@ -333,19 +310,18 @@ class _App:
|
|
|
333
310
|
App that is retrieved via `modal.App.lookup`. It is likely to be deprecated in the future.
|
|
334
311
|
|
|
335
312
|
"""
|
|
336
|
-
return self.
|
|
313
|
+
return self._image
|
|
337
314
|
|
|
338
315
|
@image.setter
|
|
339
316
|
def image(self, value):
|
|
340
317
|
"""mdmd:hidden"""
|
|
341
|
-
self.
|
|
318
|
+
self._image = value
|
|
342
319
|
|
|
343
320
|
def _uncreate_all_objects(self):
|
|
344
321
|
# TODO(erikbern): this doesn't unhydrate objects that aren't tagged
|
|
345
|
-
|
|
346
|
-
for obj in local_state.functions.values():
|
|
322
|
+
for obj in self._functions.values():
|
|
347
323
|
obj._unhydrate()
|
|
348
|
-
for obj in
|
|
324
|
+
for obj in self._classes.values():
|
|
349
325
|
obj._unhydrate()
|
|
350
326
|
|
|
351
327
|
@asynccontextmanager
|
|
@@ -481,9 +457,8 @@ class _App:
|
|
|
481
457
|
return self
|
|
482
458
|
|
|
483
459
|
def _get_default_image(self):
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return local_state.image_default
|
|
460
|
+
if self._image:
|
|
461
|
+
return self._image
|
|
487
462
|
else:
|
|
488
463
|
return _default_image
|
|
489
464
|
|
|
@@ -498,8 +473,7 @@ class _App:
|
|
|
498
473
|
return [m for m in all_mounts if m.is_local()]
|
|
499
474
|
|
|
500
475
|
def _add_function(self, function: _Function, is_web_endpoint: bool):
|
|
501
|
-
|
|
502
|
-
if old_function := local_state.functions.get(function.tag, None):
|
|
476
|
+
if old_function := self._functions.get(function.tag, None):
|
|
503
477
|
if old_function is function:
|
|
504
478
|
return # already added the same exact instance, ignore
|
|
505
479
|
|
|
@@ -510,7 +484,7 @@ class _App:
|
|
|
510
484
|
f"[{old_function._info.module_name}].{old_function._info.function_name}"
|
|
511
485
|
f" with new function [{function._info.module_name}].{function._info.function_name}"
|
|
512
486
|
)
|
|
513
|
-
if function.tag in
|
|
487
|
+
if function.tag in self._classes:
|
|
514
488
|
logger.warning(f"Warning: tag {function.tag} exists but is overridden by function")
|
|
515
489
|
|
|
516
490
|
if self._running_app:
|
|
@@ -521,9 +495,9 @@ class _App:
|
|
|
521
495
|
metadata: Message = self._running_app.object_handle_metadata[object_id]
|
|
522
496
|
function._hydrate(object_id, self._client, metadata)
|
|
523
497
|
|
|
524
|
-
|
|
498
|
+
self._functions[function.tag] = function
|
|
525
499
|
if is_web_endpoint:
|
|
526
|
-
|
|
500
|
+
self._web_endpoints.append(function.tag)
|
|
527
501
|
|
|
528
502
|
def _add_class(self, tag: str, cls: _Cls):
|
|
529
503
|
if self._running_app:
|
|
@@ -534,7 +508,7 @@ class _App:
|
|
|
534
508
|
metadata: Message = self._running_app.object_handle_metadata[object_id]
|
|
535
509
|
cls._hydrate(object_id, self._client, metadata)
|
|
536
510
|
|
|
537
|
-
self.
|
|
511
|
+
self._classes[tag] = cls
|
|
538
512
|
|
|
539
513
|
def _init_container(self, client: _Client, running_app: RunningApp):
|
|
540
514
|
self._app_id = running_app.app_id
|
|
@@ -542,18 +516,18 @@ class _App:
|
|
|
542
516
|
self._client = client
|
|
543
517
|
|
|
544
518
|
_App._container_app = self
|
|
545
|
-
|
|
519
|
+
|
|
546
520
|
# Hydrate function objects
|
|
547
521
|
for tag, object_id in running_app.function_ids.items():
|
|
548
|
-
if tag in
|
|
549
|
-
obj =
|
|
522
|
+
if tag in self._functions:
|
|
523
|
+
obj = self._functions[tag]
|
|
550
524
|
handle_metadata = running_app.object_handle_metadata[object_id]
|
|
551
525
|
obj._hydrate(object_id, client, handle_metadata)
|
|
552
526
|
|
|
553
527
|
# Hydrate class objects
|
|
554
528
|
for tag, object_id in running_app.class_ids.items():
|
|
555
|
-
if tag in
|
|
556
|
-
obj =
|
|
529
|
+
if tag in self._classes:
|
|
530
|
+
obj = self._classes[tag]
|
|
557
531
|
handle_metadata = running_app.object_handle_metadata[object_id]
|
|
558
532
|
obj._hydrate(object_id, client, handle_metadata)
|
|
559
533
|
|
|
@@ -567,7 +541,7 @@ class _App:
|
|
|
567
541
|
This method is likely to be deprecated in the future in favor of a different
|
|
568
542
|
approach for retrieving the layout of a deployed App.
|
|
569
543
|
"""
|
|
570
|
-
return self.
|
|
544
|
+
return self._functions
|
|
571
545
|
|
|
572
546
|
@property
|
|
573
547
|
def registered_classes(self) -> dict[str, _Cls]:
|
|
@@ -579,7 +553,7 @@ class _App:
|
|
|
579
553
|
This method is likely to be deprecated in the future in favor of a different
|
|
580
554
|
approach for retrieving the layout of a deployed App.
|
|
581
555
|
"""
|
|
582
|
-
return self.
|
|
556
|
+
return self._classes
|
|
583
557
|
|
|
584
558
|
@property
|
|
585
559
|
def registered_entrypoints(self) -> dict[str, _LocalEntrypoint]:
|
|
@@ -590,7 +564,7 @@ class _App:
|
|
|
590
564
|
expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
|
|
591
565
|
This method is likely to be deprecated in the future.
|
|
592
566
|
"""
|
|
593
|
-
return self.
|
|
567
|
+
return self._local_entrypoints
|
|
594
568
|
|
|
595
569
|
@property
|
|
596
570
|
def registered_web_endpoints(self) -> list[str]:
|
|
@@ -602,7 +576,7 @@ class _App:
|
|
|
602
576
|
This method is likely to be deprecated in the future in favor of a different
|
|
603
577
|
approach for retrieving the layout of a deployed App.
|
|
604
578
|
"""
|
|
605
|
-
return self.
|
|
579
|
+
return self._web_endpoints
|
|
606
580
|
|
|
607
581
|
def local_entrypoint(
|
|
608
582
|
self, _warn_parentheses_missing: Any = None, *, name: Optional[str] = None
|
|
@@ -663,11 +637,10 @@ class _App:
|
|
|
663
637
|
def wrapped(raw_f: Callable[..., Any]) -> _LocalEntrypoint:
|
|
664
638
|
info = FunctionInfo(raw_f)
|
|
665
639
|
tag = name if name is not None else raw_f.__qualname__
|
|
666
|
-
|
|
667
|
-
if tag in local_state.local_entrypoints:
|
|
640
|
+
if tag in self._local_entrypoints:
|
|
668
641
|
# TODO: get rid of this limitation.
|
|
669
642
|
raise InvalidError(f"Duplicate local entrypoint name: {tag}. Local entrypoint names must be unique.")
|
|
670
|
-
entrypoint =
|
|
643
|
+
entrypoint = self._local_entrypoints[tag] = _LocalEntrypoint(info, self)
|
|
671
644
|
return entrypoint
|
|
672
645
|
|
|
673
646
|
return wrapped
|
|
@@ -759,8 +732,7 @@ class _App:
|
|
|
759
732
|
secrets = secrets or []
|
|
760
733
|
if env:
|
|
761
734
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
762
|
-
|
|
763
|
-
secrets = [*local_state.secrets_default, *secrets]
|
|
735
|
+
secrets = [*self._secrets, *secrets]
|
|
764
736
|
|
|
765
737
|
def wrapped(
|
|
766
738
|
f: Union[_PartialFunction, Callable[..., Any], None],
|
|
@@ -803,7 +775,6 @@ class _App:
|
|
|
803
775
|
batch_max_size = f.params.batch_max_size
|
|
804
776
|
batch_wait_ms = f.params.batch_wait_ms
|
|
805
777
|
if f.flags & _PartialFunctionFlags.CONCURRENT:
|
|
806
|
-
verify_concurrent_params(params=f.params, is_flash=is_flash_object(experimental_options))
|
|
807
778
|
max_concurrent_inputs = f.params.max_concurrent_inputs
|
|
808
779
|
target_concurrent_inputs = f.params.target_concurrent_inputs
|
|
809
780
|
else:
|
|
@@ -869,7 +840,7 @@ class _App:
|
|
|
869
840
|
is_generator=is_generator,
|
|
870
841
|
gpu=gpu,
|
|
871
842
|
network_file_systems=network_file_systems,
|
|
872
|
-
volumes={**
|
|
843
|
+
volumes={**self._volumes, **volumes},
|
|
873
844
|
cpu=cpu,
|
|
874
845
|
memory=memory,
|
|
875
846
|
ephemeral_disk=ephemeral_disk,
|
|
@@ -895,7 +866,7 @@ class _App:
|
|
|
895
866
|
i6pn_enabled=i6pn_enabled,
|
|
896
867
|
cluster_size=cluster_size, # Experimental: Clustered functions
|
|
897
868
|
rdma=rdma,
|
|
898
|
-
include_source=include_source if include_source is not None else
|
|
869
|
+
include_source=include_source if include_source is not None else self._include_source_default,
|
|
899
870
|
experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
|
|
900
871
|
_experimental_proxy_ip=_experimental_proxy_ip,
|
|
901
872
|
restrict_output=_experimental_restrict_output,
|
|
@@ -992,13 +963,11 @@ class _App:
|
|
|
992
963
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
993
964
|
|
|
994
965
|
def wrapper(wrapped_cls: Union[CLS_T, _PartialFunction]) -> CLS_T:
|
|
995
|
-
local_state = self._local_state
|
|
996
966
|
# Check if the decorated object is a class
|
|
997
967
|
if isinstance(wrapped_cls, _PartialFunction):
|
|
998
968
|
wrapped_cls.registered = True
|
|
999
969
|
user_cls = wrapped_cls.user_cls
|
|
1000
970
|
if wrapped_cls.flags & _PartialFunctionFlags.CONCURRENT:
|
|
1001
|
-
verify_concurrent_params(params=wrapped_cls.params, is_flash=is_flash_object(experimental_options))
|
|
1002
971
|
max_concurrent_inputs = wrapped_cls.params.max_concurrent_inputs
|
|
1003
972
|
target_concurrent_inputs = wrapped_cls.params.target_concurrent_inputs
|
|
1004
973
|
else:
|
|
@@ -1060,10 +1029,10 @@ class _App:
|
|
|
1060
1029
|
info,
|
|
1061
1030
|
app=self,
|
|
1062
1031
|
image=image or self._get_default_image(),
|
|
1063
|
-
secrets=[*
|
|
1032
|
+
secrets=[*self._secrets, *secrets],
|
|
1064
1033
|
gpu=gpu,
|
|
1065
1034
|
network_file_systems=network_file_systems,
|
|
1066
|
-
volumes={**
|
|
1035
|
+
volumes={**self._volumes, **volumes},
|
|
1067
1036
|
cpu=cpu,
|
|
1068
1037
|
memory=memory,
|
|
1069
1038
|
ephemeral_disk=ephemeral_disk,
|
|
@@ -1088,7 +1057,7 @@ class _App:
|
|
|
1088
1057
|
i6pn_enabled=i6pn_enabled,
|
|
1089
1058
|
cluster_size=cluster_size,
|
|
1090
1059
|
rdma=rdma,
|
|
1091
|
-
include_source=include_source if include_source is not None else
|
|
1060
|
+
include_source=include_source if include_source is not None else self._include_source_default,
|
|
1092
1061
|
experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
|
|
1093
1062
|
_experimental_proxy_ip=_experimental_proxy_ip,
|
|
1094
1063
|
_experimental_custom_scaling_factor=_experimental_custom_scaling_factor,
|
|
@@ -1098,11 +1067,6 @@ class _App:
|
|
|
1098
1067
|
self._add_function(cls_func, is_web_endpoint=False)
|
|
1099
1068
|
|
|
1100
1069
|
cls: _Cls = _Cls.from_local(user_cls, self, cls_func)
|
|
1101
|
-
for method_name, partial_function in cls._method_partials.items():
|
|
1102
|
-
if partial_function.params.webhook_config is not None:
|
|
1103
|
-
full_name = f"{user_cls.__name__}.{method_name}"
|
|
1104
|
-
local_state.web_endpoints.append(full_name)
|
|
1105
|
-
partial_function.registered = True
|
|
1106
1070
|
|
|
1107
1071
|
tag: str = user_cls.__name__
|
|
1108
1072
|
self._add_class(tag, cls)
|
|
@@ -1138,14 +1102,11 @@ class _App:
|
|
|
1138
1102
|
(with this App's tags taking precedence in the case of conflicts).
|
|
1139
1103
|
|
|
1140
1104
|
"""
|
|
1141
|
-
|
|
1142
|
-
this_local_state = self._local_state
|
|
1143
|
-
|
|
1144
|
-
for tag, function in other_app_local_state.functions.items():
|
|
1105
|
+
for tag, function in other_app._functions.items():
|
|
1145
1106
|
self._add_function(function, False) # TODO(erikbern): webhook config?
|
|
1146
1107
|
|
|
1147
|
-
for tag, cls in
|
|
1148
|
-
existing_cls =
|
|
1108
|
+
for tag, cls in other_app._classes.items():
|
|
1109
|
+
existing_cls = self._classes.get(tag)
|
|
1149
1110
|
if existing_cls and existing_cls != cls:
|
|
1150
1111
|
logger.warning(
|
|
1151
1112
|
f"Named app class {tag} with existing value {existing_cls} is being "
|
|
@@ -1155,7 +1116,7 @@ class _App:
|
|
|
1155
1116
|
self._add_class(tag, cls)
|
|
1156
1117
|
|
|
1157
1118
|
if inherit_tags:
|
|
1158
|
-
|
|
1119
|
+
self._tags = {**other_app._tags, **self._tags}
|
|
1159
1120
|
|
|
1160
1121
|
return self
|
|
1161
1122
|
|
|
@@ -1171,7 +1132,7 @@ class _App:
|
|
|
1171
1132
|
|
|
1172
1133
|
"""
|
|
1173
1134
|
# Note that we are requiring the App to be "running" before we set the tags.
|
|
1174
|
-
# Alternatively, we could hold onto the tags (i.e. in `self.
|
|
1135
|
+
# Alternatively, we could hold onto the tags (i.e. in `self._tags`) and then pass
|
|
1175
1136
|
# then up when AppPublish gets called. I'm not certain we want to support it, though.
|
|
1176
1137
|
# It might not be obvious to users that `.set_tags()` is eager and has immediate effect
|
|
1177
1138
|
# when the App is running, but lazy (and potentially ignored) otherwise. There would be
|
|
@@ -74,42 +74,6 @@ class _FunctionDecoratorType:
|
|
|
74
74
|
self, func: collections.abc.Callable[P, ReturnType]
|
|
75
75
|
) -> modal.functions.Function[P, ReturnType, ReturnType]: ...
|
|
76
76
|
|
|
77
|
-
class _LocalAppState:
|
|
78
|
-
"""All state for apps that's part of the local/definition state"""
|
|
79
|
-
|
|
80
|
-
functions: dict[str, modal._functions._Function]
|
|
81
|
-
classes: dict[str, modal.cls._Cls]
|
|
82
|
-
image_default: typing.Optional[modal.image._Image]
|
|
83
|
-
web_endpoints: list[str]
|
|
84
|
-
local_entrypoints: dict[str, _LocalEntrypoint]
|
|
85
|
-
tags: dict[str, str]
|
|
86
|
-
include_source_default: bool
|
|
87
|
-
secrets_default: collections.abc.Sequence[modal.secret._Secret]
|
|
88
|
-
volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
|
|
89
|
-
|
|
90
|
-
def __init__(
|
|
91
|
-
self,
|
|
92
|
-
functions: dict[str, modal._functions._Function],
|
|
93
|
-
classes: dict[str, modal.cls._Cls],
|
|
94
|
-
image_default: typing.Optional[modal.image._Image],
|
|
95
|
-
web_endpoints: list[str],
|
|
96
|
-
local_entrypoints: dict[str, _LocalEntrypoint],
|
|
97
|
-
tags: dict[str, str],
|
|
98
|
-
include_source_default: bool,
|
|
99
|
-
secrets_default: collections.abc.Sequence[modal.secret._Secret],
|
|
100
|
-
volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume],
|
|
101
|
-
) -> None:
|
|
102
|
-
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
103
|
-
...
|
|
104
|
-
|
|
105
|
-
def __repr__(self):
|
|
106
|
-
"""Return repr(self)."""
|
|
107
|
-
...
|
|
108
|
-
|
|
109
|
-
def __eq__(self, other):
|
|
110
|
-
"""Return self==value."""
|
|
111
|
-
...
|
|
112
|
-
|
|
113
77
|
class _App:
|
|
114
78
|
"""A Modal App is a group of functions and classes that are deployed together.
|
|
115
79
|
|
|
@@ -146,15 +110,18 @@ class _App:
|
|
|
146
110
|
_container_app: typing.ClassVar[typing.Optional[_App]]
|
|
147
111
|
_name: typing.Optional[str]
|
|
148
112
|
_description: typing.Optional[str]
|
|
149
|
-
|
|
113
|
+
_tags: dict[str, str]
|
|
114
|
+
_functions: dict[str, modal._functions._Function]
|
|
115
|
+
_classes: dict[str, modal.cls._Cls]
|
|
116
|
+
_image: typing.Optional[modal.image._Image]
|
|
117
|
+
_secrets: collections.abc.Sequence[modal.secret._Secret]
|
|
118
|
+
_volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
|
|
119
|
+
_web_endpoints: list[str]
|
|
120
|
+
_local_entrypoints: dict[str, _LocalEntrypoint]
|
|
150
121
|
_app_id: typing.Optional[str]
|
|
151
122
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
|
152
123
|
_client: typing.Optional[modal.client._Client]
|
|
153
|
-
|
|
154
|
-
@property
|
|
155
|
-
def _local_state(self) -> _LocalAppState:
|
|
156
|
-
"""For internal use only. Do not use this property directly."""
|
|
157
|
-
...
|
|
124
|
+
_include_source_default: typing.Optional[bool]
|
|
158
125
|
|
|
159
126
|
def __init__(
|
|
160
127
|
self,
|
|
@@ -663,10 +630,18 @@ class App:
|
|
|
663
630
|
_container_app: typing.ClassVar[typing.Optional[App]]
|
|
664
631
|
_name: typing.Optional[str]
|
|
665
632
|
_description: typing.Optional[str]
|
|
666
|
-
|
|
633
|
+
_tags: dict[str, str]
|
|
634
|
+
_functions: dict[str, modal.functions.Function]
|
|
635
|
+
_classes: dict[str, modal.cls.Cls]
|
|
636
|
+
_image: typing.Optional[modal.image.Image]
|
|
637
|
+
_secrets: collections.abc.Sequence[modal.secret.Secret]
|
|
638
|
+
_volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume]
|
|
639
|
+
_web_endpoints: list[str]
|
|
640
|
+
_local_entrypoints: dict[str, LocalEntrypoint]
|
|
667
641
|
_app_id: typing.Optional[str]
|
|
668
642
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
|
669
643
|
_client: typing.Optional[modal.client.Client]
|
|
644
|
+
_include_source_default: typing.Optional[bool]
|
|
670
645
|
|
|
671
646
|
def __init__(
|
|
672
647
|
self,
|
|
@@ -689,11 +664,6 @@ class App:
|
|
|
689
664
|
"""
|
|
690
665
|
...
|
|
691
666
|
|
|
692
|
-
@property
|
|
693
|
-
def _local_state(self) -> _LocalAppState:
|
|
694
|
-
"""For internal use only. Do not use this property directly."""
|
|
695
|
-
...
|
|
696
|
-
|
|
697
667
|
@property
|
|
698
668
|
def name(self) -> typing.Optional[str]:
|
|
699
669
|
"""The user-provided name of the App."""
|