Skip to content

Commit e0243ef

Browse files
committed
Cleaner
1 parent c5635cc commit e0243ef

6 files changed

Lines changed: 33 additions & 39 deletions

File tree

modal/_output.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ class OutputManager(Protocol):
7070
Using a protocol allows code to work with any output manager without checking for None.
7171
"""
7272

73+
@property
74+
def is_enabled(self) -> bool:
75+
"""Whether rich output is enabled."""
76+
...
77+
7378
@property
7479
def _stdout(self) -> Any:
7580
"""The stdout stream for PTY shell output."""
@@ -162,6 +167,10 @@ class DisabledOutputManager:
162167
without checking if the output manager exists.
163168
"""
164169

170+
@property
171+
def is_enabled(self) -> bool:
172+
return False
173+
165174
@property
166175
def _stdout(self) -> Any:
167176
return sys.stdout

modal/_rich_output.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ class RichOutputManager:
165165
_show_timestamps: bool
166166
_object_tree: Tree | None
167167

168+
@property
169+
def is_enabled(self) -> bool:
170+
return True
171+
168172
def __init__(
169173
self,
170174
*,

modal/image.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from .gpu import GPU_T, parse_gpu_config
5959
from .mount import _Mount, python_standalone_mount_name
6060
from .network_file_system import _NetworkFileSystem
61-
from .output import _get_output_manager, _is_output_enabled
61+
from .output import _get_output_manager
6262
from .secret import _Secret
6363
from .volume import _Volume
6464

@@ -685,7 +685,7 @@ async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, exi
685685
raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
686686
else:
687687
msg = f"Image build for {image_id} failed. See build logs for more details."
688-
if not _is_output_enabled():
688+
if not _get_output_manager().is_enabled:
689689
msg += " (Hint: Use `modal.enable_output()` to see logs from the process building the Image.)"
690690
raise RemoteError(msg)
691691
elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:

modal/output.py

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,15 @@
88

99
import contextlib
1010
from collections.abc import Generator
11-
from typing import TYPE_CHECKING, Union
12-
13-
if TYPE_CHECKING:
14-
from ._output import DisabledOutputManager
15-
from ._rich_output import RichOutputManager
1611

12+
from ._output import _DISABLED_OUTPUT_MANAGER, OutputManager
1713

1814
# Module-level state for output management
19-
_current_output_manager: "Union[RichOutputManager, None]" = None
15+
_current_output_manager: OutputManager = _DISABLED_OUTPUT_MANAGER
2016

2117

2218
@contextlib.contextmanager
23-
def enable_output(
24-
show_progress: bool = True, show_timestamps: bool = False
25-
) -> Generator["RichOutputManager | None", None, None]:
19+
def enable_output(show_progress: bool = True, show_timestamps: bool = False) -> Generator[OutputManager, None, None]:
2620
"""Context manager that enable output when using the Python SDK.
2721
2822
This will print to stdout and stderr things such as
@@ -39,6 +33,7 @@ def enable_output(
3933
```
4034
"""
4135
global _current_output_manager
36+
previous_output_manager = _current_output_manager
4237

4338
if show_progress:
4439
from ._rich_output import RichOutputManager
@@ -47,10 +42,10 @@ def enable_output(
4742
try:
4843
yield _current_output_manager
4944
finally:
50-
_current_output_manager = None
45+
_current_output_manager = previous_output_manager
5146

5247

53-
def _get_output_manager() -> "Union[RichOutputManager, DisabledOutputManager]":
48+
def _get_output_manager() -> OutputManager:
5449
"""Get the current output manager.
5550
5651
Returns a RichOutputManager when output is enabled, otherwise returns
@@ -59,22 +54,7 @@ def _get_output_manager() -> "Union[RichOutputManager, DisabledOutputManager]":
5954
This allows code to call output methods without checking if output is enabled,
6055
simplifying the calling code.
6156
"""
62-
if _current_output_manager is not None:
63-
return _current_output_manager
64-
65-
# Return the singleton disabled output manager
66-
from ._output import _DISABLED_OUTPUT_MANAGER
67-
68-
return _DISABLED_OUTPUT_MANAGER
69-
70-
71-
def _is_output_enabled() -> bool:
72-
"""Check if rich output is enabled.
73-
74-
This is useful for code that needs to conditionally perform operations
75-
based on whether output is truly enabled (e.g., starting a logs loop).
76-
"""
77-
return _current_output_manager is not None
57+
return _current_output_manager
7858

7959

8060
def _disable_output_manager() -> None:
@@ -84,4 +64,4 @@ def _disable_output_manager() -> None:
8464
to _get_output_manager() return a DisabledOutputManager.
8565
"""
8666
global _current_output_manager
87-
_current_output_manager = None
67+
_current_output_manager = _DISABLED_OUTPUT_MANAGER

modal/runner.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from .config import config, logger
3636
from .environments import _get_environment_cached
3737
from .exception import ConnectionError, InteractiveTimeoutError, InvalidError, RemoteError, _CliUserExecutionError
38-
from .output import _get_output_manager, _is_output_enabled, enable_output
38+
from .output import _get_output_manager, enable_output
3939
from .running_app import RunningApp, running_app_from_layout
4040
from .sandbox import _Sandbox
4141
from .secret import _Secret
@@ -283,8 +283,9 @@ async def _run_app(
283283
app.set_description(__main__.__name__)
284284

285285
app_state = api_pb2.APP_STATE_DETACHED if detach else api_pb2.APP_STATE_EPHEMERAL
286+
output_mgr = _get_output_manager()
286287

287-
if interactive and not _is_output_enabled():
288+
if interactive and not output_mgr.is_enabled:
288289
msg = "Interactive mode requires output to be enabled. (Use the the `modal.enable_output()` context manager.)"
289290
raise InvalidError(msg)
290291

@@ -311,8 +312,7 @@ def heartbeat():
311312
heartbeat_loop = tc.infinite_loop(heartbeat, sleep=HEARTBEAT_INTERVAL, log_exception=not detach)
312313
logs_loop: Optional[asyncio.Task] = None
313314

314-
output_mgr = _get_output_manager()
315-
if _is_output_enabled():
315+
if output_mgr.is_enabled:
316316
from modal._output import get_app_logs_loop
317317

318318
with output_mgr.make_live(output_mgr.step_progress("Initializing...")):
@@ -393,7 +393,7 @@ def heartbeat():
393393
except ConnectionError as e:
394394
# If we lose connection to the server after a detached App has started running, it will continue
395395
# I think we can only exit "nicely" if we are able to print output though, otherwise we should raise
396-
if detach and _is_output_enabled():
396+
if detach and output_mgr.is_enabled:
397397
output_mgr.print(":white_exclamation_mark: Connection lost!")
398398
output_mgr.print(detached_disconnect_msg)
399399
return
@@ -413,8 +413,9 @@ def heartbeat():
413413
logger.warning("Timed out waiting for final app logs.")
414414

415415
# Print completion message if output is still enabled (it may have been disabled during PTY mode)
416-
if _is_output_enabled():
417-
output_mgr = _get_output_manager()
416+
# Re-fetch the output manager in case it was disabled
417+
output_mgr = _get_output_manager()
418+
if output_mgr.is_enabled:
418419
output_mgr.print(
419420
output_mgr.step_completed(
420421
f"App completed. [grey70]View run at [underline]{running_app.app_page_url}[/underline][/grey70]"

modal/serving.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from .cli.import_refs import ImportRef, import_app_from_ref
1515
from .client import _Client
1616
from .config import config
17-
from .output import _get_output_manager, _is_output_enabled, enable_output
17+
from .output import _get_output_manager, enable_output
1818
from .runner import _run_app, serve_update
1919

2020
if TYPE_CHECKING:
@@ -35,7 +35,7 @@ async def _restart_serve(
3535
) -> SpawnProcess:
3636
ctx = multiprocessing.get_context("spawn") # Needed to reload the interpreter
3737
is_ready = ctx.Event()
38-
show_progress = _is_output_enabled()
38+
show_progress = _get_output_manager().is_enabled
3939
p = ctx.Process(target=_run_serve, args=(import_ref, existing_app_id, is_ready, environment_name, show_progress))
4040
p.start()
4141
await asyncify(is_ready.wait)(timeout)

0 commit comments

Comments
 (0)