Extensions
webfluid.extensions — the base extension and the battery set. All of them
follow the same shape: a class with an expand_fluid method, instantiated once and
reachable through webfluid.core.ext.
FluidExtension
from webfluid.extensions import FluidExtension — the base class for all extensions.
expand_fluid(fluid, *args)— override to hook into the app; called automatically when an extension is constructed with a fluid.cli_entry(app, name)— mounts the extension's_cliTyper app underwf.
SQLAlchemy
Model— the declarative base; resolves__tablename__from the snake-cased class name, supports__bind_key__andModel.set_bind(key).executor(bind_key=None, model=None)/async_executor(...)— context managers yielding an executor withexec,insert,delete,flush; commit/rollback handled for you, and rows survive the block.ensured_executor(...)/ensured_async_executor(...)— reuse an already-open executor or open a fresh one;current_executor/current_async_executorreach the active one or raise.- The executors subclass
BaseContext, socurrent(),try_current()andouter()(see Core) work on them too;db.bind_keyslists the configured binds. exec(statement, scalars=True)— returns a ScalarResult by default; passscalars=Falsefor the raw Result.get_bind_for_model(model)/get_bind(key)— resolve theBind(with its lazily createdsync_engine/async_engineand its ownmetadata) for a model or key.- Config:
SQLALCHEMY_DATABASE_URI,SQLALCHEMY_BINDS,SQLALCHEMY_ENGINE_OPTIONS. Drivers are derived; don't put them in the uri.
Babel
gettext,ngettext,pgettext,npgettext, their awaitablea*twins and theirlazy_*variants (which stay synchronous, since a lazy string resolves throughstr()).locale_selector(fn)/timezone_selector(fn)— register custom resolvers.force(locale, timezone)/aforce(...)— context managers to pin locale/timezone.register_domain(name, package=None),domain_context(name),current_domain,update_translations(domain, translations)— runtime, db-backed catalogs.load_locale(locale), the CLI (wf babel extract/compile), and the helpers inwebfluid.extensions.babel.utils(translation_resolver,get_locale,get_timezone,parse_best_match, and theformat_date/format_currency/ ... filters).- Companions:
Domain,I18nKey/I18nMessage(the translation models),LazyString.
Security
Reached through webfluid.core.ext.security; needs EXT_SECURITY and EXT_SQLALCHEMY.
user_service— the current-user dependencies (current_user,require_user,require_2fa,require_admin), therequire_roles/require_permissions(plus_any_) guard factories, and a_fntwin of each that skips theDependswrapper.- Predicates for a user you already hold:
has_2fa,is_admin,has_roles,has_any_role,has_permissions,has_any_permission,check_requirement. All buthas_2faare awaitable. requirement_or_grant(requirement, grant)/requirement_and_grant(...)— accept a session, a JWT grant, or both;resolve_bearer/bearer_principalfor the token side alone.hash_service— argon2hash/verifyand the thread-pooledahash/averify.token_service— CSRF (csrf_protect,csrf_response) and signed single-use tokens (generate_token,validate_token, backed by theExpiredTokenmodel).oauth_service— authlib-backed social login: theclient,prepare_sessionanduserinfodependencies,authorize_response, andregister_provider/unregister_providerfor runtime changes.- Models in
webfluid.extensions.security.models:User,Identity,Role,Permission,TOTPSecret,WebAuthnCredential,BackupCode,ExpiredToken; validatorsvalidate_username/validate_passwordand thePasswordPolicybehind them.
EventManager
create_signal(name, singleton=False, internal=False)— declare an event channel. Needs the running loop, so call it from a startup or enable hook.event(name, ...)/query(name, ...)— decorators registering handlers;internal=Falseexposes them to the browser.trigger(event, data)— publish, synchronously and without waiting;request(query, data)— ask and await a result;listen(event)— async stream.- Client:
window.wf.ext.events.EventManager.
send(to, subject, body, ...)—bodyis a MIME-subtype map; threads delivery by default (fake_async).asend(...)— the awaited variant. Both acceptattachments,cc,bcc,from_email.client()/async_client()— bare SMTP connections as context managers. A send inside one reuses that connection instead of opening its own.
Cache
set/get/delete/clearand the asyncaset/aget/adelete/aclear.- Backends selected by
CACHE_TYPE: legacy (in-process, self-expiring) or redis. Reachable ascache.backend.
JWTManager
encode(payload, audience="default", expire=None)/decode(token, audience="default")and the asyncaencode/adecode.- Requires
EXT_SCHEDULINGandEXT_CACHE; rotates its signing secret on a schedule and keeps recent ones in the cache under their key id. - A payload carrying a
jtiis checked againstjwt:revoked:<jti>in the cache on decode.
Migrate
A CLI-only extension (wf migrate ...) wrapping Alembic. It builds your app through prepare_fluid() in main.py:
init <app>— enroll the matching single- or multi-db template.revision <app> [-a] [-m]— create a revision (optionally autogenerated).upgrade <app>/downgrade <app> [-r]— apply / revert migrations.
Continue reading
From here you can continue straight with Surface.