Skip to content

Release Notes

Latest Changes🔗

  • feat: declare first-party adapters as installed entry points and remove eager bootstrap. PR #564 by @zschumacher.
  • v1: adapters are now discovered through the standard pydapper.adapters entry-point group and load lazily. First-party and third-party adapters use one provider contract: an installed distribution declares an entry point per adapter name whose synchronous zero-argument callback registers exactly that name through the public register_adapter(). The eight stable first-party names (sqlite3, psycopg2, psycopg, aiopg, mysql, pymssql, oracledb, google) are unchanged and are now declared by the pydapper distribution itself; the private eager first-party bootstrap was removed, so a plain import pydapper no longer registers adapters, imports adapter command modules, or imports optional database drivers. DSN-based connect() / connect_async() and explicit using(..., adapter=name) / using_async(..., adapter=name) selection load only the requested provider (explicit selection still bypasses connection predicates), while automatic using() / using_async() selection loads every installed provider before evaluating predicates, so an unrelated broken provider can fail automatic selection but never exact-name selection. Precedence is deterministic: a direct runtime register_adapter() call wins for the process, the first-party provider wins over an external provider using the same name, and duplicate external providers fail before either loads with every conflicting distribution named — metadata enumeration order never chooses a winner. Provider failures identify the adapter and provider distribution, preserve the original exception as the cause, and roll back their registry mutations; successful callbacks run at most once per process and the installed catalog is cached per process. Runtime registration via register_adapter() remains fully supported, no public API is added or deprecated, and no new runtime dependency is introduced. See Adapter registration for the packaging contract for adapter authors.
  • feat: extract first-party adapter provider callbacks. PR #563 by @zschumacher.
  • feat: load installed adapter providers before automatic selection. PR #562 by @zschumacher.
  • feat: add private deterministic load-all-providers pass. PR #561 by @zschumacher.
  • feat: resolve installed adapter providers from name-based public paths. PR #560 by @zschumacher.
  • feat: add private exact-name adapter resolution and provider precedence. PR #559 by @zschumacher.
  • feat: add private transactional loader for adapter provider entry points. PR #558 by @zschumacher.
  • feat: replace dsnparse with an owned URL DSN parser. PR #557 by @zschumacher.
  • v1: pydapper now owns its narrow URL-style DSN parser using the standard library's urllib.parse. A direct implementation is smaller and lower risk than vendoring a generic parser API that pydapper does not consume. This removes the mandatory dsnparse dependency, so it is no longer installed with pydapper, while preserving default and exact explicit adapter routing, including third-party adapters. Parse-result fields now have accurate public types, and representations and parser-generated errors no longer expose credential-bearing DSNs or passwords. Decoded network hosts are revalidated so encoded controls and Unicode delimiter lookalikes cannot bypass authority parsing.
  • feat: add private lazy entry-point discovery catalog for pydapper.adapters. PR #556 by @zschumacher.
  • feat: validate capability declarations and add command preparation hooks. PR #555 by @zschumacher.
  • feat: validate adapter capability declarations and add command preparation hooks. register_adapter() now validates each supplied command class's capabilities declaration (a frozenset of AdapterCapability members) for both modes before the registry is touched, so an invalid declaration fails atomically. Every first-party command class explicitly declares its current — empty — capability set, and commands.supports(AdapterCapability.X) reports declared support. New documented, compatibility-sensitive adapter-author preparation seams run inside the command-owned cursor lifecycle for every sync and async command family: _prepare_cursor*() once per acquired cursor and _prepare_command*() once per executed handler, both receiving a normalized CommandOptions instance. No optional capability (transactions, timeouts, stored procedures, readonly, max rows, etc.) is implemented by this change. The *_or_default helpers now forward the normalized options to query_first / query_single; see Breaking Changes below for the subclass-override impact.
  • feat: add AdapterCapability vocabulary and command capability checks. PR #554 by @zschumacher.
  • test: cover query_multiple runtime failures inside the command-owned cursor lifecycle. PR #553 by @zschumacher.
  • fix: project query_single rows inside the command-owned cursor lifecycle. PR #551 by @zschumacher.
  • fix: run mysql query_first inside the command-owned cursor lifecycle. PR #552 by @zschumacher.
  • fix: project query_single rows inside the command-owned cursor lifecycle. PR #550 by @zschumacher.
  • fix: validate and extract scalar results inside the command-owned cursor lifecycle. PR #549 by @zschumacher.
  • fix: project query_first rows inside the command-owned cursor lifecycle. PR #548 by @zschumacher.
  • fix: async command-owned cursor exception precedence. PR #547 by @zschumacher.
  • fix: sync command-owned cursor exception precedence. PR #546 by @zschumacher.
  • fix: validate complete query batches before DBAPI work. PR #545 by @zschumacher.
  • fix: validate complete query batches before DBAPI work. query_multiple and query_multiple_async now construct and validate every parameter handler before acquiring a cursor, so a missing or invalid parameter in any query of the tuple fails before any query executes or fetches. This is client-side prevalidation, not transactional execution; a validated batch can still fail partway through on a runtime database, fetch, column, or mapping error.
  • chore(ai): Add CLAUDE.md symlink to AGENTS.md. PR #544 by @zschumacher.
  • fix: harden async context lifecycle. PR #540 by @zschumacher.
  • fix: harden async context lifecycle. Async resources now resolve exactly once and context-manager exception suppression propagates correctly; both await connect_async() and async with connect_async() usage remain unchanged.
  • feat: stabilize adapter registration. PR #539 by @zschumacher.

Breaking Changes🔗

  • DSNs now follow the focused <database>[+<adapter>]://... v1 grammar. Incidental inherited conveniences such as name=value strings, constructor default injection, dict-like mutation, tuple-style iteration or indexing, environment helpers, and multiple hosts are intentionally not reproduced. Multi-plus input such as db+adapter+extra://... previously invented the adapter name adapter_extra; it is now rejected. Query values are no longer implicitly coerced: for example, ?code=001&enabled=true previously produced 1 and True but now preserves "001" and "true"; a bare query key such as ?flag now maps to {"flag": ""} instead of raising. Corrected DSN examples place ports after the host; literal and percent-encoded colons in passwords remain valid. SQLite slash counts are now explicit: sqlite:///relative/path.db targets relative/path.db; use sqlite:////absolute/path.db for /absolute/path.db (the three-slash form previously targeted /relative/path.db).
  • Command delegation: query_first_or_default, query_single_or_default, and their async equivalents now forward the normalized options= keyword to query_first / query_single (and the async equivalents) instead of validating and discarding it. Subclass overrides of those four target methods must accept an options= keyword; overrides without it now raise TypeError when called through the *_or_default helpers. Add *, options=None to the override signature to migrate.
  • Adapter registration: register and register_async were removed. Use register_adapter and explicit adapter= selection where needed.

Other Changes🔗

0.13.1🔗

Internal🔗

0.13.0🔗

Internal🔗

0.12.0🔗

Features🔗

Bug Fixes🔗

  • 🔧 fix sqlite not opening in subdirectories. PR #344 by @arieroos.

Internal🔗

0.11.1🔗

Features🔗

  • ✨ Add callable that returns a model as a supported model type. PR #333 by @zschumacher.

0.11.0🔗

Breaking changes🔗

  • Python 3.8 support deprecated
  • cx_oracle support deprecated

Internal🔗

0.10.0🔗

Features🔗

Internal🔗

Docs🔗

0.9.0🔗

Bug fixes🔗

Internal🔗

0.8.0🔗

Features🔗

Internal🔗

Docs🔗

0.7.0🔗

Features🔗

Internal🔗

0.6.0🔗

Features🔗

0.5.3🔗

Internal🔗

  • 🔧 Add variable length tuple typing annotation for query_multiple. PR #67 by @enewnham.
  • 🔧 Dependabot updates 2022-10-28. PR #85 by @zschumacher.
  • 🔧 Better developer support for arm chips. PR #52 by @zschumacher.

0.5.2🔗

Docs🔗

  • 🔧 Add example for serializing one-to-many relationships to docs. PR #44 by @zschumacher.

Internal🔗

0.5.1🔗

Internal🔗

0.5.0🔗

Features🔗

Internal🔗

0.4.0🔗

Features🔗

0.3.0🔗

Features🔗

  • ✨ support PYDAPPER_DSN environment variable for connections. PR #21 by @zschumacher.

Internal🔗

  • 🔧 Cache oracle-instantclient download in test workflow. PR #20 by @zschumacher.

0.2.0🔗

Features🔗

0.1.2🔗

🚀 First stable release of pydapper!