modal 1.0.6.dev58__py3-none-any.whl → 1.2.3.dev7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (147) hide show
  1. modal/__main__.py +3 -4
  2. modal/_billing.py +80 -0
  3. modal/_clustered_functions.py +7 -3
  4. modal/_clustered_functions.pyi +4 -2
  5. modal/_container_entrypoint.py +41 -49
  6. modal/_functions.py +424 -195
  7. modal/_grpc_client.py +171 -0
  8. modal/_load_context.py +105 -0
  9. modal/_object.py +68 -20
  10. modal/_output.py +58 -45
  11. modal/_partial_function.py +36 -11
  12. modal/_pty.py +7 -3
  13. modal/_resolver.py +21 -35
  14. modal/_runtime/asgi.py +4 -3
  15. modal/_runtime/container_io_manager.py +301 -186
  16. modal/_runtime/container_io_manager.pyi +70 -61
  17. modal/_runtime/execution_context.py +18 -2
  18. modal/_runtime/execution_context.pyi +4 -1
  19. modal/_runtime/gpu_memory_snapshot.py +170 -63
  20. modal/_runtime/user_code_imports.py +28 -58
  21. modal/_serialization.py +57 -1
  22. modal/_utils/async_utils.py +33 -12
  23. modal/_utils/auth_token_manager.py +2 -5
  24. modal/_utils/blob_utils.py +110 -53
  25. modal/_utils/function_utils.py +49 -42
  26. modal/_utils/grpc_utils.py +80 -50
  27. modal/_utils/mount_utils.py +26 -1
  28. modal/_utils/name_utils.py +17 -3
  29. modal/_utils/task_command_router_client.py +536 -0
  30. modal/_utils/time_utils.py +34 -6
  31. modal/app.py +219 -83
  32. modal/app.pyi +229 -56
  33. modal/billing.py +5 -0
  34. modal/{requirements → builder}/2025.06.txt +1 -0
  35. modal/{requirements → builder}/PREVIEW.txt +1 -0
  36. modal/cli/_download.py +19 -3
  37. modal/cli/_traceback.py +3 -2
  38. modal/cli/app.py +4 -4
  39. modal/cli/cluster.py +15 -7
  40. modal/cli/config.py +5 -3
  41. modal/cli/container.py +7 -6
  42. modal/cli/dict.py +22 -16
  43. modal/cli/entry_point.py +12 -5
  44. modal/cli/environment.py +5 -4
  45. modal/cli/import_refs.py +3 -3
  46. modal/cli/launch.py +102 -5
  47. modal/cli/network_file_system.py +9 -13
  48. modal/cli/profile.py +3 -2
  49. modal/cli/programs/launch_instance_ssh.py +94 -0
  50. modal/cli/programs/run_jupyter.py +1 -1
  51. modal/cli/programs/run_marimo.py +95 -0
  52. modal/cli/programs/vscode.py +1 -1
  53. modal/cli/queues.py +57 -26
  54. modal/cli/run.py +58 -16
  55. modal/cli/secret.py +48 -22
  56. modal/cli/utils.py +3 -4
  57. modal/cli/volume.py +28 -25
  58. modal/client.py +13 -116
  59. modal/client.pyi +9 -91
  60. modal/cloud_bucket_mount.py +5 -3
  61. modal/cloud_bucket_mount.pyi +5 -1
  62. modal/cls.py +130 -102
  63. modal/cls.pyi +45 -85
  64. modal/config.py +29 -10
  65. modal/container_process.py +291 -13
  66. modal/container_process.pyi +95 -32
  67. modal/dict.py +282 -63
  68. modal/dict.pyi +423 -73
  69. modal/environments.py +15 -27
  70. modal/environments.pyi +5 -15
  71. modal/exception.py +8 -0
  72. modal/experimental/__init__.py +143 -38
  73. modal/experimental/flash.py +247 -78
  74. modal/experimental/flash.pyi +137 -9
  75. modal/file_io.py +14 -28
  76. modal/file_io.pyi +2 -2
  77. modal/file_pattern_matcher.py +25 -16
  78. modal/functions.pyi +134 -61
  79. modal/image.py +255 -86
  80. modal/image.pyi +300 -62
  81. modal/io_streams.py +436 -126
  82. modal/io_streams.pyi +236 -171
  83. modal/mount.py +62 -157
  84. modal/mount.pyi +45 -172
  85. modal/network_file_system.py +30 -53
  86. modal/network_file_system.pyi +16 -76
  87. modal/object.pyi +42 -8
  88. modal/parallel_map.py +821 -113
  89. modal/parallel_map.pyi +134 -0
  90. modal/partial_function.pyi +4 -1
  91. modal/proxy.py +16 -7
  92. modal/proxy.pyi +10 -2
  93. modal/queue.py +263 -61
  94. modal/queue.pyi +409 -66
  95. modal/runner.py +112 -92
  96. modal/runner.pyi +45 -27
  97. modal/sandbox.py +451 -124
  98. modal/sandbox.pyi +513 -67
  99. modal/secret.py +291 -67
  100. modal/secret.pyi +425 -19
  101. modal/serving.py +7 -11
  102. modal/serving.pyi +7 -8
  103. modal/snapshot.py +11 -8
  104. modal/token_flow.py +4 -4
  105. modal/volume.py +344 -98
  106. modal/volume.pyi +464 -68
  107. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +9 -8
  108. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  109. modal_docs/mdmd/mdmd.py +11 -1
  110. modal_proto/api.proto +399 -67
  111. modal_proto/api_grpc.py +241 -1
  112. modal_proto/api_pb2.py +1395 -1000
  113. modal_proto/api_pb2.pyi +1239 -79
  114. modal_proto/api_pb2_grpc.py +499 -4
  115. modal_proto/api_pb2_grpc.pyi +162 -14
  116. modal_proto/modal_api_grpc.py +175 -160
  117. modal_proto/sandbox_router.proto +145 -0
  118. modal_proto/sandbox_router_grpc.py +105 -0
  119. modal_proto/sandbox_router_pb2.py +149 -0
  120. modal_proto/sandbox_router_pb2.pyi +333 -0
  121. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  122. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  123. modal_proto/task_command_router.proto +144 -0
  124. modal_proto/task_command_router_grpc.py +105 -0
  125. modal_proto/task_command_router_pb2.py +149 -0
  126. modal_proto/task_command_router_pb2.pyi +333 -0
  127. modal_proto/task_command_router_pb2_grpc.py +203 -0
  128. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  129. modal_version/__init__.py +1 -1
  130. modal-1.0.6.dev58.dist-info/RECORD +0 -183
  131. modal_proto/modal_options_grpc.py +0 -3
  132. modal_proto/options.proto +0 -19
  133. modal_proto/options_grpc.py +0 -3
  134. modal_proto/options_pb2.py +0 -35
  135. modal_proto/options_pb2.pyi +0 -20
  136. modal_proto/options_pb2_grpc.py +0 -4
  137. modal_proto/options_pb2_grpc.pyi +0 -7
  138. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  139. /modal/{requirements → builder}/2023.12.txt +0 -0
  140. /modal/{requirements → builder}/2024.04.txt +0 -0
  141. /modal/{requirements → builder}/2024.10.txt +0 -0
  142. /modal/{requirements → builder}/README.md +0 -0
  143. /modal/{requirements → builder}/base-images.json +0 -0
  144. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  145. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  146. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  147. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/cls.pyi CHANGED
@@ -6,6 +6,7 @@ import modal._object
6
6
  import modal._partial_function
7
7
  import modal.app
8
8
  import modal.client
9
+ import modal.cloud_bucket_mount
9
10
  import modal.functions
10
11
  import modal.gpu
11
12
  import modal.object
@@ -14,7 +15,7 @@ import modal.retries
14
15
  import modal.secret
15
16
  import modal.volume
16
17
  import modal_proto.api_pb2
17
- import os
18
+ import pathlib
18
19
  import typing
19
20
  import typing_extensions
20
21
 
@@ -24,9 +25,9 @@ def _use_annotation_parameters(user_cls: type) -> bool: ...
24
25
  def _get_class_constructor_signature(user_cls: type) -> inspect.Signature: ...
25
26
 
26
27
  class _ServiceOptions:
27
- """_ServiceOptions(secrets: Collection[modal.secret._Secret] = (), validated_volumes: Sequence[tuple[str, modal.volume._Volume]] = (), resources: Optional[modal_proto.api_pb2.Resources] = None, retry_policy: Optional[modal_proto.api_pb2.FunctionRetryPolicy] = None, max_containers: Optional[int] = None, buffer_containers: Optional[int] = None, scaledown_window: Optional[int] = None, timeout_secs: Optional[int] = None, max_concurrent_inputs: Optional[int] = None, target_concurrent_inputs: Optional[int] = None, batch_max_size: Optional[int] = None, batch_wait_ms: Optional[int] = None)"""
28
+ """_ServiceOptions(secrets: collections.abc.Collection[modal.secret._Secret] = (), validated_volumes: Sequence[tuple[str, modal.volume._Volume]] = (), resources: Optional[modal_proto.api_pb2.Resources] = None, retry_policy: Optional[modal_proto.api_pb2.FunctionRetryPolicy] = None, max_containers: Optional[int] = None, buffer_containers: Optional[int] = None, scaledown_window: Optional[int] = None, timeout_secs: Optional[int] = None, max_concurrent_inputs: Optional[int] = None, target_concurrent_inputs: Optional[int] = None, batch_max_size: Optional[int] = None, batch_wait_ms: Optional[int] = None, scheduler_placement: Optional[modal_proto.api_pb2.SchedulerPlacement] = None, cloud: Optional[str] = None, cloud_bucket_mounts: Sequence[tuple[str, modal.cloud_bucket_mount._CloudBucketMount]] = ())"""
28
29
 
29
- secrets: typing.Collection[modal.secret._Secret]
30
+ secrets: collections.abc.Collection[modal.secret._Secret]
30
31
  validated_volumes: typing.Sequence[tuple[str, modal.volume._Volume]]
31
32
  resources: typing.Optional[modal_proto.api_pb2.Resources]
32
33
  retry_policy: typing.Optional[modal_proto.api_pb2.FunctionRetryPolicy]
@@ -38,6 +39,9 @@ class _ServiceOptions:
38
39
  target_concurrent_inputs: typing.Optional[int]
39
40
  batch_max_size: typing.Optional[int]
40
41
  batch_wait_ms: typing.Optional[int]
42
+ scheduler_placement: typing.Optional[modal_proto.api_pb2.SchedulerPlacement]
43
+ cloud: typing.Optional[str]
44
+ cloud_bucket_mounts: typing.Sequence[tuple[str, modal.cloud_bucket_mount._CloudBucketMount]]
41
45
 
42
46
  def merge_options(self, new_options: _ServiceOptions) -> _ServiceOptions:
43
47
  """Implement protobuf-like MergeFrom semantics for this dataclass.
@@ -48,7 +52,7 @@ class _ServiceOptions:
48
52
 
49
53
  def __init__(
50
54
  self,
51
- secrets: typing.Collection[modal.secret._Secret] = (),
55
+ secrets: collections.abc.Collection[modal.secret._Secret] = (),
52
56
  validated_volumes: typing.Sequence[tuple[str, modal.volume._Volume]] = (),
53
57
  resources: typing.Optional[modal_proto.api_pb2.Resources] = None,
54
58
  retry_policy: typing.Optional[modal_proto.api_pb2.FunctionRetryPolicy] = None,
@@ -60,6 +64,9 @@ class _ServiceOptions:
60
64
  target_concurrent_inputs: typing.Optional[int] = None,
61
65
  batch_max_size: typing.Optional[int] = None,
62
66
  batch_wait_ms: typing.Optional[int] = None,
67
+ scheduler_placement: typing.Optional[modal_proto.api_pb2.SchedulerPlacement] = None,
68
+ cloud: typing.Optional[str] = None,
69
+ cloud_bucket_mounts: typing.Sequence[tuple[str, modal.cloud_bucket_mount._CloudBucketMount]] = (),
63
70
  ) -> None:
64
71
  """Initialize self. See help(type(self)) for accurate signature."""
65
72
  ...
@@ -350,6 +357,10 @@ class _Cls(modal._object._Object):
350
357
  def _get_name(self) -> str: ...
351
358
  def _get_class_service_function(self) -> modal._functions._Function: ...
352
359
  def _get_method_names(self) -> collections.abc.Collection[str]: ...
360
+ async def _experimental_get_flash_urls(self) -> typing.Optional[list[str]]:
361
+ """URL of the flash service for the class."""
362
+ ...
363
+
353
364
  def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
354
365
  @staticmethod
355
366
  def validate_construction_mechanism(user_cls):
@@ -369,6 +380,7 @@ class _Cls(modal._object._Object):
369
380
  *,
370
381
  namespace: typing.Any = None,
371
382
  environment_name: typing.Optional[str] = None,
383
+ client: typing.Optional[modal.client._Client] = None,
372
384
  ) -> _Cls:
373
385
  """Reference a Cls from a deployed App by its name.
374
386
 
@@ -388,13 +400,19 @@ class _Cls(modal._object._Object):
388
400
  cpu: typing.Union[float, tuple[float, float], None] = None,
389
401
  memory: typing.Union[int, tuple[int, int], None] = None,
390
402
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
391
- secrets: collections.abc.Collection[modal.secret._Secret] = (),
392
- volumes: dict[typing.Union[str, os.PathLike], modal.volume._Volume] = {},
403
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
404
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
405
+ volumes: dict[
406
+ typing.Union[str, pathlib.PurePosixPath],
407
+ typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
408
+ ] = {},
393
409
  retries: typing.Union[int, modal.retries.Retries, None] = None,
394
410
  max_containers: typing.Optional[int] = None,
395
411
  buffer_containers: typing.Optional[int] = None,
396
412
  scaledown_window: typing.Optional[int] = None,
397
413
  timeout: typing.Optional[int] = None,
414
+ region: typing.Union[str, typing.Sequence[str], None] = None,
415
+ cloud: typing.Optional[str] = None,
398
416
  concurrency_limit: typing.Optional[int] = None,
399
417
  container_idle_timeout: typing.Optional[int] = None,
400
418
  allow_concurrent_inputs: typing.Optional[int] = None,
@@ -454,30 +472,6 @@ class _Cls(modal._object._Object):
454
472
  """
455
473
  ...
456
474
 
457
- @staticmethod
458
- async def lookup(
459
- app_name: str,
460
- name: str,
461
- namespace=None,
462
- client: typing.Optional[modal.client._Client] = None,
463
- environment_name: typing.Optional[str] = None,
464
- ) -> _Cls:
465
- """mdmd:hidden
466
- Lookup a Cls from a deployed App by its name.
467
-
468
- DEPRECATED: This method is deprecated in favor of `modal.Cls.from_name`.
469
-
470
- In contrast to `modal.Cls.from_name`, this is an eager method
471
- that will hydrate the local object with metadata from Modal servers.
472
-
473
- ```python notest
474
- Model = modal.Cls.from_name("other-app", "Model")
475
- model = Model()
476
- model.inference(...)
477
- ```
478
- """
479
- ...
480
-
481
475
  def __call__(self, *args, **kwargs) -> _Obj:
482
476
  """This acts as the class constructor."""
483
477
  ...
@@ -514,6 +508,18 @@ class Cls(modal.object.Object):
514
508
  def _get_name(self) -> str: ...
515
509
  def _get_class_service_function(self) -> modal.functions.Function: ...
516
510
  def _get_method_names(self) -> collections.abc.Collection[str]: ...
511
+
512
+ class ___experimental_get_flash_urls_spec(typing_extensions.Protocol[SUPERSELF]):
513
+ def __call__(self, /) -> typing.Optional[list[str]]:
514
+ """URL of the flash service for the class."""
515
+ ...
516
+
517
+ async def aio(self, /) -> typing.Optional[list[str]]:
518
+ """URL of the flash service for the class."""
519
+ ...
520
+
521
+ _experimental_get_flash_urls: ___experimental_get_flash_urls_spec[typing_extensions.Self]
522
+
517
523
  def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
518
524
  @staticmethod
519
525
  def validate_construction_mechanism(user_cls):
@@ -533,6 +539,7 @@ class Cls(modal.object.Object):
533
539
  *,
534
540
  namespace: typing.Any = None,
535
541
  environment_name: typing.Optional[str] = None,
542
+ client: typing.Optional[modal.client.Client] = None,
536
543
  ) -> Cls:
537
544
  """Reference a Cls from a deployed App by its name.
538
545
 
@@ -552,13 +559,19 @@ class Cls(modal.object.Object):
552
559
  cpu: typing.Union[float, tuple[float, float], None] = None,
553
560
  memory: typing.Union[int, tuple[int, int], None] = None,
554
561
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
555
- secrets: collections.abc.Collection[modal.secret.Secret] = (),
556
- volumes: dict[typing.Union[str, os.PathLike], modal.volume.Volume] = {},
562
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
563
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
564
+ volumes: dict[
565
+ typing.Union[str, pathlib.PurePosixPath],
566
+ typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
567
+ ] = {},
557
568
  retries: typing.Union[int, modal.retries.Retries, None] = None,
558
569
  max_containers: typing.Optional[int] = None,
559
570
  buffer_containers: typing.Optional[int] = None,
560
571
  scaledown_window: typing.Optional[int] = None,
561
572
  timeout: typing.Optional[int] = None,
573
+ region: typing.Union[str, typing.Sequence[str], None] = None,
574
+ cloud: typing.Optional[str] = None,
562
575
  concurrency_limit: typing.Optional[int] = None,
563
576
  container_idle_timeout: typing.Optional[int] = None,
564
577
  allow_concurrent_inputs: typing.Optional[int] = None,
@@ -618,59 +631,6 @@ class Cls(modal.object.Object):
618
631
  """
619
632
  ...
620
633
 
621
- class __lookup_spec(typing_extensions.Protocol):
622
- def __call__(
623
- self,
624
- /,
625
- app_name: str,
626
- name: str,
627
- namespace=None,
628
- client: typing.Optional[modal.client.Client] = None,
629
- environment_name: typing.Optional[str] = None,
630
- ) -> Cls:
631
- """mdmd:hidden
632
- Lookup a Cls from a deployed App by its name.
633
-
634
- DEPRECATED: This method is deprecated in favor of `modal.Cls.from_name`.
635
-
636
- In contrast to `modal.Cls.from_name`, this is an eager method
637
- that will hydrate the local object with metadata from Modal servers.
638
-
639
- ```python notest
640
- Model = modal.Cls.from_name("other-app", "Model")
641
- model = Model()
642
- model.inference(...)
643
- ```
644
- """
645
- ...
646
-
647
- async def aio(
648
- self,
649
- /,
650
- app_name: str,
651
- name: str,
652
- namespace=None,
653
- client: typing.Optional[modal.client.Client] = None,
654
- environment_name: typing.Optional[str] = None,
655
- ) -> Cls:
656
- """mdmd:hidden
657
- Lookup a Cls from a deployed App by its name.
658
-
659
- DEPRECATED: This method is deprecated in favor of `modal.Cls.from_name`.
660
-
661
- In contrast to `modal.Cls.from_name`, this is an eager method
662
- that will hydrate the local object with metadata from Modal servers.
663
-
664
- ```python notest
665
- Model = modal.Cls.from_name("other-app", "Model")
666
- model = Model()
667
- model.inference(...)
668
- ```
669
- """
670
- ...
671
-
672
- lookup: __lookup_spec
673
-
674
634
  def __call__(self, *args, **kwargs) -> Obj:
675
635
  """This acts as the class constructor."""
676
636
  ...
modal/config.py CHANGED
@@ -66,11 +66,15 @@ Other possible configuration options are:
66
66
  * `traceback` (in the .toml file) / `MODAL_TRACEBACK` (as an env var).
67
67
  Defaults to False. Enables printing full tracebacks on unexpected CLI
68
68
  errors, which can be useful for debugging client issues.
69
- * `log_pattern` (in the .toml file) / MODAL_LOG_PATTERN` (as an env var).
70
- Defaults to "[modal-client] %(asctime)s %(message)s"
69
+ * `log_pattern` (in the .toml file) / `MODAL_LOG_PATTERN` (as an env var).
70
+ Defaults to `"[modal-client] %(asctime)s %(message)s"`
71
71
  The log formatting pattern that will be used by the modal client itself.
72
72
  See https://docs.python.org/3/library/logging.html#logrecord-attributes for available
73
73
  log attributes.
74
+ * `dev_suffix` (in the .toml file) / `MODAL_DEV_SUFFIX` (as an env var).
75
+ Overrides the default `-dev` suffix added to URLs generated for web endpoints
76
+ when the App is ephemeral (i.e., created via `modal serve`). Must be a short
77
+ alphanumeric string.
74
78
 
75
79
  Meta-configuration
76
80
  ------------------
@@ -85,6 +89,7 @@ Some "meta-options" are set using environment variables only:
85
89
 
86
90
  import logging
87
91
  import os
92
+ import re
88
93
  import typing
89
94
  import warnings
90
95
  from typing import Any, Callable, Optional
@@ -94,7 +99,7 @@ from google.protobuf.empty_pb2 import Empty
94
99
  from modal_proto import api_pb2
95
100
 
96
101
  from ._utils.logger import configure_logger
97
- from .exception import InvalidError
102
+ from .exception import InvalidError, NotFoundError
98
103
 
99
104
  DEFAULT_SERVER_URL = "https://api.modal.com"
100
105
 
@@ -142,7 +147,7 @@ async def _lookup_workspace(server_url: str, token_id: str, token_secret: str) -
142
147
 
143
148
  credentials = (token_id, token_secret)
144
149
  async with _Client(server_url, api_pb2.CLIENT_TYPE_CLIENT, credentials) as client:
145
- return await client.stub.WorkspaceNameLookup(Empty(), timeout=3)
150
+ return await client.stub.WorkspaceNameLookup(Empty(), retry=None, timeout=3)
146
151
 
147
152
 
148
153
  def config_profiles():
@@ -158,15 +163,15 @@ def _config_active_profile() -> str:
158
163
  return "default"
159
164
 
160
165
 
161
- def config_set_active_profile(env: str) -> None:
166
+ def config_set_active_profile(profile: str) -> None:
162
167
  """Set the user's active modal profile by writing it to the `.modal.toml` file."""
163
- if env not in _user_config:
164
- raise KeyError(env)
168
+ if profile not in _user_config:
169
+ raise NotFoundError(f"No profile named '{profile}' found in {user_config_path}")
165
170
 
166
- for key, values in _user_config.items():
167
- values.pop("active", None)
171
+ for profile_data in _user_config.values():
172
+ profile_data.pop("active", None)
168
173
 
169
- _user_config[env]["active"] = True
174
+ _user_config[profile]["active"] = True # type: ignore
170
175
  _write_user_config(_user_config)
171
176
 
172
177
 
@@ -206,6 +211,12 @@ def _check_value(options: list[str]) -> Callable[[str], str]:
206
211
  return checker
207
212
 
208
213
 
214
+ def _enforce_suffix_rules(x: str) -> str:
215
+ if x and not re.match(r"^[a-zA-Z0-9]{1,8}$", x):
216
+ raise ValueError("Suffix must be an alphanumeric string of no more than 8 characters.")
217
+ return x
218
+
219
+
209
220
  class _Setting(typing.NamedTuple):
210
221
  default: typing.Any = None
211
222
  transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
@@ -236,9 +247,17 @@ _SETTINGS = {
236
247
  "traceback": _Setting(False, transform=_to_boolean),
237
248
  "image_builder_version": _Setting(),
238
249
  "strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
250
+ # Allow insecure TLS for the task command router when running locally (testing/dev only)
251
+ "task_command_router_insecure": _Setting(False, transform=_to_boolean),
239
252
  "snapshot_debug": _Setting(False, transform=_to_boolean),
240
253
  "cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
241
254
  "build_validation": _Setting("error", transform=_check_value(["error", "warn", "ignore"])),
255
+ # Payload format for function inputs/outputs: 'pickle' (default) or 'cbor'
256
+ "payload_format": _Setting(
257
+ "pickle",
258
+ transform=lambda s: _check_value(["pickle", "cbor"])(s.lower()),
259
+ ),
260
+ "dev_suffix": _Setting("", transform=_enforce_suffix_rules),
242
261
  }
243
262
 
244
263