Skip to content
Merged
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
50 changes: 50 additions & 0 deletions docs/guides/deploying/programmatically.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,53 @@ for filename in sorted(notebooks_dir.iterdir()):
server = server.with_app(path=f"/{app_name}", root=filename)
app_names.append(app_name)
```

## Accessing Request Data

Inside your marimo notebooks, you can access the current request data using `mo.app_meta().request`. This is particularly useful when implementing authentication or accessing user data.

```python
import marimo as mo

# Access request data in your notebook
request = mo.app_meta().request
if request and request.user and request.user["is_authenticated"]:
content = f"Welcome {request.user['username']}!"
else:
content = "Please log in"

mo.md(content)
```

### Authentication Middleware Example

Here's an example of how to implement authentication middleware that populates `request.user`:

```python
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request

class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Add user data to the request scope
# This will be accessible via mo.app_meta().request.user
request.scope["user"] = {
"is_authenticated": True,
"username": "example_user",
# Add any other user data
}
response = await call_next(request)
return response

# Add the middleware to your FastAPI app
app.add_middleware(AuthMiddleware)
```

The `request` object provides access to:

- `request.headers`: Request headers
- `request.cookies`: Request cookies
- `request.query_params`: Query parameters
- `request.path_params`: Path parameters
- `request.user`: User data added by authentication middleware
- `request.url`: URL information including path, query parameters
4 changes: 2 additions & 2 deletions marimo/_cli/development/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ def _generate_schema() -> dict[str, Any]:
models.SuccessResponse,
models.UpdateComponentValuesRequest,
requests.CodeCompletionRequest,
requests.CreationRequest,
requests.DeleteCellRequest,
requests.ExecuteMultipleRequest,
requests.ExecuteScratchpadRequest,
Expand Down Expand Up @@ -192,7 +191,8 @@ def _generate_schema() -> dict[str, Any]:
{"type": "boolean"},
{"type": "null"},
]
}
},
"HTTPRequest": {"type": "null"},
}
# We must override the names of some Union Types,
# otherwise, their __name__ is "Union"
Expand Down
22 changes: 22 additions & 0 deletions marimo/_messaging/context.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# Copyright 2024 Marimo. All rights reserved.
from __future__ import annotations

import uuid
from contextvars import ContextVar
from dataclasses import dataclass
from typing import Any, Optional

from marimo._runtime.requests import HTTPRequest

RunId_t = str
RUN_ID_CTX = ContextVar[Optional[RunId_t]]("run_id")

HTTP_REQUEST_CTX = ContextVar[Optional[HTTPRequest]]("http_request")


@dataclass
class run_id_context:
Expand All @@ -22,3 +28,19 @@ def __enter__(self) -> None:

def __exit__(self, *_: Any) -> None:
RUN_ID_CTX.reset(self.token)


@dataclass
class http_request_context:
"""Context manager for setting and unsetting the HTTP request."""

request: Optional[HTTPRequest]

def __init__(self, request: Optional[HTTPRequest]) -> None:
self.request = request

def __enter__(self) -> None:
self.token = HTTP_REQUEST_CTX.set(self.request)

def __exit__(self, *_: Any) -> None:
HTTP_REQUEST_CTX.reset(self.token)
1 change: 1 addition & 0 deletions marimo/_plugins/ui/_impl/chat/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ async def _send_prompt(self, args: SendMessageRequest) -> str:
SetUIElementValueRequest(
object_ids=[self._id],
values=[{"messages": self._chat_history}],
request=None,
)
)

Expand Down
53 changes: 28 additions & 25 deletions marimo/_plugins/ui/_impl/tables/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,37 @@ def format_value(
if format_mapping is None:
return value

if value is None:
if col in format_mapping:
formatter = format_mapping[col]
if callable(formatter):
return formatter(value)
if col not in format_mapping:
return value

if col in format_mapping:
formatter = format_mapping[col]
try:
if isinstance(formatter, str):
# Handle numeric formatting specially to preserve signs and separators
if isinstance(value, (int, float)):
# Keep integers as integers for 'd' format specifier
if isinstance(value, int) and "d" in formatter:
return formatter.format(value)
# Convert to float for float formatting
return formatter.format(float(value))
return formatter.format(value)
if callable(formatter):
return formatter(value)
except Exception as e:
import logging
formatter = format_mapping[col]

# If the value is None, we don't want to format it
# with strings for formatting, but we do want to
# format it with callables.
if value is None and isinstance(formatter, str):
return value

try:
if isinstance(formatter, str):
# Handle numeric formatting specially to preserve signs and separators
if isinstance(value, (int, float)):
# Keep integers as integers for 'd' format specifier
if isinstance(value, int) and "d" in formatter:
return formatter.format(value)
# Convert to float for float formatting
return formatter.format(float(value))
return formatter.format(value)
if callable(formatter):
return formatter(value)
except Exception as e:
import logging

logging.warning(
f"Error formatting for value {value} in column {col}: {str(e)}"
)
return value

logging.warning(
f"Error formatting for value {value} in column {col}: {str(e)}"
)
return value
return value


Expand Down
8 changes: 6 additions & 2 deletions marimo/_pyodide/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,19 @@ def instantiate(

app = session.app_manager.app
execution_requests = tuple(
ExecutionRequest(cell_id=cell_data.cell_id, code=cell_data.code)
ExecutionRequest(
cell_id=cell_data.cell_id,
code=cell_data.code,
request=None,
)
for cell_data in app.cell_manager.cell_data()
)

session.put_control_request(
CreationRequest(
execution_requests=execution_requests,
set_ui_element_value_request=SetUIElementValueRequest(
object_ids=[], values=[]
object_ids=[], values=[], request=None
),
auto_run=auto_instantiate,
)
Expand Down
2 changes: 1 addition & 1 deletion marimo/_runtime/app/kernel_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def globals(self) -> dict[CellId_t, Any]:

async def run(self, cells_to_run: set[CellId_t]) -> RunOutput:
execution_requests = [
ExecutionRequest(cell_id=cid, code=cell._cell.code)
ExecutionRequest(cell_id=cid, code=cell._cell.code, request=None)
for cid in cells_to_run
if (cell := self.app.cell_manager.cell_data_at(cid).cell)
is not None
Expand Down
34 changes: 34 additions & 0 deletions marimo/_runtime/app_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
get_context,
)
from marimo._runtime.context.utils import RunMode, get_mode
from marimo._runtime.requests import HTTPRequest


@mddoc
Expand Down Expand Up @@ -75,3 +76,36 @@ def mode(self) -> Optional[RunMode]:
- None: The mode could not be determined
"""
return get_mode()

@property
def request(self) -> Optional[HTTPRequest]:
"""
The current HTTP request if any. The shape of the request object depends on the ASGI framework used,
but typically includes:

- `headers`: Request headers
- `cookies`: Request cookies
- `query_params`: Query parameters
- `path_params`: Path parameters
- `user`: User data added by authentication middleware
- `url`: URL information including path, query parameters

Examples:
Get the current request and print the path:

```python
request = mo.app_meta().request
user = request.user
print(
user["is_authenticated"], user["username"], request.url["path"]
)
```

Returns:
Optional[HTTPRequest]: The current request object if available, None otherwise.
"""
try:
context = get_context()
return context.request
except ContextNotInitializedError:
return None
7 changes: 7 additions & 0 deletions marimo/_runtime/context/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
from typing import TYPE_CHECKING, Any, Iterator, Optional

from marimo._config.config import MarimoConfig
from marimo._messaging.context import HTTP_REQUEST_CTX
from marimo._messaging.types import Stderr, Stdout
from marimo._runtime import dataflow
from marimo._runtime.cell_lifecycle_registry import CellLifecycleRegistry
from marimo._runtime.functions import FunctionRegistry
from marimo._runtime.requests import HTTPRequest

if TYPE_CHECKING:
from marimo._ast.app import InternalApp
Expand Down Expand Up @@ -100,6 +102,11 @@ def marimo_config(self) -> MarimoConfig:
"""
pass

@property
def request(self) -> Optional[HTTPRequest]:
"""Get the current request context if any."""
return HTTP_REQUEST_CTX.get(None)

@property
@abc.abstractmethod
def cell_id(self) -> Optional[CellId_t]:
Expand Down
Loading
Loading