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.
Files changed (220) hide show
  1. modal/__init__.py +17 -13
  2. modal/__main__.py +41 -3
  3. modal/_clustered_functions.py +80 -0
  4. modal/_clustered_functions.pyi +22 -0
  5. modal/_container_entrypoint.py +420 -937
  6. modal/_ipython.py +3 -13
  7. modal/_location.py +17 -10
  8. modal/_output.py +243 -99
  9. modal/_pty.py +2 -2
  10. modal/_resolver.py +55 -59
  11. modal/_resources.py +51 -0
  12. modal/_runtime/__init__.py +1 -0
  13. modal/_runtime/asgi.py +519 -0
  14. modal/_runtime/container_io_manager.py +1036 -0
  15. modal/_runtime/execution_context.py +89 -0
  16. modal/_runtime/telemetry.py +169 -0
  17. modal/_runtime/user_code_imports.py +356 -0
  18. modal/_serialization.py +134 -9
  19. modal/_traceback.py +47 -187
  20. modal/_tunnel.py +52 -16
  21. modal/_tunnel.pyi +19 -36
  22. modal/_utils/app_utils.py +3 -17
  23. modal/_utils/async_utils.py +479 -100
  24. modal/_utils/blob_utils.py +157 -186
  25. modal/_utils/bytes_io_segment_payload.py +97 -0
  26. modal/_utils/deprecation.py +89 -0
  27. modal/_utils/docker_utils.py +98 -0
  28. modal/_utils/function_utils.py +460 -171
  29. modal/_utils/grpc_testing.py +47 -31
  30. modal/_utils/grpc_utils.py +62 -109
  31. modal/_utils/hash_utils.py +61 -19
  32. modal/_utils/http_utils.py +39 -9
  33. modal/_utils/logger.py +2 -1
  34. modal/_utils/mount_utils.py +34 -16
  35. modal/_utils/name_utils.py +58 -0
  36. modal/_utils/package_utils.py +14 -1
  37. modal/_utils/pattern_utils.py +205 -0
  38. modal/_utils/rand_pb_testing.py +5 -7
  39. modal/_utils/shell_utils.py +15 -49
  40. modal/_vendor/a2wsgi_wsgi.py +62 -72
  41. modal/_vendor/cloudpickle.py +1 -1
  42. modal/_watcher.py +14 -12
  43. modal/app.py +1003 -314
  44. modal/app.pyi +540 -264
  45. modal/call_graph.py +7 -6
  46. modal/cli/_download.py +63 -53
  47. modal/cli/_traceback.py +200 -0
  48. modal/cli/app.py +205 -45
  49. modal/cli/config.py +12 -5
  50. modal/cli/container.py +62 -14
  51. modal/cli/dict.py +128 -0
  52. modal/cli/entry_point.py +26 -13
  53. modal/cli/environment.py +40 -9
  54. modal/cli/import_refs.py +64 -58
  55. modal/cli/launch.py +32 -18
  56. modal/cli/network_file_system.py +64 -83
  57. modal/cli/profile.py +1 -1
  58. modal/cli/programs/run_jupyter.py +35 -10
  59. modal/cli/programs/vscode.py +60 -10
  60. modal/cli/queues.py +131 -0
  61. modal/cli/run.py +234 -131
  62. modal/cli/secret.py +8 -7
  63. modal/cli/token.py +7 -2
  64. modal/cli/utils.py +79 -10
  65. modal/cli/volume.py +110 -109
  66. modal/client.py +250 -144
  67. modal/client.pyi +157 -118
  68. modal/cloud_bucket_mount.py +108 -34
  69. modal/cloud_bucket_mount.pyi +32 -38
  70. modal/cls.py +535 -148
  71. modal/cls.pyi +190 -146
  72. modal/config.py +41 -19
  73. modal/container_process.py +177 -0
  74. modal/container_process.pyi +82 -0
  75. modal/dict.py +111 -65
  76. modal/dict.pyi +136 -131
  77. modal/environments.py +106 -5
  78. modal/environments.pyi +77 -25
  79. modal/exception.py +34 -43
  80. modal/experimental.py +61 -2
  81. modal/extensions/ipython.py +5 -5
  82. modal/file_io.py +537 -0
  83. modal/file_io.pyi +235 -0
  84. modal/file_pattern_matcher.py +197 -0
  85. modal/functions.py +906 -911
  86. modal/functions.pyi +466 -430
  87. modal/gpu.py +57 -44
  88. modal/image.py +1089 -479
  89. modal/image.pyi +584 -228
  90. modal/io_streams.py +434 -0
  91. modal/io_streams.pyi +122 -0
  92. modal/mount.py +314 -101
  93. modal/mount.pyi +241 -235
  94. modal/network_file_system.py +92 -92
  95. modal/network_file_system.pyi +152 -110
  96. modal/object.py +67 -36
  97. modal/object.pyi +166 -143
  98. modal/output.py +63 -0
  99. modal/parallel_map.py +434 -0
  100. modal/parallel_map.pyi +75 -0
  101. modal/partial_function.py +282 -117
  102. modal/partial_function.pyi +222 -129
  103. modal/proxy.py +15 -12
  104. modal/proxy.pyi +3 -8
  105. modal/queue.py +182 -65
  106. modal/queue.pyi +218 -118
  107. modal/requirements/2024.04.txt +29 -0
  108. modal/requirements/2024.10.txt +16 -0
  109. modal/requirements/README.md +21 -0
  110. modal/requirements/base-images.json +22 -0
  111. modal/retries.py +48 -7
  112. modal/runner.py +459 -156
  113. modal/runner.pyi +135 -71
  114. modal/running_app.py +38 -0
  115. modal/sandbox.py +514 -236
  116. modal/sandbox.pyi +397 -169
  117. modal/schedule.py +4 -4
  118. modal/scheduler_placement.py +20 -3
  119. modal/secret.py +56 -31
  120. modal/secret.pyi +62 -42
  121. modal/serving.py +51 -56
  122. modal/serving.pyi +44 -36
  123. modal/stream_type.py +15 -0
  124. modal/token_flow.py +5 -3
  125. modal/token_flow.pyi +37 -32
  126. modal/volume.py +285 -157
  127. modal/volume.pyi +249 -184
  128. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
  129. modal-0.72.11.dist-info/RECORD +174 -0
  130. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
  131. modal_docs/gen_reference_docs.py +3 -1
  132. modal_docs/mdmd/mdmd.py +0 -1
  133. modal_docs/mdmd/signatures.py +5 -2
  134. modal_global_objects/images/base_images.py +28 -0
  135. modal_global_objects/mounts/python_standalone.py +2 -2
  136. modal_proto/__init__.py +1 -1
  137. modal_proto/api.proto +1288 -533
  138. modal_proto/api_grpc.py +856 -456
  139. modal_proto/api_pb2.py +2165 -1157
  140. modal_proto/api_pb2.pyi +8859 -0
  141. modal_proto/api_pb2_grpc.py +1674 -855
  142. modal_proto/api_pb2_grpc.pyi +1416 -0
  143. modal_proto/modal_api_grpc.py +149 -0
  144. modal_proto/modal_options_grpc.py +3 -0
  145. modal_proto/options_pb2.pyi +20 -0
  146. modal_proto/options_pb2_grpc.pyi +7 -0
  147. modal_proto/py.typed +0 -0
  148. modal_version/__init__.py +1 -1
  149. modal_version/_version_generated.py +2 -2
  150. modal/_asgi.py +0 -370
  151. modal/_container_entrypoint.pyi +0 -378
  152. modal/_container_exec.py +0 -128
  153. modal/_sandbox_shell.py +0 -49
  154. modal/shared_volume.py +0 -23
  155. modal/shared_volume.pyi +0 -24
  156. modal/stub.py +0 -783
  157. modal/stub.pyi +0 -332
  158. modal-0.62.16.dist-info/RECORD +0 -198
  159. modal_global_objects/images/conda.py +0 -15
  160. modal_global_objects/images/debian_slim.py +0 -15
  161. modal_global_objects/images/micromamba.py +0 -15
  162. test/__init__.py +0 -1
  163. test/aio_test.py +0 -12
  164. test/async_utils_test.py +0 -262
  165. test/blob_test.py +0 -67
  166. test/cli_imports_test.py +0 -149
  167. test/cli_test.py +0 -659
  168. test/client_test.py +0 -194
  169. test/cls_test.py +0 -630
  170. test/config_test.py +0 -137
  171. test/conftest.py +0 -1420
  172. test/container_app_test.py +0 -32
  173. test/container_test.py +0 -1389
  174. test/cpu_test.py +0 -23
  175. test/decorator_test.py +0 -85
  176. test/deprecation_test.py +0 -34
  177. test/dict_test.py +0 -33
  178. test/e2e_test.py +0 -68
  179. test/error_test.py +0 -7
  180. test/function_serialization_test.py +0 -32
  181. test/function_test.py +0 -653
  182. test/function_utils_test.py +0 -101
  183. test/gpu_test.py +0 -159
  184. test/grpc_utils_test.py +0 -141
  185. test/helpers.py +0 -42
  186. test/image_test.py +0 -669
  187. test/live_reload_test.py +0 -80
  188. test/lookup_test.py +0 -70
  189. test/mdmd_test.py +0 -329
  190. test/mount_test.py +0 -162
  191. test/mounted_files_test.py +0 -329
  192. test/network_file_system_test.py +0 -181
  193. test/notebook_test.py +0 -66
  194. test/object_test.py +0 -41
  195. test/package_utils_test.py +0 -25
  196. test/queue_test.py +0 -97
  197. test/resolver_test.py +0 -58
  198. test/retries_test.py +0 -67
  199. test/runner_test.py +0 -85
  200. test/sandbox_test.py +0 -191
  201. test/schedule_test.py +0 -15
  202. test/scheduler_placement_test.py +0 -29
  203. test/secret_test.py +0 -78
  204. test/serialization_test.py +0 -42
  205. test/stub_composition_test.py +0 -10
  206. test/stub_test.py +0 -360
  207. test/test_asgi_wrapper.py +0 -234
  208. test/token_flow_test.py +0 -18
  209. test/traceback_test.py +0 -135
  210. test/tunnel_test.py +0 -29
  211. test/utils_test.py +0 -88
  212. test/version_test.py +0 -14
  213. test/volume_test.py +0 -341
  214. test/watcher_test.py +0 -30
  215. test/webhook_test.py +0 -146
  216. /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
  217. /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
  218. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
  219. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
  220. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/entry_points.txt +0 -0
@@ -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[Tuple[bytes, bytes]]
77
- client: NotRequired[Tuple[str, int]]
78
- server: NotRequired[Tuple[str, Optional[int]]]
79
- state: NotRequired[Dict[str, Any]]
80
- extensions: NotRequired[Dict[str, Dict[object, object]]]
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[Tuple[bytes, bytes]]
93
- client: NotRequired[Tuple[str, int]]
94
- server: NotRequired[Tuple[str, Optional[int]]]
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[Dict[str, Any]]
97
- extensions: NotRequired[Dict[str, Dict[object, object]]]
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[Dict[str, Any]]
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[Tuple[bytes, bytes]]]
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[Tuple[bytes, bytes]]]
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 = TypedDict(
227
- "CGIRequiredDefined",
228
- {
229
- # The HTTP request method, such as GET or POST. This cannot ever be an
230
- # empty string, and so is always required.
231
- "REQUEST_METHOD": str,
232
- # When HTTP_HOST is not set, these variables can be combined to determine
233
- # a default.
234
- # SERVER_NAME and SERVER_PORT are required strings and must never be empty.
235
- "SERVER_NAME": str,
236
- "SERVER_PORT": str,
237
- # The version of the protocol the client used to send the request.
238
- # Typically this will be something like "HTTP/1.0" or "HTTP/1.1" and
239
- # may be used by the application to determine how to treat any HTTP
240
- # request headers. (This variable should probably be called REQUEST_PROTOCOL,
241
- # since it denotes the protocol used in the request, and is not necessarily
242
- # the protocol that will be used in the server's response. However, for
243
- # compatibility with CGI we have to keep the existing name.)
244
- "SERVER_PROTOCOL": str,
245
- },
246
- )
247
-
248
- CGIOptionalDefined = TypedDict(
249
- "CGIOptionalDefined",
250
- {
251
- "REQUEST_URI": str,
252
- "REMOTE_ADDR": str,
253
- "REMOTE_PORT": str,
254
- # The initial portion of the request URL’s “path” that corresponds to the
255
- # application object, so that the application knows its virtual “location”.
256
- # This may be an empty string, if the application corresponds to the “root”
257
- # of the server.
258
- "SCRIPT_NAME": str,
259
- # The remainder of the request URL’s “path”, designating the virtual
260
- # “location” of the request’s target within the application. This may be an
261
- # empty string, if the request URL targets the application root and does
262
- # not have a trailing slash.
263
- "PATH_INFO": str,
264
- # The portion of the request URL that follows the “?”, if any. May be empty
265
- # or absent.
266
- "QUERY_STRING": str,
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, /) -> List[bytes]:
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: List[str], /) -> Any:
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": Tuple[int, int], # e.g. (1, 0)
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 = Tuple[Type[BaseException], BaseException, Optional[TracebackType]]
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: List[Tuple[str, str]],
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) -> typing.List[bytes]:
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: typing.List[typing.Tuple[str, str]],
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
@@ -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 {} instances".format(type(obj).__name__))
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 AsyncGenerator, Dict, List, Optional, Set, Tuple
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 ._output import OutputManager
12
+ from .output import _get_output_manager
12
13
 
13
14
  _TIMEOUT_SENTINEL = object()
14
15
 
15
16
 
16
- class StubFilesFilter(DefaultFilter):
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: Dict[Path, Optional[Set[Path]]],
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: Set[Path], watch_filter: StubFilesFilter) -> AsyncGenerator[Set[str], None]:
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: Set[Path], output_mgr: OutputManager):
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.print_if_visible(output_tree)
76
+ if output_mgr := _get_output_manager():
77
+ output_mgr.print(output_tree)
76
78
 
77
79
 
78
- def _watch_args_from_mounts(mounts: List[_Mount]) -> Tuple[Set[Path], StubFilesFilter]:
80
+ def _watch_args_from_mounts(mounts: list[_Mount]) -> tuple[set[Path], AppFilesFilter]:
79
81
  paths = set()
80
- dir_filters: Dict[Path, Optional[Set[Path]]] = defaultdict(set)
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 = StubFilesFilter(dir_filters=dict(dir_filters))
94
+ watch_filter = AppFilesFilter(dir_filters=dict(dir_filters))
93
95
  return paths, watch_filter
94
96
 
95
97
 
96
- async def watch(mounts: List[_Mount], output_mgr: OutputManager) -> AsyncGenerator[Set[str], None]:
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, output_mgr)
101
+ _print_watched_paths(paths)
100
102
 
101
103
  async for updated_paths in _watch_paths(paths, watch_filter):
102
104
  yield updated_paths