Skip to content

i2mint/uf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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:

  1. qh: Converts functions → HTTP endpoints
  2. ju.rjsf: Generates JSON Schema & RJSF specs from signatures
  3. i2: Provides signature introspection and manipulation

The result: A complete web UI with zero boilerplate!

Table of Contents

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.datetime
  • datetime.date
  • datetime.time
  • pathlib.Path
  • uuid.UUID
  • decimal.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 field
  • DependencyAction.HIDE - Hide the field
  • DependencyAction.ENABLE - Enable the field
  • DependencyAction.DISABLE - Disable the field
  • DependencyAction.REQUIRE - Make the field required
  • DependencyAction.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 functions
  • advanced_example.py: Customization and object-oriented interface
  • full_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 functions
  • config: Optional qh.AppConfig for HTTP configuration
  • input_trans: Optional input transformation function
  • output_trans: Optional output transformation function
  • rjsf_config: Optional RJSF configuration dict
  • ui_schema_factory: Optional callable for custom UI schemas
  • page_title: Title for the web interface (default: "Function Interface")
  • custom_css: Optional custom CSS string
  • rjsf_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 server
  • call(func_name, **kwargs): Call a function by name
  • get_spec(func_name): Get RJSF spec for a function
  • list_functions(): List all function names

FunctionSpecStore(funcs, **kwargs)

Mapping-based interface to function specifications.

RjsfFieldConfig(**kwargs)

Configuration for individual form fields.

Attributes:

  • widget: Widget type
  • format: JSON Schema format
  • enum: List of allowed values
  • placeholder: Placeholder text
  • description: Help text
  • And more...

FunctionGroup(name, funcs, **kwargs)

Group of functions with metadata.

Attributes:

  • name: Group name
  • funcs: List of functions
  • description: Group description
  • icon: Icon identifier
  • order: Display order

InputTransformRegistry()

Registry for custom type transformations.

Methods:

  • register_type(py_type, **kwargs): Register a type
  • mk_input_trans_for_funcs(funcs): Create input transformation
  • mk_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 apps
  • UfAppTester(app): Context manager for testing
  • test_ui_function(func, params, **kwargs): Test individual functions
  • FormDataBuilder(): Build test form data
  • assert_valid_rjsf_spec(spec): Assert spec is valid
  • assert_has_field(spec, name): Assert field exists
  • assert_field_type(spec, name, type): Assert field type
  • assert_field_required(spec, name): Assert field is required

Architecture

uf follows these design principles:

  1. Convention over Configuration: Works out-of-the-box with sensible defaults
  2. Mapping-based Interfaces: Access everything as dictionaries
  3. Lazy Evaluation: Generate specs only when needed
  4. Composition over Inheritance: Extend via decorators and transformations
  5. Progressive Enhancement: Start simple, customize as needed

Development Roadmap

✅ Milestone 1: MVP (Completed)

  • Core mk_rjsf_app function
  • 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 generation
  • ju: RJSF form generation and JSON utilities
  • i2: Signature introspection
  • dol: Mapping interfaces
  • meshed: 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.

About

UI Fast

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages