modal 1.2.2.dev30__py3-none-any.whl → 1.2.2.dev31__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/_functions.py +69 -37
- modal/_load_context.py +105 -0
- modal/_object.py +47 -18
- modal/_resolver.py +21 -35
- modal/app.py +7 -0
- modal/app.pyi +3 -0
- modal/cli/dict.py +5 -2
- modal/cli/queues.py +4 -2
- modal/client.pyi +2 -2
- modal/cls.py +71 -32
- modal/cls.pyi +3 -0
- modal/dict.py +14 -5
- modal/dict.pyi +2 -0
- modal/environments.py +16 -7
- modal/environments.pyi +6 -2
- modal/functions.pyi +10 -3
- modal/image.py +22 -22
- modal/mount.py +34 -24
- modal/mount.pyi +33 -7
- modal/network_file_system.py +14 -5
- modal/network_file_system.pyi +12 -2
- modal/object.pyi +35 -8
- modal/proxy.py +14 -6
- modal/proxy.pyi +10 -2
- modal/queue.py +14 -5
- modal/queue.pyi +12 -2
- modal/runner.py +43 -47
- modal/runner.pyi +2 -2
- modal/sandbox.py +21 -12
- modal/secret.py +34 -17
- modal/secret.pyi +12 -2
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +11 -5
- modal/volume.py +25 -7
- modal/volume.pyi +2 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev31.dist-info}/METADATA +1 -1
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev31.dist-info}/RECORD +43 -42
- modal_version/__init__.py +1 -1
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev31.dist-info}/WHEEL +0 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev31.dist-info}/entry_points.txt +0 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev31.dist-info}/licenses/LICENSE +0 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev31.dist-info}/top_level.txt +0 -0
modal/runner.py
CHANGED
|
@@ -8,7 +8,6 @@ import asyncio
|
|
|
8
8
|
import dataclasses
|
|
9
9
|
import os
|
|
10
10
|
import time
|
|
11
|
-
import typing
|
|
12
11
|
from collections.abc import AsyncGenerator
|
|
13
12
|
from contextlib import nullcontext
|
|
14
13
|
from multiprocessing.synchronize import Event
|
|
@@ -19,6 +18,7 @@ from synchronicity.async_wrap import asynccontextmanager
|
|
|
19
18
|
|
|
20
19
|
import modal._runtime.execution_context
|
|
21
20
|
import modal_proto.api_pb2
|
|
21
|
+
from modal._load_context import LoadContext
|
|
22
22
|
from modal._utils.grpc_utils import Retry
|
|
23
23
|
from modal_proto import api_pb2
|
|
24
24
|
|
|
@@ -125,18 +125,14 @@ async def _init_local_app_from_name(
|
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
async def _create_all_objects(
|
|
128
|
-
client: _Client,
|
|
129
128
|
running_app: RunningApp,
|
|
130
129
|
local_app_state: "modal.app._LocalAppState",
|
|
131
|
-
|
|
130
|
+
load_context: LoadContext,
|
|
132
131
|
) -> None:
|
|
133
132
|
"""Create objects that have been defined but not created on the server."""
|
|
134
133
|
indexed_objects: dict[str, _Object] = {**local_app_state.functions, **local_app_state.classes}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
environment_name=environment_name,
|
|
138
|
-
app_id=running_app.app_id,
|
|
139
|
-
)
|
|
134
|
+
|
|
135
|
+
resolver = Resolver()
|
|
140
136
|
with resolver.display():
|
|
141
137
|
# Get current objects, and reset all objects
|
|
142
138
|
tag_to_object_id = {**running_app.function_ids, **running_app.class_ids}
|
|
@@ -159,7 +155,7 @@ async def _create_all_objects(
|
|
|
159
155
|
# Note: preload only currently implemented for Functions, returns None otherwise
|
|
160
156
|
# this is to ensure that directly referenced functions from the global scope has
|
|
161
157
|
# ids associated with them when they are serialized into other functions
|
|
162
|
-
await resolver.preload(obj, existing_object_id)
|
|
158
|
+
await resolver.preload(obj, load_context, existing_object_id)
|
|
163
159
|
if obj.is_hydrated:
|
|
164
160
|
tag_to_object_id[tag] = obj.object_id
|
|
165
161
|
|
|
@@ -167,7 +163,8 @@ async def _create_all_objects(
|
|
|
167
163
|
|
|
168
164
|
async def _load(tag, obj):
|
|
169
165
|
existing_object_id = tag_to_object_id.get(tag)
|
|
170
|
-
|
|
166
|
+
# Pass load_context so dependencies can inherit app_id, client, etc.
|
|
167
|
+
await resolver.load(obj, load_context, existing_object_id=existing_object_id)
|
|
171
168
|
if _Function._is_id_type(obj.object_id):
|
|
172
169
|
running_app.function_ids[tag] = obj.object_id
|
|
173
170
|
elif _Cls._is_id_type(obj.object_id):
|
|
@@ -266,8 +263,9 @@ async def _run_app(
|
|
|
266
263
|
interactive: bool = False,
|
|
267
264
|
) -> AsyncGenerator["modal.app._App", None]:
|
|
268
265
|
"""mdmd:hidden"""
|
|
269
|
-
|
|
270
|
-
|
|
266
|
+
load_context = await app._root_load_context.reset().in_place_upgrade(
|
|
267
|
+
client=client, environment_name=environment_name
|
|
268
|
+
)
|
|
271
269
|
|
|
272
270
|
if modal._runtime.execution_context._is_currently_importing:
|
|
273
271
|
raise InvalidError("Can not run an app in global scope within a container")
|
|
@@ -288,9 +286,6 @@ async def _run_app(
|
|
|
288
286
|
# https://docs.python.org/3/library/__main__.html#import-main
|
|
289
287
|
app.set_description(__main__.__name__)
|
|
290
288
|
|
|
291
|
-
if client is None:
|
|
292
|
-
client = await _Client.from_env()
|
|
293
|
-
|
|
294
289
|
app_state = api_pb2.APP_STATE_DETACHED if detach else api_pb2.APP_STATE_EPHEMERAL
|
|
295
290
|
|
|
296
291
|
output_mgr = _get_output_manager()
|
|
@@ -301,21 +296,22 @@ async def _run_app(
|
|
|
301
296
|
local_app_state = app._local_state
|
|
302
297
|
|
|
303
298
|
running_app: RunningApp = await _init_local_app_new(
|
|
304
|
-
client,
|
|
299
|
+
load_context.client,
|
|
305
300
|
app.description or "",
|
|
306
301
|
local_app_state.tags,
|
|
307
|
-
environment_name=environment_name
|
|
302
|
+
environment_name=load_context.environment_name,
|
|
308
303
|
app_state=app_state,
|
|
309
304
|
interactive=interactive,
|
|
310
305
|
)
|
|
306
|
+
await load_context.in_place_upgrade(app_id=running_app.app_id)
|
|
311
307
|
|
|
312
308
|
logs_timeout = config["logs_timeout"]
|
|
313
|
-
async with app._set_local_app(client, running_app), TaskContext(grace=logs_timeout) as tc:
|
|
309
|
+
async with app._set_local_app(load_context.client, running_app), TaskContext(grace=logs_timeout) as tc:
|
|
314
310
|
# Start heartbeats loop to keep the client alive
|
|
315
311
|
# we don't log heartbeat exceptions in detached mode
|
|
316
312
|
# as losing the local connection will not affect the running app
|
|
317
313
|
def heartbeat():
|
|
318
|
-
return _heartbeat(client, running_app.app_id)
|
|
314
|
+
return _heartbeat(load_context.client, running_app.app_id)
|
|
319
315
|
|
|
320
316
|
heartbeat_loop = tc.infinite_loop(heartbeat, sleep=HEARTBEAT_INTERVAL, log_exception=not detach)
|
|
321
317
|
logs_loop: Optional[asyncio.Task] = None
|
|
@@ -336,24 +332,26 @@ async def _run_app(
|
|
|
336
332
|
# Start logs loop
|
|
337
333
|
|
|
338
334
|
logs_loop = tc.create_task(
|
|
339
|
-
get_app_logs_loop(
|
|
335
|
+
get_app_logs_loop(
|
|
336
|
+
load_context.client, output_mgr, app_id=running_app.app_id, app_logs_url=running_app.app_logs_url
|
|
337
|
+
)
|
|
340
338
|
)
|
|
341
339
|
|
|
342
340
|
try:
|
|
343
341
|
# Create all members
|
|
344
|
-
await _create_all_objects(
|
|
342
|
+
await _create_all_objects(running_app, local_app_state, load_context)
|
|
345
343
|
|
|
346
344
|
# Publish the app
|
|
347
|
-
await _publish_app(client, running_app, app_state, local_app_state)
|
|
345
|
+
await _publish_app(load_context.client, running_app, app_state, local_app_state)
|
|
348
346
|
except asyncio.CancelledError as e:
|
|
349
347
|
# this typically happens on sigint/ctrl-C during setup (the KeyboardInterrupt happens in the main thread)
|
|
350
348
|
if output_mgr := _get_output_manager():
|
|
351
349
|
output_mgr.print("Aborting app initialization...\n")
|
|
352
350
|
|
|
353
|
-
await _status_based_disconnect(client, running_app.app_id, e)
|
|
351
|
+
await _status_based_disconnect(load_context.client, running_app.app_id, e)
|
|
354
352
|
raise
|
|
355
353
|
except BaseException as e:
|
|
356
|
-
await _status_based_disconnect(client, running_app.app_id, e)
|
|
354
|
+
await _status_based_disconnect(load_context.client, running_app.app_id, e)
|
|
357
355
|
raise
|
|
358
356
|
|
|
359
357
|
detached_disconnect_msg = (
|
|
@@ -381,7 +379,7 @@ async def _run_app(
|
|
|
381
379
|
yield app
|
|
382
380
|
# successful completion!
|
|
383
381
|
heartbeat_loop.cancel()
|
|
384
|
-
await _status_based_disconnect(client, running_app.app_id, exc_info=None)
|
|
382
|
+
await _status_based_disconnect(load_context.client, running_app.app_id, exc_info=None)
|
|
385
383
|
except KeyboardInterrupt as e:
|
|
386
384
|
# this happens only if sigint comes in during the yield block above
|
|
387
385
|
if detach:
|
|
@@ -390,13 +388,13 @@ async def _run_app(
|
|
|
390
388
|
output_mgr.print(detached_disconnect_msg)
|
|
391
389
|
if logs_loop:
|
|
392
390
|
logs_loop.cancel()
|
|
393
|
-
await _status_based_disconnect(client, running_app.app_id, e)
|
|
391
|
+
await _status_based_disconnect(load_context.client, running_app.app_id, e)
|
|
394
392
|
else:
|
|
395
393
|
if output_mgr := _get_output_manager():
|
|
396
394
|
output_mgr.print(
|
|
397
395
|
"Disconnecting from Modal - This will terminate your Modal app in a few seconds.\n"
|
|
398
396
|
)
|
|
399
|
-
await _status_based_disconnect(client, running_app.app_id, e)
|
|
397
|
+
await _status_based_disconnect(load_context.client, running_app.app_id, e)
|
|
400
398
|
if logs_loop:
|
|
401
399
|
try:
|
|
402
400
|
await asyncio.wait_for(logs_loop, timeout=logs_timeout)
|
|
@@ -421,7 +419,7 @@ async def _run_app(
|
|
|
421
419
|
raise
|
|
422
420
|
except BaseException as e:
|
|
423
421
|
logger.info("Exception during app run")
|
|
424
|
-
await _status_based_disconnect(client, running_app.app_id, e)
|
|
422
|
+
await _status_based_disconnect(load_context.client, running_app.app_id, e)
|
|
425
423
|
raise
|
|
426
424
|
|
|
427
425
|
# wait for logs gracefully, even though the task context would do the same
|
|
@@ -449,21 +447,17 @@ async def _serve_update(
|
|
|
449
447
|
) -> None:
|
|
450
448
|
"""mdmd:hidden"""
|
|
451
449
|
# Used by child process to reinitialize a served app
|
|
452
|
-
|
|
450
|
+
load_context = await app._root_load_context.reset().in_place_upgrade(environment_name=environment_name)
|
|
453
451
|
try:
|
|
454
|
-
running_app: RunningApp = await _init_local_app_existing(client, existing_app_id, environment_name)
|
|
452
|
+
running_app: RunningApp = await _init_local_app_existing(load_context.client, existing_app_id, environment_name)
|
|
453
|
+
await load_context.in_place_upgrade(app_id=running_app.app_id)
|
|
455
454
|
local_app_state = app._local_state
|
|
456
455
|
# Create objects
|
|
457
|
-
await _create_all_objects(
|
|
458
|
-
client,
|
|
459
|
-
running_app,
|
|
460
|
-
local_app_state,
|
|
461
|
-
environment_name,
|
|
462
|
-
)
|
|
456
|
+
await _create_all_objects(running_app, local_app_state, load_context)
|
|
463
457
|
|
|
464
458
|
# Publish the updated app
|
|
465
459
|
await _publish_app(
|
|
466
|
-
client,
|
|
460
|
+
load_context.client,
|
|
467
461
|
running_app,
|
|
468
462
|
app_state=api_pb2.APP_STATE_UNSPECIFIED,
|
|
469
463
|
app_local_state=local_app_state,
|
|
@@ -498,9 +492,6 @@ async def _deploy_app(
|
|
|
498
492
|
|
|
499
493
|
Users should prefer the `modal deploy` CLI or the `App.deploy` method.
|
|
500
494
|
"""
|
|
501
|
-
if environment_name is None:
|
|
502
|
-
environment_name = typing.cast(str, config.get("environment"))
|
|
503
|
-
|
|
504
495
|
warn_if_passing_namespace(namespace, "modal.runner.deploy_app")
|
|
505
496
|
|
|
506
497
|
name = name or app.name or ""
|
|
@@ -532,8 +523,18 @@ async def _deploy_app(
|
|
|
532
523
|
# Get git information to track deployment history
|
|
533
524
|
commit_info_task = asyncio.create_task(get_git_commit_info())
|
|
534
525
|
|
|
526
|
+
# We need to do in-place replacement of fields in self._root_load_context in case it has already "spread"
|
|
527
|
+
# to with_options() instances or similar before load
|
|
528
|
+
root_load_context = await app._root_load_context.reset().in_place_upgrade(
|
|
529
|
+
client=client,
|
|
530
|
+
environment_name=environment_name,
|
|
531
|
+
)
|
|
535
532
|
running_app: RunningApp = await _init_local_app_from_name(
|
|
536
|
-
client, name, local_app_state.tags, environment_name=environment_name
|
|
533
|
+
root_load_context.client, name, local_app_state.tags, environment_name=root_load_context.environment_name
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
await root_load_context.in_place_upgrade(
|
|
537
|
+
app_id=running_app.app_id,
|
|
537
538
|
)
|
|
538
539
|
|
|
539
540
|
async with TaskContext(0) as tc:
|
|
@@ -545,12 +546,7 @@ async def _deploy_app(
|
|
|
545
546
|
|
|
546
547
|
try:
|
|
547
548
|
# Create all members
|
|
548
|
-
await _create_all_objects(
|
|
549
|
-
client,
|
|
550
|
-
running_app,
|
|
551
|
-
local_app_state,
|
|
552
|
-
environment_name=environment_name,
|
|
553
|
-
)
|
|
549
|
+
await _create_all_objects(running_app, local_app_state, root_load_context)
|
|
554
550
|
|
|
555
551
|
commit_info = None
|
|
556
552
|
try:
|
modal/runner.pyi
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import modal._load_context
|
|
1
2
|
import modal.app
|
|
2
3
|
import modal.client
|
|
3
4
|
import modal.running_app
|
|
@@ -25,10 +26,9 @@ async def _init_local_app_from_name(
|
|
|
25
26
|
client: modal.client._Client, name: str, tags: dict[str, str], environment_name: str = ""
|
|
26
27
|
) -> modal.running_app.RunningApp: ...
|
|
27
28
|
async def _create_all_objects(
|
|
28
|
-
client: modal.client._Client,
|
|
29
29
|
running_app: modal.running_app.RunningApp,
|
|
30
30
|
local_app_state: modal.app._LocalAppState,
|
|
31
|
-
|
|
31
|
+
load_context: modal._load_context.LoadContext,
|
|
32
32
|
) -> None:
|
|
33
33
|
"""Create objects that have been defined but not created on the server."""
|
|
34
34
|
...
|
modal/sandbox.py
CHANGED
|
@@ -23,6 +23,7 @@ from modal.mount import _Mount
|
|
|
23
23
|
from modal.volume import _Volume
|
|
24
24
|
from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
25
25
|
|
|
26
|
+
from ._load_context import LoadContext
|
|
26
27
|
from ._object import _get_environment_name, _Object
|
|
27
28
|
from ._resolver import Resolver
|
|
28
29
|
from ._resources import convert_fn_config_to_resources_config
|
|
@@ -191,7 +192,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
191
192
|
deps.append(proxy)
|
|
192
193
|
return deps
|
|
193
194
|
|
|
194
|
-
async def _load(
|
|
195
|
+
async def _load(
|
|
196
|
+
self: _Sandbox, resolver: Resolver, load_context: LoadContext, _existing_object_id: Optional[str]
|
|
197
|
+
):
|
|
195
198
|
# Relies on dicts being ordered (true as of Python 3.6).
|
|
196
199
|
volume_mounts = [
|
|
197
200
|
api_pb2.VolumeMount(
|
|
@@ -260,18 +263,18 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
260
263
|
experimental_options=experimental_options,
|
|
261
264
|
)
|
|
262
265
|
|
|
263
|
-
create_req = api_pb2.SandboxCreateRequest(app_id=
|
|
266
|
+
create_req = api_pb2.SandboxCreateRequest(app_id=load_context.app_id, definition=definition)
|
|
264
267
|
try:
|
|
265
|
-
create_resp = await
|
|
268
|
+
create_resp = await load_context.client.stub.SandboxCreate(create_req)
|
|
266
269
|
except GRPCError as exc:
|
|
267
270
|
if exc.status == Status.ALREADY_EXISTS:
|
|
268
271
|
raise AlreadyExistsError(exc.message)
|
|
269
272
|
raise exc
|
|
270
273
|
|
|
271
274
|
sandbox_id = create_resp.sandbox_id
|
|
272
|
-
self._hydrate(sandbox_id,
|
|
275
|
+
self._hydrate(sandbox_id, load_context.client, None)
|
|
273
276
|
|
|
274
|
-
return _Sandbox._from_loader(_load, "Sandbox()", deps=_deps)
|
|
277
|
+
return _Sandbox._from_loader(_load, "Sandbox()", deps=_deps, load_context_overrides=LoadContext.empty())
|
|
275
278
|
|
|
276
279
|
@staticmethod
|
|
277
280
|
async def create(
|
|
@@ -486,6 +489,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
486
489
|
app_id = app.app_id
|
|
487
490
|
app_client = app._client
|
|
488
491
|
elif (container_app := _App._get_container_app()) is not None:
|
|
492
|
+
# implicit app/client provided by running in a modal Function
|
|
489
493
|
app_id = container_app.app_id
|
|
490
494
|
app_client = container_app._client
|
|
491
495
|
else:
|
|
@@ -498,10 +502,11 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
498
502
|
"```",
|
|
499
503
|
)
|
|
500
504
|
|
|
501
|
-
client = client or app_client
|
|
505
|
+
client = client or app_client
|
|
502
506
|
|
|
503
|
-
resolver = Resolver(
|
|
504
|
-
|
|
507
|
+
resolver = Resolver()
|
|
508
|
+
load_context = LoadContext(client=client, app_id=app_id)
|
|
509
|
+
await resolver.load(obj, load_context)
|
|
505
510
|
return obj
|
|
506
511
|
|
|
507
512
|
def _hydrate_metadata(self, handle_metadata: Optional[Message]):
|
|
@@ -606,12 +611,13 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
606
611
|
image_id = resp.image_id
|
|
607
612
|
metadata = resp.image_metadata
|
|
608
613
|
|
|
609
|
-
async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
|
|
614
|
+
async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
|
|
610
615
|
# no need to hydrate again since we do it eagerly below
|
|
611
616
|
pass
|
|
612
617
|
|
|
613
618
|
rep = "Image()"
|
|
614
|
-
|
|
619
|
+
# TODO: use ._new_hydrated instead
|
|
620
|
+
image = _Image._from_loader(_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty())
|
|
615
621
|
image._hydrate(image_id, self._client, metadata) # hydrating eagerly since we have all of the data
|
|
616
622
|
|
|
617
623
|
return image
|
|
@@ -990,12 +996,15 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
990
996
|
if wait_resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
|
|
991
997
|
raise ExecutionError(wait_resp.result.exception)
|
|
992
998
|
|
|
993
|
-
async def _load(
|
|
999
|
+
async def _load(
|
|
1000
|
+
self: _SandboxSnapshot, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
1001
|
+
):
|
|
994
1002
|
# we eagerly hydrate the sandbox snapshot below
|
|
995
1003
|
pass
|
|
996
1004
|
|
|
997
1005
|
rep = "SandboxSnapshot()"
|
|
998
|
-
|
|
1006
|
+
# TODO: use ._new_hydrated instead
|
|
1007
|
+
obj = _SandboxSnapshot._from_loader(_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty())
|
|
999
1008
|
obj._hydrate(snapshot_id, self._client, None)
|
|
1000
1009
|
|
|
1001
1010
|
return obj
|
modal/secret.py
CHANGED
|
@@ -10,6 +10,7 @@ from synchronicity import classproperty
|
|
|
10
10
|
|
|
11
11
|
from modal_proto import api_pb2
|
|
12
12
|
|
|
13
|
+
from ._load_context import LoadContext
|
|
13
14
|
from ._object import _get_environment_name, _Object, live_method
|
|
14
15
|
from ._resolver import Resolver
|
|
15
16
|
from ._runtime.execution_context import is_local
|
|
@@ -259,8 +260,10 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
259
260
|
if not all(isinstance(v, str) for v in env_dict_filtered.values()):
|
|
260
261
|
raise InvalidError(ENV_DICT_WRONG_TYPE_ERR)
|
|
261
262
|
|
|
262
|
-
async def _load(
|
|
263
|
-
|
|
263
|
+
async def _load(
|
|
264
|
+
self: _Secret, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
265
|
+
):
|
|
266
|
+
if load_context.app_id is not None:
|
|
264
267
|
object_creation_type = api_pb2.OBJECT_CREATION_TYPE_ANONYMOUS_OWNED_BY_APP
|
|
265
268
|
else:
|
|
266
269
|
object_creation_type = api_pb2.OBJECT_CREATION_TYPE_EPHEMERAL
|
|
@@ -268,21 +271,22 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
268
271
|
req = api_pb2.SecretGetOrCreateRequest(
|
|
269
272
|
object_creation_type=object_creation_type,
|
|
270
273
|
env_dict=env_dict_filtered,
|
|
271
|
-
app_id=
|
|
272
|
-
environment_name=
|
|
274
|
+
app_id=load_context.app_id,
|
|
275
|
+
environment_name=load_context.environment_name,
|
|
273
276
|
)
|
|
274
277
|
try:
|
|
275
|
-
resp = await
|
|
278
|
+
resp = await load_context.client.stub.SecretGetOrCreate(req)
|
|
276
279
|
except GRPCError as exc:
|
|
277
280
|
if exc.status == Status.INVALID_ARGUMENT:
|
|
278
281
|
raise InvalidError(exc.message)
|
|
279
282
|
if exc.status == Status.FAILED_PRECONDITION:
|
|
280
283
|
raise InvalidError(exc.message)
|
|
281
284
|
raise
|
|
282
|
-
self._hydrate(resp.secret_id,
|
|
285
|
+
self._hydrate(resp.secret_id, load_context.client, resp.metadata)
|
|
283
286
|
|
|
284
287
|
rep = f"Secret.from_dict([{', '.join(env_dict.keys())}])"
|
|
285
|
-
|
|
288
|
+
# TODO: scoping - these should probably not be lazily hydrated without having an app and/or sandbox association
|
|
289
|
+
return _Secret._from_loader(_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty())
|
|
286
290
|
|
|
287
291
|
@staticmethod
|
|
288
292
|
def from_local_environ(
|
|
@@ -330,7 +334,9 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
330
334
|
```
|
|
331
335
|
"""
|
|
332
336
|
|
|
333
|
-
async def _load(
|
|
337
|
+
async def _load(
|
|
338
|
+
self: _Secret, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
339
|
+
):
|
|
334
340
|
try:
|
|
335
341
|
from dotenv import dotenv_values, find_dotenv
|
|
336
342
|
from dotenv.main import _walk_to_root
|
|
@@ -359,13 +365,15 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
359
365
|
req = api_pb2.SecretGetOrCreateRequest(
|
|
360
366
|
object_creation_type=api_pb2.OBJECT_CREATION_TYPE_ANONYMOUS_OWNED_BY_APP,
|
|
361
367
|
env_dict=env_dict,
|
|
362
|
-
app_id=
|
|
368
|
+
app_id=load_context.app_id, # TODO: what if app_id isn't set here (e.g. .hydrate())
|
|
363
369
|
)
|
|
364
|
-
resp = await
|
|
370
|
+
resp = await load_context.client.stub.SecretGetOrCreate(req)
|
|
365
371
|
|
|
366
|
-
self._hydrate(resp.secret_id,
|
|
372
|
+
self._hydrate(resp.secret_id, load_context.client, resp.metadata)
|
|
367
373
|
|
|
368
|
-
return _Secret._from_loader(
|
|
374
|
+
return _Secret._from_loader(
|
|
375
|
+
_load, "Secret.from_dotenv()", hydrate_lazily=True, load_context_overrides=LoadContext.empty()
|
|
376
|
+
)
|
|
369
377
|
|
|
370
378
|
@staticmethod
|
|
371
379
|
def from_name(
|
|
@@ -376,6 +384,7 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
376
384
|
required_keys: list[
|
|
377
385
|
str
|
|
378
386
|
] = [], # Optionally, a list of required environment variables (will be asserted server-side)
|
|
387
|
+
client: Optional[_Client] = None,
|
|
379
388
|
) -> "_Secret":
|
|
380
389
|
"""Reference a Secret by its name.
|
|
381
390
|
|
|
@@ -393,23 +402,31 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
393
402
|
"""
|
|
394
403
|
warn_if_passing_namespace(namespace, "modal.Secret.from_name")
|
|
395
404
|
|
|
396
|
-
async def _load(
|
|
405
|
+
async def _load(
|
|
406
|
+
self: _Secret, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
407
|
+
):
|
|
397
408
|
req = api_pb2.SecretGetOrCreateRequest(
|
|
398
409
|
deployment_name=name,
|
|
399
|
-
environment_name=
|
|
410
|
+
environment_name=load_context.environment_name,
|
|
400
411
|
required_keys=required_keys,
|
|
401
412
|
)
|
|
402
413
|
try:
|
|
403
|
-
response = await
|
|
414
|
+
response = await load_context.client.stub.SecretGetOrCreate(req)
|
|
404
415
|
except GRPCError as exc:
|
|
405
416
|
if exc.status == Status.NOT_FOUND:
|
|
406
417
|
raise NotFoundError(exc.message)
|
|
407
418
|
else:
|
|
408
419
|
raise
|
|
409
|
-
self._hydrate(response.secret_id,
|
|
420
|
+
self._hydrate(response.secret_id, load_context.client, response.metadata)
|
|
410
421
|
|
|
411
422
|
rep = _Secret._repr(name, environment_name)
|
|
412
|
-
return _Secret._from_loader(
|
|
423
|
+
return _Secret._from_loader(
|
|
424
|
+
_load,
|
|
425
|
+
rep,
|
|
426
|
+
hydrate_lazily=True,
|
|
427
|
+
name=name,
|
|
428
|
+
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
429
|
+
)
|
|
413
430
|
|
|
414
431
|
@staticmethod
|
|
415
432
|
async def create_deployed(
|
modal/secret.pyi
CHANGED
|
@@ -423,7 +423,12 @@ class _Secret(modal._object._Object):
|
|
|
423
423
|
|
|
424
424
|
@staticmethod
|
|
425
425
|
def from_name(
|
|
426
|
-
name: str,
|
|
426
|
+
name: str,
|
|
427
|
+
*,
|
|
428
|
+
namespace=None,
|
|
429
|
+
environment_name: typing.Optional[str] = None,
|
|
430
|
+
required_keys: list[str] = [],
|
|
431
|
+
client: typing.Optional[modal.client._Client] = None,
|
|
427
432
|
) -> _Secret:
|
|
428
433
|
"""Reference a Secret by its name.
|
|
429
434
|
|
|
@@ -543,7 +548,12 @@ class Secret(modal.object.Object):
|
|
|
543
548
|
|
|
544
549
|
@staticmethod
|
|
545
550
|
def from_name(
|
|
546
|
-
name: str,
|
|
551
|
+
name: str,
|
|
552
|
+
*,
|
|
553
|
+
namespace=None,
|
|
554
|
+
environment_name: typing.Optional[str] = None,
|
|
555
|
+
required_keys: list[str] = [],
|
|
556
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
547
557
|
) -> Secret:
|
|
548
558
|
"""Reference a Secret by its name.
|
|
549
559
|
|
modal/serving.py
CHANGED
|
@@ -4,13 +4,13 @@ import platform
|
|
|
4
4
|
from collections.abc import AsyncGenerator
|
|
5
5
|
from multiprocessing.context import SpawnProcess
|
|
6
6
|
from multiprocessing.synchronize import Event
|
|
7
|
-
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
from typing import TYPE_CHECKING, Optional
|
|
8
8
|
|
|
9
9
|
from synchronicity.async_wrap import asynccontextmanager
|
|
10
10
|
|
|
11
11
|
from modal._output import OutputManager
|
|
12
12
|
|
|
13
|
-
from ._utils.async_utils import TaskContext, asyncify, synchronize_api
|
|
13
|
+
from ._utils.async_utils import TaskContext, asyncify, synchronize_api
|
|
14
14
|
from ._utils.logger import logger
|
|
15
15
|
from ._watcher import watch
|
|
16
16
|
from .cli.import_refs import ImportRef, import_app_from_ref
|
|
@@ -20,20 +20,16 @@ from .output import _get_output_manager, enable_output
|
|
|
20
20
|
from .runner import _run_app, serve_update
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
-
|
|
24
|
-
else:
|
|
25
|
-
_App = TypeVar("_App")
|
|
23
|
+
import modal.app
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
def _run_serve(
|
|
29
27
|
import_ref: ImportRef, existing_app_id: str, is_ready: Event, environment_name: str, show_progress: bool
|
|
30
28
|
):
|
|
31
|
-
|
|
32
|
-
_app = import_app_from_ref(import_ref, base_cmd="modal serve")
|
|
33
|
-
blocking_app = synchronizer._translate_out(_app)
|
|
29
|
+
app = import_app_from_ref(import_ref, base_cmd="modal serve")
|
|
34
30
|
|
|
35
31
|
with enable_output(show_progress=show_progress):
|
|
36
|
-
serve_update(
|
|
32
|
+
serve_update(app, existing_app_id, is_ready, environment_name)
|
|
37
33
|
|
|
38
34
|
|
|
39
35
|
async def _restart_serve(
|
|
@@ -97,12 +93,12 @@ async def _run_watch_loop(
|
|
|
97
93
|
|
|
98
94
|
@asynccontextmanager
|
|
99
95
|
async def _serve_app(
|
|
100
|
-
app: "_App",
|
|
96
|
+
app: "modal.app._App",
|
|
101
97
|
import_ref: ImportRef,
|
|
102
98
|
*,
|
|
103
99
|
_watcher: Optional[AsyncGenerator[set[str], None]] = None, # for testing
|
|
104
100
|
environment_name: Optional[str] = None,
|
|
105
|
-
) -> AsyncGenerator["_App", None]:
|
|
101
|
+
) -> AsyncGenerator["modal.app._App", None]:
|
|
106
102
|
if environment_name is None:
|
|
107
103
|
environment_name = config.get("environment")
|
|
108
104
|
|
modal/serving.pyi
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections.abc
|
|
2
|
+
import modal.app
|
|
2
3
|
import modal.cli.import_refs
|
|
3
4
|
import multiprocessing.context
|
|
4
5
|
import multiprocessing.synchronize
|
|
@@ -6,8 +7,6 @@ import synchronicity.combined_types
|
|
|
6
7
|
import typing
|
|
7
8
|
import typing_extensions
|
|
8
9
|
|
|
9
|
-
_App = typing.TypeVar("_App")
|
|
10
|
-
|
|
11
10
|
def _run_serve(
|
|
12
11
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
13
12
|
existing_app_id: str,
|
|
@@ -27,31 +26,31 @@ async def _run_watch_loop(
|
|
|
27
26
|
environment_name: str,
|
|
28
27
|
): ...
|
|
29
28
|
def _serve_app(
|
|
30
|
-
app: _App,
|
|
29
|
+
app: modal.app._App,
|
|
31
30
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
32
31
|
*,
|
|
33
32
|
_watcher: typing.Optional[collections.abc.AsyncGenerator[set[str], None]] = None,
|
|
34
33
|
environment_name: typing.Optional[str] = None,
|
|
35
|
-
) -> typing.AsyncContextManager[_App]: ...
|
|
34
|
+
) -> typing.AsyncContextManager[modal.app._App]: ...
|
|
36
35
|
|
|
37
36
|
class __serve_app_spec(typing_extensions.Protocol):
|
|
38
37
|
def __call__(
|
|
39
38
|
self,
|
|
40
39
|
/,
|
|
41
|
-
app:
|
|
40
|
+
app: modal.app.App,
|
|
42
41
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
43
42
|
*,
|
|
44
43
|
_watcher: typing.Optional[typing.Generator[set[str], None, None]] = None,
|
|
45
44
|
environment_name: typing.Optional[str] = None,
|
|
46
|
-
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[
|
|
45
|
+
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[modal.app.App]: ...
|
|
47
46
|
def aio(
|
|
48
47
|
self,
|
|
49
48
|
/,
|
|
50
|
-
app:
|
|
49
|
+
app: modal.app.App,
|
|
51
50
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
52
51
|
*,
|
|
53
52
|
_watcher: typing.Optional[collections.abc.AsyncGenerator[set[str], None]] = None,
|
|
54
53
|
environment_name: typing.Optional[str] = None,
|
|
55
|
-
) -> typing.AsyncContextManager[
|
|
54
|
+
) -> typing.AsyncContextManager[modal.app.App]: ...
|
|
56
55
|
|
|
57
56
|
serve_app: __serve_app_spec
|
modal/snapshot.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Optional
|
|
|
3
3
|
|
|
4
4
|
from modal_proto import api_pb2
|
|
5
5
|
|
|
6
|
+
from ._load_context import LoadContext
|
|
6
7
|
from ._object import _Object
|
|
7
8
|
from ._resolver import Resolver
|
|
8
9
|
from ._utils.async_utils import synchronize_api
|
|
@@ -23,14 +24,19 @@ class _SandboxSnapshot(_Object, type_prefix="sn"):
|
|
|
23
24
|
"""
|
|
24
25
|
Construct a `SandboxSnapshot` object from a sandbox snapshot ID.
|
|
25
26
|
"""
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
# TODO: remove this - from_id constructor should not do io:
|
|
28
|
+
client = client or await _Client.from_env()
|
|
28
29
|
|
|
29
|
-
async def _load(
|
|
30
|
-
|
|
30
|
+
async def _load(
|
|
31
|
+
self: _SandboxSnapshot, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
32
|
+
):
|
|
33
|
+
await load_context.client.stub.SandboxSnapshotGet(
|
|
34
|
+
api_pb2.SandboxSnapshotGetRequest(snapshot_id=sandbox_snapshot_id)
|
|
35
|
+
)
|
|
31
36
|
|
|
32
37
|
rep = "SandboxSnapshot()"
|
|
33
|
-
obj = _SandboxSnapshot._from_loader(_load, rep)
|
|
38
|
+
obj = _SandboxSnapshot._from_loader(_load, rep, load_context_overrides=LoadContext(client=client))
|
|
39
|
+
# TODO: should this be a _Object._new_hydrated instead?
|
|
34
40
|
obj._hydrate(sandbox_snapshot_id, client, None)
|
|
35
41
|
|
|
36
42
|
return obj
|