-
Notifications
You must be signed in to change notification settings - Fork 2
update binding to support LiveComponents #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -211,6 +211,48 @@ async def test_get_current_user_no_session(): | |
| | `handle_event` | Yes | No | | ||
| | `@event` handlers | Yes | No | | ||
|
|
||
| ## LiveComponents | ||
|
|
||
| `Depends()` also works in LiveComponent lifecycle methods: | ||
|
|
||
| | Method | Async Deps | Session Available | | ||
| |--------|------------|-------------------| | ||
| | `mount` | Yes | No | | ||
| | `update` | Yes | No | | ||
| | `handle_event` | Yes | No | | ||
|
|
||
| Lifecycle parameters are resolved by name or type. Use `socket`, `assigns`, `event`, and `payload` as parameter names, or annotate with a socket type if you prefer different names. | ||
|
|
||
| Components don't have direct session access—they receive data from their parent via `assigns`. If a component needs session data, pass it from the parent: | ||
|
|
||
| ```python | ||
| from pyview import Depends | ||
| from pyview.components.base import LiveComponent, ComponentSocket | ||
|
|
||
| def get_formatter(): | ||
| return Formatter(locale="en-US") | ||
|
|
||
| class PriceDisplay(LiveComponent): | ||
|
Comment on lines
+230
to
+235
|
||
| async def mount( | ||
| self, | ||
| socket: ComponentSocket, | ||
| assigns: dict, | ||
| formatter=Depends(get_formatter), | ||
| ): | ||
| # user_id comes from parent via assigns, not session | ||
| socket.context = { | ||
| "price": formatter.format_currency(assigns["amount"]), | ||
| "user_id": assigns.get("user_id"), | ||
| } | ||
| ``` | ||
|
|
||
| In the parent LiveView: | ||
|
|
||
| ```python | ||
| # Pass session data to component via assigns | ||
| live_component(PriceDisplay, id="price", amount=99.99, user_id=session.get("user_id")) | ||
| ``` | ||
|
|
||
| ## Available Injectables | ||
|
|
||
| ### Type-based injection | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| """Binding helpers for LiveComponent lifecycle methods.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import Any | ||
|
|
||
| from pyview.binding.binder import Binder | ||
| from pyview.binding.context import BindContext | ||
| from pyview.binding.params import Params | ||
|
|
||
| from .base import ComponentSocket, LiveComponent | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| async def call_component_mount( | ||
| component: LiveComponent, | ||
| socket: ComponentSocket, | ||
| assigns: dict[str, Any], | ||
| ) -> None: | ||
| """Bind and call component.mount() with Depends() support. | ||
|
|
||
| Args: | ||
| component: The LiveComponent instance | ||
| socket: The ComponentSocket instance | ||
| assigns: Props from parent template | ||
|
|
||
| Note: | ||
| Session is not available to components. Session-dependent data | ||
| should be passed from parent LiveView via assigns. | ||
| """ | ||
| ctx = BindContext( | ||
| params=Params({}), | ||
| payload=None, | ||
| url=None, | ||
| socket=socket, | ||
| event=None, | ||
| extra={"assigns": assigns}, | ||
| ) | ||
| binder = Binder() | ||
| result = await binder.abind(component.mount, ctx) | ||
|
|
||
|
Comment on lines
+17
to
+43
|
||
| if not result.success: | ||
| component_name = component.__class__.__name__ | ||
| for err in result.errors: | ||
| logger.warning(f"Component {component_name} mount binding error: {err}") | ||
| raise ValueError(f"Component mount binding failed: {result.errors}") | ||
|
|
||
| await component.mount(**result.bound_args) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This switches component lifecycle invocation to Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| async def call_component_update( | ||
| component: LiveComponent, | ||
| socket: ComponentSocket, | ||
| assigns: dict[str, Any], | ||
| ) -> None: | ||
| """Bind and call component.update() with Depends() support. | ||
|
|
||
| Args: | ||
| component: The LiveComponent instance | ||
| socket: The ComponentSocket instance | ||
| assigns: Updated props from parent template | ||
| """ | ||
| ctx = BindContext( | ||
| params=Params({}), | ||
| payload=None, | ||
| url=None, | ||
| socket=socket, | ||
| event=None, | ||
| extra={"assigns": assigns}, | ||
| ) | ||
| binder = Binder() | ||
| result = await binder.abind(component.update, ctx) | ||
|
|
||
| if not result.success: | ||
| component_name = component.__class__.__name__ | ||
| for err in result.errors: | ||
| logger.warning(f"Component {component_name} update binding error: {err}") | ||
| raise ValueError(f"Component update binding failed: {result.errors}") | ||
|
|
||
| await component.update(**result.bound_args) | ||
|
|
||
|
|
||
| async def call_component_handle_event( | ||
| component: LiveComponent, | ||
| event: str, | ||
| payload: dict[str, Any], | ||
| socket: ComponentSocket, | ||
| ) -> None: | ||
| """Bind and call component.handle_event() with Depends() support. | ||
|
|
||
| Args: | ||
| component: The LiveComponent instance | ||
| event: Event name | ||
| payload: Event payload dict | ||
| socket: The ComponentSocket instance | ||
| """ | ||
| ctx = BindContext( | ||
| params=Params({}), | ||
| payload=payload, | ||
| url=None, | ||
| socket=socket, | ||
| event=event, | ||
| extra={}, | ||
| ) | ||
| binder = Binder() | ||
| result = await binder.abind(component.handle_event, ctx) | ||
|
|
||
| if not result.success: | ||
| component_name = component.__class__.__name__ | ||
| for err in result.errors: | ||
| logger.warning(f"Component {component_name} event binding error: {err}") | ||
| raise ValueError(f"Component event binding failed: {result.errors}") | ||
|
|
||
| await component.handle_event(**result.bound_args) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc update introduces LiveComponents DI support, but the “Type-based injection” section below still only lists
Session. With this PR, socket types can also be injected by type annotation (e.g.,ComponentSocket,ConnectedLiveViewSocket,UnconnectedSocket).Please update the “Available Injectables” docs to include the new type-based socket injections (and clarify whether
LiveViewSocketaliases are supported).