modal 0.62.16__py3-none-any.whl → 0.72.11__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/__init__.py +17 -13
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +420 -937
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -59
- modal/_resources.py +51 -0
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1036 -0
- modal/_runtime/execution_context.py +89 -0
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +134 -9
- modal/_traceback.py +47 -187
- modal/_tunnel.py +52 -16
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +479 -100
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +460 -171
- modal/_utils/grpc_testing.py +47 -31
- modal/_utils/grpc_utils.py +62 -109
- modal/_utils/hash_utils.py +61 -19
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +5 -7
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +14 -12
- modal/app.py +1003 -314
- modal/app.pyi +540 -264
- modal/call_graph.py +7 -6
- modal/cli/_download.py +63 -53
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +205 -45
- modal/cli/config.py +12 -5
- modal/cli/container.py +62 -14
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +64 -58
- modal/cli/launch.py +32 -18
- modal/cli/network_file_system.py +64 -83
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +35 -10
- modal/cli/programs/vscode.py +60 -10
- modal/cli/queues.py +131 -0
- modal/cli/run.py +234 -131
- modal/cli/secret.py +8 -7
- modal/cli/token.py +7 -2
- modal/cli/utils.py +79 -10
- modal/cli/volume.py +110 -109
- modal/client.py +250 -144
- modal/client.pyi +157 -118
- modal/cloud_bucket_mount.py +108 -34
- modal/cloud_bucket_mount.pyi +32 -38
- modal/cls.py +535 -148
- modal/cls.pyi +190 -146
- modal/config.py +41 -19
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +111 -65
- modal/dict.pyi +136 -131
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +34 -43
- modal/experimental.py +61 -2
- modal/extensions/ipython.py +5 -5
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +197 -0
- modal/functions.py +906 -911
- modal/functions.pyi +466 -430
- modal/gpu.py +57 -44
- modal/image.py +1089 -479
- modal/image.pyi +584 -228
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +314 -101
- modal/mount.pyi +241 -235
- modal/network_file_system.py +92 -92
- modal/network_file_system.pyi +152 -110
- modal/object.py +67 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +434 -0
- modal/parallel_map.pyi +75 -0
- modal/partial_function.py +282 -117
- modal/partial_function.pyi +222 -129
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +182 -65
- modal/queue.pyi +218 -118
- modal/requirements/2024.04.txt +29 -0
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +48 -7
- modal/runner.py +459 -156
- modal/runner.pyi +135 -71
- modal/running_app.py +38 -0
- modal/sandbox.py +514 -236
- modal/sandbox.pyi +397 -169
- modal/schedule.py +4 -4
- modal/scheduler_placement.py +20 -3
- modal/secret.py +56 -31
- modal/secret.pyi +62 -42
- modal/serving.py +51 -56
- modal/serving.pyi +44 -36
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +285 -157
- modal/volume.pyi +249 -184
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
- modal-0.72.11.dist-info/RECORD +174 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +5 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1288 -533
- modal_proto/api_grpc.py +856 -456
- modal_proto/api_pb2.py +2165 -1157
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1674 -855
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_entrypoint.pyi +0 -378
- modal/_container_exec.py +0 -128
- modal/_sandbox_shell.py +0 -49
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal/stub.py +0 -783
- modal/stub.pyi +0 -332
- modal-0.62.16.dist-info/RECORD +0 -198
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -262
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -659
- test/client_test.py +0 -194
- test/cls_test.py +0 -630
- test/config_test.py +0 -137
- test/conftest.py +0 -1420
- test/container_app_test.py +0 -32
- test/container_test.py +0 -1389
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -33
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -653
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -141
- test/helpers.py +0 -42
- test/image_test.py +0 -669
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -329
- test/network_file_system_test.py +0 -181
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -97
- test/resolver_test.py +0 -58
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -29
- test/secret_test.py +0 -78
- test/serialization_test.py +0 -42
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -360
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -341
- test/watcher_test.py +0 -30
- test/webhook_test.py +0 -146
- /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
- /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/entry_points.txt +0 -0
modal/_vendor/a2wsgi_wsgi.py
CHANGED
@@ -35,10 +35,8 @@ from concurrent.futures import ThreadPoolExecutor
|
|
35
35
|
from types import TracebackType
|
36
36
|
from typing import (
|
37
37
|
Any,
|
38
|
-
Awaitable,
|
39
38
|
Callable,
|
40
39
|
Dict,
|
41
|
-
Iterable,
|
42
40
|
List,
|
43
41
|
Literal,
|
44
42
|
Optional,
|
@@ -48,6 +46,7 @@ from typing import (
|
|
48
46
|
TypedDict,
|
49
47
|
Union,
|
50
48
|
)
|
49
|
+
from collections.abc import Awaitable, Iterable
|
51
50
|
|
52
51
|
|
53
52
|
## BEGIN a2wsgi/asgi_typing.py
|
@@ -73,11 +72,11 @@ class HTTPScope(TypedDict):
|
|
73
72
|
raw_path: NotRequired[bytes]
|
74
73
|
query_string: bytes
|
75
74
|
root_path: str
|
76
|
-
headers: Iterable[
|
77
|
-
client: NotRequired[
|
78
|
-
server: NotRequired[
|
79
|
-
state: NotRequired[
|
80
|
-
extensions: NotRequired[
|
75
|
+
headers: Iterable[tuple[bytes, bytes]]
|
76
|
+
client: NotRequired[tuple[str, int]]
|
77
|
+
server: NotRequired[tuple[str, Optional[int]]]
|
78
|
+
state: NotRequired[dict[str, Any]]
|
79
|
+
extensions: NotRequired[dict[str, dict[object, object]]]
|
81
80
|
|
82
81
|
|
83
82
|
class WebSocketScope(TypedDict):
|
@@ -89,18 +88,18 @@ class WebSocketScope(TypedDict):
|
|
89
88
|
raw_path: bytes
|
90
89
|
query_string: bytes
|
91
90
|
root_path: str
|
92
|
-
headers: Iterable[
|
93
|
-
client: NotRequired[
|
94
|
-
server: NotRequired[
|
91
|
+
headers: Iterable[tuple[bytes, bytes]]
|
92
|
+
client: NotRequired[tuple[str, int]]
|
93
|
+
server: NotRequired[tuple[str, Optional[int]]]
|
95
94
|
subprotocols: Iterable[str]
|
96
|
-
state: NotRequired[
|
97
|
-
extensions: NotRequired[
|
95
|
+
state: NotRequired[dict[str, Any]]
|
96
|
+
extensions: NotRequired[dict[str, dict[object, object]]]
|
98
97
|
|
99
98
|
|
100
99
|
class LifespanScope(TypedDict):
|
101
100
|
type: Literal["lifespan"]
|
102
101
|
asgi: ASGIVersions
|
103
|
-
state: NotRequired[
|
102
|
+
state: NotRequired[dict[str, Any]]
|
104
103
|
|
105
104
|
|
106
105
|
WWWScope = Union[HTTPScope, WebSocketScope]
|
@@ -116,7 +115,7 @@ class HTTPRequestEvent(TypedDict):
|
|
116
115
|
class HTTPResponseStartEvent(TypedDict):
|
117
116
|
type: Literal["http.response.start"]
|
118
117
|
status: int
|
119
|
-
headers: NotRequired[Iterable[
|
118
|
+
headers: NotRequired[Iterable[tuple[bytes, bytes]]]
|
120
119
|
trailers: NotRequired[bool]
|
121
120
|
|
122
121
|
|
@@ -137,7 +136,7 @@ class WebSocketConnectEvent(TypedDict):
|
|
137
136
|
class WebSocketAcceptEvent(TypedDict):
|
138
137
|
type: Literal["websocket.accept"]
|
139
138
|
subprotocol: NotRequired[str]
|
140
|
-
headers: NotRequired[Iterable[
|
139
|
+
headers: NotRequired[Iterable[tuple[bytes, bytes]]]
|
141
140
|
|
142
141
|
|
143
142
|
class WebSocketReceiveEvent(TypedDict):
|
@@ -223,56 +222,47 @@ ASGIApp = Callable[[Scope, Receive, Send], Awaitable[None]]
|
|
223
222
|
|
224
223
|
## BEGIN a2wsgi/wsgi_typing.py
|
225
224
|
|
226
|
-
CGIRequiredDefined
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
# The contents of any Content-Type fields in the HTTP request. May be empty
|
268
|
-
# or absent.
|
269
|
-
"CONTENT_TYPE": str,
|
270
|
-
# The contents of any Content-Length fields in the HTTP request. May be empty
|
271
|
-
# or absent.
|
272
|
-
"CONTENT_LENGTH": str,
|
273
|
-
},
|
274
|
-
total=False,
|
275
|
-
)
|
225
|
+
class CGIRequiredDefined(TypedDict):
|
226
|
+
# The HTTP request method, such as GET or POST. This cannot ever be an
|
227
|
+
# empty string, and so is always required.
|
228
|
+
REQUEST_METHOD: str
|
229
|
+
# When HTTP_HOST is not set, these variables can be combined to determine
|
230
|
+
# a default.
|
231
|
+
# SERVER_NAME and SERVER_PORT are required strings and must never be empty.
|
232
|
+
SERVER_NAME: str
|
233
|
+
SERVER_PORT: str
|
234
|
+
# The version of the protocol the client used to send the request.
|
235
|
+
# Typically this will be something like "HTTP/1.0" or "HTTP/1.1" and
|
236
|
+
# may be used by the application to determine how to treat any HTTP
|
237
|
+
# request headers. (This variable should probably be called REQUEST_PROTOCOL,
|
238
|
+
# since it denotes the protocol used in the request, and is not necessarily
|
239
|
+
# the protocol that will be used in the server's response. However, for
|
240
|
+
# compatibility with CGI we have to keep the existing name.)
|
241
|
+
SERVER_PROTOCOL: str
|
242
|
+
|
243
|
+
class CGIOptionalDefined(TypedDict, total=False):
|
244
|
+
REQUEST_URI: str
|
245
|
+
REMOTE_ADDR: str
|
246
|
+
REMOTE_PORT: str
|
247
|
+
# The initial portion of the request URL’s “path” that corresponds to the
|
248
|
+
# application object, so that the application knows its virtual “location”.
|
249
|
+
# This may be an empty string, if the application corresponds to the “root”
|
250
|
+
# of the server.
|
251
|
+
SCRIPT_NAME: str
|
252
|
+
# The remainder of the request URL’s “path”, designating the virtual
|
253
|
+
# “location” of the request’s target within the application. This may be an
|
254
|
+
# empty string, if the request URL targets the application root and does
|
255
|
+
# not have a trailing slash.
|
256
|
+
PATH_INFO: str
|
257
|
+
# The portion of the request URL that follows the “?”, if any. May be empty
|
258
|
+
# or absent.
|
259
|
+
QUERY_STRING: str
|
260
|
+
# The contents of any Content-Type fields in the HTTP request. May be empty
|
261
|
+
# or absent.
|
262
|
+
CONTENT_TYPE: str
|
263
|
+
# The contents of any Content-Length fields in the HTTP request. May be empty
|
264
|
+
# or absent.
|
265
|
+
CONTENT_LENGTH: str
|
276
266
|
|
277
267
|
|
278
268
|
class InputStream(Protocol):
|
@@ -308,7 +298,7 @@ class InputStream(Protocol):
|
|
308
298
|
"""
|
309
299
|
raise NotImplementedError
|
310
300
|
|
311
|
-
def readlines(self, hint: int = -1, /) ->
|
301
|
+
def readlines(self, hint: int = -1, /) -> list[bytes]:
|
312
302
|
"""
|
313
303
|
Note that the hint argument to readlines() is optional for both caller and
|
314
304
|
implementer. The application is free not to supply it, and the server or gateway
|
@@ -349,14 +339,14 @@ class ErrorStream(Protocol):
|
|
349
339
|
def write(self, s: str, /) -> Any:
|
350
340
|
raise NotImplementedError
|
351
341
|
|
352
|
-
def writelines(self, seq:
|
342
|
+
def writelines(self, seq: list[str], /) -> Any:
|
353
343
|
raise NotImplementedError
|
354
344
|
|
355
345
|
|
356
346
|
WSGIDefined = TypedDict(
|
357
347
|
"WSGIDefined",
|
358
348
|
{
|
359
|
-
"wsgi.version":
|
349
|
+
"wsgi.version": tuple[int, int], # e.g. (1, 0)
|
360
350
|
"wsgi.url_scheme": str, # e.g. "http" or "https"
|
361
351
|
"wsgi.input": InputStream,
|
362
352
|
"wsgi.errors": ErrorStream,
|
@@ -381,7 +371,7 @@ class Environ(CGIRequiredDefined, CGIOptionalDefined, WSGIDefined):
|
|
381
371
|
"""
|
382
372
|
|
383
373
|
|
384
|
-
ExceptionInfo =
|
374
|
+
ExceptionInfo = tuple[type[BaseException], BaseException, Optional[TracebackType]]
|
385
375
|
|
386
376
|
# https://peps.python.org/pep-3333/#the-write-callable
|
387
377
|
WriteCallable = Callable[[bytes], None]
|
@@ -391,7 +381,7 @@ class StartResponse(Protocol):
|
|
391
381
|
def __call__(
|
392
382
|
self,
|
393
383
|
status: str,
|
394
|
-
response_headers:
|
384
|
+
response_headers: list[tuple[str, str]],
|
395
385
|
exc_info: Optional[ExceptionInfo] = None,
|
396
386
|
/,
|
397
387
|
) -> WriteCallable:
|
@@ -460,7 +450,7 @@ class Body:
|
|
460
450
|
self.buffer.clear()
|
461
451
|
return result
|
462
452
|
|
463
|
-
def readlines(self, hint: int = -1) ->
|
453
|
+
def readlines(self, hint: int = -1) -> list[bytes]:
|
464
454
|
if not self.has_more:
|
465
455
|
return []
|
466
456
|
if hint == -1:
|
@@ -626,7 +616,7 @@ class WSGIResponder:
|
|
626
616
|
def start_response(
|
627
617
|
self,
|
628
618
|
status: str,
|
629
|
-
response_headers:
|
619
|
+
response_headers: list[tuple[str, str]],
|
630
620
|
exc_info: typing.Optional[ExceptionInfo] = None,
|
631
621
|
) -> WriteCallable:
|
632
622
|
self.exc_info = exc_info
|
modal/_vendor/cloudpickle.py
CHANGED
@@ -256,7 +256,7 @@ def _should_pickle_by_reference(obj, name=None):
|
|
256
256
|
return False
|
257
257
|
return obj.__name__ in sys.modules
|
258
258
|
else:
|
259
|
-
raise TypeError("cannot check importability of {
|
259
|
+
raise TypeError(f"cannot check importability of {type(obj).__name__} instances")
|
260
260
|
|
261
261
|
|
262
262
|
def _lookup_module_and_qualname(obj, name=None):
|
modal/_watcher.py
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
from collections import defaultdict
|
3
|
+
from collections.abc import AsyncGenerator
|
3
4
|
from pathlib import Path
|
4
|
-
from typing import
|
5
|
+
from typing import Optional
|
5
6
|
|
6
7
|
from rich.tree import Tree
|
7
8
|
from watchfiles import Change, DefaultFilter, awatch
|
8
9
|
|
9
10
|
from modal.mount import _Mount
|
10
11
|
|
11
|
-
from .
|
12
|
+
from .output import _get_output_manager
|
12
13
|
|
13
14
|
_TIMEOUT_SENTINEL = object()
|
14
15
|
|
15
16
|
|
16
|
-
class
|
17
|
+
class AppFilesFilter(DefaultFilter):
|
17
18
|
def __init__(
|
18
19
|
self,
|
19
20
|
*,
|
@@ -21,7 +22,7 @@ class StubFilesFilter(DefaultFilter):
|
|
21
22
|
# Watching specific files is discouraged on Linux, so to watch a file we watch its
|
22
23
|
# containing directory and then filter that directory's changes for relevant files.
|
23
24
|
# https://github.com/notify-rs/notify/issues/394
|
24
|
-
dir_filters:
|
25
|
+
dir_filters: dict[Path, Optional[set[Path]]],
|
25
26
|
) -> None:
|
26
27
|
self.dir_filters = dir_filters
|
27
28
|
super().__init__()
|
@@ -54,7 +55,7 @@ class StubFilesFilter(DefaultFilter):
|
|
54
55
|
return super().__call__(change, path)
|
55
56
|
|
56
57
|
|
57
|
-
async def _watch_paths(paths:
|
58
|
+
async def _watch_paths(paths: set[Path], watch_filter: AppFilesFilter) -> AsyncGenerator[set[str], None]:
|
58
59
|
try:
|
59
60
|
async for changes in awatch(*paths, step=500, watch_filter=watch_filter):
|
60
61
|
changed_paths = {stringpath for _, stringpath in changes}
|
@@ -64,7 +65,7 @@ async def _watch_paths(paths: Set[Path], watch_filter: StubFilesFilter) -> Async
|
|
64
65
|
pass
|
65
66
|
|
66
67
|
|
67
|
-
def _print_watched_paths(paths:
|
68
|
+
def _print_watched_paths(paths: set[Path]):
|
68
69
|
msg = "️️⚡️ Serving... hit Ctrl-C to stop!"
|
69
70
|
|
70
71
|
output_tree = Tree(msg, guide_style="gray50")
|
@@ -72,12 +73,13 @@ def _print_watched_paths(paths: Set[Path], output_mgr: OutputManager):
|
|
72
73
|
for path in paths:
|
73
74
|
output_tree.add(f"Watching {path}.")
|
74
75
|
|
75
|
-
output_mgr
|
76
|
+
if output_mgr := _get_output_manager():
|
77
|
+
output_mgr.print(output_tree)
|
76
78
|
|
77
79
|
|
78
|
-
def _watch_args_from_mounts(mounts:
|
80
|
+
def _watch_args_from_mounts(mounts: list[_Mount]) -> tuple[set[Path], AppFilesFilter]:
|
79
81
|
paths = set()
|
80
|
-
dir_filters:
|
82
|
+
dir_filters: dict[Path, Optional[set[Path]]] = defaultdict(set)
|
81
83
|
for mount in mounts:
|
82
84
|
# TODO(elias): Make this part of the mount class instead, since it uses so much internals
|
83
85
|
for entry in mount._entries:
|
@@ -89,14 +91,14 @@ def _watch_args_from_mounts(mounts: List[_Mount]) -> Tuple[Set[Path], StubFilesF
|
|
89
91
|
elif dir_filters[path] is not None:
|
90
92
|
dir_filters[path].add(filter_file.absolute().resolve())
|
91
93
|
|
92
|
-
watch_filter =
|
94
|
+
watch_filter = AppFilesFilter(dir_filters=dict(dir_filters))
|
93
95
|
return paths, watch_filter
|
94
96
|
|
95
97
|
|
96
|
-
async def watch(mounts:
|
98
|
+
async def watch(mounts: list[_Mount]) -> AsyncGenerator[set[str], None]:
|
97
99
|
paths, watch_filter = _watch_args_from_mounts(mounts)
|
98
100
|
|
99
|
-
_print_watched_paths(paths
|
101
|
+
_print_watched_paths(paths)
|
100
102
|
|
101
103
|
async for updated_paths in _watch_paths(paths, watch_filter):
|
102
104
|
yield updated_paths
|