Skip to main content

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:

  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.

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

uf-0.1.1.tar.gz (48.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

uf-0.1.1-py3-none-any.whl (48.0 kB view details)

Uploaded Python 3

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

Hashes for uf-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c7280069fccaf7fb60a757490377c5a01c8bf854218c1797b91ddf6b4c7534cf
MD5 1d58330eadfb5be564a9185824e9f4a2
BLAKE2b-256 c63b27fee0a7d86e9ebbcdf24169a2992197d4275a1bfa8cbf6417cfec15ab43

See more details on using hashes here.

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

Hashes for uf-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f0f1360502b5f4fc394ba158366951d65fb9ac11f57c16b34d5c52b7edd7463c
MD5 f2abea5ee3973e8a993fa67ec934706e
BLAKE2b-256 b348e7ffa2bfe75bcb1fc57add7534602017bb90ef20a62c5a42c94c4fd973ef

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page