API Reference¶
This page documents the public surface of the top-level arvel package — every symbol in arvel.__all__. It's generated from the installed package's docstrings and type signatures, so it always matches the version you have.
Note
Anything not listed here is internal and may change without a major version bump. Import from arvel (e.g. from arvel import Route), not from deep submodules, unless a guide tells you otherwise.
Application & lifecycle¶
Application ¶
The framework kernel — owns the root container + provider lifecycle.
register_service ¶
Register a BaseService into the managed lifecycle.
connect() runs at boot (registration order); disconnect() at shutdown (reverse). Registering after boot connects immediately is not supported — register before boot().
register ¶
Bootstrap the framework's baseline providers without a project base_path.
Convenience for test setups that need a minimal Application with container bindings but don't need a full project directory. Production code should use Application.configure(base_path).create() instead.
iter_providers ¶
Yield each registered ServiceProvider instance, in registration order.
Used by ConsoleServiceProvider.boot to collect commands from every provider without reaching into _provider_instances directly.
into_asgi ¶
Produce a fully wired ASGI app. The framework owns the lifecycle.
Boot and shutdown are driven by the ASGI lifespan protocol, not by this factory call. That keeps the method loop-agnostic — it works the same whether called from a plain sync entrypoint or from inside a uvicorn --factory callback (which uvicorn invokes from within its own running event loop).
Behaviour:
- Always returns a configured
FastAPIinstance (re-exported asarvel.ASGIApp). Routes are mounted, the exception handler is wired, and the container is stashed onapp.state.arvel_container. - When
lifespanis omitted, wires a default lifespan that callsawait self.boot()on startup (skipped if already booted) andawait self.shutdown()on graceful exit. - When
lifespanis provided, uses it verbatim — the caller takes ownership of boot and shutdown. **fastapi_kwargsare forwarded verbatim to theFastAPIconstructor (e.g.docs_url,redoc_url,openapi_url,title,version).
The returned app expects to be driven by a lifespan-aware ASGI host (uvicorn, hypercorn, Starlette TestClient used as a context manager). Routing still works without a lifespan, but providers will not have booted.
ApplicationBuilder ¶
Fluent builder for Application.
with_routing ¶
with_routing(
*,
web: Path | str | None = None,
api: Path | str | None = None,
console: Path | str | None = None,
) -> Self
Register routing file paths to be loaded at register time.
``web`` and ``api`` are loaded by ``HttpServiceProvider.register``.
``console`` is stored on the application but not loaded until the
Console provider ships.
At least one of the three must be non-None. Subsequent calls
accumulate (last-write-wins per key).
with_config_dir ¶
Discover and load every .py config file in path at register() time.
See arvel.application._loader.discover_config_files for the discovery rules and arvel.config.lookup for the runtime accessor. Files are loaded under namespaced module names so a user's config/logging.py never shadows stdlib logging.
serve ¶
Run the app under uvicorn.
Auto-boots the application if it has not been booted yet (via Application.into_asgi()), so this is callable from a plain sync entrypoint.
BootError ¶
Bases: RuntimeError
Raised when a provider fails during register() or boot().
ShutdownError ¶
Bases: RuntimeError
Raised when a provider fails during shutdown().
Service container & dependency injection¶
Container ¶
The DI container.
call ¶
Resolve cls from the container, then call method with injected params.
Parameters are resolved from the container; overrides bypass injection for the specified parameter names. If cls is not bound, instantiates it directly (no-arg constructor assumed).
acall async ¶
Async variant of call() — supports async methods and async-resolved deps.
Scope ¶
Bases: StrEnum
Lifetime of a binding's resolved instance.
dep ¶
FastAPI bridge: Depends(arvel.dep(MyService)) resolves from the request scope.
dep ¶
Return a FastAPI-compatible resolver for abstract.
The resolver expects the request to expose request.state.arvel_scope, an instance of arvel.container.Container (created per-request by the framework's scope middleware, shipped fully).
AsyncBindingError ¶
BindingResolutionError ¶
Bases: Exception
Raised when the container cannot resolve a binding.
CircularDependencyError ¶
Configuration & environment¶
config ¶
Typed configuration layer (pydantic-settings v2 based).
ConfigKeyError ¶
Bases: KeyError
Raised when a dotted config key cannot be resolved.
Distinct from the stdlib KeyError so callers can disambiguate config-lookup failures from generic dict misses.
CacheConfig ¶
Bases: ArvelSettings
Cache subsystem settings.
Two sources, in priority order:
CACHE_URL— full Redis URL (e.g.redis://host:6379/0). Wins when set.-
CACHE_CONNECTION+ fine-grainedCACHE_*: -
CACHE_CONNECTIONdriver name:redis,file,array,database,null CACHE_HOSTRedis host (default:localhost)CACHE_PORTRedis port (default: 6379)CACHE_PASSWORDRedis password (default: empty)CACHE_DATABASERedis DB index (default: 0)CACHE_PREFIXkey prefix (default:arvel_cache)CACHE_TTLdefault TTL in seconds (default: 3600)CACHE_FILE_PATHpath for file store (default:storage/framework/cache)CACHE_GC_PROBABILITYfile store GC % (default: 2)
When neither CACHE_URL nor CACHE_CONNECTION is set, enabled is False and the manager defaults to the array store (safe in-process default, no external connection needed).
Redis has no async driver suffix like SQLAlchemy. The redis.asyncio package handles async natively — specify CACHE_CONNECTION=redis and the manager uses redis.asyncio.Redis (or from_url when CACHE_URL is set).
array cache is fully supported: an in-process dict store with optional TTL. Useful in tests and single-process dev environments.
DbConfig ¶
Bases: ArvelSettings
Database connection settings.
Two sources, in priority order:
DB_URL— full SQLAlchemy async URL. Wins when set; all other fine-grained vars are ignored for the connection string itself (echo,pool_size,max_overflow,pool_recyclestill come from their own vars).-
DB_CONNECTION+ fine-grainedDB_*— composed into a URL byasync_url(): -
DB_CONNECTIONfriendly name:postgresql,mysql,sqlite,memory DB_HOST(default: empty)DB_PORT(default: 0)DB_DATABASE(default:<base_path>/database/database.sqlitefor sqlite,:memory:whenDB_CONNECTION=memory)DB_USERNAME(default: empty)DB_PASSWORD(default: empty)DB_ECHObool; defaultFalseDB_POOL_SIZE(default: 5)DB_MAX_OVERFLOW(default: 10)DB_POOL_RECYCLEseconds; default 1800
When neither DB_URL nor DB_CONNECTION is set the database subsystem is treated as disabled (enabled == False). The provider skips its startup ping and the engine falls back to sqlite+aiosqlite:///:memory: so code that accidentally touches the ORM fails fast rather than silently persisting to a stale file.
async_url ¶
Return the async SQLAlchemy URL.
DB_URL wins when set. Otherwise the URL is built from DB_CONNECTION + fine-grained fields.
base_path is used to resolve the default SQLite file path (<base_path>/database/database.sqlite) when DB_DATABASE is not set and the driver is SQLite.
ConfigError ¶
Bases: Exception
Base class for all arvel.config errors.
ConfigNotRegisteredError ¶
NoPrefix ¶
Marker: read this field from the bare uppercase env var, no prefix.
Config ¶
Looks up a registered ArvelSettings subclass from the bound container.
SessionConfig ¶
Bases: ArvelSettings
Session subsystem settings.
Env vars (auto-prefixed SESSION_):
SESSION_DRIVER(default:cookie)SESSION_LIFETIME(seconds; default: 7200)SESSION_ENCRYPT(bool; default: True)SESSION_COOKIE_NAME(default:arvel_session)SESSION_SECURE(bool; default: False — True in production)SESSION_SAME_SITE(default:lax)SESSION_FILES_PATH(default:storage/framework/sessions)SESSION_GC_PROBABILITY(default: 2 — percentage)SESSION_SECRET_KEY(required when encrypt=True)SESSION_REDIS_URL(default:redis://127.0.0.1:6379/0)SESSION_REDIS_PREFIX(default:arvel:)SESSION_DATABASE_URL(default:sqlite+aiosqlite:///sessions.db)
ArvelSettings ¶
Bases: BaseSettings
Base class for typed configuration sections.
Behavior: - env_prefix is auto-derived from the class name (DbConfig → DB_). Override by setting model_config["env_prefix"] in the subclass. - env_nested_delimiter="_" — nested fields use a single underscore. - env_file=".env" — loaded if present. - extra="ignore" — unknown env vars don't blow up. - case_sensitive=False.
Subclasses that need strict .env parsing (extra="forbid") should opt into dotenv_filtering="match_prefix" themselves — applying it on the base would silently drop legitimate aliased fields (e.g. DB_URL reaching DbConfig.url).
from_environment classmethod ¶
Load + validate the settings, wrapping ValidationError into ConfigError.
S3Config ¶
Bases: ArvelSettings
AWS S3 or any S3-compatible provider (MinIO, R2, Hetzner, B2, …).
Point endpoint at a non-AWS S3 endpoint to use a compatible provider. See docs/site/docs/filesystem.md for per-provider worked examples.
StorageConfig ¶
Bases: ArvelSettings
Storage subsystem settings.
Env vars (auto-prefixed STORAGE_):
STORAGE_DEFAULT(default:local)
config ¶
Laravel-style config accessor with optional default.
Reads from the modules loaded via ApplicationBuilder.with_config_dir(). Uses the same dotted-key syntax as lookup():
config("app.timezone")— returns the value, orNoneif not foundconfig("app.timezone", "UTC")— returns"UTC"when the key is missingconfig("db.pool_size", 5)— default type informs the return type
Unlike lookup(), this never raises ConfigKeyError — a missing key returns the default (or None when no default is given).
lookup ¶
Resolve a dotted config key against the loaded config modules.
key is dotted: "<module_stem>.<ATTR>[.<sub_attr>...]". The first segment names the config/<stem>.py file; subsequent segments traverse attribute or subscript access on the result.
Examples (after with_config_dir(p / 'config') loaded config/database.py and config/app.py):
lookup('database.DEFAULT')→ theDEFAULTattribute on the database module.lookup('database.CONNECTIONS.sqlite')→ thesqliteentry of theCONNECTIONSdict.lookup('app.NAME')→ theNAMEattribute on the app module.
Raises ConfigKeyError if any segment cannot be resolved.
register ¶
Mark a config class for auto-registration. Usable as a decorator.
NoPrefix ¶
Marker: read this field from the bare uppercase env var, no prefix.
ConfigError ¶
Bases: Exception
Base class for all arvel.config errors.
ConfigNotRegisteredError ¶
Routing¶
Router ¶
url ¶
Resolve a relative path against APP_URL.
Idempotent for already-absolute URLs.
RouteServiceProvider ¶
RoutingError ¶
Bases: ValueError
Raised on routing-layer misuse — missing params, missing APP_URL, etc.
Subclasses ValueError so callers that handled the previous behaviour (ValueError raised when a param was missing) still match.
HTTP — controllers, requests & resources¶
Controller ¶
Marker / convenience base for HTTP controllers.
Subclasses may be: - Multi-action: declare methods (index, show, store, ...) and bind one route per method via Route.get("/...", controller=MyController, action="index"). - Invokable: declare async def __call__(self, ...) and bind via Route.get("/...", controller=MyController).
All controllers are resolved via the Arvel container.
FormRequest ¶
Bases: Generic[T]
Holds a validated payload + per-request authorization decision.
Subclasses parameterize the payload model:
class StoreUserRequest(FormRequest[StoreUserPayload]):
async def authorize(self, request: Any) -> bool:
return request.state.user is not None
The Arvel routing layer rewrites the handler signature so FastAPI parses the body as StoreUserPayload, then constructs StoreUserRequest(payload) and awaits authorize(request) before the handler runs.
JsonResource ¶
Bases: Generic[T]
Transform a single domain object into a JSON-ready dict.
Subclasses set the generic parameter and implement to_dict(request). Optionally set schema: ClassVar[type[BaseModel]] to surface an OpenAPI schema for this resource — opt-in only.
Any value returned as self.when(...) or self.when_loaded(...) that evaluates to the internal sentinel is automatically stripped from the dict returned by to_dict().
additional ¶
Merge extra root-level keys into the dict returned by to_dict.
Extras win on key clashes. Chainable.
collection classmethod ¶
Wrap resources (a list or any paginator) in a ResourceCollection.
With a list, the output is {"data": [...]} — overridable via ResourceCollection.wrap. With a paginator, the output mirrors the paginator's own {data, meta, links} envelope with each item transformed by this resource class.
when ¶
Return value when condition is truthy, otherwise a missing sentinel.
The sentinel is stripped from the dict returned by to_dict() automatically.
when_loaded ¶
Return the relation value if it's already in the resource's __dict__.
Never triggers a lazy load — safe to call on SQLAlchemy models.
merge_when ¶
Return data when condition is truthy, otherwise an empty dict.
response ¶
response(
request: Any,
*,
status_code: int = 200,
headers: Mapping[str, str] | None = None,
) -> ResourceResponse
Build a Starlette JSON response from to_dict(request).
ResourceCollection ¶
ResourceCollection(
resources: list[T],
resource_cls: type[JsonResource[T]],
*,
paginator: Paginatable[T] | None = None,
)
Bases: Generic[T]
Transform a list (or paginator) of domain objects under one envelope.
Default wrap returns {"data": data} for the list path. The paginator path bypasses wrap — its envelope is whatever the paginator's own to_dict returns, with items transformed by resource_cls.
additional ¶
Merge extra root-level keys into the envelope returned by to_dict.
Extras win on key clashes — they merge after the default envelope (or the paginator's envelope) is built. Chainable.
response ¶
response(
request: Any,
*,
status_code: int = 200,
headers: Mapping[str, str] | None = None,
) -> ResourceResponse
Build a Starlette JSON response from to_dict(request).
wants_json ¶
Returns True if the caller wants a JSON response.
Heuristics, in order: 1. URL path starts with /api (case-insensitive). 2. Accept header mentions application/json. 3. X-Requested-With header equals XMLHttpRequest (XHR sentinel).
HTTP — middleware¶
Middleware ¶
Bases: Protocol
Route-level middleware. Composed by the arvel.support.Pipeline.
Authenticate ¶
Resolves request.state.user via a named guard from AuthManager.
guard_name selects the guard configured in config/auth.py. Defaults to "web", which maps to AuthManager.guard(None).
Cors ¶
Cors(
app: ASGIApp,
*,
allowed_origins: Sequence[str] = (),
allowed_methods: Sequence[str] = (
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
),
allowed_headers: Sequence[str] = (
"Authorization",
"Content-Type",
"X-Requested-With",
),
allow_credentials: bool = False,
max_age: int = 600,
)
Bases: CORSMiddleware
Hardened wrapper around Starlette's CORSMiddleware.
Refuses the wildcard origin with credentials — a well-known browser footgun.
Throttle ¶
Throttle(
max_attempts: int,
*,
decay_seconds: int = 60,
key: Callable[[Any], str] | None = None,
store: RateLimiterStore | None = None,
)
Rate-limit by key. Adds X-RateLimit-* headers on every response.
VerifyCsrf ¶
Double-submit CSRF check. Skips safe methods and except_paths.
Uses secrets.compare_digest for constant-time token comparison.
HTTP — authentication & rate limiting¶
Guard ¶
JwtGuard ¶
Bases: Guard
issue_token async ¶
issue_token(
*,
subject: str,
expires_in: timedelta,
claims: dict[str, object] | None = None,
) -> str
Mint a short-lived access JWT (typ=access).
Used by the upcoming AuthBroker to mint the access leg of the access+refresh token pair. Refresh tokens are opaque (not JWTs) and live in the refresh_tokens table via :class:RefreshToken.
SessionGuard ¶
UserResolver ¶
Bases: Protocol
Lookup contract for guards.
RateLimiterStore ¶
Bases: Protocol
Pluggable backend for the Throttle middleware.
RedisStore ¶
Redis-backed store for multi-process deployments.
Imports redis lazily so the dependency is only required when this store is actually instantiated (arvel[redis] extra).
HTTP — exceptions¶
HttpException ¶
HttpException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: Exception
Base class for typed HTTP errors thrown by handlers/middleware.
HttpExceptionHandler ¶
HttpExceptionHandler(
*,
translators: Mapping[
type[Exception], ExceptionTranslator
]
| None = None,
)
Central translator: HttpException → JSON response. Wires into a FastAPI app.
add_translator ¶
Register a foreign exception → HttpException translator.
AuthorizationException ¶
AuthorizationException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
BadRequestException ¶
BadRequestException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
ConflictException ¶
ConflictException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
MethodNotAllowedException ¶
MethodNotAllowedException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
NotFoundException ¶
NotFoundException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
ServerErrorException ¶
ServerErrorException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
ThrottleException ¶
ThrottleException(
message: str,
*,
retry_after_seconds: int,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
UnauthenticatedException ¶
UnauthenticatedException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
ValidationException ¶
ValidationException(
message: str,
*,
status_code: int | None = None,
details: Sequence[dict[str, Any]] | None = None,
)
Bases: HttpException
Service providers¶
ServiceProvider ¶
Bootstrap unit. Subclass to register bindings and run boot/shutdown logic.
safe_config ¶
Resolve a config class from the container; return default on any failure.
Use this when config is optional — the provider falls back to a safe default when the application hasn't registered the settings class.
commands ¶
Console commands shipped by this provider.
Each item may be either a Command subclass (instantiated with no args by ConsoleServiceProvider.boot) or a pre-built Command instance (used when the provider needs to inject dependencies that come from the container).
provides ¶
Abstracts this provider promises to bind. Used by deferred-provider logic (future WI).
publishes ¶
publishes(
paths: Mapping[str | Path, str | Path],
*,
tag: str = "default",
is_migrations: bool = False,
) -> None
Register source-to-destination publishables under tag.
Mirrors Laravel's $this->publishes([...], 'tag'). Consumers run arvel vendor:publish --tag=<tag> (or --provider=<class>) to copy the registered files into their app.
Parameters¶
paths: Mapping of source file path → destination path. Both may be str or Path. Relative destinations resolve against Application.base_path. tag: Group label used by vendor:publish --tag=.... is_migrations: When True, each destination is treated as a target directory and the basename is rewritten with a UTC timestamp at publish time so the file lands chronologically in database/migrations/.
HttpServiceProvider ¶
Support helpers¶
Arr ¶
Laravel Illuminate\Support\Arr parity helpers.
flatten staticmethod ¶
Flatten nested lists/tuples. depth=-1 (default) flattens fully.
dot staticmethod ¶
Flatten a nested mapping into a.b.c keys.
undot staticmethod ¶
Inverse of :meth:dot — rebuild nested dicts from dotted keys.
wrap staticmethod ¶
Normalize None/scalar/tuple to a list. Returns list[Any] by design.
shuffle staticmethod ¶
Cryptographically secure shuffle. For deterministic shuffles use random.Random.
Str ¶
Laravel Illuminate\Support\Str parity helpers, all static.
plural staticmethod ¶
Naive English pluralization used by the code generators.
post → posts, category → categories. Already-plural words (trailing s) are left alone. Irregulars (person, child) aren't handled — override the table name when it matters.
headline staticmethod ¶
hello_world_greeting / helloWorld → Hello World ….
password staticmethod ¶
password(
length: int = 32,
*,
letters: bool = True,
numbers: bool = True,
symbols: bool = True,
spaces: bool = False,
) -> str
Generate a cryptographically secure random password string.
Matches Laravel's Str::password — returns the password plaintext, not a hash. Pass the result to Hash.make() to store it.