Rewrite SQLAlchemy with Type Annotations Has Fix
Description
SQLAlchemy 2.0 recommends using type annotations with Mapped type for modern declarative mapping. The mapped_column() construct can derive its configuration from PEP 484 type annotations.
This rule helps migrate legacy SQLAlchemy code that explicitly uses String type and nullable=True to the modern type annotation approach using Mapped[str | None].
The key technique demonstrated here is using rewriters to selectively filter arguments. The rewriter:
- Matches each argument inside the
argument_list - Excludes the
Stringtype argument - Excludes the
nullable=Truekeyword argument - Keeps all other arguments
YAML
yaml
id: remove-nullable-arg
language: python
rule:
pattern: $X = mapped_column($$$ARGS)
any:
- pattern: $X = mapped_column($$$BEFORE, String, $$$MID, nullable=True, $$$AFTER)
- pattern: $X = mapped_column($$$BEFORE, String, $$$MID, nullable=True)
rewriters:
- id: filter-string-nullable
rule:
pattern: $ARG
inside:
kind: argument_list
all:
- not:
pattern: String
- not:
pattern:
context: a(nullable=True)
selector: keyword_argument
fix: $ARG
transform:
NEWARGS:
rewrite:
rewriters: [filter-string-nullable]
source: $$$ARGS
joinBy: ', '
fix: |-
$X: Mapped[str | None] = mapped_column($NEWARGS)Example
python
message = mapped_column(String, default="hello", nullable=True)
message = mapped_column(String, nullable=True)
_message = mapped_column("message", String, nullable=True)
message = mapped_column(String, nullable=True, unique=True)
message = mapped_column(
String, index=True, nullable=True, unique=True)
# Should not be transformed
message = mapped_column(String, default="hello")
message = mapped_column(String, default="hello", nullable=False)
message = mapped_column(Integer, default="hello")Diff
python
message = mapped_column(String, default="hello", nullable=True)
message: Mapped[str | None] = mapped_column(default="hello")
message = mapped_column(String, nullable=True)
message: Mapped[str | None] = mapped_column()
_message = mapped_column("message", String, nullable=True)
_message: Mapped[str | None] = mapped_column("message")
message = mapped_column(String, nullable=True, unique=True)
message: Mapped[str | None] = mapped_column(unique=True)
message = mapped_column(
String, index=True, nullable=True, unique=True)
message: Mapped[str | None] = mapped_column(
index=True, unique=True) Contributed by
Inspired by discussion #2319