Skip to content

Latest commit

 

History

History
166 lines (114 loc) · 4.78 KB

File metadata and controls

166 lines (114 loc) · 4.78 KB

Params

Params is a base class that tells FuncToWeb to expand its annotated fields into the form automatically. Subclassing it turns your class into a frozen dataclass: instances are constructible with keyword arguments, comparable, hashable and immutable. If you know the dataclasses standard library, you already know Params.

Basic Usage

from typing import Annotated
from pydantic import Field
from func_to_web import run, Params
from func_to_web.types import Email

class UserData(Params):
    name:  Annotated[str, Field(min_length=2, max_length=50)]
    email: Email
    age:   int = 18

def basic(data: UserData):
    return f"Created: {data.name}, {data.email}, {data.age}"

run(basic)

FuncToWeb expands UserData into three individual form fields — name, email, and age — exactly as if you had declared them directly on the function.

Basic Usage

Reusing Across Functions

The main use case for Params is sharing the same fields across multiple functions without repeating yourself:

from typing import Annotated
from pydantic import Field
from func_to_web import run, Params
from func_to_web.types import Email

class UserData(Params):
    name:  Annotated[str, Field(min_length=2, max_length=50)]
    email: Email

def create_user(data: UserData):
    return f"Created: {data.name}"

def edit_user(id: int, data: UserData):
    return f"Edited user {id}: {data.name}"

run([create_user, edit_user])

Change UserData once and it updates every function that uses it.

Creating Users Editing Users

It's a Frozen Dataclass

Subclassing Params applies @dataclass(frozen=True) for you. Instances behave like any frozen dataclass — constructible anywhere, comparable by value, hashable, and immutable:

from func_to_web import Params
from func_to_web.types import Email

class UserData(Params):
    name:  str
    email: Email

UserData(name="Ana", email="a@b.com")   # build one anywhere — tests, scripts
# == compares by value, instances are hashable, repr is readable
# data.name = "other"  -> raises FrozenInstanceError (immutable)

You can still add methods, properties and class variables — FuncToWeb reads only the type-annotated fields; everything else is ignored by the form renderer:

class UserData(Params):
    name:  str
    email: Email

    @property
    def display(self):
        return f"{self.name} <{self.email}>"

It's a Frozen Dataclass

Cross-field validation with __post_init__

Validation that spans more than one field goes in __post_init__. A ValueError raised there surfaces in the form as a 422 validation error. This validation runs on the server — the error appears on submit, not while you type:

from func_to_web import run, Params

class Range(Params):
    start: int = 0
    end:   int = 10

    def __post_init__(self):
        if self.start > self.end:
            raise ValueError("start must be <= end")

def use_range(r: Range):
    return f"Range: {r.start}..{r.end}"

run(use_range)

To derive a field inside __post_init__, assign it with object.__setattr__ (the standard pattern for frozen dataclasses, since direct assignment is blocked):

def __post_init__(self):
    object.__setattr__(self, "span", self.end - self.start)

Variants with dataclasses.replace()

Instances are immutable, so to get a modified copy use dataclasses.replace() instead of mutating:

from dataclasses import replace

base = UserData(name="Ana", email="a@b.com")
other = replace(base, name="Bob")   # new instance; base is untouched

Mixing Params with Other Parameters

from typing import Annotated
from pydantic import Field
from func_to_web import run, Params

class Address(Params):
    street: str
    city:   str
    zip:    Annotated[str, Field(pattern=r'^\d{5}$')]

def mixing(user_id: int, address: Address, notify: bool = True):
    return f"User {user_id} registered at {address.city}"

run(mixing)

Default Values

Default values work exactly as in any Python class:

from func_to_web import run, Params

class Settings(Params):
    theme:    str = "dark"
    language: str = "en"
    retries:  int = 3

def defaults(settings: Settings):
    return f"Theme: {settings.theme}, Lang: {settings.language}"

run(defaults)

Limitations

  • Params cannot be nested: a Params field inside another Params class is rejected at startup
  • Params cannot be optional: data: UserData | None is rejected at startup — make individual fields optional inside the class instead
  • Field names must be unique across the flattened form: a Params field clashing with a function parameter (or a field from another Params) is rejected at startup