init
This commit is contained in:
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
77
README.md
77
README.md
@@ -1,2 +1,79 @@
|
||||
# NiceGuiAsyncElement
|
||||
|
||||
Async element wrapper for NiceGUI with proper typing support. Create UI elements that require asynchronous initialization while maintaining full type hints and IDE support.
|
||||
|
||||
## Installation
|
||||
|
||||
Install directly from GitHub using uv or pip:
|
||||
|
||||
```bash
|
||||
# Using uv
|
||||
uv add git+https://github.com/yourusername/NiceGuiAsyncElement.git
|
||||
|
||||
# Using pip
|
||||
pip install git+https://github.com/yourusername/NiceGuiAsyncElement.git
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```python
|
||||
from nicegui import ui
|
||||
from niceguiasyncelement import AsyncElement, AsyncColumn, AsyncCard
|
||||
|
||||
# Create an async element that fetches data
|
||||
class UserCard(AsyncElement[ui.column]):
|
||||
async def build(self, user_id: int) -> None:
|
||||
# Simulate async data fetching
|
||||
user_data = await fetch_user(user_id)
|
||||
|
||||
with self:
|
||||
with ui.card():
|
||||
ui.label(user_data['name'])
|
||||
ui.label(user_data['email'])
|
||||
|
||||
# Use in your NiceGUI app
|
||||
@ui.page('/')
|
||||
async def main():
|
||||
# Creates the element and returns a properly typed ui.column
|
||||
user_card = await UserCard.create(user_id=123)
|
||||
user_card.classes('w-full') # Full IDE support!
|
||||
```
|
||||
|
||||
### Using Inheritance
|
||||
|
||||
For even better typing, inherit from specific element types:
|
||||
|
||||
```python
|
||||
class DataTable(AsyncColumn):
|
||||
async def build(self, api_endpoint: str) -> None:
|
||||
# Fetch data asynchronously
|
||||
data = await fetch_data(api_endpoint)
|
||||
|
||||
with self:
|
||||
ui.table(columns=data['columns'], rows=data['rows'])
|
||||
|
||||
# Usage
|
||||
data_table = await DataTable.create(api_endpoint="/api/users")
|
||||
# data_table is fully typed as DataTable/AsyncColumn
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Async Initialization**: Build UI elements that require async operations (API calls, database queries, etc.)
|
||||
- **Full Type Support**: Maintains complete typing for IDE autocomplete and type checking
|
||||
- **Context Manager Support**: Works seamlessly with NiceGUI's context manager pattern
|
||||
- **Multiple Approaches**: Choose between generic `AsyncElement` or inherit from specific element types
|
||||
|
||||
## Development
|
||||
|
||||
Run the example:
|
||||
|
||||
```bash
|
||||
python example.py
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
89
example.py
Normal file
89
example.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from nicegui import ui
|
||||
from src.niceguiasyncelement import AsyncElement, AsyncColumn, AsyncCard
|
||||
|
||||
|
||||
# Example using the generic AsyncElement (returns proper NiceGUI element type)
|
||||
class UserCard(AsyncElement[ui.column]):
|
||||
async def build(self, user_id: int, *args, **kwargs) -> None:
|
||||
user_data = {
|
||||
'name': f'User {user_id}',
|
||||
'email': f'user{user_id}@example.com',
|
||||
'status': 'online' if user_id % 2 == 0 else 'offline'
|
||||
}
|
||||
|
||||
with self:
|
||||
with ui.card().classes('w-full max-w-md'):
|
||||
ui.label(user_data['name']).classes('text-h6')
|
||||
ui.label(user_data['email']).classes('text-caption')
|
||||
|
||||
with ui.row().classes('w-full justify-between items-center'):
|
||||
status_color = 'green' if user_data['status'] == 'online' else 'grey'
|
||||
ui.badge(user_data['status']).props(f'color={status_color}')
|
||||
ui.button('Edit', icon='edit').props('flat dense')
|
||||
|
||||
|
||||
# Example using direct inheritance approach
|
||||
class LoadingCard(AsyncCard):
|
||||
async def build(self, title: str, delay: float = 1.0, *args, **kwargs) -> None:
|
||||
with self:
|
||||
ui.label(title).classes('text-h6 mb-2')
|
||||
ui.label('Content loaded successfully!').classes('text-positive')
|
||||
with ui.row().classes('gap-2'):
|
||||
ui.button('Action 1', icon='star')
|
||||
ui.button('Action 2', icon='favorite')
|
||||
|
||||
|
||||
class DataColumn(AsyncColumn):
|
||||
async def build(self, data_source: str, *args, **kwargs) -> None:
|
||||
columns = [
|
||||
{'name': 'name', 'label': 'Name', 'field': 'name', 'required': True, 'align': 'left'},
|
||||
{'name': 'age', 'label': 'Age', 'field': 'age', 'sortable': True},
|
||||
{'name': 'city', 'label': 'City', 'field': 'city'},
|
||||
]
|
||||
rows = [
|
||||
{'id': 1, 'name': 'Alice', 'age': 25, 'city': 'New York'},
|
||||
{'id': 2, 'name': 'Bob', 'age': 30, 'city': 'San Francisco'},
|
||||
{'id': 3, 'name': 'Charlie', 'age': 35, 'city': 'London'},
|
||||
]
|
||||
|
||||
with self:
|
||||
ui.label(f'Data from {data_source}').classes('text-caption mb-2')
|
||||
ui.table(columns=columns, rows=rows, row_key='id').classes('w-full')
|
||||
|
||||
|
||||
@ui.page('/')
|
||||
async def main_page():
|
||||
ui.label('Typed Async Elements Demo').classes('text-h4 mb-4')
|
||||
|
||||
# Example 1: Generic AsyncElement - returns ui.column with proper typing
|
||||
user_card: ui.column = await UserCard.create(element_type=ui.column, user_id=123)
|
||||
user_card.classes('w-full mb-8')
|
||||
|
||||
# Example 2: Direct inheritance - perfect typing, returns the class instance
|
||||
loading_card: LoadingCard = await LoadingCard.create(title="Dashboard Section", delay=0.8)
|
||||
loading_card.classes('w-full p-4 mb-8')
|
||||
|
||||
# Example 3: Column inheritance
|
||||
data_column: DataColumn = await DataColumn.create(data_source="users_api")
|
||||
data_column.classes('w-full mb-8')
|
||||
|
||||
# Example 4: Multiple elements with proper typing
|
||||
with ui.row().classes('w-full gap-4'):
|
||||
for i in range(1, 4):
|
||||
card: ui.column = await UserCard.create(element_type=ui.column, user_id=i)
|
||||
card.classes('flex-1')
|
||||
|
||||
# Example 5: Type checker knows all methods are available
|
||||
another_card = await LoadingCard.create(title="Another Section")
|
||||
another_card.classes('w-full p-4')
|
||||
another_card.style('border: 1px solid red') # Type checker knows this method exists
|
||||
|
||||
|
||||
if __name__ in {"__main__", "__mp_main__"}:
|
||||
ui.run(
|
||||
title='Typed AsyncElements',
|
||||
favicon='🔒',
|
||||
show=False,
|
||||
dark=False,
|
||||
port=8080
|
||||
)
|
||||
36
pyproject.toml
Normal file
36
pyproject.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "niceguiasyncelement"
|
||||
version = "0.1.0"
|
||||
description = "Async element wrapper for NiceGUI with proper typing support"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "Your Name", email = "your.email@example.com"}
|
||||
]
|
||||
keywords = ["nicegui", "async", "ui", "web"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
dependencies = [
|
||||
"nicegui>=2.0.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/yourusername/NiceGuiAsyncElement"
|
||||
Issues = "https://github.com/yourusername/NiceGuiAsyncElement/issues"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/niceguiasyncelement"]
|
||||
7
src/niceguiasyncelement/__init__.py
Normal file
7
src/niceguiasyncelement/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""NiceGUI Async Element - Async element wrapper for NiceGUI with proper typing support"""
|
||||
|
||||
from .base import AsyncElement
|
||||
from .elements import AsyncColumn, AsyncRow, AsyncCard, AsyncScrollarea
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__all__ = ['AsyncElement', 'AsyncColumn', 'AsyncCard', 'AsyncRow', 'AsyncScrollarea']
|
||||
47
src/niceguiasyncelement/base.py
Normal file
47
src/niceguiasyncelement/base.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Type, Any, TypeVar, Generic
|
||||
from nicegui import ui
|
||||
|
||||
# Type variable for the wrapped element type
|
||||
T = TypeVar('T', bound=ui.element)
|
||||
|
||||
|
||||
class AsyncElement(ABC, Generic[T]):
|
||||
"""Base class for UI elements with async initialization that behaves like a NiceGUI element"""
|
||||
|
||||
def __init__(self, element_type: Type[T] = ui.column, *element_args, **element_kwargs) -> None:
|
||||
# Create the NiceGUI element
|
||||
self._element: T = element_type(*element_args, **element_kwargs)
|
||||
|
||||
@abstractmethod
|
||||
async def build(self, *args, **kwargs) -> None:
|
||||
"""Build/setup the element - must be implemented by subclasses"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
async def create(cls, element_type: Type[T] = ui.column, *args, **kwargs) -> T:
|
||||
"""Factory method to create and build an element instance, returns the NiceGUI element"""
|
||||
# Separate element constructor args from build args
|
||||
element_args = kwargs.pop('element_args', ())
|
||||
element_kwargs = kwargs.pop('element_kwargs', {})
|
||||
|
||||
# Create and build the instance
|
||||
instance = cls(element_type, *element_args, **element_kwargs)
|
||||
await instance.build(*args, **kwargs)
|
||||
|
||||
# Add a reference to the async instance on the element for potential later access
|
||||
instance._element._async_instance = instance # pyright: ignore[reportAttributeAccessIssue]
|
||||
|
||||
return instance._element # Return the NiceGUI element with proper typing
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""Delegate any missing attribute access to the wrapped element"""
|
||||
return getattr(self._element, name)
|
||||
|
||||
def __enter__(self):
|
||||
"""Support context manager protocol"""
|
||||
return self._element.__enter__()
|
||||
|
||||
def __exit__(self, *args):
|
||||
"""Support context manager protocol"""
|
||||
return self._element.__exit__(*args)
|
||||
79
src/niceguiasyncelement/elements.py
Normal file
79
src/niceguiasyncelement/elements.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Self
|
||||
from nicegui import ui
|
||||
|
||||
|
||||
class AsyncColumn(ui.column, ABC):
|
||||
"""Async column that inherits from ui.column for perfect typing"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
async def build(self, *args, **kwargs) -> None:
|
||||
"""Build/setup the element - must be implemented by subclasses"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
async def create(cls, *args, **kwargs) -> Self:
|
||||
"""Factory method to create and build a column instance"""
|
||||
instance = cls()
|
||||
await instance.build(*args, **kwargs)
|
||||
return instance
|
||||
|
||||
|
||||
class AsyncRow(ui.row, ABC):
|
||||
"""Async row that inherits from ui.row for perfect typing"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
async def build(self, *args, **kwargs) -> None:
|
||||
"""Build/setup the element - must be implemented by subclasses"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
async def create(cls, *args, **kwargs) -> Self:
|
||||
"""Factory method to create and build a column instance"""
|
||||
instance = cls()
|
||||
await instance.build(*args, **kwargs)
|
||||
return instance
|
||||
|
||||
|
||||
class AsyncCard(ui.card, ABC):
|
||||
"""Async card that inherits from ui.card for perfect typing"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
async def build(self, *args, **kwargs) -> None:
|
||||
"""Build/setup the element - must be implemented by subclasses"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
async def create(cls, *args, **kwargs) -> Self:
|
||||
"""Factory method to create and build a card instance"""
|
||||
instance = cls()
|
||||
await instance.build(*args, **kwargs)
|
||||
return instance
|
||||
|
||||
|
||||
class AsyncScrollarea(ui.scroll_area, ABC):
|
||||
"""Async ScrollArea that inherits from ui.scrol_area for perfect typing"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
async def build(self, *args, **kwargs) -> None:
|
||||
"""Build/setup the element - must be implemented by subclasses"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
async def create(cls, *args, **kwargs) -> Self:
|
||||
"""Factory method to create and build a column instance"""
|
||||
instance = cls()
|
||||
await instance.build(*args, **kwargs)
|
||||
return instance
|
||||
Reference in New Issue
Block a user