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

Authors#

Part of the i2mint ecosystem.

This documentation as a single file: uf.md (Markdown, for agents).

built 2026-10-13 17:57 UTC from cf171c5 (master) · uf 0.1.16 · about this build