The binding is a thin native layer, and every rule below follows from that.
- Processing stays native. Parameter binding, percent-decoding, conversion, reply serialization and the blocking client calls run in the extension, and
ucall/__init__.pyonly re-exports it. A handler’s signature is resolved once at registration, so a request costs a table walk and one call, and a typed handler never sees aRequestobject at all. - Verbs share one name across languages.
take_call,take_calls,take_batch,take_batches,submitandtake_completedname the same calls in Python and Rust. - Ownership follows the request. A reply consumes its
Request, a released request answers 500, views over a body pin it until released, and aResponsefrees itself. - Handlers never stop a loop. An exception inside a handler or an authorizer reaches
sys.unraisablehookand answers 500 or rejects the peer, and Ctrl+C raised inside one is re-armed for the main thread. - Blocking stays simple. Every method looks as it would without asynchrony, and only a reply with file or path parts answers a
Deferred. - Deferred replies are symmetric through language idioms. A
Deferredowns its outcome:wait()blocks,awaitborrows the running loop’s executor, and releasing it waits, as releasing any owner does. The verbs that may defer exist only on aDetachedRequest, so by type no polling thread waits on its own loop. - No extra cost until used. Bytes-only replies keep their outcome on the stack, up to sixteen reply parts lay out on the stack, and a
Deferredis the one allocation a file reply adds. - No life of its own. The binding starts no threads, keeps no global state beyond imported helpers, and needs no async runtime, so it drops into any threading model.
- Statuses and modes are enums.
ucall.Status,Backend,CPUClass,Security,Verb,BodyDeliveryandLogLevelareenum.IntEnumclasses, so nothing is ever compared as a string.