modal 0.71.11__py3-none-any.whl → 0.71.13__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.
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.71.11"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.13"
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.71.11"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.13"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
@@ -62,6 +62,27 @@ class _CustomPatternMatcher(_AbstractPatternMatcher):
62
62
  class FilePatternMatcher(_AbstractPatternMatcher):
63
63
  """Allows matching file paths against a list of patterns."""
64
64
 
65
+ patterns: list[Pattern]
66
+ _delayed_init: Callable[[], None] = None
67
+
68
+ def _set_patterns(self, patterns: Sequence[str]) -> None:
69
+ self.patterns = []
70
+ for pattern in list(patterns):
71
+ pattern = pattern.strip()
72
+ if not pattern:
73
+ continue
74
+ pattern = os.path.normpath(pattern)
75
+ new_pattern = Pattern()
76
+ if pattern[0] == "!":
77
+ if len(pattern) == 1:
78
+ raise ValueError('Illegal exclusion pattern: "!"')
79
+ new_pattern.exclusion = True
80
+ pattern = pattern[1:]
81
+ # In Python, we can proceed without explicit syntax checking
82
+ new_pattern.cleaned_pattern = pattern
83
+ new_pattern.dirs = pattern.split(os.path.sep)
84
+ self.patterns.append(new_pattern)
85
+
65
86
  def __init__(self, *pattern: str) -> None:
66
87
  """Initialize a new FilePatternMatcher instance.
67
88
 
@@ -71,24 +92,25 @@ class FilePatternMatcher(_AbstractPatternMatcher):
71
92
  Raises:
72
93
  ValueError: If an illegal exclusion pattern is provided.
73
94
  """
74
- self.patterns: list[Pattern] = []
75
- self.exclusions = False
76
- for p in list(pattern):
77
- p = p.strip()
78
- if not p:
79
- continue
80
- p = os.path.normpath(p)
81
- new_pattern = Pattern()
82
- if p[0] == "!":
83
- if len(p) == 1:
84
- raise ValueError('Illegal exclusion pattern: "!"')
85
- new_pattern.exclusion = True
86
- p = p[1:]
87
- self.exclusions = True
88
- # In Python, we can proceed without explicit syntax checking
89
- new_pattern.cleaned_pattern = p
90
- new_pattern.dirs = p.split(os.path.sep)
91
- self.patterns.append(new_pattern)
95
+ self._set_patterns(pattern)
96
+
97
+ @classmethod
98
+ def from_file(cls, file_path: Path) -> "FilePatternMatcher":
99
+ """Initialize a new FilePatternMatcher instance from a file.
100
+
101
+ The patterns in the file will be read lazily when the matcher is first used.
102
+
103
+ Args:
104
+ file_path (Path): The path to the file containing patterns.
105
+ """
106
+ uninitialized = cls.__new__(cls)
107
+
108
+ def _delayed_init():
109
+ uninitialized._set_patterns(file_path.read_text("utf8").splitlines())
110
+ uninitialized._delayed_init = None
111
+
112
+ uninitialized._delayed_init = _delayed_init
113
+ return uninitialized
92
114
 
93
115
  def _matches(self, file_path: str) -> bool:
94
116
  """Check if the file path or any of its parent directories match the patterns.
@@ -97,6 +119,7 @@ class FilePatternMatcher(_AbstractPatternMatcher):
97
119
  library. The reason is that `Matches()` in the original library is
98
120
  deprecated due to buggy behavior.
99
121
  """
122
+
100
123
  matched = False
101
124
  file_path = os.path.normpath(file_path)
102
125
  if file_path == ".":
@@ -146,6 +169,8 @@ class FilePatternMatcher(_AbstractPatternMatcher):
146
169
  assert matcher(Path("foo.py"))
147
170
  ```
148
171
  """
172
+ if self._delayed_init:
173
+ self._delayed_init()
149
174
  return self._matches(str(file_path))
150
175
 
151
176
 
modal/functions.pyi CHANGED
@@ -462,11 +462,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
462
462
 
463
463
  _call_generator_nowait: ___call_generator_nowait_spec
464
464
 
465
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
465
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
466
466
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
467
467
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
468
468
 
469
- remote: __remote_spec[ReturnType, P]
469
+ remote: __remote_spec[P, ReturnType]
470
470
 
471
471
  class __remote_gen_spec(typing_extensions.Protocol):
472
472
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -479,17 +479,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
479
479
  def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
480
480
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
481
481
 
482
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
482
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
483
483
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
484
484
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
485
485
 
486
- _experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
486
+ _experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
487
487
 
488
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
488
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
489
489
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
490
490
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
491
491
 
492
- spawn: __spawn_spec[ReturnType, P]
492
+ spawn: __spawn_spec[P, ReturnType]
493
493
 
494
494
  def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
495
495
 
modal/image.py CHANGED
@@ -683,6 +683,7 @@ class _Image(_Object, type_prefix="im"):
683
683
  **Usage:**
684
684
 
685
685
  ```python
686
+ from pathlib import Path
686
687
  from modal import FilePatternMatcher
687
688
 
688
689
  image = modal.Image.debian_slim().add_local_dir(
@@ -697,18 +698,25 @@ class _Image(_Object, type_prefix="im"):
697
698
  ignore=lambda p: p.is_relative_to(".venv"),
698
699
  )
699
700
 
700
- image = modal.Image.debian_slim().copy_local_dir(
701
+ image = modal.Image.debian_slim().add_local_dir(
701
702
  "~/assets",
702
703
  remote_path="/assets",
703
704
  ignore=FilePatternMatcher("**/*.txt"),
704
705
  )
705
706
 
706
707
  # When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
707
- image = modal.Image.debian_slim().copy_local_dir(
708
+ image = modal.Image.debian_slim().add_local_dir(
708
709
  "~/assets",
709
710
  remote_path="/assets",
710
711
  ignore=~FilePatternMatcher("**/*.py"),
711
712
  )
713
+
714
+ # You can also read ignore patterns from a file.
715
+ image = modal.Image.debian_slim().add_local_dir(
716
+ "~/assets",
717
+ remote_path="/assets",
718
+ ignore=FilePatternMatcher.from_file(Path("/path/to/ignorefile")),
719
+ )
712
720
  ```
713
721
  """
714
722
  if not PurePosixPath(remote_path).is_absolute():
@@ -792,6 +800,7 @@ class _Image(_Object, type_prefix="im"):
792
800
  **Usage:**
793
801
 
794
802
  ```python
803
+ from pathlib import Path
795
804
  from modal import FilePatternMatcher
796
805
 
797
806
  image = modal.Image.debian_slim().copy_local_dir(
@@ -818,6 +827,13 @@ class _Image(_Object, type_prefix="im"):
818
827
  remote_path="/assets",
819
828
  ignore=~FilePatternMatcher("**/*.py"),
820
829
  )
830
+
831
+ # You can also read ignore patterns from a file.
832
+ image = modal.Image.debian_slim().copy_local_dir(
833
+ "~/assets",
834
+ remote_path="/assets",
835
+ ignore=FilePatternMatcher.from_file(Path("/path/to/ignorefile")),
836
+ )
821
837
  ```
822
838
  """
823
839
 
@@ -1207,7 +1223,43 @@ class _Image(_Object, type_prefix="im"):
1207
1223
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1208
1224
  ignore: Union[Sequence[str], Callable[[Path], bool]] = (),
1209
1225
  ) -> "_Image":
1210
- """Extend an image with arbitrary Dockerfile-like commands."""
1226
+ """
1227
+ Extend an image with arbitrary Dockerfile-like commands.
1228
+
1229
+ **Usage:**
1230
+
1231
+ ```python
1232
+ from pathlib import Path
1233
+ from modal import FilePatternMatcher
1234
+
1235
+ image = modal.Image.debian_slim().dockerfile_commands(
1236
+ ["COPY data /data"],
1237
+ ignore=["*.venv"],
1238
+ )
1239
+
1240
+ image = modal.Image.debian_slim().dockerfile_commands(
1241
+ ["COPY data /data"],
1242
+ ignore=lambda p: p.is_relative_to(".venv"),
1243
+ )
1244
+
1245
+ image = modal.Image.debian_slim().dockerfile_commands(
1246
+ ["COPY data /data"],
1247
+ ignore=FilePatternMatcher("**/*.txt"),
1248
+ )
1249
+
1250
+ # When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
1251
+ image = modal.Image.debian_slim().dockerfile_commands(
1252
+ ["COPY data /data"],
1253
+ ignore=~FilePatternMatcher("**/*.py"),
1254
+ )
1255
+
1256
+ # You can also read ignore patterns from a file.
1257
+ image = modal.Image.debian_slim().dockerfile_commands(
1258
+ ["COPY data /data"],
1259
+ ignore=FilePatternMatcher.from_file(Path("/path/to/dockerignore")),
1260
+ )
1261
+ ```
1262
+ """
1211
1263
  cmds = _flatten_str_args("dockerfile_commands", "dockerfile_commands", dockerfile_commands)
1212
1264
  if not cmds:
1213
1265
  return self
@@ -1608,10 +1660,43 @@ class _Image(_Object, type_prefix="im"):
1608
1660
  If your Dockerfile does not have Python installed, you can use the `add_python` parameter
1609
1661
  to specify a version of Python to add to the image.
1610
1662
 
1611
- **Example**
1663
+ **Usage:**
1612
1664
 
1613
1665
  ```python
1614
- image = modal.Image.from_dockerfile("./Dockerfile", add_python="3.12")
1666
+ from pathlib import Path
1667
+ from modal import FilePatternMatcher
1668
+
1669
+ image = modal.Image.from_dockerfile(
1670
+ "./Dockerfile",
1671
+ add_python="3.12",
1672
+ ignore=["*.venv"],
1673
+ )
1674
+
1675
+ image = modal.Image.from_dockerfile(
1676
+ "./Dockerfile",
1677
+ add_python="3.12",
1678
+ ignore=lambda p: p.is_relative_to(".venv"),
1679
+ )
1680
+
1681
+ image = modal.Image.from_dockerfile(
1682
+ "./Dockerfile",
1683
+ add_python="3.12",
1684
+ ignore=FilePatternMatcher("**/*.txt"),
1685
+ )
1686
+
1687
+ # When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
1688
+ image = modal.Image.from_dockerfile(
1689
+ "./Dockerfile",
1690
+ add_python="3.12",
1691
+ ignore=~FilePatternMatcher("**/*.py"),
1692
+ )
1693
+
1694
+ # You can also read ignore patterns from a file.
1695
+ image = modal.Image.from_dockerfile(
1696
+ "./Dockerfile",
1697
+ add_python="3.12",
1698
+ ignore=FilePatternMatcher.from_file(Path("/path/to/dockerignore")),
1699
+ )
1615
1700
  ```
1616
1701
  """
1617
1702
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.71.11
3
+ Version: 0.71.13
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -19,7 +19,7 @@ modal/app.py,sha256=vEE0cK5QPF6_cdW5AJvcuWxz5KmeprHwBEtlDkVRHgE,45582
19
19
  modal/app.pyi,sha256=Gx7gxjfQ70sxhbwfpx1VjvzEON-ZEMTJ_Vy8qt0oQvo,25302
20
20
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
21
21
  modal/client.py,sha256=JAnd4-GCN093BwkvOFAK5a6iy5ycxofjpUncMxlrIMw,15253
22
- modal/client.pyi,sha256=jqYgrQjYnu7lAMwMBBYq0xgDfThCeXi9pTUVaQe6Dr0,7280
22
+ modal/client.pyi,sha256=8J3-EAE51lpcCOdLSSKFWuC3NhItuXhgxIv5NXpy4_0,7280
23
23
  modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
24
24
  modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
25
25
  modal/cls.py,sha256=3hjb0JcoPjxKZNeK22f5rR43bZRBjoRI7_EMZXY7YrE,31172
@@ -35,11 +35,11 @@ modal/exception.py,sha256=4JyO-SACaLNDe2QC48EjsK8GMkZ8AgEurZ8j1YdRu8E,5263
35
35
  modal/experimental.py,sha256=npfKbyMpI41uZZs9HW_QiB3E4ykWfDXZbACXXbw6qeA,2385
36
36
  modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
37
37
  modal/file_io.pyi,sha256=NrIoB0YjIqZ8MDMe826xAnybT0ww_kxQM3iPLo82REU,8898
38
- modal/file_pattern_matcher.py,sha256=LaI7Paxg0xR9D-D7Tgc60xR0w1KZee22LjGbFie1Vms,5571
38
+ modal/file_pattern_matcher.py,sha256=uksEpQG4LSNdW57NQVqP9oOWPpD2-c9QVaAN_dmzKIQ,6415
39
39
  modal/functions.py,sha256=3uJPbrEAWhpFfLfUnoRjGmvEUC-_wVh-8yNJBx8eVeM,68249
40
- modal/functions.pyi,sha256=3ESJ61f8oEDycDmrpnuNB2vjFKuLBG_aqyliXPTdY7M,25407
40
+ modal/functions.pyi,sha256=LiSDgH-X7jcZ56pAoLMwo3x9Dzdp_3Sd7W5MVAJPoCg,25407
41
41
  modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
42
- modal/image.py,sha256=oKqqLhc3Ap2XMG5MKVlERKkMTwJPkNMNcSzxoZh4zuw,85259
42
+ modal/image.py,sha256=l1qJRQwkeD1Fq5KWY80ZwNyxpcEJwA92fbrAizBRtdk,87985
43
43
  modal/image.pyi,sha256=Pa1_LVr3FyNsnu_MhBO08fBgCeLazTEe25phYdu0bzE,25365
44
44
  modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
45
45
  modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
@@ -165,10 +165,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
165
165
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
166
166
  modal_version/__init__.py,sha256=BEBWj9tcbFUwzEjUrqly601rauw5cYsHdcmJHs3iu0s,470
167
167
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
168
- modal_version/_version_generated.py,sha256=fQQSIukEWsXCWwuMN72ljSfl8oj3I6r0wlCYhDTmb3A,149
169
- modal-0.71.11.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
170
- modal-0.71.11.dist-info/METADATA,sha256=BYd6BdWjfQ9O-SwR0Q9gHj727pfTmPMvEfP959JL2j4,2329
171
- modal-0.71.11.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
172
- modal-0.71.11.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
173
- modal-0.71.11.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
174
- modal-0.71.11.dist-info/RECORD,,
168
+ modal_version/_version_generated.py,sha256=cnSvdhptI6bv1AtiTbV-_FVYatlo2RYUt2MB2J1rdVs,149
169
+ modal-0.71.13.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
170
+ modal-0.71.13.dist-info/METADATA,sha256=iN3iHC6_4sMlOR9fHSl0HGvlELImU8UanLI2qt3DMpE,2329
171
+ modal-0.71.13.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
172
+ modal-0.71.13.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
173
+ modal-0.71.13.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
174
+ modal-0.71.13.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 11 # git: 9743e02
4
+ build_number = 13 # git: 1c4cfc0