UI Fast
Project description
uf - UI Fast
Minimal-boilerplate web UIs for Python functions
uf bridges functions → HTTP services (via qh) → Web UI forms (via ju.rjsf), following the "convention over configuration" philosophy.
Features
- One-line app creation: Just pass your functions to
mk_rjsf_app() - Automatic form generation: RJSF forms created from function signatures
- Type-aware: Uses type hints to generate appropriate form fields
- Zero configuration required: Sensible defaults for everything
- Progressive enhancement: Customize only what you need
- Mapping-based interfaces: Access specs and configs as dictionaries
- Framework agnostic: Works with Bottle and FastAPI
- UI decorators: Rich metadata via
@ui_config,@group, etc. - Function grouping: Organize functions into categories
- Field customization: Configure widgets, validation, and interactions
- Custom type support: Register transformations for any Python type
- Field dependencies: Conditional display and dynamic forms
- Testing utilities: Built-in tools for testing your apps
Installation
pip install uf
Quick Start
from uf import mk_rjsf_app
def add(x: int, y: int) -> int:
"""Add two numbers"""
return x + y
def greet(name: str) -> str:
"""Greet a person"""
return f"Hello, {name}!"
# Create the app
app = mk_rjsf_app([add, greet])
# Run it (for Bottle apps)
app.run(host='localhost', port=8080)
Then open http://localhost:8080 in your browser!
How It Works
uf combines three powerful packages from the i2mint ecosystem:
- qh: Converts functions → HTTP endpoints
- ju.rjsf: Generates JSON Schema & RJSF specs from signatures
- i2: Provides signature introspection and manipulation
The result: A complete web UI with zero boilerplate!
Table of Contents
- Basic Usage
- UI Decorators
- Field Configuration
- Function Grouping
- Custom Types
- Field Dependencies
- Testing
- API Reference
- Examples
Basic Usage
Simple Example
from uf import mk_rjsf_app
def multiply(x: float, y: float) -> float:
"""Multiply two numbers"""
return x * y
app = mk_rjsf_app([multiply], page_title="Calculator")
Object-Oriented Interface
For more control, use the UfApp class:
from uf import UfApp
def fibonacci(n: int) -> list:
"""Generate Fibonacci sequence"""
if n <= 0:
return []
elif n == 1:
return [0]
fib = [0, 1]
for i in range(2, n):
fib.append(fib[i-1] + fib[i-2])
return fib
# Create app
uf_app = UfApp([fibonacci])
# Call functions programmatically
result = uf_app.call('fibonacci', n=10)
# Access specs
spec = uf_app.get_spec('fibonacci')
# List available functions
functions = uf_app.list_functions()
# Run the server
uf_app.run(host='localhost', port=8080)
UI Decorators
Add rich metadata to your functions using decorators:
@ui_config - Complete UI Configuration
from uf import ui_config, RjsfFieldConfig, get_field_config
@ui_config(
title="User Registration",
description="Create a new user account",
group="Admin",
icon="user-plus",
order=1,
fields={
'email': get_field_config('email'),
'bio': get_field_config('multiline_text'),
}
)
def register_user(email: str, name: str, bio: str = ''):
"""Register a new user."""
return {'email': email, 'name': name, 'bio': bio}
@group - Simple Grouping
from uf import group
@group("Admin")
def delete_user(user_id: int):
"""Delete a user from the system."""
pass
@field_config - Field-Level Configuration
from uf import field_config, get_field_config
@field_config(
email=get_field_config('email'),
message=get_field_config('multiline_text'),
)
def send_message(email: str, message: str):
"""Send a message to a user."""
pass
@hidden - Hide from UI
from uf import hidden
@hidden
def internal_function():
"""This won't appear in the UI but is accessible via API."""
pass
@with_example - Provide Test Data
from uf import with_example
@with_example(x=10, y=20)
def add(x: int, y: int) -> int:
"""Add two numbers."""
return x + y
Other Decorators
from uf import deprecated, requires_auth, rate_limit
@deprecated("Use new_function instead")
def old_function():
pass
@requires_auth(roles=['admin'], permissions=['user:delete'])
def delete_user(user_id: int):
pass
@rate_limit(calls=10, period=60) # 10 calls per minute
def send_email(to: str, subject: str):
pass
Field Configuration
Predefined Field Configurations
from uf import get_field_config
# Available configurations:
email_config = get_field_config('email')
password_config = get_field_config('password')
url_config = get_field_config('url')
multiline_config = get_field_config('multiline_text')
long_text_config = get_field_config('long_text')
date_config = get_field_config('date')
datetime_config = get_field_config('datetime')
color_config = get_field_config('color')
file_config = get_field_config('file')
Custom Field Configuration
from uf import RjsfFieldConfig
custom_field = RjsfFieldConfig(
widget='select',
enum=['option1', 'option2', 'option3'],
placeholder='Choose an option',
description='Please select one option',
)
@field_config(status=custom_field)
def update_status(status: str):
pass
Field Configuration Builder
from uf import RjsfConfigBuilder, RjsfFieldConfig
builder = RjsfConfigBuilder()
builder.field('name', RjsfFieldConfig(placeholder='Enter name'))
builder.field('email', RjsfFieldConfig(format='email'))
builder.order(['name', 'email', 'phone'])
builder.class_names('custom-form')
spec = builder.build(base_schema)
Function Grouping
Manual Grouping
from uf import FunctionGroup, mk_grouped_app
admin_group = FunctionGroup(
name="Admin",
funcs=[create_user, delete_user, update_user],
description="User administration functions",
icon="shield",
order=1,
)
reports_group = FunctionGroup(
name="Reports",
funcs=[generate_report, export_data],
description="Reporting functions",
icon="file-text",
order=2,
)
app = mk_grouped_app([admin_group, reports_group])
Auto-Grouping by Prefix
from uf import auto_group_by_prefix
# Functions named user_create, user_delete, report_generate, etc.
# will be automatically grouped into "User", "Report", etc.
organizer = auto_group_by_prefix(
[user_create, user_delete, report_generate],
separator="_"
)
Auto-Grouping by Module
from uf import auto_group_by_module
organizer = auto_group_by_module([func1, func2, func3])
Auto-Grouping by Tag
from uf import auto_group_by_tag
def my_function():
pass
my_function.__uf_group__ = "Admin"
organizer = auto_group_by_tag([my_function])
Custom Types
Register custom type transformations for seamless JSON serialization:
Using the Global Registry
from uf import register_type
from pathlib import Path
from decimal import Decimal
# Register Path type
register_type(
Path,
to_json=str,
from_json=Path,
json_schema_type='string'
)
# Register Decimal type
register_type(
Decimal,
to_json=float,
from_json=Decimal,
json_schema_type='number'
)
Using a Custom Registry
from uf import InputTransformRegistry
registry = InputTransformRegistry()
registry.register_type(
MyCustomType,
to_json=lambda x: x.to_dict(),
from_json=MyCustomType.from_dict,
ui_widget='textarea',
json_schema_type='object'
)
# Use with mk_rjsf_app
input_trans = registry.mk_input_trans_for_funcs([my_func])
output_trans = registry.mk_output_trans()
app = mk_rjsf_app(
[my_func],
input_trans=input_trans,
output_trans=output_trans
)
Pre-registered Types
The following types are automatically supported:
datetime.datetimedatetime.datedatetime.timepathlib.Pathuuid.UUIDdecimal.Decimal
Field Dependencies
Create dynamic forms where fields show/hide based on other field values:
Simple Dependency
from uf import FieldDependency, DependencyAction, with_dependencies
@with_dependencies(
FieldDependency(
source_field='reason',
target_field='other_reason',
condition=lambda v: v == 'other',
action=DependencyAction.SHOW,
)
)
def submit_feedback(reason: str, other_reason: str = ''):
"""Submit feedback with conditional 'other' field."""
pass
Dependency Builder
from uf import DependencyBuilder
builder = DependencyBuilder()
builder.when('age').greater_than(18).enable('alcohol_consent')
builder.when('country').equals('US').show('state')
builder.when('priority').in_list(['high', 'urgent']).require('manager_approval')
dependencies = builder.build()
Available Actions
DependencyAction.SHOW- Show the fieldDependencyAction.HIDE- Hide the fieldDependencyAction.ENABLE- Enable the fieldDependencyAction.DISABLE- Disable the fieldDependencyAction.REQUIRE- Make the field requiredDependencyAction.OPTIONAL- Make the field optional
Testing
Built-in testing utilities for your uf apps:
Test Client
from uf import UfTestClient
client = UfTestClient(app)
# List functions
functions = client.list_functions()
# Get spec
spec = client.get_spec('my_function')
# Call function
result = client.call_function('my_function', {'x': 10, 'y': 20})
assert result['success']
assert result['result'] == 30
Test Context Manager
from uf import UfAppTester
with UfAppTester(app) as tester:
result = tester.submit_form('add', {'x': 10, 'y': 20})
tester.assert_success(result)
tester.assert_result_equals(result, 30)
Testing Individual Functions
from uf import test_ui_function
def add(x: int, y: int) -> int:
return x + y
# Test with expected output
test_ui_function(add, {'x': 10, 'y': 20}, expected_output=30)
# Test with expected exception
test_ui_function(
divide,
{'x': 10, 'y': 0},
expected_exception=ZeroDivisionError
)
Form Data Builder
from uf import FormDataBuilder
form_data = (
FormDataBuilder()
.field('name', 'John Doe')
.field('email', 'john@example.com')
.fields(age=30, city='NYC')
.build()
)
Schema Assertions
from uf import (
assert_valid_rjsf_spec,
assert_has_field,
assert_field_type,
assert_field_required,
)
spec = app.function_specs['my_function']
assert_valid_rjsf_spec(spec)
assert_has_field(spec, 'email')
assert_field_type(spec, 'age', 'integer')
assert_field_required(spec, 'name')
Customization
Custom CSS
CUSTOM_CSS = """
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
"""
app = mk_rjsf_app(
[func1, func2],
page_title="My Custom App",
custom_css=CUSTOM_CSS,
)
Advanced qh Configuration
from qh import AppConfig
qh_config = AppConfig(
cors=True,
log_requests=True,
)
app = mk_rjsf_app(
[my_func],
config=qh_config,
input_trans=my_input_transformer,
output_trans=my_output_transformer,
)
Examples
See the examples/ directory for complete working examples:
basic_example.py: Simple math and text functionsadvanced_example.py: Customization and object-oriented interfacefull_featured_example.py: Complete showcase of all features
API Reference
Core Functions
mk_rjsf_app(funcs, **kwargs)
Main entry point for creating a web app from functions.
Parameters:
funcs: Iterable of callable functionsconfig: Optional qh.AppConfig for HTTP configurationinput_trans: Optional input transformation functionoutput_trans: Optional output transformation functionrjsf_config: Optional RJSF configuration dictui_schema_factory: Optional callable for custom UI schemaspage_title: Title for the web interface (default: "Function Interface")custom_css: Optional custom CSS stringrjsf_theme: RJSF theme name (default: "default")add_ui: Whether to add UI routes (default: True)**qh_kwargs: Additional arguments passed to qh.mk_app
Returns: Configured web application (Bottle or FastAPI)
mk_grouped_app(groups, **kwargs)
Create a uf app with grouped function navigation.
Parameters:
groups: Iterable of FunctionGroup objects**kwargs: Same as mk_rjsf_app
Returns: Configured web application with grouped navigation
Classes
UfApp(funcs, **kwargs)
Object-oriented wrapper for uf applications.
Methods:
run(host, port, **kwargs): Run the web servercall(func_name, **kwargs): Call a function by nameget_spec(func_name): Get RJSF spec for a functionlist_functions(): List all function names
FunctionSpecStore(funcs, **kwargs)
Mapping-based interface to function specifications.
RjsfFieldConfig(**kwargs)
Configuration for individual form fields.
Attributes:
widget: Widget typeformat: JSON Schema formatenum: List of allowed valuesplaceholder: Placeholder textdescription: Help text- And more...
FunctionGroup(name, funcs, **kwargs)
Group of functions with metadata.
Attributes:
name: Group namefuncs: List of functionsdescription: Group descriptionicon: Icon identifierorder: Display order
InputTransformRegistry()
Registry for custom type transformations.
Methods:
register_type(py_type, **kwargs): Register a typemk_input_trans_for_funcs(funcs): Create input transformationmk_output_trans(): Create output transformation
FieldDependency(**kwargs)
Define a dependency between form fields.
DependencyBuilder()
Fluent interface for building field dependencies.
Decorators
@ui_config(...): Add complete UI configuration@group(name): Assign to a group@hidden: Hide from UI@field_config(**fields): Configure specific fields@with_example(**kwargs): Attach example data@deprecated(message): Mark as deprecated@requires_auth(...): Mark as requiring authentication@rate_limit(calls, period): Add rate limit metadata
Testing Utilities
UfTestClient(app): Test client for uf appsUfAppTester(app): Context manager for testingtest_ui_function(func, params, **kwargs): Test individual functionsFormDataBuilder(): Build test form dataassert_valid_rjsf_spec(spec): Assert spec is validassert_has_field(spec, name): Assert field existsassert_field_type(spec, name, type): Assert field typeassert_field_required(spec, name): Assert field is required
Architecture
uf follows these design principles:
- Convention over Configuration: Works out-of-the-box with sensible defaults
- Mapping-based Interfaces: Access everything as dictionaries
- Lazy Evaluation: Generate specs only when needed
- Composition over Inheritance: Extend via decorators and transformations
- Progressive Enhancement: Start simple, customize as needed
Development Roadmap
✅ Milestone 1: MVP (Completed)
- Core
mk_rjsf_appfunction - FunctionSpecStore for spec management
- HTML template generation
- Essential API routes
✅ Milestone 2: Configuration (Completed)
- RJSF customization layer
- Input transformation registry
- Custom field widgets
✅ Milestone 3: Enhancement (Completed)
- Function grouping and organization
- UI metadata decorators (
@ui_config) - Auto-grouping utilities
✅ Milestone 4: Advanced (Completed)
- Field dependencies and interactions
- Testing utilities
- Comprehensive examples
Dependencies
qh: HTTP service generationju: RJSF form generation and JSON utilitiesi2: Signature introspectiondol: Mapping interfacesmeshed: Function composition utilities
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details
Related Projects
- qh: HTTP services from functions
- ju: JSON Schema and RJSF utilities
- i2: Signature introspection
- dol: Mapping interfaces
- meshed: Function composition
Authors
Part of the i2mint ecosystem.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file uf-0.1.1.tar.gz.
File metadata
- Download URL: uf-0.1.1.tar.gz
- Upload date:
- Size: 48.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7280069fccaf7fb60a757490377c5a01c8bf854218c1797b91ddf6b4c7534cf
|
|
| MD5 |
1d58330eadfb5be564a9185824e9f4a2
|
|
| BLAKE2b-256 |
c63b27fee0a7d86e9ebbcdf24169a2992197d4275a1bfa8cbf6417cfec15ab43
|
File details
Details for the file uf-0.1.1-py3-none-any.whl.
File metadata
- Download URL: uf-0.1.1-py3-none-any.whl
- Upload date:
- Size: 48.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0f1360502b5f4fc394ba158366951d65fb9ac11f57c16b34d5c52b7edd7463c
|
|
| MD5 |
f2abea5ee3973e8a993fa67ec934706e
|
|
| BLAKE2b-256 |
b348e7ffa2bfe75bcb1fc57add7534602017bb90ef20a62c5a42c94c4fd973ef
|