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/cli/run.py CHANGED
@@ -579,7 +579,8 @@ def shell(
579
579
  modal shell hello_world.py::my_function
580
580
  ```
581
581
 
582
- Or, if you're using a [modal.Cls](/docs/reference/modal.Cls), you can refer to a `@modal.method` directly:
582
+ Or, if you're using a [modal.Cls](https://modal.com/docs/reference/modal.Cls)
583
+ you can refer to a `@modal.method` directly:
583
584
 
584
585
  ```
585
586
  modal shell hello_world.py::MyClass.my_method
modal/client.pyi CHANGED
@@ -27,32 +27,94 @@ class _Client:
27
27
  _snapshotted: bool
28
28
 
29
29
  def __init__(
30
- self,
31
- server_url: str,
32
- client_type: int,
33
- credentials: typing.Optional[tuple[str, str]],
34
- version: str = "1.0.4.dev10",
35
- ): ...
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.0.5"
31
+ ):
32
+ """mdmd:hidden
33
+ The Modal client object is not intended to be instantiated directly by users.
34
+ """
35
+ ...
36
+
36
37
  def is_closed(self) -> bool: ...
37
38
  @property
38
- def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal: ...
39
- async def get_stub(self, server_url: str) -> modal_proto.modal_api_grpc.ModalClientModal: ...
39
+ def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
40
+ """mdmd:hidden
41
+ The default stub. Stubs can safely be used across forks / client snapshots.
42
+
43
+ This is useful if you want to make requests to the default Modal server in us-east, for example
44
+ control plane requests.
45
+
46
+ This is equivalent to client.get_stub(default_server_url), but it's cached, so it's a bit faster.
47
+ """
48
+ ...
49
+
50
+ async def get_stub(self, server_url: str) -> modal_proto.modal_api_grpc.ModalClientModal:
51
+ """mdmd:hidden
52
+ Get a stub for a specific server URL. Stubs can safely be used across forks / client snapshots.
53
+
54
+ This is useful if you want to make requests to a regional Modal server, for example low-latency
55
+ function calls in us-west.
56
+
57
+ This function is O(n) where n is the number of RPCs in ModalClient.
58
+ """
59
+ ...
60
+
40
61
  async def _open(self): ...
41
62
  async def _close(self, prep_for_restore: bool = False): ...
42
- async def hello(self): ...
63
+ async def hello(self):
64
+ """Connect to server and retrieve version information; raise appropriate error for various failures."""
65
+ ...
66
+
43
67
  async def __aenter__(self): ...
44
68
  async def __aexit__(self, exc_type, exc, tb): ...
45
69
  @classmethod
46
- def anonymous(cls, server_url: str) -> typing.AsyncContextManager[_Client]: ...
70
+ def anonymous(cls, server_url: str) -> typing.AsyncContextManager[_Client]:
71
+ """mdmd:hidden
72
+ Create a connection with no credentials; to be used for token creation.
73
+ """
74
+ ...
75
+
47
76
  @classmethod
48
- async def from_env(cls, _override_config=None) -> _Client: ...
77
+ async def from_env(cls, _override_config=None) -> _Client:
78
+ """mdmd:hidden
79
+ Singleton that is instantiated from the Modal config and reused on subsequent calls.
80
+ """
81
+ ...
82
+
49
83
  @classmethod
50
- async def from_credentials(cls, token_id: str, token_secret: str) -> _Client: ...
84
+ async def from_credentials(cls, token_id: str, token_secret: str) -> _Client:
85
+ """Constructor based on token credentials; useful for managing Modal on behalf of third-party users.
86
+
87
+ **Usage:**
88
+
89
+ ```python notest
90
+ client = modal.Client.from_credentials("my_token_id", "my_token_secret")
91
+
92
+ modal.Sandbox.create("echo", "hi", client=client, app=app)
93
+ ```
94
+ """
95
+ ...
96
+
51
97
  @classmethod
52
- async def verify(cls, server_url: str, credentials: tuple[str, str]) -> None: ...
98
+ async def verify(cls, server_url: str, credentials: tuple[str, str]) -> None:
99
+ """mdmd:hidden
100
+ Check whether can the client can connect to this server with these credentials; raise if not.
101
+ """
102
+ ...
103
+
53
104
  @classmethod
54
- def set_env_client(cls, client: typing.Optional[_Client]): ...
55
- async def _call_safely(self, coro, readable_method: str): ...
105
+ def set_env_client(cls, client: typing.Optional[_Client]):
106
+ """mdmd:hidden"""
107
+ ...
108
+
109
+ async def _call_safely(self, coro, readable_method: str):
110
+ """Runs coroutine wrapped in a task that's part of the client's task context
111
+
112
+ * Raises ClientClosed in case the client is closed while the coroutine is executed
113
+ * Logs warning if call is made outside of the event loop that the client is running in,
114
+ and execute without the cancellation context in that case
115
+ """
116
+ ...
117
+
56
118
  async def _reset_on_pid_change(self): ...
57
119
  async def _get_channel(self, server_url: str) -> grpclib.client.Channel: ...
58
120
  async def _call_unary(
@@ -90,19 +152,48 @@ class Client:
90
152
  _snapshotted: bool
91
153
 
92
154
  def __init__(
93
- self,
94
- server_url: str,
95
- client_type: int,
96
- credentials: typing.Optional[tuple[str, str]],
97
- version: str = "1.0.4.dev10",
98
- ): ...
155
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.0.5"
156
+ ):
157
+ """mdmd:hidden
158
+ The Modal client object is not intended to be instantiated directly by users.
159
+ """
160
+ ...
161
+
99
162
  def is_closed(self) -> bool: ...
100
163
  @property
101
- def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal: ...
164
+ def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
165
+ """mdmd:hidden
166
+ The default stub. Stubs can safely be used across forks / client snapshots.
167
+
168
+ This is useful if you want to make requests to the default Modal server in us-east, for example
169
+ control plane requests.
170
+
171
+ This is equivalent to client.get_stub(default_server_url), but it's cached, so it's a bit faster.
172
+ """
173
+ ...
102
174
 
103
175
  class __get_stub_spec(typing_extensions.Protocol[SUPERSELF]):
104
- def __call__(self, /, server_url: str) -> modal_proto.modal_api_grpc.ModalClientModal: ...
105
- async def aio(self, /, server_url: str) -> modal_proto.modal_api_grpc.ModalClientModal: ...
176
+ def __call__(self, /, server_url: str) -> modal_proto.modal_api_grpc.ModalClientModal:
177
+ """mdmd:hidden
178
+ Get a stub for a specific server URL. Stubs can safely be used across forks / client snapshots.
179
+
180
+ This is useful if you want to make requests to a regional Modal server, for example low-latency
181
+ function calls in us-west.
182
+
183
+ This function is O(n) where n is the number of RPCs in ModalClient.
184
+ """
185
+ ...
186
+
187
+ async def aio(self, /, server_url: str) -> modal_proto.modal_api_grpc.ModalClientModal:
188
+ """mdmd:hidden
189
+ Get a stub for a specific server URL. Stubs can safely be used across forks / client snapshots.
190
+
191
+ This is useful if you want to make requests to a regional Modal server, for example low-latency
192
+ function calls in us-west.
193
+
194
+ This function is O(n) where n is the number of RPCs in ModalClient.
195
+ """
196
+ ...
106
197
 
107
198
  get_stub: __get_stub_spec[typing_extensions.Self]
108
199
 
@@ -119,8 +210,13 @@ class Client:
119
210
  _close: ___close_spec[typing_extensions.Self]
120
211
 
121
212
  class __hello_spec(typing_extensions.Protocol[SUPERSELF]):
122
- def __call__(self, /): ...
123
- async def aio(self, /): ...
213
+ def __call__(self, /):
214
+ """Connect to server and retrieve version information; raise appropriate error for various failures."""
215
+ ...
216
+
217
+ async def aio(self, /):
218
+ """Connect to server and retrieve version information; raise appropriate error for various failures."""
219
+ ...
124
220
 
125
221
  hello: __hello_spec[typing_extensions.Self]
126
222
 
@@ -129,19 +225,63 @@ class Client:
129
225
  def __exit__(self, exc_type, exc, tb): ...
130
226
  async def __aexit__(self, exc_type, exc, tb): ...
131
227
  @classmethod
132
- def anonymous(cls, server_url: str) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Client]: ...
228
+ def anonymous(cls, server_url: str) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Client]:
229
+ """mdmd:hidden
230
+ Create a connection with no credentials; to be used for token creation.
231
+ """
232
+ ...
233
+
133
234
  @classmethod
134
- def from_env(cls, _override_config=None) -> Client: ...
235
+ def from_env(cls, _override_config=None) -> Client:
236
+ """mdmd:hidden
237
+ Singleton that is instantiated from the Modal config and reused on subsequent calls.
238
+ """
239
+ ...
240
+
135
241
  @classmethod
136
- def from_credentials(cls, token_id: str, token_secret: str) -> Client: ...
242
+ def from_credentials(cls, token_id: str, token_secret: str) -> Client:
243
+ """Constructor based on token credentials; useful for managing Modal on behalf of third-party users.
244
+
245
+ **Usage:**
246
+
247
+ ```python notest
248
+ client = modal.Client.from_credentials("my_token_id", "my_token_secret")
249
+
250
+ modal.Sandbox.create("echo", "hi", client=client, app=app)
251
+ ```
252
+ """
253
+ ...
254
+
137
255
  @classmethod
138
- def verify(cls, server_url: str, credentials: tuple[str, str]) -> None: ...
256
+ def verify(cls, server_url: str, credentials: tuple[str, str]) -> None:
257
+ """mdmd:hidden
258
+ Check whether can the client can connect to this server with these credentials; raise if not.
259
+ """
260
+ ...
261
+
139
262
  @classmethod
140
- def set_env_client(cls, client: typing.Optional[Client]): ...
263
+ def set_env_client(cls, client: typing.Optional[Client]):
264
+ """mdmd:hidden"""
265
+ ...
141
266
 
142
267
  class ___call_safely_spec(typing_extensions.Protocol[SUPERSELF]):
143
- def __call__(self, /, coro, readable_method: str): ...
144
- async def aio(self, /, coro, readable_method: str): ...
268
+ def __call__(self, /, coro, readable_method: str):
269
+ """Runs coroutine wrapped in a task that's part of the client's task context
270
+
271
+ * Raises ClientClosed in case the client is closed while the coroutine is executed
272
+ * Logs warning if call is made outside of the event loop that the client is running in,
273
+ and execute without the cancellation context in that case
274
+ """
275
+ ...
276
+
277
+ async def aio(self, /, coro, readable_method: str):
278
+ """Runs coroutine wrapped in a task that's part of the client's task context
279
+
280
+ * Raises ClientClosed in case the client is closed while the coroutine is executed
281
+ * Logs warning if call is made outside of the event loop that the client is running in,
282
+ and execute without the cancellation context in that case
283
+ """
284
+ ...
145
285
 
146
286
  _call_safely: ___call_safely_spec[typing_extensions.Self]
147
287
 
@@ -182,6 +322,26 @@ class Client:
182
322
  ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
183
323
 
184
324
  class UnaryUnaryWrapper(typing.Generic[RequestType, ResponseType]):
325
+ """Abstract base class for generic types.
326
+
327
+ A generic type is typically declared by inheriting from
328
+ this class parameterized with one or more type variables.
329
+ For example, a generic mapping type might be defined as::
330
+
331
+ class Mapping(Generic[KT, VT]):
332
+ def __getitem__(self, key: KT) -> VT:
333
+ ...
334
+ # Etc.
335
+
336
+ This class can then be used as follows::
337
+
338
+ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
339
+ try:
340
+ return mapping[key]
341
+ except KeyError:
342
+ return default
343
+ """
344
+
185
345
  wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]
186
346
  client: _Client
187
347
 
@@ -190,7 +350,10 @@ class UnaryUnaryWrapper(typing.Generic[RequestType, ResponseType]):
190
350
  wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
191
351
  client: _Client,
192
352
  server_url: str,
193
- ): ...
353
+ ):
354
+ """Initialize self. See help(type(self)) for accurate signature."""
355
+ ...
356
+
194
357
  @property
195
358
  def name(self) -> str: ...
196
359
  async def __call__(
@@ -203,9 +366,31 @@ class UnaryUnaryWrapper(typing.Generic[RequestType, ResponseType]):
203
366
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
204
367
  None,
205
368
  ] = None,
206
- ) -> ResponseType: ...
369
+ ) -> ResponseType:
370
+ """Call self as a function."""
371
+ ...
207
372
 
208
373
  class UnaryStreamWrapper(typing.Generic[RequestType, ResponseType]):
374
+ """Abstract base class for generic types.
375
+
376
+ A generic type is typically declared by inheriting from
377
+ this class parameterized with one or more type variables.
378
+ For example, a generic mapping type might be defined as::
379
+
380
+ class Mapping(Generic[KT, VT]):
381
+ def __getitem__(self, key: KT) -> VT:
382
+ ...
383
+ # Etc.
384
+
385
+ This class can then be used as follows::
386
+
387
+ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
388
+ try:
389
+ return mapping[key]
390
+ except KeyError:
391
+ return default
392
+ """
393
+
209
394
  wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType]
210
395
 
211
396
  def __init__(
@@ -213,7 +398,10 @@ class UnaryStreamWrapper(typing.Generic[RequestType, ResponseType]):
213
398
  wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
214
399
  client: _Client,
215
400
  server_url: str,
216
- ): ...
401
+ ):
402
+ """Initialize self. See help(type(self)) for accurate signature."""
403
+ ...
404
+
217
405
  @property
218
406
  def name(self) -> str: ...
219
407
  def unary_stream(self, request, metadata: typing.Optional[typing.Any] = None): ...
@@ -3,6 +3,95 @@ import modal_proto.api_pb2
3
3
  import typing
4
4
 
5
5
  class _CloudBucketMount:
6
+ """Mounts a cloud bucket to your container. Currently supports AWS S3 buckets.
7
+
8
+ S3 buckets are mounted using [AWS S3 Mountpoint](https://github.com/awslabs/mountpoint-s3).
9
+ S3 mounts are optimized for reading large files sequentially. It does not support every file operation; consult
10
+ [the AWS S3 Mountpoint documentation](https://github.com/awslabs/mountpoint-s3/blob/main/doc/SEMANTICS.md)
11
+ for more information.
12
+
13
+ **AWS S3 Usage**
14
+
15
+ ```python
16
+ import subprocess
17
+
18
+ app = modal.App()
19
+ secret = modal.Secret.from_name(
20
+ "aws-secret",
21
+ required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
22
+ # Note: providing AWS_REGION can help when automatic detection of the bucket region fails.
23
+ )
24
+
25
+ @app.function(
26
+ volumes={
27
+ "/my-mount": modal.CloudBucketMount(
28
+ bucket_name="s3-bucket-name",
29
+ secret=secret,
30
+ read_only=True
31
+ )
32
+ }
33
+ )
34
+ def f():
35
+ subprocess.run(["ls", "/my-mount"], check=True)
36
+ ```
37
+
38
+ **Cloudflare R2 Usage**
39
+
40
+ Cloudflare R2 is [S3-compatible](https://developers.cloudflare.com/r2/api/s3/api/) so its setup looks
41
+ very similar to S3. But additionally the `bucket_endpoint_url` argument must be passed.
42
+
43
+ ```python
44
+ import subprocess
45
+
46
+ app = modal.App()
47
+ secret = modal.Secret.from_name(
48
+ "r2-secret",
49
+ required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
50
+ )
51
+
52
+ @app.function(
53
+ volumes={
54
+ "/my-mount": modal.CloudBucketMount(
55
+ bucket_name="my-r2-bucket",
56
+ bucket_endpoint_url="https://<ACCOUNT ID>.r2.cloudflarestorage.com",
57
+ secret=secret,
58
+ read_only=True
59
+ )
60
+ }
61
+ )
62
+ def f():
63
+ subprocess.run(["ls", "/my-mount"], check=True)
64
+ ```
65
+
66
+ **Google GCS Usage**
67
+
68
+ Google Cloud Storage (GCS) is [S3-compatible](https://cloud.google.com/storage/docs/interoperability).
69
+ GCS Buckets also require a secret with Google-specific key names (see below) populated with
70
+ a [HMAC key](https://cloud.google.com/storage/docs/authentication/managing-hmackeys#create).
71
+
72
+ ```python
73
+ import subprocess
74
+
75
+ app = modal.App()
76
+ gcp_hmac_secret = modal.Secret.from_name(
77
+ "gcp-secret",
78
+ required_keys=["GOOGLE_ACCESS_KEY_ID", "GOOGLE_ACCESS_KEY_SECRET"]
79
+ )
80
+
81
+ @app.function(
82
+ volumes={
83
+ "/my-mount": modal.CloudBucketMount(
84
+ bucket_name="my-gcs-bucket",
85
+ bucket_endpoint_url="https://storage.googleapis.com",
86
+ secret=gcp_hmac_secret,
87
+ )
88
+ }
89
+ )
90
+ def f():
91
+ subprocess.run(["ls", "/my-mount"], check=True)
92
+ ```
93
+ """
94
+
6
95
  bucket_name: str
7
96
  bucket_endpoint_url: typing.Optional[str]
8
97
  key_prefix: typing.Optional[str]
@@ -20,15 +109,114 @@ class _CloudBucketMount:
20
109
  oidc_auth_role_arn: typing.Optional[str] = None,
21
110
  read_only: bool = False,
22
111
  requester_pays: bool = False,
23
- ) -> None: ...
24
- def __repr__(self): ...
25
- def __eq__(self, other): ...
112
+ ) -> None:
113
+ """Initialize self. See help(type(self)) for accurate signature."""
114
+ ...
115
+
116
+ def __repr__(self):
117
+ """Return repr(self)."""
118
+ ...
119
+
120
+ def __eq__(self, other):
121
+ """Return self==value."""
122
+ ...
26
123
 
27
124
  def cloud_bucket_mounts_to_proto(
28
125
  mounts: list[tuple[str, _CloudBucketMount]],
29
- ) -> list[modal_proto.api_pb2.CloudBucketMount]: ...
126
+ ) -> list[modal_proto.api_pb2.CloudBucketMount]:
127
+ """Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server."""
128
+ ...
30
129
 
31
130
  class CloudBucketMount:
131
+ """Mounts a cloud bucket to your container. Currently supports AWS S3 buckets.
132
+
133
+ S3 buckets are mounted using [AWS S3 Mountpoint](https://github.com/awslabs/mountpoint-s3).
134
+ S3 mounts are optimized for reading large files sequentially. It does not support every file operation; consult
135
+ [the AWS S3 Mountpoint documentation](https://github.com/awslabs/mountpoint-s3/blob/main/doc/SEMANTICS.md)
136
+ for more information.
137
+
138
+ **AWS S3 Usage**
139
+
140
+ ```python
141
+ import subprocess
142
+
143
+ app = modal.App()
144
+ secret = modal.Secret.from_name(
145
+ "aws-secret",
146
+ required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
147
+ # Note: providing AWS_REGION can help when automatic detection of the bucket region fails.
148
+ )
149
+
150
+ @app.function(
151
+ volumes={
152
+ "/my-mount": modal.CloudBucketMount(
153
+ bucket_name="s3-bucket-name",
154
+ secret=secret,
155
+ read_only=True
156
+ )
157
+ }
158
+ )
159
+ def f():
160
+ subprocess.run(["ls", "/my-mount"], check=True)
161
+ ```
162
+
163
+ **Cloudflare R2 Usage**
164
+
165
+ Cloudflare R2 is [S3-compatible](https://developers.cloudflare.com/r2/api/s3/api/) so its setup looks
166
+ very similar to S3. But additionally the `bucket_endpoint_url` argument must be passed.
167
+
168
+ ```python
169
+ import subprocess
170
+
171
+ app = modal.App()
172
+ secret = modal.Secret.from_name(
173
+ "r2-secret",
174
+ required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
175
+ )
176
+
177
+ @app.function(
178
+ volumes={
179
+ "/my-mount": modal.CloudBucketMount(
180
+ bucket_name="my-r2-bucket",
181
+ bucket_endpoint_url="https://<ACCOUNT ID>.r2.cloudflarestorage.com",
182
+ secret=secret,
183
+ read_only=True
184
+ )
185
+ }
186
+ )
187
+ def f():
188
+ subprocess.run(["ls", "/my-mount"], check=True)
189
+ ```
190
+
191
+ **Google GCS Usage**
192
+
193
+ Google Cloud Storage (GCS) is [S3-compatible](https://cloud.google.com/storage/docs/interoperability).
194
+ GCS Buckets also require a secret with Google-specific key names (see below) populated with
195
+ a [HMAC key](https://cloud.google.com/storage/docs/authentication/managing-hmackeys#create).
196
+
197
+ ```python
198
+ import subprocess
199
+
200
+ app = modal.App()
201
+ gcp_hmac_secret = modal.Secret.from_name(
202
+ "gcp-secret",
203
+ required_keys=["GOOGLE_ACCESS_KEY_ID", "GOOGLE_ACCESS_KEY_SECRET"]
204
+ )
205
+
206
+ @app.function(
207
+ volumes={
208
+ "/my-mount": modal.CloudBucketMount(
209
+ bucket_name="my-gcs-bucket",
210
+ bucket_endpoint_url="https://storage.googleapis.com",
211
+ secret=gcp_hmac_secret,
212
+ )
213
+ }
214
+ )
215
+ def f():
216
+ subprocess.run(["ls", "/my-mount"], check=True)
217
+ ```
218
+ """
219
+
32
220
  bucket_name: str
33
221
  bucket_endpoint_url: typing.Optional[str]
34
222
  key_prefix: typing.Optional[str]