Skip to content

v0.4

The 0.4 line modernizes Uvicore's web stack (Pydantic v2, the latest FastAPI and Starlette) and sharpens its two most foundational pieces, the database layer and the ORM, making them more expressive, more portable across database engines, and more robust.

This page tracks every release in the 0.4 line, newest first.

Python 3.12.x, 3.13.x and 3.14.x validated working with Uvicore 0.4.x versions!


0.4.2

Consolidates Uvicore's async HTTP client onto a single library.

Breaking: aiohttp removed

The bundled HTTP Client switched from aiohttp to httpx, and aiohttp is no longer a dependency. Code that calls uvicore.ioc.make('aiohttp') must migrate. See Upgrade 0.3 to 0.4 → HTTP Client.

HTTP Client: aiohttp → httpx

Uvicore previously bundled two async HTTP libraries: aiohttp for the runtime client (the http_client IoC service and the Mailgun mail backend) and httpx for the in‑process ASGI test client. Since httpx is already part of the FastAPI/Starlette stack — and is the only one of the two that can drive an ASGI app in‑process for tests — 0.4.2 drops aiohttp and standardizes everything on httpx.

  • Shared client is now httpx.AsyncClient. Resolve it with uvicore.ioc.make('http_client') or uvicore.ioc.make('httpx') (the old 'aiohttp' alias is gone).
  • Mailgun mail backend re‑implemented on httpx (data=/files= multipart, (user, pass) basic auth).
  • Requests are awaited directlyr = await http.get(url) — with no async with, and the response is read synchronously (r.status_code, r.text, r.json()).
  • httpx unpinned from 0.26.* to 0.28.*: the test client now uses the modern AsyncClient(transport=ASGITransport(app=...)) API instead of the removed app= shortcut.

The HTTP Client guide was rewritten around httpx, with a full aiohttp → httpx migration table.


0.4.0

The opening 0.4 release. The headline change is a modernized web stack (Pydantic v1 → v2, FastAPI 0.137, Starlette 1.3), alongside inline table definitions, a greatly expanded query operator set, cross-database hardening and ORM fixes.

Breaking release

The Pydantic v2 upgrade makes 0.4.0 breaking for application code, model update_forward_refs(), optional-field defaults, on_event hooks, custom validators, and serialization method names all change. Read Upgrade 0.3 to 0.4 before upgrading.

Modernized Web Stack (Pydantic v2, FastAPI, Starlette)

0.4.0 brings the web stack fully up to date:

  • Pydantic v1 → v2. The ORM, models, responses and typing layer were ported to Pydantic v2. FastAPI dropped Pydantic v1 in 0.126, so this had to move together with the framework.
  • FastAPI 0.115 → 0.137 and Starlette 0.45 → 1.3.
  • Central model rebuild. Uvicore now resolves model forward references (relations) for every registered model centrally at boot, so you no longer add update_forward_refs() / model_rebuild() to each model file (keep from __future__ import annotations and the bottom-of-file relation imports).
  • Lifespan startup/shutdown. The HTTP server now uses Starlette's lifespan (Starlette 1.0 removed on_event); the uvicore.http.events.server.Startup / Shutdown events fire exactly as before.

This is the source of 0.4.0's breaking changes. The framework absorbs most of the churn, your Field() definitions, relations, tables, query builder and configs are unchanged, but a few app-side updates are required. See Upgrade 0.3 to 0.4.

Inline Table Definitions

ORM models can now define their schema fully inline, no separate Table class required. Set __connection__, __tablename__ and a raw __table__ list of SQLAlchemy columns, and Uvicore builds the real sa.Table for you, associating it with the connection's metadata and applying any table prefix, exactly like a Table class would.

@uvicore.model()
class Post(Model['Post'], metaclass=ModelMetaclass):
    __connection__ = 'wiki'
    __tablename__ = 'posts'
    __table__ = [
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('unique_slug', sa.String(length=100), unique=True),
        sa.Column('title', sa.String(length=100)),
    ]
    __table_kwargs__ = {'sqlite_autoincrement': True}   # optional

    id: Optional[int] = Field('id', primary=True, read_only=True)
    slug: str = Field('unique_slug')
    title: str = Field('title')

You now have three ways to attach a table to a model, a Table class in a separate file, a Table class in the same file, or fully inline, and all three produce an identical SQLAlchemy table. See DB Tables.

Expanded Query Operators

The .where(), .or_where(), .filter() and .or_filter() clauses (in both the DB Query Builder and the ORM Query Builder) now support a much larger, case-insensitive, whitespace-tolerant operator set:

  • <> as an alias for !=
  • <= (previously undocumented/missing)
  • not in and not like as natural-language aliases of !in and !like
  • ilike / !ilike for case-insensitive matching that behaves the same on every database
  • between / !between (value is a [low, high] list)
  • explicit is / is null and is not / is not null

Unknown operators (and unknown columns) now raise a clear, actionable error instead of failing cryptically.

Tip

like is case-sensitive on Postgres but case-insensitive on SQLite/MySQL. Use the new ilike operator whenever you want portable, case-insensitive matching.

Cross-Database Robustness

0.4.0 was validated end to end against real Postgres, MySQL and MariaDB (in addition to SQLite), which surfaced and fixed several portability issues:

  • Postgres just works. The postgres dialect alias is normalized to postgresql (SQLAlchemy dropped the old alias), and additional server dialects (mariadb, mssql, oracle, cockroachdb) are recognized.
  • Portable auto-increment primary keys. Inserts no longer send an explicit NULL primary key, which SQLite tolerated but Postgres/MySQL rejected. The database now generates the key on every engine.
  • Type-safe find(). Model.query().find(id) coerces the value to the primary key's column type, so a string id (for example from a URL path) no longer errors with integer = varchar on Postgres.

ORM Fixes

  • Multiple *Many includes no longer multiply. Eager-loading several HasMany / BelongsToMany / MorphMany / MorphToMany relations in one query previously returned a cartesian product of rows. Each relation now returns its correct cardinality.
  • delete() and set() now work for HasMany. Previously these raised for one-to-many relations; they now delete (or replace) the related children like the other relation types.
  • Auto-API create fixed. The automatic Model Router POST /{model} endpoint now returns the created record with a real primary key (it previously failed response validation).