modAL 1.4.4.dev18__py3-none-any.whl → 1.4.4.dev20__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/_image.py CHANGED
@@ -40,6 +40,7 @@ from ._utils.docker_utils import (
40
40
  )
41
41
  from ._utils.function_utils import FunctionInfo, parse_gpu_config
42
42
  from ._utils.mount_utils import validate_only_modal_volumes, validate_volumes_by_object_id
43
+ from ._utils.name_utils import check_object_name
43
44
  from .client import _Client
44
45
  from .cloud_bucket_mount import _CloudBucketMount
45
46
  from .config import config, logger, user_config_path
@@ -103,6 +104,27 @@ See https://modal.com/docs/guide/modal-1-0-migration for more details.
103
104
  P = typing_extensions.ParamSpec("P")
104
105
 
105
106
 
107
+ def _validate_image_name(name: str) -> None:
108
+ check_object_name(name, "Image")
109
+ # Reserve the "im-" prefix for image IDs so CLI args can be parsed
110
+ # unambiguously as either an image ID or an image name.
111
+ if name.startswith("im-"):
112
+ raise InvalidError("Image name cannot start with 'im-' (reserved for image IDs).")
113
+
114
+
115
+ def _validate_image_tag(tag: str) -> None:
116
+ check_object_name(tag, "Image tag")
117
+
118
+
119
+ def _parse_named_image_ref(name: str) -> str:
120
+ image_name, sep, tag = name.partition(":")
121
+ if not sep:
122
+ tag = "latest"
123
+ _validate_image_name(image_name)
124
+ _validate_image_tag(tag)
125
+ return f"{image_name}:{tag}"
126
+
127
+
106
128
  def _validate_python_version(
107
129
  python_version: str | None,
108
130
  builder_version: ImageBuilderVersion,
@@ -2844,6 +2866,86 @@ class _Image(_Object, type_prefix="im"):
2844
2866
  if task_log.data:
2845
2867
  yield task_log.data
2846
2868
 
2869
+ @staticmethod
2870
+ def from_name(
2871
+ name: str,
2872
+ *,
2873
+ environment_name: str | None = None,
2874
+ client: _Client | None = None,
2875
+ ) -> "_Image":
2876
+ """Reference a named Image that was previously published with `.publish()`.
2877
+
2878
+ Names can contain an optional `:tag` part - if no tag part is included `":latest"` is used,
2879
+ matching Docker conventions.
2880
+
2881
+ ```python notest
2882
+ image = modal.Image.from_name("my-image") # references my-image:latest
2883
+ image_v1 = modal.Image.from_name("my-image:v1")
2884
+
2885
+ @app.function(image=image)
2886
+ def run():
2887
+ ...
2888
+ ```
2889
+ """
2890
+ tag = _parse_named_image_ref(name)
2891
+
2892
+ async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
2893
+ req = api_pb2.ImageGetByTagRequest(
2894
+ tag=tag,
2895
+ environment_name=load_context.environment_name,
2896
+ )
2897
+ response = await load_context.client.stub.ImageGetByTag(req)
2898
+ self._hydrate(response.image_id, load_context.client, None)
2899
+
2900
+ rep = _Image._repr(tag, environment_name)
2901
+ return _Image._from_loader(
2902
+ _load,
2903
+ rep,
2904
+ hydrate_lazily=True,
2905
+ skip_reload=True,
2906
+ load_context_overrides=LoadContext(environment_name=environment_name, client=client),
2907
+ )
2908
+
2909
+ async def publish(
2910
+ self,
2911
+ name: str,
2912
+ *,
2913
+ environment_name: str | None = None,
2914
+ client: _Client | None = None,
2915
+ ) -> None:
2916
+ """Publish this image under the given name
2917
+
2918
+ The Image must already be created (typically by calling `image.build()` or `sandbox.snapshot_filesystem()`).
2919
+
2920
+ Image names can contain an explicit tag designation (using the `name:tag`). If no tag is included in the name,
2921
+ `":latest"` is used, matching Docker conventions. To publish multiple tags, call `.publish()` once per tag.
2922
+
2923
+ ```python notest
2924
+ image = modal.Image.debian_slim().pip_install("numpy")
2925
+ image.build(app)
2926
+ image.publish("my-image-with-numpy") # my-image-with-numpy:latest
2927
+ image.publish("my-image-with-numpy:v1")
2928
+ ```
2929
+ """
2930
+ tag = _parse_named_image_ref(name)
2931
+
2932
+ if not self.is_hydrated:
2933
+ raise InvalidError("Cannot publish an image that has not been created yet. Call `.build()` first.")
2934
+
2935
+ _client = client or self._client
2936
+ assert _client is not None
2937
+
2938
+ resolved_env = environment_name or config.get("environment") or ""
2939
+
2940
+ await _client.stub.ImagePublish(
2941
+ api_pb2.ImagePublishRequest(
2942
+ image_id=self.object_id,
2943
+ environment_name=resolved_env,
2944
+ is_public=False,
2945
+ tag=tag,
2946
+ )
2947
+ )
2948
+
2847
2949
  async def hydrate(self, client: _Client | None = None) -> Self:
2848
2950
  """mdmd:hidden"""
2849
2951
  # Image inherits hydrate() from Object but can't be hydrated on demand
modal/client.pyi CHANGED
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.4.4.dev18",
38
+ version: str = "1.4.4.dev20",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -205,7 +205,7 @@ class Client:
205
205
  server_url: str,
206
206
  client_type: int,
207
207
  credentials: typing.Optional[tuple[str, str]],
208
- version: str = "1.4.4.dev18",
208
+ version: str = "1.4.4.dev20",
209
209
  ):
210
210
  """mdmd:hidden
211
211
  The Modal client object is not intended to be instantiated directly by users.
modal/functions.pyi CHANGED
@@ -465,7 +465,7 @@ class Function(
465
465
 
466
466
  _call_generator: ___call_generator_spec
467
467
 
468
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
468
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
469
469
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
470
470
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
471
471
 
@@ -490,7 +490,7 @@ class Function(
490
490
  """
491
491
  ...
492
492
 
493
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
493
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
494
494
 
495
495
  class __remote_gen_spec(typing_extensions.Protocol):
496
496
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -540,7 +540,7 @@ class Function(
540
540
  """
541
541
  ...
542
542
 
543
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
543
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
544
544
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
545
545
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
546
546
 
@@ -573,7 +573,7 @@ class Function(
573
573
  """
574
574
  ...
575
575
 
576
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
576
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
577
577
 
578
578
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
579
579
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -581,7 +581,7 @@ class Function(
581
581
 
582
582
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
583
583
 
584
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
584
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
585
585
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
586
586
  """Calls the function with the given arguments, without waiting for the results.
587
587
 
@@ -614,7 +614,7 @@ class Function(
614
614
  """
615
615
  ...
616
616
 
617
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
617
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
618
618
 
619
619
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
620
620
  """Return the inner Python object wrapped by this Modal Function.
modal/image.pyi CHANGED
@@ -1356,6 +1356,77 @@ class Image(modal.object.Object):
1356
1356
 
1357
1357
  _logs: ___logs_spec
1358
1358
 
1359
+ @staticmethod
1360
+ def from_name(
1361
+ name: str, *, environment_name: typing.Optional[str] = None, client: typing.Optional[modal.client.Client] = None
1362
+ ) -> Image:
1363
+ """Reference a named Image that was previously published with `.publish()`.
1364
+
1365
+ Names can contain an optional `:tag` part - if no tag part is included `":latest"` is used,
1366
+ matching Docker conventions.
1367
+
1368
+ ```python notest
1369
+ image = modal.Image.from_name("my-image") # references my-image:latest
1370
+ image_v1 = modal.Image.from_name("my-image:v1")
1371
+
1372
+ @app.function(image=image)
1373
+ def run():
1374
+ ...
1375
+ ```
1376
+ """
1377
+ ...
1378
+
1379
+ class __publish_spec(typing_extensions.Protocol):
1380
+ def __call__(
1381
+ self,
1382
+ /,
1383
+ name: str,
1384
+ *,
1385
+ environment_name: typing.Optional[str] = None,
1386
+ client: typing.Optional[modal.client.Client] = None,
1387
+ ) -> None:
1388
+ """Publish this image under the given name
1389
+
1390
+ The Image must already be created (typically by calling `image.build()` or `sandbox.snapshot_filesystem()`).
1391
+
1392
+ Image names can contain an explicit tag designation (using the `name:tag`). If no tag is included in the name,
1393
+ `":latest"` is used, matching Docker conventions. To publish multiple tags, call `.publish()` once per tag.
1394
+
1395
+ ```python notest
1396
+ image = modal.Image.debian_slim().pip_install("numpy")
1397
+ image.build(app)
1398
+ image.publish("my-image-with-numpy") # my-image-with-numpy:latest
1399
+ image.publish("my-image-with-numpy:v1")
1400
+ ```
1401
+ """
1402
+ ...
1403
+
1404
+ async def aio(
1405
+ self,
1406
+ /,
1407
+ name: str,
1408
+ *,
1409
+ environment_name: typing.Optional[str] = None,
1410
+ client: typing.Optional[modal.client.Client] = None,
1411
+ ) -> None:
1412
+ """Publish this image under the given name
1413
+
1414
+ The Image must already be created (typically by calling `image.build()` or `sandbox.snapshot_filesystem()`).
1415
+
1416
+ Image names can contain an explicit tag designation (using the `name:tag`). If no tag is included in the name,
1417
+ `":latest"` is used, matching Docker conventions. To publish multiple tags, call `.publish()` once per tag.
1418
+
1419
+ ```python notest
1420
+ image = modal.Image.debian_slim().pip_install("numpy")
1421
+ image.build(app)
1422
+ image.publish("my-image-with-numpy") # my-image-with-numpy:latest
1423
+ image.publish("my-image-with-numpy:v1")
1424
+ ```
1425
+ """
1426
+ ...
1427
+
1428
+ publish: __publish_spec
1429
+
1359
1430
  class __hydrate_spec(typing_extensions.Protocol[SUPERSELF]):
1360
1431
  def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> SUPERSELF:
1361
1432
  """mdmd:hidden"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.4.dev18
3
+ Version: 1.4.4.dev20
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -8,7 +8,7 @@ modal/_environments.py,sha256=mVDsN-hYyqjaz1-7JTD0X7D7HlQ-QUETssfEq7yHQ5g,17525
8
8
  modal/_function_variants.py,sha256=Oa1hwNGLmZrnKlp5u9jTbouOm9zlVaz5OmwxVe18d0o,12377
9
9
  modal/_functions.py,sha256=so9wDRbP0cBDq0IoNxxcaOVt8L2b0EpRo1kFnFtROro,93181
10
10
  modal/_grpc_client.py,sha256=7PPZIF68MaIKxOTqjCysSpPmjIGCJl5kzudrMK-Eqpg,6895
11
- modal/_image.py,sha256=ECtnm5enBzLwbzPEDHovAbXxTJHdLAB7ft5X_-cc1tg,122765
11
+ modal/_image.py,sha256=LeE8d9KT2mSMafsASNgwFMxzRWMj15T5hpGKtfkqH3Q,126305
12
12
  modal/_ipython.py,sha256=Bu9fc3hm3xt2O47qLe_d-Mu2jS81LwMsmXNJ1sawBA0,575
13
13
  modal/_load_context.py,sha256=wrDqD3KehhFepp0vBhyz_PoLWoRVD0gZHP8dlLHCTiw,4818
14
14
  modal/_location.py,sha256=joiX-0ZeutEUDTrrqLF1GHXCdVLF-rHzstocbMcd_-k,366
@@ -30,7 +30,7 @@ modal/app.pyi,sha256=rjmh5TLrvgQ26hnNm0NqLgmMKoA8pzWIPJ1G1D1_AGg,84061
30
30
  modal/billing.py,sha256=V2WA_TAp8T3eEe5nE1nqXs9PL-CG-HhNpdr18IzsSMk,304
31
31
  modal/call_graph.py,sha256=k_wH3DuuUHCeuycHsOqlcOpIlIVbCYi9RPobg21hJdI,2550
32
32
  modal/client.py,sha256=KaAbJuglWWm_xR-WMGCPVTXDR56xdzenP9Q1b8Ynry0,16132
33
- modal/client.pyi,sha256=BsG4QvAbCYRkWhwB9VK6A3i7STqIlp8KAkXrj1sYvAg,17854
33
+ modal/client.pyi,sha256=NsVL_-_zt6lSz4C9dCMgrhAXXzcBbe9XNwXJ1RyXGWg,17854
34
34
  modal/cloud_bucket_mount.py,sha256=03gECcV9l2Lnt9k23YrDOTGFW7ajfXMW5O8b1prhtwQ,6254
35
35
  modal/cloud_bucket_mount.pyi,sha256=lMOb44pbNE9kDVEqWfcmvpsnh1sdNNFzuKoVyZZ3d5M,8058
36
36
  modal/cls.py,sha256=wmtUfe0ewkIGK1AhyAnQ9IHTe6XJqcFvL8tCnfIjzZs,40928
@@ -47,9 +47,9 @@ modal/file_io.py,sha256=kfojtm0euzOMhfkVTAGyfuygDJ1XPPHQa5uUhkCsThg,21800
47
47
  modal/file_io.pyi,sha256=Tr0OtfbkTNa8v1dkGtl8l3RH58pMc9uGTYKxL1jNwho,15422
48
48
  modal/file_pattern_matcher.py,sha256=6g_awxudMy_ontBvkznybyeNnwGMj297YrmIHCbREI0,8132
49
49
  modal/functions.py,sha256=EZRcHFfnoYbvawUJnAHF5nIvoo9GX2Djy7mUmOCXmXc,258
50
- modal/functions.pyi,sha256=XgHw-z8RO-5RERK-ZR_DL22BA_qv6_lzpsFgdyoYG9E,46656
50
+ modal/functions.pyi,sha256=SW12AMmvyolwO80aO57RM9YHlZwoqTSWGvEdlW9Kflg,46656
51
51
  modal/image.py,sha256=c0lUSAoU-Ip3g5DGZ0uhdCzoWSQWh1g0i-7Rxo-Sz-I,160
52
- modal/image.pyi,sha256=KEKOS1e79Yq98-3LBFkloRzhEA1q6qqpNbdzRzxPjLo,57031
52
+ modal/image.pyi,sha256=Whbfmda2YLAyzctLEF9uF4FQ5b1canwLojSksRJ4gx8,59716
53
53
  modal/io_streams.py,sha256=3fhhb7SDJ69pVUu9J3PiMLBePD9bpiIxPN05SCzf8FE,29118
54
54
  modal/io_streams.pyi,sha256=5uQAgwxN3aouY8UJbRmDnRj9vOId5psRaMrU4IpyQgw,24471
55
55
  modal/mount.py,sha256=n1jfXvZVG1zKcVHDG8_0ECnzS7eaZCCRsk0XCx-GESo,34632
@@ -186,7 +186,7 @@ modal/experimental/flash.py,sha256=GAJ1c-EGL8M3_loAkqr5gdUfySMHY3U_oc6RywfZ8-0,3
186
186
  modal/experimental/flash.pyi,sha256=lpTqJFZRMGWscw0xpYZVdBMnAYjj16asXE4_64A6itQ,17774
187
187
  modal/experimental/ipython.py,sha256=kJ6o9wkAY_MiSb7paewI7f1MJAeqdi8TX5pBdhr_mUI,2925
188
188
  modal/skills/modal/SKILL.md,sha256=WzLjYJ1utjc_ZOf6qZTPwB6wxxwRYB5PibdvIf_FLy0,5316
189
- modal-1.4.4.dev18.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
189
+ modal-1.4.4.dev20.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
190
190
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
191
191
  modal_docs/gen_cli_docs.py,sha256=R--gpKWbXopgLw7A1QcuLgvam9R8DtSR6Q_Oe1pLgkY,3729
192
192
  modal_docs/gen_cli_docs_main.py,sha256=UPGr7O1DYTIRLwfINe2rbdh89J3cPVTU1OWL-SjxnOQ,198
@@ -198,8 +198,8 @@ modal_docs/mdmd/signatures.py,sha256=Xx1FF6oND90MfbHJ24E9rej_GjmEGOAaOp6oJpkv1Os
198
198
  modal_docs/mdmd/types.py,sha256=UjPSjVmdiys9244vJ0DMDJcM9LrWWmI3alW98Ugy8tU,682
199
199
  modal_proto/__init__.py,sha256=tFdSv9eaHBvYcwhgq-XqIlYDNDw57clrvO91Fb-oUV4,130
200
200
  modal_proto/api_grpc.py,sha256=QPj19d4rnASiqYWzt4sJ-7pjGRdQhZHPGVWcX3m53E0,157648
201
- modal_proto/api_pb2.py,sha256=Mmn674jYYxyy9VcP_OqzSfrk1o-rLLVkkZOxMW9-9e4,441601
202
- modal_proto/api_pb2.pyi,sha256=cZ7qEl1OkceZo8IVKKhrh2i9NNtBs5ONL3-NZ19hvzk,620524
201
+ modal_proto/api_pb2.py,sha256=6KiGxJGZIjq8_V_t8VbBGXjky9T6_-LayVMn3K93pTc,441645
202
+ modal_proto/api_pb2.pyi,sha256=MbV4GAaNLNAo_h3jksY1zOY4chDV8lnDGPE50BpnWx4,620669
203
203
  modal_proto/api_pb2_grpc.py,sha256=T-aJ0Yet8uEHi-olGKmnLlaWY7KYDdzBcfBrUGxOI_s,339667
204
204
  modal_proto/api_pb2_grpc.pyi,sha256=0ExAtHDvSQ5ysWo-eB3Xlfrfr0XTkAYwZzI-W2uY3Gc,79270
205
205
  modal_proto/modal_api_grpc.py,sha256=-O55R7LC7XjC2sdfBydQN7uQpCzSYwSGag_G5aWTnZo,24857
@@ -209,10 +209,10 @@ modal_proto/task_command_router_pb2.py,sha256=5KEqPiP-8wfByo0YmwvHTYg1Zmd9d3kToj
209
209
  modal_proto/task_command_router_pb2.pyi,sha256=RwwMMv90wA1AT-_P0HDxfTaBa7bh-OSaXk9A71b-PzM,40167
210
210
  modal_proto/task_command_router_pb2_grpc.py,sha256=368j8b_h19L7bidFTRGSdSG4bK0zU9Y_Oq46_B2ACDg,34666
211
211
  modal_proto/task_command_router_pb2_grpc.pyi,sha256=PbzkCj-uSlN1inYSdhncPQ1-04vhBnj3Wzvb-D2pLHY,11226
212
- modal_version/__init__.py,sha256=Jo_Cl3179-gnOLvWbjXjrdK9cb00i2KmiX12P8oSmPo,121
212
+ modal_version/__init__.py,sha256=rkRFGGh_bDjqt_D_DuEmQbj_z01w8nLNoyzJUpsTSPk,121
213
213
  modal_version/__main__.py,sha256=nLUbubxKx7GXNzJIkTB7M7_u8080LyLM0IS0rzFkC_c,119
214
- modal-1.4.4.dev18.dist-info/METADATA,sha256=tlso2-tDr6eo7KNWJQIZaTnQvai_84FkWQTG5b9nIKc,2339
215
- modal-1.4.4.dev18.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
216
- modal-1.4.4.dev18.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
217
- modal-1.4.4.dev18.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
218
- modal-1.4.4.dev18.dist-info/RECORD,,
214
+ modal-1.4.4.dev20.dist-info/METADATA,sha256=3pZC-cokMWl1gR6cUbbBKPI4hsPZb-wspZydikN9Gdk,2339
215
+ modal-1.4.4.dev20.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
216
+ modal-1.4.4.dev20.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
217
+ modal-1.4.4.dev20.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
218
+ modal-1.4.4.dev20.dist-info/RECORD,,