modal 0.68.31__py3-none-any.whl → 0.68.50__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.
Files changed (48) hide show
  1. modal/_runtime/asgi.py +11 -4
  2. modal/_traceback.py +6 -2
  3. modal/_utils/deprecation.py +45 -0
  4. modal/_utils/function_utils.py +24 -13
  5. modal/app.py +5 -4
  6. modal/app.pyi +3 -3
  7. modal/cli/dict.py +6 -2
  8. modal/cli/network_file_system.py +1 -1
  9. modal/cli/run.py +1 -0
  10. modal/cli/volume.py +1 -1
  11. modal/client.pyi +2 -2
  12. modal/cls.py +15 -9
  13. modal/cls.pyi +5 -5
  14. modal/dict.py +11 -17
  15. modal/dict.pyi +8 -12
  16. modal/environments.py +10 -7
  17. modal/environments.pyi +6 -6
  18. modal/functions.py +11 -7
  19. modal/functions.pyi +7 -5
  20. modal/gpu.py +22 -0
  21. modal/image.py +0 -15
  22. modal/image.pyi +0 -26
  23. modal/mount.py +14 -8
  24. modal/mount.pyi +4 -4
  25. modal/network_file_system.py +12 -19
  26. modal/network_file_system.pyi +8 -12
  27. modal/partial_function.py +0 -29
  28. modal/queue.py +11 -17
  29. modal/queue.pyi +8 -12
  30. modal/sandbox.py +2 -0
  31. modal/secret.py +7 -4
  32. modal/secret.pyi +5 -5
  33. modal/volume.py +11 -17
  34. modal/volume.pyi +8 -12
  35. {modal-0.68.31.dist-info → modal-0.68.50.dist-info}/METADATA +2 -2
  36. {modal-0.68.31.dist-info → modal-0.68.50.dist-info}/RECORD +48 -48
  37. modal_proto/api.proto +9 -0
  38. modal_proto/api_grpc.py +16 -0
  39. modal_proto/api_pb2.py +785 -765
  40. modal_proto/api_pb2.pyi +30 -0
  41. modal_proto/api_pb2_grpc.py +33 -0
  42. modal_proto/api_pb2_grpc.pyi +10 -0
  43. modal_proto/modal_api_grpc.py +1 -0
  44. modal_version/_version_generated.py +1 -1
  45. {modal-0.68.31.dist-info → modal-0.68.50.dist-info}/LICENSE +0 -0
  46. {modal-0.68.31.dist-info → modal-0.68.50.dist-info}/WHEEL +0 -0
  47. {modal-0.68.31.dist-info → modal-0.68.50.dist-info}/entry_points.txt +0 -0
  48. {modal-0.68.31.dist-info → modal-0.68.50.dist-info}/top_level.txt +0 -0
modal/_runtime/asgi.py CHANGED
@@ -26,6 +26,7 @@ class LifespanManager:
26
26
  shutdown: asyncio.Future
27
27
  queue: asyncio.Queue
28
28
  has_run_init: bool = False
29
+ lifespan_supported: bool = False
29
30
 
30
31
  def __init__(self, asgi_app, state):
31
32
  self.asgi_app = asgi_app
@@ -46,6 +47,7 @@ class LifespanManager:
46
47
  await self.ensure_init()
47
48
 
48
49
  async def receive():
50
+ self.lifespan_supported = True
49
51
  return await self.queue.get()
50
52
 
51
53
  async def send(message):
@@ -63,16 +65,21 @@ class LifespanManager:
63
65
  try:
64
66
  await self.asgi_app({"type": "lifespan", "state": self.state}, receive, send)
65
67
  except Exception as e:
68
+ if not self.lifespan_supported:
69
+ logger.info(f"ASGI lifespan task exited before receiving any messages with exception:\n{e}")
70
+ self.startup.set_result(None)
71
+ self.shutdown.set_result(None)
72
+ return
73
+
66
74
  logger.error(f"Error in ASGI lifespan task: {e}")
67
75
  if not self.startup.done():
68
76
  self.startup.set_exception(ExecutionError("ASGI lifespan task exited startup"))
69
77
  if not self.shutdown.done():
70
78
  self.shutdown.set_exception(ExecutionError("ASGI lifespan task exited shutdown"))
71
79
  else:
72
- if not self.startup.done():
73
- self.startup.set_result("ASGI Lifespan protocol is probably not supported by this library")
74
- if not self.shutdown.done():
75
- self.shutdown.set_result("ASGI Lifespan protocol is probably not supported by this library")
80
+ logger.info("ASGI Lifespan protocol is probably not supported by this library")
81
+ self.startup.set_result(None)
82
+ self.shutdown.set_result(None)
76
83
 
77
84
  async def lifespan_startup(self):
78
85
  await self.ensure_init()
modal/_traceback.py CHANGED
@@ -74,11 +74,15 @@ def append_modal_tb(exc: BaseException, tb_dict: TBDictType, line_cache: LineCac
74
74
 
75
75
  def reduce_traceback_to_user_code(tb: Optional[TracebackType], user_source: str) -> TracebackType:
76
76
  """Return a traceback that does not contain modal entrypoint or synchronicity frames."""
77
- # Step forward all the way through the traceback and drop any synchronicity frames
77
+
78
+ # Step forward all the way through the traceback and drop any "Modal support" frames
79
+ def skip_frame(filename: str) -> bool:
80
+ return "/site-packages/synchronicity/" in filename or "modal/_utils/deprecation" in filename
81
+
78
82
  tb_root = tb
79
83
  while tb is not None:
80
84
  while tb.tb_next is not None:
81
- if "/site-packages/synchronicity/" in tb.tb_next.tb_frame.f_code.co_filename:
85
+ if skip_frame(tb.tb_next.tb_frame.f_code.co_filename):
82
86
  tb.tb_next = tb.tb_next.tb_next
83
87
  else:
84
88
  break
@@ -1,7 +1,11 @@
1
1
  # Copyright Modal Labs 2024
2
+ import functools
2
3
  import sys
3
4
  import warnings
4
5
  from datetime import date
6
+ from typing import Any, Callable, TypeVar
7
+
8
+ from typing_extensions import ParamSpec # Needed for Python 3.9
5
9
 
6
10
  from ..exception import DeprecationError, PendingDeprecationError
7
11
 
@@ -42,3 +46,44 @@ def deprecation_warning(
42
46
 
43
47
  # This is a lower-level function that warnings.warn uses
44
48
  warnings.warn_explicit(f"{date(*deprecated_on)}: {msg}", warning_cls, filename, lineno)
49
+
50
+
51
+ P = ParamSpec("P")
52
+ R = TypeVar("R")
53
+
54
+
55
+ def renamed_parameter(
56
+ date: tuple[int, int, int],
57
+ old_name: str,
58
+ new_name: str,
59
+ show_source: bool = True,
60
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
61
+ """Decorator for semi-gracefully changing a parameter name.
62
+
63
+ Functions wrapped with this decorator can be defined using only the `new_name` of the parameter.
64
+ If the function is invoked with the `old_name`, the wrapper will pass the value as a keyword
65
+ argument for `new_name` and issue a Modal deprecation warning about the change.
66
+
67
+ Note that this only prevents parameter renamings from breaking code at runtime.
68
+ Type checking will fail when code uses `old_name`. To avoid this, the `old_name` can be
69
+ preserved in the function signature with an `Annotated` type hint indicating the renaming.
70
+ """
71
+
72
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
73
+ @functools.wraps(func)
74
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
75
+ mut_kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
76
+ if old_name in mut_kwargs:
77
+ mut_kwargs[new_name] = mut_kwargs.pop(old_name)
78
+ func_name = func.__qualname__.removeprefix("_") # Avoid confusion when synchronicity-wrapped
79
+ message = (
80
+ f"The '{old_name}' parameter of `{func_name}` has been renamed to '{new_name}'."
81
+ "\nUsing the old name will become an error in a future release. Please update your code."
82
+ )
83
+ deprecation_warning(date, message, show_source=show_source)
84
+
85
+ return func(*args, **kwargs)
86
+
87
+ return wrapper
88
+
89
+ return decorator
@@ -99,16 +99,18 @@ def get_function_type(is_generator: Optional[bool]) -> "api_pb2.Function.Functio
99
99
 
100
100
 
101
101
  class FunctionInfo:
102
- """Class that helps us extract a bunch of information about a locally defined function.
102
+ """Utility that determines serialization/deserialization mechanisms for functions
103
103
 
104
- Used for populating the definition of a remote function, and for making .local() calls
105
- on a host with the local definition available.
104
+ * Stored as file vs serialized
105
+ * If serialized: how to serialize the function
106
+ * If file: which module/function name should be used to retrieve
107
+
108
+ Used for populating the definition of a remote function
106
109
  """
107
110
 
108
111
  raw_f: Optional[Callable[..., Any]] # if None - this is a "class service function"
109
112
  function_name: str
110
113
  user_cls: Optional[type[Any]]
111
- definition_type: "modal_proto.api_pb2.Function.DefinitionType.ValueType"
112
114
  module_name: Optional[str]
113
115
 
114
116
  _type: FunctionInfoType
@@ -116,6 +118,12 @@ class FunctionInfo:
116
118
  _base_dir: str
117
119
  _remote_dir: Optional[PurePosixPath] = None
118
120
 
121
+ def get_definition_type(self) -> "modal_proto.api_pb2.Function.DefinitionType.ValueType":
122
+ if self.is_serialized():
123
+ return modal_proto.api_pb2.Function.DEFINITION_TYPE_SERIALIZED
124
+ else:
125
+ return modal_proto.api_pb2.Function.DEFINITION_TYPE_FILE
126
+
119
127
  def is_service_class(self):
120
128
  if self.raw_f is None:
121
129
  assert self.user_cls
@@ -172,7 +180,7 @@ class FunctionInfo:
172
180
  self._base_dir = base_dirs[0]
173
181
  self.module_name = module.__spec__.name
174
182
  self._remote_dir = ROOT_DIR / PurePosixPath(module.__package__.split(".")[0])
175
- self.definition_type = api_pb2.Function.DEFINITION_TYPE_FILE
183
+ self._is_serialized = False
176
184
  self._type = FunctionInfoType.PACKAGE
177
185
  elif hasattr(module, "__file__") and not serialized:
178
186
  # This generally covers the case where it's invoked with
@@ -182,18 +190,18 @@ class FunctionInfo:
182
190
  self._file = os.path.abspath(inspect.getfile(module))
183
191
  self.module_name = inspect.getmodulename(self._file)
184
192
  self._base_dir = os.path.dirname(self._file)
185
- self.definition_type = api_pb2.Function.DEFINITION_TYPE_FILE
193
+ self._is_serialized = False
186
194
  self._type = FunctionInfoType.FILE
187
195
  else:
188
196
  self.module_name = None
189
197
  self._base_dir = os.path.abspath("") # get current dir
190
- self.definition_type = api_pb2.Function.DEFINITION_TYPE_SERIALIZED
191
- if serialized:
198
+ self._is_serialized = True # either explicitly, or by being in a notebook
199
+ if serialized: # if explicit
192
200
  self._type = FunctionInfoType.SERIALIZED
193
201
  else:
194
202
  self._type = FunctionInfoType.NOTEBOOK
195
203
 
196
- if self.definition_type == api_pb2.Function.DEFINITION_TYPE_FILE:
204
+ if not self.is_serialized():
197
205
  # Sanity check that this function is defined in global scope
198
206
  # Unfortunately, there's no "clean" way to do this in Python
199
207
  qualname = f.__qualname__ if f else user_cls.__qualname__
@@ -203,7 +211,7 @@ class FunctionInfo:
203
211
  )
204
212
 
205
213
  def is_serialized(self) -> bool:
206
- return self.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED
214
+ return self._is_serialized
207
215
 
208
216
  def serialized_function(self) -> bytes:
209
217
  # Note: this should only be called from .load() and not at function decoration time
@@ -312,7 +320,7 @@ class FunctionInfo:
312
320
  if self._type == FunctionInfoType.PACKAGE:
313
321
  if config.get("automount"):
314
322
  return [_Mount.from_local_python_packages(self.module_name)]
315
- elif self.definition_type == api_pb2.Function.DEFINITION_TYPE_FILE:
323
+ elif not self.is_serialized():
316
324
  # mount only relevant file and __init__.py:s
317
325
  return [
318
326
  _Mount.from_local_dir(
@@ -322,7 +330,7 @@ class FunctionInfo:
322
330
  condition=entrypoint_only_package_mount_condition(self._file),
323
331
  )
324
332
  ]
325
- elif self.definition_type == api_pb2.Function.DEFINITION_TYPE_FILE:
333
+ elif not self.is_serialized():
326
334
  remote_path = ROOT_DIR / Path(self._file).name
327
335
  if not _is_modal_path(remote_path):
328
336
  return [
@@ -570,12 +578,15 @@ class FunctionCreationStatus:
570
578
 
571
579
  elif self.response.function.web_url:
572
580
  url_info = self.response.function.web_url_info
581
+ requires_proxy_auth = self.response.function.webhook_config.requires_proxy_auth
582
+ proxy_auth_suffix = " 🔑" if requires_proxy_auth else ""
573
583
  # Ensure terms used here match terms used in modal.com/docs/guide/webhook-urls doc.
574
584
  suffix = _get_suffix_from_web_url_info(url_info)
575
585
  # TODO: this is only printed when we're showing progress. Maybe move this somewhere else.
576
586
  web_url = self.response.handle_metadata.web_url
577
587
  self.status_row.finish(
578
- f"Created web function {self.tag} => [magenta underline]{web_url}[/magenta underline]{suffix}"
588
+ f"Created web function {self.tag} => [magenta underline]{web_url}[/magenta underline]"
589
+ f"{proxy_auth_suffix}{suffix}"
579
590
  )
580
591
 
581
592
  # Print custom domain in terminal
modal/app.py CHANGED
@@ -22,7 +22,7 @@ from modal_proto import api_pb2
22
22
 
23
23
  from ._ipython import is_notebook
24
24
  from ._utils.async_utils import synchronize_api
25
- from ._utils.deprecation import deprecation_error, deprecation_warning
25
+ from ._utils.deprecation import deprecation_error, deprecation_warning, renamed_parameter
26
26
  from ._utils.function_utils import FunctionInfo, is_global_object, is_method_fn
27
27
  from ._utils.grpc_utils import retry_transient_errors
28
28
  from ._utils.mount_utils import validate_volumes
@@ -260,8 +260,9 @@ class _App:
260
260
  return self._description
261
261
 
262
262
  @staticmethod
263
+ @renamed_parameter((2024, 12, 18), "label", "name")
263
264
  async def lookup(
264
- label: str,
265
+ name: str,
265
266
  client: Optional[_Client] = None,
266
267
  environment_name: Optional[str] = None,
267
268
  create_if_missing: bool = False,
@@ -283,14 +284,14 @@ class _App:
283
284
  environment_name = _get_environment_name(environment_name)
284
285
 
285
286
  request = api_pb2.AppGetOrCreateRequest(
286
- app_name=label,
287
+ app_name=name,
287
288
  environment_name=environment_name,
288
289
  object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
289
290
  )
290
291
 
291
292
  response = await retry_transient_errors(client.stub.AppGetOrCreate, request)
292
293
 
293
- app = _App(label)
294
+ app = _App(name)
294
295
  app._app_id = response.app_id
295
296
  app._client = client
296
297
  app._running_app = RunningApp(
modal/app.pyi CHANGED
@@ -107,7 +107,7 @@ class _App:
107
107
  def description(self) -> typing.Optional[str]: ...
108
108
  @staticmethod
109
109
  async def lookup(
110
- label: str,
110
+ name: str,
111
111
  client: typing.Optional[modal.client._Client] = None,
112
112
  environment_name: typing.Optional[str] = None,
113
113
  create_if_missing: bool = False,
@@ -306,14 +306,14 @@ class App:
306
306
  class __lookup_spec(typing_extensions.Protocol):
307
307
  def __call__(
308
308
  self,
309
- label: str,
309
+ name: str,
310
310
  client: typing.Optional[modal.client.Client] = None,
311
311
  environment_name: typing.Optional[str] = None,
312
312
  create_if_missing: bool = False,
313
313
  ) -> App: ...
314
314
  async def aio(
315
315
  self,
316
- label: str,
316
+ name: str,
317
317
  client: typing.Optional[modal.client.Client] = None,
318
318
  environment_name: typing.Optional[str] = None,
319
319
  create_if_missing: bool = False,
modal/cli/dict.py CHANGED
@@ -89,6 +89,11 @@ async def get(name: str, key: str, *, env: Optional[str] = ENV_OPTION):
89
89
  console.print(val)
90
90
 
91
91
 
92
+ def _display(input: str, use_repr: bool) -> str:
93
+ val = repr(input) if use_repr else str(input)
94
+ return val[:80] + "..." if len(val) > 80 else val
95
+
96
+
92
97
  @dict_cli.command(name="items", rich_help_panel="Inspection")
93
98
  @synchronizer.create_blocking
94
99
  async def items(
@@ -117,8 +122,7 @@ async def items(
117
122
  if json:
118
123
  display_item = key, val
119
124
  else:
120
- cast = repr if use_repr else str
121
- display_item = cast(key), cast(val) # type: ignore # mypy/issue/12056
125
+ display_item = _display(key, use_repr), _display(val, use_repr) # type: ignore # mypy/issue/12056
122
126
  items.append(display_item)
123
127
 
124
128
  display_table(["Key", "Value"], items, json)
@@ -237,4 +237,4 @@ async def delete(
237
237
  abort=True,
238
238
  )
239
239
 
240
- await _NetworkFileSystem.delete(label=nfs_name, environment_name=env)
240
+ await _NetworkFileSystem.delete(nfs_name, environment_name=env)
modal/cli/run.py CHANGED
@@ -482,6 +482,7 @@ def shell(
482
482
  volumes=function_spec.volumes,
483
483
  region=function_spec.scheduler_placement.proto.regions if function_spec.scheduler_placement else None,
484
484
  pty=pty,
485
+ proxy=function_spec.proxy,
485
486
  )
486
487
  else:
487
488
  modal_image = Image.from_registry(image, add_python=add_python) if image else None
modal/cli/volume.py CHANGED
@@ -286,4 +286,4 @@ async def delete(
286
286
  abort=True,
287
287
  )
288
288
 
289
- await _Volume.delete(label=volume_name, environment_name=env)
289
+ await _Volume.delete(volume_name, environment_name=env)
modal/client.pyi CHANGED
@@ -26,7 +26,7 @@ class _Client:
26
26
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
27
27
 
28
28
  def __init__(
29
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.31"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.50"
30
30
  ): ...
31
31
  def is_closed(self) -> bool: ...
32
32
  @property
@@ -81,7 +81,7 @@ class Client:
81
81
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
82
82
 
83
83
  def __init__(
84
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.31"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.50"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/cls.py CHANGED
@@ -16,6 +16,7 @@ from ._resources import convert_fn_config_to_resources_config
16
16
  from ._serialization import check_valid_cls_constructor_arg
17
17
  from ._traceback import print_server_warnings
18
18
  from ._utils.async_utils import synchronize_api, synchronizer
19
+ from ._utils.deprecation import renamed_parameter
19
20
  from ._utils.grpc_utils import retry_transient_errors
20
21
  from ._utils.mount_utils import validate_volumes
21
22
  from .client import _Client
@@ -513,10 +514,11 @@ class _Cls(_Object, type_prefix="cs"):
513
514
  return self._class_service_function is not None
514
515
 
515
516
  @classmethod
517
+ @renamed_parameter((2024, 12, 18), "tag", "name")
516
518
  def from_name(
517
519
  cls: type["_Cls"],
518
520
  app_name: str,
519
- tag: str,
521
+ name: str,
520
522
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
521
523
  environment_name: Optional[str] = None,
522
524
  workspace: Optional[str] = None,
@@ -528,7 +530,7 @@ class _Cls(_Object, type_prefix="cs"):
528
530
  Modal servers until the first time it is actually used.
529
531
 
530
532
  ```python
531
- Class = modal.Cls.from_name("other-app", "Class")
533
+ Model = modal.Cls.from_name("other-app", "Model")
532
534
  ```
533
535
  """
534
536
 
@@ -536,7 +538,7 @@ class _Cls(_Object, type_prefix="cs"):
536
538
  _environment_name = _get_environment_name(environment_name, resolver)
537
539
  request = api_pb2.ClassGetRequest(
538
540
  app_name=app_name,
539
- object_tag=tag,
541
+ object_tag=name,
540
542
  namespace=namespace,
541
543
  environment_name=_environment_name,
542
544
  lookup_published=workspace is not None,
@@ -555,11 +557,11 @@ class _Cls(_Object, type_prefix="cs"):
555
557
 
556
558
  print_server_warnings(response.server_warnings)
557
559
 
558
- class_function_tag = f"{tag}.*" # special name of the base service function for the class
560
+ class_service_name = f"{name}.*" # special name of the base service function for the class
559
561
 
560
562
  class_service_function = _Function.from_name(
561
563
  app_name,
562
- class_function_tag,
564
+ class_service_name,
563
565
  environment_name=_environment_name,
564
566
  )
565
567
  try:
@@ -635,9 +637,10 @@ class _Cls(_Object, type_prefix="cs"):
635
637
  return cls
636
638
 
637
639
  @staticmethod
640
+ @renamed_parameter((2024, 12, 18), "tag", "name")
638
641
  async def lookup(
639
642
  app_name: str,
640
- tag: str,
643
+ name: str,
641
644
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
642
645
  client: Optional[_Client] = None,
643
646
  environment_name: Optional[str] = None,
@@ -649,11 +652,14 @@ class _Cls(_Object, type_prefix="cs"):
649
652
  that will hydrate the local object with metadata from Modal servers.
650
653
 
651
654
  ```python notest
652
- Class = modal.Cls.lookup("other-app", "Class")
653
- obj = Class()
655
+ Model = modal.Cls.lookup("other-app", "Model")
656
+ model = Model()
657
+ model.inference(...)
654
658
  ```
655
659
  """
656
- obj = _Cls.from_name(app_name, tag, namespace=namespace, environment_name=environment_name, workspace=workspace)
660
+ obj = _Cls.from_name(
661
+ app_name, name, namespace=namespace, environment_name=environment_name, workspace=workspace
662
+ )
657
663
  if client is None:
658
664
  client = await _Client.from_env()
659
665
  resolver = Resolver(client=client)
modal/cls.pyi CHANGED
@@ -112,7 +112,7 @@ class _Cls(modal.object._Object):
112
112
  def from_name(
113
113
  cls: type[_Cls],
114
114
  app_name: str,
115
- tag: str,
115
+ name: str,
116
116
  namespace=1,
117
117
  environment_name: typing.Optional[str] = None,
118
118
  workspace: typing.Optional[str] = None,
@@ -133,7 +133,7 @@ class _Cls(modal.object._Object):
133
133
  @staticmethod
134
134
  async def lookup(
135
135
  app_name: str,
136
- tag: str,
136
+ name: str,
137
137
  namespace=1,
138
138
  client: typing.Optional[modal.client._Client] = None,
139
139
  environment_name: typing.Optional[str] = None,
@@ -164,7 +164,7 @@ class Cls(modal.object.Object):
164
164
  def from_name(
165
165
  cls: type[Cls],
166
166
  app_name: str,
167
- tag: str,
167
+ name: str,
168
168
  namespace=1,
169
169
  environment_name: typing.Optional[str] = None,
170
170
  workspace: typing.Optional[str] = None,
@@ -187,7 +187,7 @@ class Cls(modal.object.Object):
187
187
  def __call__(
188
188
  self,
189
189
  app_name: str,
190
- tag: str,
190
+ name: str,
191
191
  namespace=1,
192
192
  client: typing.Optional[modal.client.Client] = None,
193
193
  environment_name: typing.Optional[str] = None,
@@ -196,7 +196,7 @@ class Cls(modal.object.Object):
196
196
  async def aio(
197
197
  self,
198
198
  app_name: str,
199
- tag: str,
199
+ name: str,
200
200
  namespace=1,
201
201
  client: typing.Optional[modal.client.Client] = None,
202
202
  environment_name: typing.Optional[str] = None,
modal/dict.py CHANGED
@@ -10,7 +10,7 @@ from modal_proto import api_pb2
10
10
  from ._resolver import Resolver
11
11
  from ._serialization import deserialize, serialize
12
12
  from ._utils.async_utils import TaskContext, synchronize_api
13
- from ._utils.deprecation import deprecation_error
13
+ from ._utils.deprecation import renamed_parameter
14
14
  from ._utils.grpc_utils import retry_transient_errors
15
15
  from ._utils.name_utils import check_object_name
16
16
  from .client import _Client
@@ -58,15 +58,6 @@ class _Dict(_Object, type_prefix="di"):
58
58
  For more examples, see the [guide](/docs/guide/dicts-and-queues#modal-dicts).
59
59
  """
60
60
 
61
- @staticmethod
62
- def new(data: Optional[dict] = None):
63
- """mdmd:hidden"""
64
- message = (
65
- "`Dict.new` is deprecated."
66
- " Please use `Dict.from_name` (for persisted) or `Dict.ephemeral` (for ephemeral) dicts instead."
67
- )
68
- deprecation_error((2024, 3, 19), message)
69
-
70
61
  def __init__(self, data={}):
71
62
  """mdmd:hidden"""
72
63
  raise RuntimeError(
@@ -112,8 +103,9 @@ class _Dict(_Object, type_prefix="di"):
112
103
  yield cls._new_hydrated(response.dict_id, client, None, is_another_app=True)
113
104
 
114
105
  @staticmethod
106
+ @renamed_parameter((2024, 12, 18), "label", "name")
115
107
  def from_name(
116
- label: str,
108
+ name: str,
117
109
  data: Optional[dict] = None,
118
110
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
119
111
  environment_name: Optional[str] = None,
@@ -130,12 +122,12 @@ class _Dict(_Object, type_prefix="di"):
130
122
  d[123] = 456
131
123
  ```
132
124
  """
133
- check_object_name(label, "Dict")
125
+ check_object_name(name, "Dict")
134
126
 
135
127
  async def _load(self: _Dict, resolver: Resolver, existing_object_id: Optional[str]):
136
128
  serialized = _serialize_dict(data if data is not None else {})
137
129
  req = api_pb2.DictGetOrCreateRequest(
138
- deployment_name=label,
130
+ deployment_name=name,
139
131
  namespace=namespace,
140
132
  environment_name=_get_environment_name(environment_name, resolver),
141
133
  object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
@@ -148,8 +140,9 @@ class _Dict(_Object, type_prefix="di"):
148
140
  return _Dict._from_loader(_load, "Dict()", is_another_app=True, hydrate_lazily=True)
149
141
 
150
142
  @staticmethod
143
+ @renamed_parameter((2024, 12, 18), "label", "name")
151
144
  async def lookup(
152
- label: str,
145
+ name: str,
153
146
  data: Optional[dict] = None,
154
147
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
155
148
  client: Optional[_Client] = None,
@@ -167,7 +160,7 @@ class _Dict(_Object, type_prefix="di"):
167
160
  ```
168
161
  """
169
162
  obj = _Dict.from_name(
170
- label,
163
+ name,
171
164
  data=data,
172
165
  namespace=namespace,
173
166
  environment_name=environment_name,
@@ -180,13 +173,14 @@ class _Dict(_Object, type_prefix="di"):
180
173
  return obj
181
174
 
182
175
  @staticmethod
176
+ @renamed_parameter((2024, 12, 18), "label", "name")
183
177
  async def delete(
184
- label: str,
178
+ name: str,
185
179
  *,
186
180
  client: Optional[_Client] = None,
187
181
  environment_name: Optional[str] = None,
188
182
  ):
189
- obj = await _Dict.lookup(label, client=client, environment_name=environment_name)
183
+ obj = await _Dict.lookup(name, client=client, environment_name=environment_name)
190
184
  req = api_pb2.DictDeleteRequest(dict_id=obj.object_id)
191
185
  await retry_transient_errors(obj._client.stub.DictDelete, req)
192
186
 
modal/dict.pyi CHANGED
@@ -8,8 +8,6 @@ import typing_extensions
8
8
  def _serialize_dict(data): ...
9
9
 
10
10
  class _Dict(modal.object._Object):
11
- @staticmethod
12
- def new(data: typing.Optional[dict] = None): ...
13
11
  def __init__(self, data={}): ...
14
12
  @classmethod
15
13
  def ephemeral(
@@ -21,7 +19,7 @@ class _Dict(modal.object._Object):
21
19
  ) -> typing.AsyncContextManager[_Dict]: ...
22
20
  @staticmethod
23
21
  def from_name(
24
- label: str,
22
+ name: str,
25
23
  data: typing.Optional[dict] = None,
26
24
  namespace=1,
27
25
  environment_name: typing.Optional[str] = None,
@@ -29,7 +27,7 @@ class _Dict(modal.object._Object):
29
27
  ) -> _Dict: ...
30
28
  @staticmethod
31
29
  async def lookup(
32
- label: str,
30
+ name: str,
33
31
  data: typing.Optional[dict] = None,
34
32
  namespace=1,
35
33
  client: typing.Optional[modal.client._Client] = None,
@@ -38,7 +36,7 @@ class _Dict(modal.object._Object):
38
36
  ) -> _Dict: ...
39
37
  @staticmethod
40
38
  async def delete(
41
- label: str,
39
+ name: str,
42
40
  *,
43
41
  client: typing.Optional[modal.client._Client] = None,
44
42
  environment_name: typing.Optional[str] = None,
@@ -60,8 +58,6 @@ class _Dict(modal.object._Object):
60
58
 
61
59
  class Dict(modal.object.Object):
62
60
  def __init__(self, data={}): ...
63
- @staticmethod
64
- def new(data: typing.Optional[dict] = None): ...
65
61
  @classmethod
66
62
  def ephemeral(
67
63
  cls: type[Dict],
@@ -72,7 +68,7 @@ class Dict(modal.object.Object):
72
68
  ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Dict]: ...
73
69
  @staticmethod
74
70
  def from_name(
75
- label: str,
71
+ name: str,
76
72
  data: typing.Optional[dict] = None,
77
73
  namespace=1,
78
74
  environment_name: typing.Optional[str] = None,
@@ -82,7 +78,7 @@ class Dict(modal.object.Object):
82
78
  class __lookup_spec(typing_extensions.Protocol):
83
79
  def __call__(
84
80
  self,
85
- label: str,
81
+ name: str,
86
82
  data: typing.Optional[dict] = None,
87
83
  namespace=1,
88
84
  client: typing.Optional[modal.client.Client] = None,
@@ -91,7 +87,7 @@ class Dict(modal.object.Object):
91
87
  ) -> Dict: ...
92
88
  async def aio(
93
89
  self,
94
- label: str,
90
+ name: str,
95
91
  data: typing.Optional[dict] = None,
96
92
  namespace=1,
97
93
  client: typing.Optional[modal.client.Client] = None,
@@ -104,14 +100,14 @@ class Dict(modal.object.Object):
104
100
  class __delete_spec(typing_extensions.Protocol):
105
101
  def __call__(
106
102
  self,
107
- label: str,
103
+ name: str,
108
104
  *,
109
105
  client: typing.Optional[modal.client.Client] = None,
110
106
  environment_name: typing.Optional[str] = None,
111
107
  ): ...
112
108
  async def aio(
113
109
  self,
114
- label: str,
110
+ name: str,
115
111
  *,
116
112
  client: typing.Optional[modal.client.Client] = None,
117
113
  environment_name: typing.Optional[str] = None,