Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions examples/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Remote Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
}
]
}
20 changes: 20 additions & 0 deletions launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Remote Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
}
]
}
55 changes: 33 additions & 22 deletions marimo/_ast/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
else:
from typing import TypeAlias

# TODO: Hack for POC
lookup = {}

LOGGER = _loggers.marimo_logger()
Cls: TypeAlias = type

Expand Down Expand Up @@ -213,12 +216,19 @@ def compile_cell(
expr.end_col_offset = final_expr.end_col_offset # type: ignore[attr-defined]

filename: str
print(source_position)
if source_position:
print("source_position details")
# Modify the "source" position for meaningful stacktraces
fix_source_position(module, source_position)
fix_source_position(expr, source_position)
filename = source_position.filename
lookup[cell_id] = source_position
else:
print("caching cell code")
raise NotImplementedError(
"Source position is required for non-anonymous files."
)
# store the cell's code in Python's linecache so debuggers can find it
filename = get_filename(cell_id)
# cache the entire cell's code, doesn't need to be done in source case
Expand Down Expand Up @@ -296,24 +306,24 @@ def get_source_position(
f: Cls | Callable[..., Any], lineno: int, col_offset: int
) -> Optional[SourcePosition]:
# Fallback won't capture embedded scripts
if inspect.isclass(f):
is_script = f.__module__ == "__main__"
# Could be something wrapped in a decorator, like
# functools._lru_cache_wrapper.
elif hasattr(f, "__wrapped__"):
return get_source_position(f.__wrapped__, lineno, col_offset)
# Larger catch all than if inspect.isfunction(f):
elif hasattr(f, "__globals__") and hasattr(f, "__name__"):
is_script = f.__globals__["__name__"] == "__main__" # type: ignore
else:
return None
# TODO: spec is None for markdown notebooks, which is fine for now
if module := inspect.getmodule(f):
spec = module.__spec__
is_script = spec is None or spec.name != "marimo_app"

if not is_script:
return None
# if inspect.isclass(f):
# is_script = f.__module__ == "__main__"
# # Could be something wrapped in a decorator, like
# # functools._lru_cache_wrapper.
# elif hasattr(f, "__wrapped__"):
# return get_source_position(f.__wrapped__, lineno, col_offset)
# # Larger catch all than if inspect.isfunction(f):
# elif hasattr(f, "__globals__") and hasattr(f, "__name__"):
# is_script = f.__globals__["__name__"] == "__main__" # type: ignore
# else:
# return None
# # TODO: spec is None for markdown notebooks, which is fine for now
# if module := inspect.getmodule(f):
# spec = module.__spec__
# is_script = spec is None or spec.name != "marimo_app"

# if not is_script:
# return None

return SourcePosition(
filename=inspect.getfile(f),
Expand Down Expand Up @@ -461,10 +471,11 @@ def cell_factory(

# anonymous file is required for deterministic testing.
source_position = None
if not anonymous_file:
source_position = get_source_position(
f, lnum + cell_def.lineno - 1, cell_def.col_offset
)
print("anonymous_file", anonymous_file)
# if not anonymous_file:
source_position = get_source_position(
f, lnum + cell_def.lineno - 1, cell_def.col_offset
)

cell = compile_cell(
cell_def.code,
Expand Down
26 changes: 13 additions & 13 deletions marimo/_ast/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,16 @@ def load_app(filename: Optional[str]) -> Optional[App]:
elif not path.suffix == ".py":
raise MarimoFileError("File must end with .py or .md")

try:
return _static_load(path)
except MarimoFileError:
# Security advantages of static load are lost here, but reasonable
# fallback for now.
_app = _dynamic_load(filename)
LOGGER.warning(
"Static loading of notebook failed; "
"falling back to dynamic loading. "
"If you can, please report this issue to the marimo team β€”Β "
"https://github.com/marimo-team/marimo/issues/new?template=bug_report.yaml"
)
return _app
# try:
# return _static_load(path)
# except MarimoFileError:
# Security advantages of static load are lost here, but reasonable
# fallback for now.
_app = _dynamic_load(filename)
LOGGER.warning(
"Static loading of notebook failed; "
"falling back to dynamic loading. "
"If you can, please report this issue to the marimo team β€”Β "
"https://github.com/marimo-team/marimo/issues/new?template=bug_report.yaml"
)
return _app
7 changes: 6 additions & 1 deletion marimo/_runtime/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,13 @@ def _try_compiling_cell(
) -> tuple[Optional[CellImpl], Optional[Error]]:
error: Optional[Error] = None
try:
from marimo._ast.compiler import lookup

cell = compile_cell(
code, cell_id=cell_id, carried_imports=carried_imports
code,
cell_id=cell_id,
carried_imports=carried_imports,
source_position=lookup.get(cell_id, None),
)
except Exception as e:
cell = None
Expand Down
42 changes: 42 additions & 0 deletions marimo/_server/api/lifespans.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,48 @@ async def mcp(app: Starlette) -> AsyncIterator[None]:
LOGGER.error(f"Error during MCP cleanup: {e}")


@contextlib.asynccontextmanager
async def debug_server(app: Starlette) -> AsyncIterator[None]:
state = AppState.from_app(app)
session_mgr = state.session_manager

dap_server = None # Track DAP server for cleanup

# Only start the debug server in Edit mode
if session_mgr.mode == SessionMode.EDIT:
try:
from marimo._server.debug.dap_server import get_dap_server
from marimo._server.utils import find_free_port

dap_server = get_dap_server(session_mgr)
LOGGER.info("Starting DAP debug server")

# Find a free port for debug server
debug_port = find_free_port(5678, addr="localhost")

actual_port = await dap_server.start(
host="localhost", port=debug_port
)
LOGGER.info(f"DAP debug server started on localhost:{actual_port}")

# Store the port in app state for middleware
app.state.debug_port = actual_port

except Exception as e:
LOGGER.warning(f"Failed to start DAP debug server: {e}")

yield

# Clean up DAP server on shutdown
if dap_server:
try:
LOGGER.info("Stopping DAP debug server")
await dap_server.stop()
LOGGER.info("DAP debug server stopped")
except Exception as e:
LOGGER.error(f"Error during DAP server cleanup: {e}")


@contextlib.asynccontextmanager
async def open_browser(app: Starlette) -> AsyncIterator[None]:
state = AppState.from_app(app)
Expand Down
Loading
Loading