modAL 1.4.4.dev18__py3-none-any.whl → 1.4.4.dev19__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.dev19",
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.dev19",
209
209
  ):
210
210
  """mdmd:hidden
211
211
  The Modal client object is not intended to be instantiated directly by users.
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.dev19
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=dyI7dgDNmpQXPb7tgOnMIcxwiIcqkseT690nreTMWUM,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
@@ -49,7 +49,7 @@ modal/file_pattern_matcher.py,sha256=6g_awxudMy_ontBvkznybyeNnwGMj297YrmIHCbREI0
49
49
  modal/functions.py,sha256=EZRcHFfnoYbvawUJnAHF5nIvoo9GX2Djy7mUmOCXmXc,258
50
50
  modal/functions.pyi,sha256=XgHw-z8RO-5RERK-ZR_DL22BA_qv6_lzpsFgdyoYG9E,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.dev19.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
@@ -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=OMzASjCWLgkf6ulH5Y8qsrdlxkBVrn-QkcxuUkI4XgY,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.dev19.dist-info/METADATA,sha256=08oWkWwtK6J83jCQxXM_g9kVZPSwTiQyHNSHscAieS8,2339
215
+ modal-1.4.4.dev19.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
216
+ modal-1.4.4.dev19.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
217
+ modal-1.4.4.dev19.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
218
+ modal-1.4.4.dev19.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2026
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.4.4.dev18"
4
+ __version__ = "1.4.4.dev19"