Typing¶
Wybthon requires Python 3.14. Its accessors, components, actions, and stores carry type information that both pyright (including Pylance) and mypy understand with no plugin or extra configuration.
Component props¶
Components declare their inputs on a Props subclass. Props is a PEP 681 dataclass_transform base, so a type checker treats each props class as a keyword-only constructor and checks every component call against it:
from collections.abc import Callable
from wybthon import Accessor, Prop, Props, VNode, component, create_signal, p, prop
class GreetingProps(Props):
name: Prop[str]
copies: Prop[int] = prop(default=1)
on_wave: Callable[[], None] | None = None
@component
def Greeting(props: GreetingProps) -> VNode:
label: Accessor[str] = props.name
return p(lambda: label() * props.copies())
name, set_name = create_signal("Ada")
Greeting(name="Ada")
Greeting(name=name, copies=2)
Greeting(name=lambda: "Grace", on_wave=lambda: print("hi"))
- A
Prop[T]field accepts a plainT, anAccessor[T], or a zero-argument function returningT. Inside the component, reading the field returns anAccessor[T]. - A plain field (any annotation other than
Prop[...]) is typed as declared.props.on_waveisCallable[[], None] | None. - A missing required prop, a value of the wrong type, and an unknown keyword are all type errors:
Greeting(name=42) # incompatible type
Greeting() # missing "name"
Greeting(name="Ada", typo=1) # unexpected keyword "typo"
At run time, dev mode raises TypeError for the same unknown and missing props, so code that isn't type checked still fails loudly.
Defaults need default=¶
Write defaults as prop(default=value), or prop(default_factory=list) for mutable values. Type checkers only recognize a field specifier's default when it's passed by keyword, so a positional prop(0) isn't accepted. Plain fields use ordinary defaults (on_wave: ... = None); prop() is only for Prop[T] fields.
Children¶
ParentProps declares children: Prop[Any]. Both the children keyword and item syntax are checked:
from wybthon import ParentProps, Prop, VNode, component, div, h2, p
class CardProps(ParentProps):
title: Prop[str]
@component
def Card(props: CardProps) -> VNode:
return div(h2(props.title), props.children)
Card(title="Hi")[p("a"), p("b")]
Card(title="Hi", children=[p("a"), p("b")])
Positional children (Card(p("a"), title="Hi")) work at run time, but checkers reject them because the props constructor is keyword-only.
What a component call returns¶
@component is typed so that calling a component looks like constructing its props class; that's how the checker validates the keywords. At run time the call returns a VNode. Every child position in the HTML helpers accepts the props type, so this mismatch only shows if you inspect the result yourself: an isinstance check sees a VNode. A component with no parameters is typed as a function returning VNode.
Under mypy --strict, annotate component functions with -> VNode, as above; the HTML helpers return VNode.
Accessors, setters, and memos¶
Accessor[T]is readable: call it for a tracked read, or.peek()for an untracked one.Setter[T]accepts a value or an updater functionT -> T.Memo[T]is a disposable derived accessor. Anasync defpassed tocreate_memoproduces aMemoof the awaited type.
from wybthon import Accessor, Memo, create_memo, create_signal
count, set_count = create_signal(0)
doubled: Memo[int] = create_memo(lambda: count() * 2)
reader: Accessor[int] = count
set_count(lambda n: n + 1)
Actions¶
An @action keeps its parameters and result type. An async action returns an asyncio.Future[T], which supports cancellation, and the action object has .pending().
from wybthon import action
@action
def save(name: str) -> int:
return len(name)
length: int = save("Ada")
Stores¶
create_store returns a Store[S] (a read-only mapping) or a StoreList[T] (a read-only sequence), and setters receive the matching draft type. A store keeps its schema as a type parameter, but reads of individual keys (store.name, store["name"]) are typed Any, because a mapping proxy can't express per-key types without a checker plugin. Annotate where you read when the type matters:
from typing import TypedDict
from wybthon import create_store
class Person(TypedDict):
name: str
age: int
person, write = create_store(Person(name="Ada", age=36))
name: str = person.name
age: int = person["age"]
write(lambda draft: setattr(draft, "name", "Grace"))
Mapping method names stay methods, so read a data key that collides with one (items, keys, get) with []. A TypedDict annotation isn't runtime validation.
Running the checkers¶
No plugin is needed. A minimal configuration:
[tool.mypy]
python_version = "3.14"
strict = true
[tool.pyright]
pythonVersion = "3.14"
typeCheckingMode = "strict"
The repository's tests/typing fixtures check valid calls and expected errors (missing, mistyped, and unknown props) as part of the unit test gate.
Next steps¶
- Read Components for the full props model.
- See Authoring patterns for plain fields, children, and forwarding attributes.
- Browse the
reactivityAPI forProps,Prop, andprop.