Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ repos:
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.16.3"
rev: "v0.16.5"
hooks:
- id: ruff-check
args: ["--fix"]
Expand Down
13 changes: 13 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ important operational fixes.
Recent Updates
==============

v0.62.2 - Litestar config lookup diagnostics
---------------------------------------------

**Fixed:**

* :meth:`SQLSpecPlugin.get_config() <sqlspec.extensions.litestar.SQLSpecPlugin.get_config>`
raises ``KeyError`` listing the available bind keys and dependency keys when an
identifier matches no configuration. On a plugin not yet registered with a
Litestar application, unknown names, unmatched config types, and configs from
another registry previously raised ``ImproperConfigurationError`` about
registration instead. Generated Litestar dependency keys remain
registration-bound.

v0.62.1 - PostgreSQL ADK memory and migration fixes
---------------------------------------------------

Expand Down
4 changes: 4 additions & 0 deletions docs/usage/frameworks/litestar/dependency_injection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ A ``bind_key`` takes precedence over a dependency key of the same value in
``get_config()``, while the request-scoped accessors always read strings as
dependency keys.

An identifier that matches neither a registry identity nor a dependency key raises
``KeyError`` listing every available bind key and dependency key. This holds both
before and after the plugin is registered with a ``Litestar`` application.

Advanced DuckDB Configuration
-----------------------------

Expand Down
12 changes: 7 additions & 5 deletions sqlspec/builder/_ddl.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ def _create_base_expression(self) -> exp.Expr:
self._require(self._name, f"{self._object_label} name must be set for DROP {self._drop_kind}.")
return exp.Drop(
kind=self._drop_kind,
this=self._build_drop_this(),
tables=[self._build_drop_this()],
exists=self._if_exists,
cascade=self._cascade,
**self._drop_expression_args(),
Expand Down Expand Up @@ -1464,10 +1464,10 @@ def _build_operation_expression(self, op: "AlterOperation") -> exp.Expr:
return build_column_expression(op.column_definition)

if op_type == "DROP COLUMN":
return exp.Drop(this=exp.to_identifier(op.column_name), kind="COLUMN", exists=True)
return exp.Drop(tables=[exp.to_identifier(op.column_name)], kind="COLUMN", exists=True)

if op_type == "DROP COLUMN CASCADE":
return exp.Drop(this=exp.to_identifier(op.column_name), kind="COLUMN", cascade=True, exists=True)
return exp.Drop(tables=[exp.to_identifier(op.column_name)], kind="COLUMN", cascade=True, exists=True)

if op_type == "ALTER COLUMN TYPE":
if not op.new_type:
Expand All @@ -1488,10 +1488,12 @@ def _build_operation_expression(self, op: "AlterOperation") -> exp.Expr:
return exp.AddConstraint(expressions=[constraint_expr])

if op_type == "DROP CONSTRAINT":
return exp.Drop(this=exp.to_identifier(op.constraint_name), kind="CONSTRAINT", exists=True)
return exp.Drop(tables=[exp.to_identifier(op.constraint_name)], kind="CONSTRAINT", exists=True)

if op_type == "DROP CONSTRAINT CASCADE":
return exp.Drop(this=exp.to_identifier(op.constraint_name), kind="CONSTRAINT", cascade=True, exists=True)
return exp.Drop(
tables=[exp.to_identifier(op.constraint_name)], kind="CONSTRAINT", cascade=True, exists=True
)

if op_type == "ALTER COLUMN SET NOT NULL":
return exp.AlterColumn(this=exp.to_identifier(op.column_name), allow_null=False)
Expand Down
15 changes: 12 additions & 3 deletions sqlspec/extensions/litestar/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,12 +833,21 @@ def _dependency_state(self, name: Any) -> PluginConfigState:
Args:
name: A ``session_key``, ``connection_key``, or ``pool_key``.

Raises:
KeyError: If ``name`` is not a known dependency key.

Returns:
The matching plugin state.
"""
if any(state.annotation is None for state in self._plugin_configs):
self._raise_plugin_not_registered()
return self._get_plugin_state(name)
if isinstance(name, str) and any(
name in {state.connection_key, state.pool_key, state.session_key} for state in self._plugin_configs
):
if any(state.annotation is None for state in self._plugin_configs):
self._raise_plugin_not_registered()
return self._get_plugin_state(name)
self._raise_config_not_found(name)
msg = "unreachable"
raise AssertionError(msg)

def _get_plugin_state(
self, key: "str | DatabaseConfigProtocol[Any, Any, Any] | type[DatabaseConfigProtocol[Any, Any, Any]]"
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/builder/test_ddl_arg_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,23 @@ def test_drop_index_on_table_renders_table() -> None:
result = sql.drop_index("idx").on_table("t").build(dialect="mysql")
assert "ON" in result.sql
assert "t" in result.sql.split("ON")[-1]


def test_drop_table_renders_table_name() -> None:
"""Verify DROP TABLE includes the target table identifier."""
result = sql.drop_table("users").build()
assert result.sql == 'DROP TABLE "users"'


def test_alter_table_drop_column_renders_column_name() -> None:
"""Verify ALTER TABLE DROP COLUMN includes the target column identifier."""
result = sql.alter_table("users").drop_column("email").build()
assert "DROP COLUMN" in result.sql
assert "email" in result.sql


def test_alter_table_drop_constraint_renders_constraint_name() -> None:
"""Verify ALTER TABLE DROP CONSTRAINT includes the target constraint identifier."""
result = sql.alter_table("users").drop_constraint("fk_users_orders").build()
assert "DROP CONSTRAINT" in result.sql
assert "fk_users_orders" in result.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from typing import Any

import pytest
from litestar.config.app import AppConfig

from sqlspec.adapters.aiosqlite.config import AiosqliteConfig
from sqlspec.adapters.sqlite.config import SqliteConfig
from sqlspec.base import SQLSpec
from sqlspec.exceptions import ImproperConfigurationError
from sqlspec.extensions.litestar.plugin import (
Expand Down Expand Up @@ -76,3 +78,33 @@ def test_get_config_by_custom_di_key_before_registration_raises(di_key: str) ->
plugin, _ = _build_unregistered_plugin(bind_key="primary", extension_config={"litestar": CUSTOM_KEYS})
with pytest.raises(ImproperConfigurationError, match="on_app_init"):
plugin.get_config(di_key)


def test_get_config_unknown_string_before_registration_raises_key_error() -> None:
"""An unknown identifier reports available keys instead of a registration error."""
plugin, _ = _build_unregistered_plugin(bind_key="primary")
with pytest.raises(KeyError, match="Available keys"):
plugin.get_config("missing")


def test_get_config_unknown_type_before_registration_raises_key_error() -> None:
"""An unmatched config type is an unknown identifier, not a registration error."""
plugin, _ = _build_unregistered_plugin(bind_key="primary")
with pytest.raises(KeyError, match="Available keys"):
plugin.get_config(SqliteConfig)


def test_get_config_foreign_instance_before_registration_raises_key_error() -> None:
"""A config instance from another registry is an unknown identifier."""
plugin, _ = _build_unregistered_plugin(bind_key="primary")
foreign = AiosqliteConfig(connection_config={"database": ":memory:"}, bind_key="foreign")
with pytest.raises(KeyError, match="Available keys"):
plugin.get_config(foreign)


def test_get_config_unknown_string_after_registration_raises_key_error() -> None:
"""The unknown-identifier contract holds in both lifecycle phases."""
plugin, _ = _build_unregistered_plugin(bind_key="primary")
plugin.on_app_init(AppConfig())
with pytest.raises(KeyError, match="Available keys"):
plugin.get_config("missing")
Loading
Loading