modal 1.2.2.dev30__py3-none-any.whl → 1.2.2.dev36__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 (46) hide show
  1. modal/_functions.py +77 -52
  2. modal/_load_context.py +105 -0
  3. modal/_object.py +47 -18
  4. modal/_resolver.py +21 -35
  5. modal/app.py +7 -0
  6. modal/app.pyi +3 -0
  7. modal/cli/dict.py +5 -2
  8. modal/cli/queues.py +4 -2
  9. modal/client.pyi +2 -2
  10. modal/cls.py +71 -32
  11. modal/cls.pyi +3 -0
  12. modal/dict.py +14 -5
  13. modal/dict.pyi +2 -0
  14. modal/environments.py +16 -7
  15. modal/environments.pyi +6 -2
  16. modal/functions.pyi +10 -4
  17. modal/image.py +22 -22
  18. modal/mount.py +35 -25
  19. modal/mount.pyi +33 -7
  20. modal/network_file_system.py +14 -5
  21. modal/network_file_system.pyi +12 -2
  22. modal/object.pyi +35 -8
  23. modal/proxy.py +14 -6
  24. modal/proxy.pyi +10 -2
  25. modal/queue.py +14 -5
  26. modal/queue.pyi +12 -2
  27. modal/runner.py +43 -47
  28. modal/runner.pyi +2 -2
  29. modal/sandbox.py +21 -12
  30. modal/secret.py +57 -39
  31. modal/secret.pyi +21 -4
  32. modal/serving.py +7 -11
  33. modal/serving.pyi +7 -8
  34. modal/snapshot.py +11 -5
  35. modal/volume.py +25 -7
  36. modal/volume.pyi +2 -0
  37. {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/METADATA +1 -1
  38. {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/RECORD +46 -45
  39. modal_proto/api.proto +4 -0
  40. modal_proto/api_pb2.py +684 -684
  41. modal_proto/api_pb2.pyi +24 -3
  42. modal_version/__init__.py +1 -1
  43. {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/WHEEL +0 -0
  44. {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/entry_points.txt +0 -0
  45. {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/licenses/LICENSE +0 -0
  46. {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/top_level.txt +0 -0
modal/image.py CHANGED
@@ -28,6 +28,7 @@ from typing_extensions import Self
28
28
  from modal._serialization import serialize_data_format
29
29
  from modal_proto import api_pb2
30
30
 
31
+ from ._load_context import LoadContext
31
32
  from ._object import _Object, live_method_gen
32
33
  from ._resolver import Resolver
33
34
  from ._serialization import get_preferred_payload_format, serialize
@@ -434,12 +435,16 @@ class _Image(_Object, type_prefix="im"):
434
435
 
435
436
  base_image = self
436
437
 
437
- async def _load(self2: "_Image", resolver: Resolver, existing_object_id: Optional[str]):
438
+ async def _load(
439
+ self2: "_Image", resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
440
+ ):
438
441
  self2._hydrate_from_other(base_image) # same image id as base image as long as it's lazy
439
442
  self2._deferred_mounts = tuple(base_image._deferred_mounts) + (mount,)
440
443
  self2._serve_mounts = base_image._serve_mounts | ({mount} if mount.is_local() else set())
441
444
 
442
- img = _Image._from_loader(_load, "Image(local files)", deps=lambda: [base_image, mount])
445
+ img = _Image._from_loader(
446
+ _load, "Image(local files)", deps=lambda: [base_image, mount], load_context_overrides=LoadContext.empty()
447
+ )
443
448
  img._added_python_source_set = base_image._added_python_source_set
444
449
  return img
445
450
 
@@ -523,18 +528,18 @@ class _Image(_Object, type_prefix="im"):
523
528
  deps += (vol,)
524
529
  return deps
525
530
 
526
- async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
531
+ async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
527
532
  context_mount = context_mount_function() if context_mount_function else None
528
533
  if context_mount:
529
- await resolver.load(context_mount)
534
+ await resolver.load(context_mount, load_context)
530
535
 
531
536
  if _do_assert_no_mount_layers:
532
537
  for image in base_images.values():
533
538
  # base images can't have
534
539
  image._assert_no_mount_layers()
535
540
 
536
- assert resolver.app_id # type narrowing
537
- environment = await _get_environment_cached(resolver.environment_name or "", resolver.client)
541
+ assert load_context.app_id # type narrowing
542
+ environment = await _get_environment_cached(load_context.environment_name or "", load_context.client)
538
543
  # A bit hacky,but assume that the environment provides a valid builder version
539
544
  image_builder_version = cast(ImageBuilderVersion, environment._settings.image_builder_version)
540
545
  builder_version = _get_image_builder_version(image_builder_version)
@@ -626,7 +631,7 @@ class _Image(_Object, type_prefix="im"):
626
631
  )
627
632
 
628
633
  req = api_pb2.ImageGetOrCreateRequest(
629
- app_id=resolver.app_id,
634
+ app_id=load_context.app_id,
630
635
  image=image_definition,
631
636
  existing_image_id=existing_object_id or "", # TODO: ignored
632
637
  build_function_id=build_function_id,
@@ -638,7 +643,7 @@ class _Image(_Object, type_prefix="im"):
638
643
  allow_global_deployment=os.environ.get("MODAL_IMAGE_ALLOW_GLOBAL_DEPLOYMENT") == "1",
639
644
  ignore_cache=config.get("ignore_cache"),
640
645
  )
641
- resp = await resolver.client.stub.ImageGetOrCreate(req)
646
+ resp = await load_context.client.stub.ImageGetOrCreate(req)
642
647
  image_id = resp.image_id
643
648
  result: api_pb2.GenericResult
644
649
  metadata: Optional[api_pb2.ImageMetadata] = None
@@ -651,7 +656,7 @@ class _Image(_Object, type_prefix="im"):
651
656
  else:
652
657
  # not built or in the process of building - wait for build
653
658
  logger.debug("Waiting for image %s" % image_id)
654
- resp = await _image_await_build_result(image_id, resolver.client)
659
+ resp = await _image_await_build_result(image_id, load_context.client)
655
660
  result = resp.result
656
661
  if resp.HasField("metadata"):
657
662
  metadata = resp.metadata
@@ -681,7 +686,7 @@ class _Image(_Object, type_prefix="im"):
681
686
  else:
682
687
  raise RemoteError("Unknown status %s!" % result.status)
683
688
 
684
- self._hydrate(image_id, resolver.client, metadata)
689
+ self._hydrate(image_id, load_context.client, metadata)
685
690
  local_mounts = set()
686
691
  for base in base_images.values():
687
692
  local_mounts |= base._serve_mounts
@@ -690,7 +695,7 @@ class _Image(_Object, type_prefix="im"):
690
695
  self._serve_mounts = frozenset(local_mounts)
691
696
 
692
697
  rep = f"Image({dockerfile_function})"
693
- obj = _Image._from_loader(_load, rep, deps=_deps)
698
+ obj = _Image._from_loader(_load, rep, deps=_deps, load_context_overrides=LoadContext.empty())
694
699
  obj.force_build = force_build
695
700
  obj._added_python_source_set = frozenset.union(
696
701
  frozenset(), *(base._added_python_source_set for base in base_images.values())
@@ -863,15 +868,13 @@ class _Image(_Object, type_prefix="im"):
863
868
 
864
869
  The ID of an Image object can be accessed using `.object_id`.
865
870
  """
866
- if client is None:
867
- client = await _Client.from_env()
868
871
 
869
- async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
870
- resp = await client.stub.ImageFromId(api_pb2.ImageFromIdRequest(image_id=image_id))
871
- self._hydrate(resp.image_id, resolver.client, resp.metadata)
872
+ async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
873
+ resp = await load_context.client.stub.ImageFromId(api_pb2.ImageFromIdRequest(image_id=image_id))
874
+ self._hydrate(resp.image_id, load_context.client, resp.metadata)
872
875
 
873
876
  rep = f"Image.from_id({image_id!r})"
874
- obj = _Image._from_loader(_load, rep)
877
+ obj = _Image._from_loader(_load, rep, load_context_overrides=LoadContext(client=client))
875
878
 
876
879
  return obj
877
880
 
@@ -930,11 +933,8 @@ class _Image(_Object, type_prefix="im"):
930
933
  if app.app_id is None:
931
934
  raise InvalidError("App has not been initialized yet. Use the content manager `app.run()` or `App.lookup`")
932
935
 
933
- app_id = app.app_id
934
- app_client = app._client or await _Client.from_env()
935
-
936
- resolver = Resolver(app_client, app_id=app_id)
937
- await resolver.load(self)
936
+ resolver = Resolver()
937
+ await resolver.load(self, app._root_load_context)
938
938
  return self
939
939
 
940
940
  def pip_install(
modal/mount.py CHANGED
@@ -20,7 +20,8 @@ import modal.file_pattern_matcher
20
20
  from modal_proto import api_pb2
21
21
  from modal_version import __version__
22
22
 
23
- from ._object import _get_environment_name, _Object
23
+ from ._load_context import LoadContext
24
+ from ._object import _Object
24
25
  from ._resolver import Resolver
25
26
  from ._utils.async_utils import TaskContext, aclosing, async_map, synchronize_api
26
27
  from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
@@ -310,7 +311,7 @@ class _Mount(_Object, type_prefix="mo"):
310
311
  _entries: Optional[list[_MountEntry]] = None
311
312
  _deployment_name: Optional[str] = None
312
313
  _namespace: Optional[int] = None
313
- _environment_name: Optional[str] = None
314
+
314
315
  _allow_overwrite: bool = False
315
316
  _content_checksum_sha256_hex: Optional[str] = None
316
317
 
@@ -325,7 +326,12 @@ class _Mount(_Object, type_prefix="mo"):
325
326
  return None
326
327
  return (_Mount._type_prefix, "local", frozenset(included_files))
327
328
 
328
- obj = _Mount._from_loader(_Mount._load_mount, rep, deduplication_key=mount_content_deduplication_key)
329
+ obj = _Mount._from_loader(
330
+ _Mount._load_mount,
331
+ rep,
332
+ deduplication_key=mount_content_deduplication_key,
333
+ load_context_overrides=LoadContext.empty(),
334
+ )
329
335
  obj._entries = entries
330
336
  obj._is_local = True
331
337
  return obj
@@ -477,6 +483,7 @@ class _Mount(_Object, type_prefix="mo"):
477
483
  async def _load_mount(
478
484
  self: "_Mount",
479
485
  resolver: Resolver,
486
+ load_context: LoadContext,
480
487
  existing_object_id: Optional[str],
481
488
  ):
482
489
  t0 = time.monotonic()
@@ -518,7 +525,7 @@ class _Mount(_Object, type_prefix="mo"):
518
525
 
519
526
  request = api_pb2.MountPutFileRequest(sha256_hex=file_spec.sha256_hex)
520
527
  accounted_hashes.add(file_spec.sha256_hex)
521
- response = await resolver.client.stub.MountPutFile(request, retry=Retry(base_delay=1))
528
+ response = await load_context.client.stub.MountPutFile(request, retry=Retry(base_delay=1))
522
529
 
523
530
  if response.exists:
524
531
  n_finished += 1
@@ -532,7 +539,7 @@ class _Mount(_Object, type_prefix="mo"):
532
539
  async with blob_upload_concurrency:
533
540
  with file_spec.source() as fp:
534
541
  blob_id = await blob_upload_file(
535
- fp, resolver.client.stub, sha256_hex=file_spec.sha256_hex, md5_hex=file_spec.md5_hex
542
+ fp, load_context.client.stub, sha256_hex=file_spec.sha256_hex, md5_hex=file_spec.md5_hex
536
543
  )
537
544
  logger.debug(f"Uploading blob file {file_spec.source_description} as {remote_filename}")
538
545
  request2 = api_pb2.MountPutFileRequest(data_blob_id=blob_id, sha256_hex=file_spec.sha256_hex)
@@ -544,7 +551,7 @@ class _Mount(_Object, type_prefix="mo"):
544
551
 
545
552
  start_time = time.monotonic()
546
553
  while time.monotonic() - start_time < MOUNT_PUT_FILE_CLIENT_TIMEOUT:
547
- response = await resolver.client.stub.MountPutFile(request2, retry=Retry(base_delay=1))
554
+ response = await load_context.client.stub.MountPutFile(request2, retry=Retry(base_delay=1))
548
555
  if response.exists:
549
556
  n_finished += 1
550
557
  return mount_file
@@ -552,7 +559,7 @@ class _Mount(_Object, type_prefix="mo"):
552
559
  raise modal.exception.MountUploadTimeoutError(f"Mounting of {file_spec.source_description} timed out")
553
560
 
554
561
  # Upload files, or check if they already exist.
555
- n_concurrent_uploads = 512
562
+ n_concurrent_uploads = 64
556
563
  files: list[api_pb2.MountFile] = []
557
564
  async with aclosing(
558
565
  async_map(_Mount._get_files(self._entries), _put_file, concurrency=n_concurrent_uploads)
@@ -574,28 +581,28 @@ class _Mount(_Object, type_prefix="mo"):
574
581
  req = api_pb2.MountGetOrCreateRequest(
575
582
  deployment_name=self._deployment_name,
576
583
  namespace=self._namespace,
577
- environment_name=self._environment_name,
584
+ environment_name=load_context.environment_name,
578
585
  object_creation_type=creation_type,
579
586
  files=files,
580
587
  )
581
- elif resolver.app_id is not None:
588
+ elif load_context.app_id is not None:
582
589
  req = api_pb2.MountGetOrCreateRequest(
583
590
  object_creation_type=api_pb2.OBJECT_CREATION_TYPE_ANONYMOUS_OWNED_BY_APP,
584
591
  files=files,
585
- app_id=resolver.app_id,
592
+ app_id=load_context.app_id,
586
593
  )
587
594
  else:
588
595
  req = api_pb2.MountGetOrCreateRequest(
589
596
  object_creation_type=api_pb2.OBJECT_CREATION_TYPE_EPHEMERAL,
590
597
  files=files,
591
- environment_name=resolver.environment_name,
598
+ environment_name=load_context.environment_name,
592
599
  )
593
600
 
594
- resp = await resolver.client.stub.MountGetOrCreate(req, retry=Retry(base_delay=1))
601
+ resp = await load_context.client.stub.MountGetOrCreate(req, retry=Retry(base_delay=1))
595
602
  status_row.finish(f"Created mount {message_label}")
596
603
 
597
604
  logger.debug(f"Uploaded {total_uploads} new files and {total_bytes} bytes in {time.monotonic() - t0}s")
598
- self._hydrate(resp.mount_id, resolver.client, resp.handle_metadata)
605
+ self._hydrate(resp.mount_id, load_context.client, resp.handle_metadata)
599
606
 
600
607
  @staticmethod
601
608
  def _from_local_python_packages(
@@ -628,19 +635,25 @@ class _Mount(_Object, type_prefix="mo"):
628
635
  *,
629
636
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
630
637
  environment_name: Optional[str] = None,
638
+ client: Optional[_Client] = None,
631
639
  ) -> "_Mount":
632
640
  """mdmd:hidden"""
633
641
 
634
- async def _load(provider: _Mount, resolver: Resolver, existing_object_id: Optional[str]):
642
+ async def _load(provider: _Mount, resolver: Resolver, load_context, existing_object_id: Optional[str]):
635
643
  req = api_pb2.MountGetOrCreateRequest(
636
644
  deployment_name=name,
637
645
  namespace=namespace,
638
- environment_name=_get_environment_name(environment_name, resolver),
646
+ environment_name=load_context.environment_name,
639
647
  )
640
- response = await resolver.client.stub.MountGetOrCreate(req)
641
- provider._hydrate(response.mount_id, resolver.client, response.handle_metadata)
642
-
643
- return _Mount._from_loader(_load, "Mount()", hydrate_lazily=True)
648
+ response = await load_context.client.stub.MountGetOrCreate(req)
649
+ provider._hydrate(response.mount_id, load_context.client, response.handle_metadata)
650
+
651
+ return _Mount._from_loader(
652
+ _load,
653
+ "Mount()",
654
+ hydrate_lazily=True,
655
+ load_context_overrides=LoadContext(environment_name=environment_name, client=client),
656
+ )
644
657
 
645
658
  async def _deploy(
646
659
  self: "_Mount",
@@ -652,15 +665,12 @@ class _Mount(_Object, type_prefix="mo"):
652
665
  client: Optional[_Client] = None,
653
666
  ) -> None:
654
667
  check_object_name(deployment_name, "Mount")
655
- environment_name = _get_environment_name(environment_name, resolver=None)
656
668
  self._deployment_name = deployment_name
657
669
  self._namespace = namespace
658
- self._environment_name = environment_name
659
670
  self._allow_overwrite = allow_overwrite
660
- if client is None:
661
- client = await _Client.from_env()
662
- resolver = Resolver(client=client, environment_name=environment_name)
663
- await resolver.load(self)
671
+ resolver = Resolver()
672
+ root_metadata = LoadContext(client=client, environment_name=environment_name)
673
+ await resolver.load(self, root_metadata)
664
674
 
665
675
  def _get_metadata(self) -> api_pb2.MountHandleMetadata:
666
676
  if self._content_checksum_sha256_hex is None:
modal/mount.pyi CHANGED
@@ -1,5 +1,6 @@
1
1
  import collections.abc
2
2
  import google.protobuf.message
3
+ import modal._load_context
3
4
  import modal._object
4
5
  import modal._resolver
5
6
  import modal._utils.blob_utils
@@ -154,7 +155,6 @@ class _Mount(modal._object._Object):
154
155
  _entries: typing.Optional[list[_MountEntry]]
155
156
  _deployment_name: typing.Optional[str]
156
157
  _namespace: typing.Optional[int]
157
- _environment_name: typing.Optional[str]
158
158
  _allow_overwrite: bool
159
159
  _content_checksum_sha256_hex: typing.Optional[str]
160
160
 
@@ -216,7 +216,10 @@ class _Mount(modal._object._Object):
216
216
  entries: list[_MountEntry],
217
217
  ) -> collections.abc.AsyncGenerator[modal._utils.blob_utils.FileUploadSpec, None]: ...
218
218
  async def _load_mount(
219
- self: _Mount, resolver: modal._resolver.Resolver, existing_object_id: typing.Optional[str]
219
+ self: _Mount,
220
+ resolver: modal._resolver.Resolver,
221
+ load_context: modal._load_context.LoadContext,
222
+ existing_object_id: typing.Optional[str],
220
223
  ): ...
221
224
  @staticmethod
222
225
  def _from_local_python_packages(
@@ -226,7 +229,13 @@ class _Mount(modal._object._Object):
226
229
  ignore: typing.Union[typing.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], None] = None,
227
230
  ) -> _Mount: ...
228
231
  @staticmethod
229
- def from_name(name: str, *, namespace=1, environment_name: typing.Optional[str] = None) -> _Mount:
232
+ def from_name(
233
+ name: str,
234
+ *,
235
+ namespace=1,
236
+ environment_name: typing.Optional[str] = None,
237
+ client: typing.Optional[modal.client._Client] = None,
238
+ ) -> _Mount:
230
239
  """mdmd:hidden"""
231
240
  ...
232
241
 
@@ -269,7 +278,6 @@ class Mount(modal.object.Object):
269
278
  _entries: typing.Optional[list[_MountEntry]]
270
279
  _deployment_name: typing.Optional[str]
271
280
  _namespace: typing.Optional[int]
272
- _environment_name: typing.Optional[str]
273
281
  _allow_overwrite: bool
274
282
  _content_checksum_sha256_hex: typing.Optional[str]
275
283
 
@@ -342,8 +350,20 @@ class Mount(modal.object.Object):
342
350
  _get_files: ___get_files_spec
343
351
 
344
352
  class ___load_mount_spec(typing_extensions.Protocol[SUPERSELF]):
345
- def __call__(self, /, resolver: modal._resolver.Resolver, existing_object_id: typing.Optional[str]): ...
346
- async def aio(self, /, resolver: modal._resolver.Resolver, existing_object_id: typing.Optional[str]): ...
353
+ def __call__(
354
+ self,
355
+ /,
356
+ resolver: modal._resolver.Resolver,
357
+ load_context: modal._load_context.LoadContext,
358
+ existing_object_id: typing.Optional[str],
359
+ ): ...
360
+ async def aio(
361
+ self,
362
+ /,
363
+ resolver: modal._resolver.Resolver,
364
+ load_context: modal._load_context.LoadContext,
365
+ existing_object_id: typing.Optional[str],
366
+ ): ...
347
367
 
348
368
  _load_mount: ___load_mount_spec[typing_extensions.Self]
349
369
 
@@ -355,7 +375,13 @@ class Mount(modal.object.Object):
355
375
  ignore: typing.Union[typing.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], None] = None,
356
376
  ) -> Mount: ...
357
377
  @staticmethod
358
- def from_name(name: str, *, namespace=1, environment_name: typing.Optional[str] = None) -> Mount:
378
+ def from_name(
379
+ name: str,
380
+ *,
381
+ namespace=1,
382
+ environment_name: typing.Optional[str] = None,
383
+ client: typing.Optional[modal.client.Client] = None,
384
+ ) -> Mount:
359
385
  """mdmd:hidden"""
360
386
  ...
361
387
 
@@ -11,6 +11,7 @@ from synchronicity.async_wrap import asynccontextmanager
11
11
  import modal
12
12
  from modal_proto import api_pb2
13
13
 
14
+ from ._load_context import LoadContext
14
15
  from ._object import (
15
16
  EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
16
17
  _get_environment_name,
@@ -95,6 +96,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
95
96
  namespace=None, # mdmd:line-hidden
96
97
  environment_name: Optional[str] = None,
97
98
  create_if_missing: bool = False,
99
+ client: Optional[_Client] = None,
98
100
  ) -> "_NetworkFileSystem":
99
101
  """Reference a NetworkFileSystem by its name, creating if necessary.
100
102
 
@@ -113,15 +115,17 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
113
115
  check_object_name(name, "NetworkFileSystem")
114
116
  warn_if_passing_namespace(namespace, "modal.NetworkFileSystem.from_name")
115
117
 
116
- async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
118
+ async def _load(
119
+ self: _NetworkFileSystem, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
120
+ ):
117
121
  req = api_pb2.SharedVolumeGetOrCreateRequest(
118
122
  deployment_name=name,
119
- environment_name=_get_environment_name(environment_name, resolver),
123
+ environment_name=load_context.environment_name,
120
124
  object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
121
125
  )
122
126
  try:
123
- response = await resolver.client.stub.SharedVolumeGetOrCreate(req)
124
- self._hydrate(response.shared_volume_id, resolver.client, None)
127
+ response = await load_context.client.stub.SharedVolumeGetOrCreate(req)
128
+ self._hydrate(response.shared_volume_id, load_context.client, None)
125
129
  except modal.exception.NotFoundError as exc:
126
130
  if exc.args[0] == "App has wrong entity vo":
127
131
  raise InvalidError(
@@ -129,7 +133,12 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
129
133
  )
130
134
  raise
131
135
 
132
- return _NetworkFileSystem._from_loader(_load, "NetworkFileSystem()", hydrate_lazily=True)
136
+ return _NetworkFileSystem._from_loader(
137
+ _load,
138
+ "NetworkFileSystem()",
139
+ hydrate_lazily=True,
140
+ load_context_overrides=LoadContext(environment_name=environment_name, client=client),
141
+ )
133
142
 
134
143
  @classmethod
135
144
  @asynccontextmanager
@@ -54,7 +54,12 @@ class _NetworkFileSystem(modal._object._Object):
54
54
  """
55
55
  @staticmethod
56
56
  def from_name(
57
- name: str, *, namespace=None, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
57
+ name: str,
58
+ *,
59
+ namespace=None,
60
+ environment_name: typing.Optional[str] = None,
61
+ create_if_missing: bool = False,
62
+ client: typing.Optional[modal.client._Client] = None,
58
63
  ) -> _NetworkFileSystem:
59
64
  """Reference a NetworkFileSystem by its name, creating if necessary.
60
65
 
@@ -210,7 +215,12 @@ class NetworkFileSystem(modal.object.Object):
210
215
 
211
216
  @staticmethod
212
217
  def from_name(
213
- name: str, *, namespace=None, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
218
+ name: str,
219
+ *,
220
+ namespace=None,
221
+ environment_name: typing.Optional[str] = None,
222
+ create_if_missing: bool = False,
223
+ client: typing.Optional[modal.client.Client] = None,
214
224
  ) -> NetworkFileSystem:
215
225
  """Reference a NetworkFileSystem by its name, creating if necessary.
216
226
 
modal/object.pyi CHANGED
@@ -1,5 +1,6 @@
1
1
  import collections.abc
2
2
  import google.protobuf.message
3
+ import modal._load_context
3
4
  import modal._resolver
4
5
  import modal.client
5
6
  import typing
@@ -12,12 +13,14 @@ class Object:
12
13
  _prefix_to_type: typing.ClassVar[dict[str, type]]
13
14
  _load: typing.Optional[
14
15
  collections.abc.Callable[
15
- [typing_extensions.Self, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
16
+ [typing_extensions.Self, modal._resolver.Resolver, modal._load_context.LoadContext, typing.Optional[str]],
17
+ collections.abc.Awaitable[None],
16
18
  ]
17
19
  ]
18
20
  _preload: typing.Optional[
19
21
  collections.abc.Callable[
20
- [typing_extensions.Self, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
22
+ [typing_extensions.Self, modal._resolver.Resolver, modal._load_context.LoadContext, typing.Optional[str]],
23
+ collections.abc.Awaitable[None],
21
24
  ]
22
25
  ]
23
26
  _rep: str
@@ -27,6 +30,7 @@ class Object:
27
30
  _deduplication_key: typing.Optional[
28
31
  collections.abc.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
29
32
  ]
33
+ _load_context_overrides: modal._load_context.LoadContext
30
34
  _object_id: typing.Optional[str]
31
35
  _client: typing.Optional[modal.client.Client]
32
36
  _is_hydrated: bool
@@ -46,16 +50,22 @@ class Object:
46
50
  /,
47
51
  rep: str,
48
52
  load: typing.Optional[
49
- collections.abc.Callable[[SUPERSELF, modal._resolver.Resolver, typing.Optional[str]], None]
53
+ collections.abc.Callable[
54
+ [SUPERSELF, modal._resolver.Resolver, modal._load_context.LoadContext, typing.Optional[str]], None
55
+ ]
50
56
  ] = None,
51
57
  is_another_app: bool = False,
52
58
  preload: typing.Optional[
53
- collections.abc.Callable[[SUPERSELF, modal._resolver.Resolver, typing.Optional[str]], None]
59
+ collections.abc.Callable[
60
+ [SUPERSELF, modal._resolver.Resolver, modal._load_context.LoadContext, typing.Optional[str]], None
61
+ ]
54
62
  ] = None,
55
63
  hydrate_lazily: bool = False,
56
64
  deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]] = None,
57
65
  deduplication_key: typing.Optional[collections.abc.Callable[[], collections.abc.Hashable]] = None,
58
66
  name: typing.Optional[str] = None,
67
+ *,
68
+ load_context_overrides: typing.Optional[modal._load_context.LoadContext] = None,
59
69
  ): ...
60
70
  def aio(
61
71
  self,
@@ -63,13 +73,15 @@ class Object:
63
73
  rep: str,
64
74
  load: typing.Optional[
65
75
  collections.abc.Callable[
66
- [SUPERSELF, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
76
+ [SUPERSELF, modal._resolver.Resolver, modal._load_context.LoadContext, typing.Optional[str]],
77
+ collections.abc.Awaitable[None],
67
78
  ]
68
79
  ] = None,
69
80
  is_another_app: bool = False,
70
81
  preload: typing.Optional[
71
82
  collections.abc.Callable[
72
- [SUPERSELF, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
83
+ [SUPERSELF, modal._resolver.Resolver, modal._load_context.LoadContext, typing.Optional[str]],
84
+ collections.abc.Awaitable[None],
73
85
  ]
74
86
  ] = None,
75
87
  hydrate_lazily: bool = False,
@@ -78,6 +90,8 @@ class Object:
78
90
  collections.abc.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
79
91
  ] = None,
80
92
  name: typing.Optional[str] = None,
93
+ *,
94
+ load_context_overrides: typing.Optional[modal._load_context.LoadContext] = None,
81
95
  ): ...
82
96
 
83
97
  _init: ___init_spec[typing_extensions.Self]
@@ -101,16 +115,29 @@ class Object:
101
115
  @classmethod
102
116
  def _from_loader(
103
117
  cls,
104
- load: collections.abc.Callable[[typing_extensions.Self, modal._resolver.Resolver, typing.Optional[str]], None],
118
+ load: collections.abc.Callable[
119
+ [typing_extensions.Self, modal._resolver.Resolver, modal._load_context.LoadContext, typing.Optional[str]],
120
+ None,
121
+ ],
105
122
  rep: str,
106
123
  is_another_app: bool = False,
107
124
  preload: typing.Optional[
108
- collections.abc.Callable[[typing_extensions.Self, modal._resolver.Resolver, typing.Optional[str]], None]
125
+ collections.abc.Callable[
126
+ [
127
+ typing_extensions.Self,
128
+ modal._resolver.Resolver,
129
+ modal._load_context.LoadContext,
130
+ typing.Optional[str],
131
+ ],
132
+ None,
133
+ ]
109
134
  ] = None,
110
135
  hydrate_lazily: bool = False,
111
136
  deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]] = None,
112
137
  deduplication_key: typing.Optional[collections.abc.Callable[[], collections.abc.Hashable]] = None,
113
138
  name: typing.Optional[str] = None,
139
+ *,
140
+ load_context_overrides: modal._load_context.LoadContext,
114
141
  ): ...
115
142
  @staticmethod
116
143
  def _get_type_from_id(object_id: str) -> type[Object]: ...
modal/proxy.py CHANGED
@@ -3,9 +3,11 @@ from typing import Optional
3
3
 
4
4
  from modal_proto import api_pb2
5
5
 
6
- from ._object import _get_environment_name, _Object
6
+ from ._load_context import LoadContext
7
+ from ._object import _Object
7
8
  from ._resolver import Resolver
8
9
  from ._utils.async_utils import synchronize_api
10
+ from .client import _Client
9
11
 
10
12
 
11
13
  class _Proxy(_Object, type_prefix="pr"):
@@ -20,6 +22,7 @@ class _Proxy(_Object, type_prefix="pr"):
20
22
  name: str,
21
23
  *,
22
24
  environment_name: Optional[str] = None,
25
+ client: Optional[_Client] = None,
23
26
  ) -> "_Proxy":
24
27
  """Reference a Proxy by its name.
25
28
 
@@ -28,16 +31,21 @@ class _Proxy(_Object, type_prefix="pr"):
28
31
 
29
32
  """
30
33
 
31
- async def _load(self: _Proxy, resolver: Resolver, existing_object_id: Optional[str]):
34
+ async def _load(self: _Proxy, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
32
35
  req = api_pb2.ProxyGetRequest(
33
36
  name=name,
34
- environment_name=_get_environment_name(environment_name, resolver),
37
+ environment_name=load_context.environment_name,
35
38
  )
36
- response: api_pb2.ProxyGetResponse = await resolver.client.stub.ProxyGet(req)
37
- self._hydrate(response.proxy.proxy_id, resolver.client, None)
39
+ response: api_pb2.ProxyGetResponse = await load_context.client.stub.ProxyGet(req)
40
+ self._hydrate(response.proxy.proxy_id, load_context.client, None)
38
41
 
39
42
  rep = _Proxy._repr(name, environment_name)
40
- return _Proxy._from_loader(_load, rep, is_another_app=True)
43
+ return _Proxy._from_loader(
44
+ _load,
45
+ rep,
46
+ is_another_app=True,
47
+ load_context_overrides=LoadContext(client=client, environment_name=environment_name),
48
+ )
41
49
 
42
50
 
43
51
  Proxy = synchronize_api(_Proxy, target_module=__name__)
modal/proxy.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import modal._object
2
+ import modal.client
2
3
  import modal.object
3
4
  import typing
4
5
 
@@ -9,7 +10,12 @@ class _Proxy(modal._object._Object):
9
10
  a database. See [the guide](https://modal.com/docs/guide/proxy-ips) for more information.
10
11
  """
11
12
  @staticmethod
12
- def from_name(name: str, *, environment_name: typing.Optional[str] = None) -> _Proxy:
13
+ def from_name(
14
+ name: str,
15
+ *,
16
+ environment_name: typing.Optional[str] = None,
17
+ client: typing.Optional[modal.client._Client] = None,
18
+ ) -> _Proxy:
13
19
  """Reference a Proxy by its name.
14
20
 
15
21
  In contrast to most other Modal objects, new Proxy objects must be
@@ -28,7 +34,9 @@ class Proxy(modal.object.Object):
28
34
  ...
29
35
 
30
36
  @staticmethod
31
- def from_name(name: str, *, environment_name: typing.Optional[str] = None) -> Proxy:
37
+ def from_name(
38
+ name: str, *, environment_name: typing.Optional[str] = None, client: typing.Optional[modal.client.Client] = None
39
+ ) -> Proxy:
32
40
  """Reference a Proxy by its name.
33
41
 
34
42
  In contrast to most other Modal objects, new Proxy objects must be