modal 1.1.5.dev48__tar.gz → 1.1.5.dev49__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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/PKG-INFO +1 -1
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/name_utils.py +18 -3
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/app.py +16 -4
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/app.pyi +12 -2
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/client.pyi +2 -2
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/runner.py +23 -13
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/runner.pyi +2 -1
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_version/__init__.py +1 -1
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/LICENSE +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/README.md +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/__main__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_clustered_functions.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_container_entrypoint.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_functions.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_ipython.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_location.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_object.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_output.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_partial_function.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_pty.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_resolver.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_resources.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/asgi.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_serialization.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_traceback.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_tunnel.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_tunnel.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_type_manager.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/async_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/blob_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/function_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/logger.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/mount_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/_watcher.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/2025.06.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/README.md +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/builder/base-images.json +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/call_graph.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/_download.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/_traceback.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/app.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/cluster.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/config.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/container.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/dict.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/entry_point.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/environment.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/import_refs.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/launch.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/network_file_system.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/profile.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/programs/vscode.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/queues.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/run.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/secret.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/token.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/utils.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cli/volume.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/client.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cls.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/cls.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/config.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/container_process.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/container_process.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/dict.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/dict.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/environments.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/environments.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/exception.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/experimental/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/experimental/flash.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/experimental/flash.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/experimental/ipython.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/file_io.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/file_io.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/functions.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/functions.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/gpu.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/image.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/image.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/io_streams.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/io_streams.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/mount.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/mount.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/network_file_system.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/network_file_system.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/object.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/object.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/output.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/parallel_map.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/parallel_map.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/partial_function.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/partial_function.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/proxy.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/proxy.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/py.typed +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/queue.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/queue.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/retries.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/running_app.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/sandbox.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/sandbox.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/schedule.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/scheduler_placement.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/secret.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/secret.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/serving.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/serving.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/snapshot.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/snapshot.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/stream_type.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/token_flow.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/token_flow.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/volume.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal/volume.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal.egg-info/requires.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_docs/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/__init__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/api.proto +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/api_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/api_pb2.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/options.proto +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/options_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/options_pb2.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/py.typed +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/sandbox_router.proto +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/sandbox_router_pb2.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/modal_version/__main__.py +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/pyproject.toml +0 -0
- {modal-1.1.5.dev48 → modal-1.1.5.dev49}/setup.cfg +0 -0
|
@@ -31,12 +31,27 @@ def is_valid_environment_name(name: str) -> bool:
|
|
|
31
31
|
return len(name) <= 64 and re.match(r"^[a-zA-Z0-9][a-zA-Z0-9-_.]+$", name) is not None
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def is_valid_tag(tag: str) -> bool:
|
|
35
|
-
"""Tags are alphanumeric, dashes, periods, and underscores, and
|
|
36
|
-
pattern =
|
|
34
|
+
def is_valid_tag(tag: str, max_length: int = 50) -> bool:
|
|
35
|
+
"""Tags are alphanumeric, dashes, periods, and underscores, and not longer than the max_length."""
|
|
36
|
+
pattern = rf"^[a-zA-Z0-9._-]{{1,{max_length}}}$"
|
|
37
37
|
return bool(re.match(pattern, tag))
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def check_tag_dict(tags: dict[str, str]) -> dict[str, str]:
|
|
41
|
+
rules = (
|
|
42
|
+
"\n\nTags may contain only alphanumeric characters, dashes, periods, or underscores, "
|
|
43
|
+
"and must be 63 characters or less."
|
|
44
|
+
)
|
|
45
|
+
max_length = 63
|
|
46
|
+
for key, value in tags.items():
|
|
47
|
+
if not is_valid_tag(key, max_length):
|
|
48
|
+
raise InvalidError(f"Invalid tag key: {key!r}.{rules}")
|
|
49
|
+
if not is_valid_tag(value, max_length):
|
|
50
|
+
raise InvalidError(f"Invalid tag value: {value!r}.{rules}")
|
|
51
|
+
|
|
52
|
+
return tags
|
|
53
|
+
|
|
54
|
+
|
|
40
55
|
def check_object_name(name: str, object_type: str) -> None:
|
|
41
56
|
message = (
|
|
42
57
|
f"Invalid {object_type} name: '{name}'."
|
|
@@ -35,7 +35,7 @@ from ._utils.deprecation import (
|
|
|
35
35
|
from ._utils.function_utils import FunctionInfo, is_global_object, is_method_fn
|
|
36
36
|
from ._utils.grpc_utils import retry_transient_errors
|
|
37
37
|
from ._utils.mount_utils import validate_volumes
|
|
38
|
-
from ._utils.name_utils import check_object_name
|
|
38
|
+
from ._utils.name_utils import check_object_name, check_tag_dict
|
|
39
39
|
from .client import _Client
|
|
40
40
|
from .cloud_bucket_mount import _CloudBucketMount
|
|
41
41
|
from .cls import _Cls, parameter
|
|
@@ -151,6 +151,8 @@ class _App:
|
|
|
151
151
|
|
|
152
152
|
_name: Optional[str]
|
|
153
153
|
_description: Optional[str]
|
|
154
|
+
_tags: dict[str, str]
|
|
155
|
+
|
|
154
156
|
_functions: dict[str, _Function]
|
|
155
157
|
_classes: dict[str, _Cls]
|
|
156
158
|
|
|
@@ -171,6 +173,7 @@ class _App:
|
|
|
171
173
|
self,
|
|
172
174
|
name: Optional[str] = None,
|
|
173
175
|
*,
|
|
176
|
+
tags: Optional[dict[str, str]] = None, # Additional metadata to set on the App
|
|
174
177
|
image: Optional[_Image] = None, # Default Image for the App (otherwise default to `modal.Image.debian_slim()`)
|
|
175
178
|
secrets: Sequence[_Secret] = [], # Secrets to add for all Functions in the App
|
|
176
179
|
volumes: dict[Union[str, PurePosixPath], _Volume] = {}, # Volume mounts to use for all Functions
|
|
@@ -190,6 +193,7 @@ class _App:
|
|
|
190
193
|
|
|
191
194
|
self._name = name
|
|
192
195
|
self._description = name
|
|
196
|
+
self._tags = check_tag_dict(tags or {})
|
|
193
197
|
self._include_source_default = include_source
|
|
194
198
|
|
|
195
199
|
check_sequence(secrets, _Secret, "`secrets=` has to be a list or tuple of `modal.Secret` objects")
|
|
@@ -371,8 +375,8 @@ class _App:
|
|
|
371
375
|
*,
|
|
372
376
|
name: Optional[str] = None, # Name for the deployment, overriding any set on the App
|
|
373
377
|
environment_name: Optional[str] = None, # Environment to deploy the App in
|
|
374
|
-
tag: str = "", # Optional metadata that
|
|
375
|
-
client: Optional[_Client] = None, # Alternate client to use for
|
|
378
|
+
tag: str = "", # Optional metadata that is specific to this deployment
|
|
379
|
+
client: Optional[_Client] = None, # Alternate client to use for communication with the server
|
|
376
380
|
) -> typing_extensions.Self:
|
|
377
381
|
"""Deploy the App so that it is available persistently.
|
|
378
382
|
|
|
@@ -1044,7 +1048,7 @@ class _App:
|
|
|
1044
1048
|
|
|
1045
1049
|
return wrapper
|
|
1046
1050
|
|
|
1047
|
-
def include(self, /, other_app: "_App") -> typing_extensions.Self:
|
|
1051
|
+
def include(self, /, other_app: "_App", inherit_tags: bool = True) -> typing_extensions.Self:
|
|
1048
1052
|
"""Include another App's objects in this one.
|
|
1049
1053
|
|
|
1050
1054
|
Useful for splitting up Modal Apps across different self-contained files.
|
|
@@ -1067,6 +1071,10 @@ class _App:
|
|
|
1067
1071
|
# use function declared on the included app
|
|
1068
1072
|
bar.remote()
|
|
1069
1073
|
```
|
|
1074
|
+
|
|
1075
|
+
When `inherit_tags=True` any tags set on the other App will be inherited by this App
|
|
1076
|
+
(with this App's tags taking precedence in the case of conflicts).
|
|
1077
|
+
|
|
1070
1078
|
"""
|
|
1071
1079
|
for tag, function in other_app._functions.items():
|
|
1072
1080
|
self._add_function(function, False) # TODO(erikbern): webhook config?
|
|
@@ -1080,6 +1088,10 @@ class _App:
|
|
|
1080
1088
|
)
|
|
1081
1089
|
|
|
1082
1090
|
self._add_class(tag, cls)
|
|
1091
|
+
|
|
1092
|
+
if inherit_tags:
|
|
1093
|
+
self._tags = {**other_app._tags, **self._tags}
|
|
1094
|
+
|
|
1083
1095
|
return self
|
|
1084
1096
|
|
|
1085
1097
|
async def _logs(self, client: Optional[_Client] = None) -> AsyncGenerator[str, None]:
|
|
@@ -110,6 +110,7 @@ class _App:
|
|
|
110
110
|
_container_app: typing.ClassVar[typing.Optional[_App]]
|
|
111
111
|
_name: typing.Optional[str]
|
|
112
112
|
_description: typing.Optional[str]
|
|
113
|
+
_tags: dict[str, str]
|
|
113
114
|
_functions: dict[str, modal._functions._Function]
|
|
114
115
|
_classes: dict[str, modal.cls._Cls]
|
|
115
116
|
_image: typing.Optional[modal.image._Image]
|
|
@@ -126,6 +127,7 @@ class _App:
|
|
|
126
127
|
self,
|
|
127
128
|
name: typing.Optional[str] = None,
|
|
128
129
|
*,
|
|
130
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
129
131
|
image: typing.Optional[modal.image._Image] = None,
|
|
130
132
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
|
131
133
|
volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume] = {},
|
|
@@ -492,7 +494,7 @@ class _App:
|
|
|
492
494
|
"""Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
|
|
493
495
|
...
|
|
494
496
|
|
|
495
|
-
def include(self, /, other_app: _App) -> typing_extensions.Self:
|
|
497
|
+
def include(self, /, other_app: _App, inherit_tags: bool = True) -> typing_extensions.Self:
|
|
496
498
|
"""Include another App's objects in this one.
|
|
497
499
|
|
|
498
500
|
Useful for splitting up Modal Apps across different self-contained files.
|
|
@@ -515,6 +517,9 @@ class _App:
|
|
|
515
517
|
# use function declared on the included app
|
|
516
518
|
bar.remote()
|
|
517
519
|
```
|
|
520
|
+
|
|
521
|
+
When `inherit_tags=True` any tags set on the other App will be inherited by this App
|
|
522
|
+
(with this App's tags taking precedence in the case of conflicts).
|
|
518
523
|
"""
|
|
519
524
|
...
|
|
520
525
|
|
|
@@ -576,6 +581,7 @@ class App:
|
|
|
576
581
|
_container_app: typing.ClassVar[typing.Optional[App]]
|
|
577
582
|
_name: typing.Optional[str]
|
|
578
583
|
_description: typing.Optional[str]
|
|
584
|
+
_tags: dict[str, str]
|
|
579
585
|
_functions: dict[str, modal.functions.Function]
|
|
580
586
|
_classes: dict[str, modal.cls.Cls]
|
|
581
587
|
_image: typing.Optional[modal.image.Image]
|
|
@@ -592,6 +598,7 @@ class App:
|
|
|
592
598
|
self,
|
|
593
599
|
name: typing.Optional[str] = None,
|
|
594
600
|
*,
|
|
601
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
595
602
|
image: typing.Optional[modal.image.Image] = None,
|
|
596
603
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
|
597
604
|
volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume] = {},
|
|
@@ -1101,7 +1108,7 @@ class App:
|
|
|
1101
1108
|
"""Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
|
|
1102
1109
|
...
|
|
1103
1110
|
|
|
1104
|
-
def include(self, /, other_app: App) -> typing_extensions.Self:
|
|
1111
|
+
def include(self, /, other_app: App, inherit_tags: bool = True) -> typing_extensions.Self:
|
|
1105
1112
|
"""Include another App's objects in this one.
|
|
1106
1113
|
|
|
1107
1114
|
Useful for splitting up Modal Apps across different self-contained files.
|
|
@@ -1124,6 +1131,9 @@ class App:
|
|
|
1124
1131
|
# use function declared on the included app
|
|
1125
1132
|
bar.remote()
|
|
1126
1133
|
```
|
|
1134
|
+
|
|
1135
|
+
When `inherit_tags=True` any tags set on the other App will be inherited by this App
|
|
1136
|
+
(with this App's tags taking precedence in the case of conflicts).
|
|
1127
1137
|
"""
|
|
1128
1138
|
...
|
|
1129
1139
|
|
|
@@ -33,7 +33,7 @@ class _Client:
|
|
|
33
33
|
server_url: str,
|
|
34
34
|
client_type: int,
|
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.1.5.
|
|
36
|
+
version: str = "1.1.5.dev49",
|
|
37
37
|
):
|
|
38
38
|
"""mdmd:hidden
|
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -164,7 +164,7 @@ class Client:
|
|
|
164
164
|
server_url: str,
|
|
165
165
|
client_type: int,
|
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
|
167
|
-
version: str = "1.1.5.
|
|
167
|
+
version: str = "1.1.5.dev49",
|
|
168
168
|
):
|
|
169
169
|
"""mdmd:hidden
|
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -183,8 +183,9 @@ async def _publish_app(
|
|
|
183
183
|
app_state: int, # api_pb2.AppState.value
|
|
184
184
|
functions: dict[str, _Function],
|
|
185
185
|
classes: dict[str, _Cls],
|
|
186
|
-
name: str = "",
|
|
187
|
-
|
|
186
|
+
name: str = "",
|
|
187
|
+
tags: dict[str, str] = {}, # Additional App metadata
|
|
188
|
+
deployment_tag: str = "", # Only relevant for deployments
|
|
188
189
|
commit_info: Optional[api_pb2.CommitInfo] = None, # Git commit information
|
|
189
190
|
) -> tuple[str, list[api_pb2.Warning]]:
|
|
190
191
|
"""Wrapper for AppPublish RPC."""
|
|
@@ -194,12 +195,13 @@ async def _publish_app(
|
|
|
194
195
|
request = api_pb2.AppPublishRequest(
|
|
195
196
|
app_id=running_app.app_id,
|
|
196
197
|
name=name,
|
|
197
|
-
|
|
198
|
+
tags=tags,
|
|
199
|
+
deployment_tag=deployment_tag,
|
|
200
|
+
commit_info=commit_info,
|
|
198
201
|
app_state=app_state, # type: ignore : should be a api_pb2.AppState.value
|
|
199
202
|
function_ids=running_app.function_ids,
|
|
200
203
|
class_ids=running_app.class_ids,
|
|
201
204
|
definition_ids=definition_ids,
|
|
202
|
-
commit_info=commit_info,
|
|
203
205
|
)
|
|
204
206
|
|
|
205
207
|
try:
|
|
@@ -340,7 +342,7 @@ async def _run_app(
|
|
|
340
342
|
await _create_all_objects(client, running_app, app._functions, app._classes, environment_name)
|
|
341
343
|
|
|
342
344
|
# Publish the app
|
|
343
|
-
await _publish_app(client, running_app, app_state, app._functions, app._classes)
|
|
345
|
+
await _publish_app(client, running_app, app_state, app._functions, app._classes, tags=app._tags)
|
|
344
346
|
except asyncio.CancelledError as e:
|
|
345
347
|
# this typically happens on sigint/ctrl-C during setup (the KeyboardInterrupt happens in the main thread)
|
|
346
348
|
if output_mgr := _get_output_manager():
|
|
@@ -448,7 +450,14 @@ async def _serve_update(
|
|
|
448
450
|
)
|
|
449
451
|
|
|
450
452
|
# Publish the updated app
|
|
451
|
-
await _publish_app(
|
|
453
|
+
await _publish_app(
|
|
454
|
+
client,
|
|
455
|
+
running_app,
|
|
456
|
+
app_state=api_pb2.APP_STATE_UNSPECIFIED,
|
|
457
|
+
functions=app._functions,
|
|
458
|
+
classes=app._classes,
|
|
459
|
+
tags=app._tags,
|
|
460
|
+
)
|
|
452
461
|
|
|
453
462
|
# Communicate to the parent process
|
|
454
463
|
is_ready.set()
|
|
@@ -497,7 +506,7 @@ async def _deploy_app(
|
|
|
497
506
|
else:
|
|
498
507
|
check_object_name(name, "App")
|
|
499
508
|
|
|
500
|
-
if tag and not is_valid_tag(tag):
|
|
509
|
+
if tag and not is_valid_tag(tag, max_length=50):
|
|
501
510
|
raise InvalidError(
|
|
502
511
|
f"Deployment tag {tag!r} is invalid."
|
|
503
512
|
"\n\nTags may only contain alphanumeric characters, dashes, periods, and underscores, "
|
|
@@ -540,12 +549,13 @@ async def _deploy_app(
|
|
|
540
549
|
app_url, warnings = await _publish_app(
|
|
541
550
|
client,
|
|
542
551
|
running_app,
|
|
543
|
-
api_pb2.APP_STATE_DEPLOYED,
|
|
544
|
-
app._functions,
|
|
545
|
-
app._classes,
|
|
546
|
-
name,
|
|
547
|
-
|
|
548
|
-
|
|
552
|
+
app_state=api_pb2.APP_STATE_DEPLOYED,
|
|
553
|
+
functions=app._functions,
|
|
554
|
+
classes=app._classes,
|
|
555
|
+
name=name,
|
|
556
|
+
tags=app._tags,
|
|
557
|
+
deployment_tag=tag,
|
|
558
|
+
commit_info=commit_info,
|
|
549
559
|
)
|
|
550
560
|
except Exception as e:
|
|
551
561
|
# Note that AppClientDisconnect only stops the app if it's still initializing, and is a no-op otherwise.
|
|
@@ -43,7 +43,8 @@ async def _publish_app(
|
|
|
43
43
|
functions: dict[str, modal._functions._Function],
|
|
44
44
|
classes: dict[str, modal.cls._Cls],
|
|
45
45
|
name: str = "",
|
|
46
|
-
|
|
46
|
+
tags: dict[str, str] = {},
|
|
47
|
+
deployment_tag: str = "",
|
|
47
48
|
commit_info: typing.Optional[modal_proto.api_pb2.CommitInfo] = None,
|
|
48
49
|
) -> tuple[str, list[modal_proto.api_pb2.Warning]]:
|
|
49
50
|
"""Wrapper for AppPublish RPC."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|