modal 1.0.4.dev10__py3-none-any.whl → 1.0.5__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 (67) hide show
  1. modal/_clustered_functions.pyi +13 -3
  2. modal/_functions.py +84 -46
  3. modal/_partial_function.py +1 -1
  4. modal/_runtime/container_io_manager.pyi +222 -40
  5. modal/_runtime/execution_context.pyi +60 -6
  6. modal/_serialization.py +25 -2
  7. modal/_tunnel.pyi +380 -12
  8. modal/_utils/async_utils.py +1 -1
  9. modal/_utils/blob_utils.py +56 -19
  10. modal/_utils/function_utils.py +33 -7
  11. modal/_utils/grpc_utils.py +11 -4
  12. modal/app.py +5 -5
  13. modal/app.pyi +658 -48
  14. modal/cli/run.py +2 -1
  15. modal/client.pyi +224 -36
  16. modal/cloud_bucket_mount.pyi +192 -4
  17. modal/cls.py +57 -16
  18. modal/cls.pyi +442 -34
  19. modal/container_process.pyi +103 -14
  20. modal/dict.py +4 -4
  21. modal/dict.pyi +453 -51
  22. modal/environments.pyi +41 -9
  23. modal/exception.py +6 -2
  24. modal/experimental/__init__.py +90 -0
  25. modal/experimental/ipython.py +11 -7
  26. modal/file_io.pyi +236 -45
  27. modal/functions.pyi +573 -65
  28. modal/gpu.py +1 -1
  29. modal/image.py +1 -1
  30. modal/image.pyi +1256 -74
  31. modal/io_streams.py +8 -4
  32. modal/io_streams.pyi +348 -38
  33. modal/mount.pyi +261 -31
  34. modal/network_file_system.py +3 -3
  35. modal/network_file_system.pyi +307 -26
  36. modal/object.pyi +48 -9
  37. modal/parallel_map.py +93 -19
  38. modal/parallel_map.pyi +160 -15
  39. modal/partial_function.pyi +255 -14
  40. modal/proxy.py +1 -1
  41. modal/proxy.pyi +28 -3
  42. modal/queue.py +4 -4
  43. modal/queue.pyi +447 -30
  44. modal/runner.pyi +160 -22
  45. modal/sandbox.py +8 -7
  46. modal/sandbox.pyi +310 -50
  47. modal/schedule.py +1 -1
  48. modal/secret.py +2 -2
  49. modal/secret.pyi +164 -15
  50. modal/snapshot.pyi +25 -4
  51. modal/token_flow.pyi +28 -8
  52. modal/volume.py +41 -4
  53. modal/volume.pyi +693 -59
  54. {modal-1.0.4.dev10.dist-info → modal-1.0.5.dist-info}/METADATA +3 -3
  55. {modal-1.0.4.dev10.dist-info → modal-1.0.5.dist-info}/RECORD +67 -67
  56. modal_proto/api.proto +57 -0
  57. modal_proto/api_grpc.py +48 -0
  58. modal_proto/api_pb2.py +874 -780
  59. modal_proto/api_pb2.pyi +198 -9
  60. modal_proto/api_pb2_grpc.py +100 -0
  61. modal_proto/api_pb2_grpc.pyi +32 -0
  62. modal_proto/modal_api_grpc.py +3 -0
  63. modal_version/__init__.py +1 -1
  64. {modal-1.0.4.dev10.dist-info → modal-1.0.5.dist-info}/WHEEL +0 -0
  65. {modal-1.0.4.dev10.dist-info → modal-1.0.5.dist-info}/entry_points.txt +0 -0
  66. {modal-1.0.4.dev10.dist-info → modal-1.0.5.dist-info}/licenses/LICENSE +0 -0
  67. {modal-1.0.4.dev10.dist-info → modal-1.0.5.dist-info}/top_level.txt +0 -0
modal/cls.py CHANGED
@@ -54,7 +54,7 @@ def _get_class_constructor_signature(user_cls: type) -> inspect.Signature:
54
54
  return inspect.signature(user_cls)
55
55
  else:
56
56
  constructor_parameters = []
57
- for name, annotation_value in user_cls.__dict__.get("__annotations__", {}).items():
57
+ for name, annotation_value in typing.get_type_hints(user_cls).items():
58
58
  if hasattr(user_cls, name):
59
59
  parameter_spec = getattr(user_cls, name)
60
60
  if is_parameter(parameter_spec):
@@ -75,6 +75,7 @@ def _get_class_constructor_signature(user_cls: type) -> inspect.Signature:
75
75
 
76
76
  @dataclasses.dataclass()
77
77
  class _ServiceOptions:
78
+ # Note that default values should always be "untruthy" so we can detect when they are not set
78
79
  secrets: typing.Collection[_Secret] = ()
79
80
  validated_volumes: typing.Sequence[tuple[str, _Volume]] = ()
80
81
  resources: Optional[api_pb2.Resources] = None
@@ -88,6 +89,25 @@ class _ServiceOptions:
88
89
  batch_max_size: Optional[int] = None
89
90
  batch_wait_ms: Optional[int] = None
90
91
 
92
+ def merge_options(self, new_options: "_ServiceOptions") -> "_ServiceOptions":
93
+ """Implement protobuf-like MergeFrom semantics for this dataclass.
94
+
95
+ This mostly exists to support "stacking" of `.with_options()` calls.
96
+ """
97
+ new_options_dict = dataclasses.asdict(new_options)
98
+
99
+ # Resources needs special merge handling because individual fields are parameters in the public API
100
+ merged_resources = api_pb2.Resources()
101
+ if self.resources:
102
+ merged_resources.MergeFrom(self.resources)
103
+ if new_resources := new_options_dict.pop("resources"):
104
+ merged_resources.MergeFrom(new_resources)
105
+ self.resources = merged_resources
106
+
107
+ for key, value in new_options_dict.items():
108
+ if value: # Only overwrite data when the value was set in the new options
109
+ setattr(self, key, value)
110
+
91
111
 
92
112
  def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name: str):
93
113
  """Binds an "instance service function" to a specific method using metadata for that method
@@ -418,11 +438,11 @@ Obj = synchronize_api(_Obj)
418
438
 
419
439
  class _Cls(_Object, type_prefix="cs"):
420
440
  """
421
- Cls adds method pooling and [lifecycle hook](/docs/guide/lifecycle-functions) behavior
422
- to [modal.Function](/docs/reference/modal.Function).
441
+ Cls adds method pooling and [lifecycle hook](https://modal.com/docs/guide/lifecycle-functions) behavior
442
+ to [modal.Function](https://modal.com/docs/reference/modal.Function).
423
443
 
424
444
  Generally, you will not construct a Cls directly.
425
- Instead, use the [`@app.cls()`](/docs/reference/modal.App#cls) decorator on the App object.
445
+ Instead, use the [`@app.cls()`](https://modal.com/docs/reference/modal.App#cls) decorator on the App object.
426
446
  """
427
447
 
428
448
  _class_service_function: Optional[_Function] # The _Function (read "service") serving *all* methods of the class
@@ -597,9 +617,9 @@ More information on class parameterization can be found here: https://modal.com/
597
617
  ) -> "_Cls":
598
618
  """Reference a Cls from a deployed App by its name.
599
619
 
600
- In contrast to `modal.Cls.lookup`, this is a lazy method
601
- that defers hydrating the local object with metadata from
602
- Modal servers until the first time it is actually used.
620
+ This is a lazy method that defers hydrating the local
621
+ object with metadata from Modal servers until the first
622
+ time it is actually used.
603
623
 
604
624
  ```python
605
625
  Model = modal.Cls.from_name("other-app", "Model")
@@ -664,15 +684,32 @@ More information on class parameterization can be found here: https://modal.com/
664
684
  container_idle_timeout: Optional[int] = None, # Now called `scaledown_window`
665
685
  allow_concurrent_inputs: Optional[int] = None, # See `.with_concurrency`
666
686
  ) -> "_Cls":
667
- """Create an instance of the Cls with configuration options overridden with new values.
687
+ """Override the static Function configuration at runtime.
688
+
689
+ This method will return a new instance of the cls that will autoscale independently of the
690
+ original instance. Note that options cannot be "unset" with this method (i.e., if a GPU
691
+ is configured in the `@app.cls()` decorator, passing `gpu=None` here will not create a
692
+ CPU-only instance).
668
693
 
669
694
  **Usage:**
670
695
 
696
+ You can use this method after looking up the Cls from a deployed App or if you have a
697
+ direct reference to a Cls from another Function or local entrypoint on its App:
698
+
671
699
  ```python notest
672
700
  Model = modal.Cls.from_name("my_app", "Model")
673
701
  ModelUsingGPU = Model.with_options(gpu="A100")
674
- ModelUsingGPU().generate.remote(42) # will run with an A100 GPU
702
+ ModelUsingGPU().generate.remote(input_prompt) # Run with an A100 GPU
675
703
  ```
704
+
705
+ The method can be called multiple times to "stack" updates:
706
+
707
+ ```python notest
708
+ Model.with_options(gpu="A100").with_options(scaledown_window=300) # Use an A100 with slow scaledown
709
+ ```
710
+
711
+ Note that container arguments (i.e. `volumes` and `secrets`) passed in subsequent calls
712
+ will not be merged.
676
713
  """
677
714
  retry_policy = _parse_retries(retries, f"Class {self.__name__}" if self._user_cls else "")
678
715
  if gpu or cpu or memory:
@@ -705,21 +742,23 @@ More information on class parameterization can be found here: https://modal.com/
705
742
 
706
743
  cls = _Cls._from_loader(_load_from_base, rep=f"{self._name}.with_options(...)", is_another_app=True, deps=_deps)
707
744
  cls._initialize_from_other(self)
708
- cls._options = dataclasses.replace(
709
- cls._options,
745
+
746
+ new_options = _ServiceOptions(
710
747
  secrets=secrets,
748
+ validated_volumes=validate_volumes(volumes),
711
749
  resources=resources,
712
750
  retry_policy=retry_policy,
713
751
  max_containers=max_containers,
714
752
  buffer_containers=buffer_containers,
715
753
  scaledown_window=scaledown_window,
716
754
  timeout_secs=timeout,
717
- validated_volumes=validate_volumes(volumes),
718
755
  # Note: set both for backwards / forwards compatibility
719
756
  # But going forward `.with_concurrency` is the preferred method with distinct parameterization
720
757
  max_concurrent_inputs=allow_concurrent_inputs,
721
758
  target_concurrent_inputs=allow_concurrent_inputs,
722
759
  )
760
+
761
+ cls._options.merge_options(new_options)
723
762
  return cls
724
763
 
725
764
  def with_concurrency(self: "_Cls", *, max_inputs: int, target_inputs: Optional[int] = None) -> "_Cls":
@@ -746,9 +785,9 @@ More information on class parameterization can be found here: https://modal.com/
746
785
  _load_from_base, rep=f"{self._name}.with_concurrency(...)", is_another_app=True, deps=_deps
747
786
  )
748
787
  cls._initialize_from_other(self)
749
- cls._options = dataclasses.replace(
750
- cls._options, max_concurrent_inputs=max_inputs, target_concurrent_inputs=target_inputs
751
- )
788
+
789
+ concurrency_options = _ServiceOptions(max_concurrent_inputs=max_inputs, target_concurrent_inputs=target_inputs)
790
+ cls._options.merge_options(concurrency_options)
752
791
  return cls
753
792
 
754
793
  def with_batching(self: "_Cls", *, max_batch_size: int, wait_ms: int) -> "_Cls":
@@ -775,7 +814,9 @@ More information on class parameterization can be found here: https://modal.com/
775
814
  _load_from_base, rep=f"{self._name}.with_concurrency(...)", is_another_app=True, deps=_deps
776
815
  )
777
816
  cls._initialize_from_other(self)
778
- cls._options = dataclasses.replace(cls._options, batch_max_size=max_batch_size, batch_wait_ms=wait_ms)
817
+
818
+ batching_options = _ServiceOptions(batch_max_size=max_batch_size, batch_wait_ms=wait_ms)
819
+ cls._options.merge_options(batching_options)
779
820
  return cls
780
821
 
781
822
  @staticmethod