Skip to content

Commit 57e130d

Browse files
Add SQLAlchemy mapped_column rewrite example with type annotations (#855)
* Initial plan * Add SQLAlchemy mapped_column rewrite example Co-authored-by: HerringtonDarkholme <2883231+HerringtonDarkholme@users.noreply.github.com> * Fix YAML id to match playground configuration Co-authored-by: HerringtonDarkholme <2883231+HerringtonDarkholme@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: HerringtonDarkholme <2883231+HerringtonDarkholme@users.noreply.github.com>
1 parent c7f7ced commit 57e130d

File tree

2 files changed

+98
-1
lines changed

2 files changed

+98
-1
lines changed

website/catalog/python/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ This page curates a list of example ast-grep rules to check and to rewrite Pytho
88
<!--@include: ./remove-async-await.md-->
99
<!--@include: ./refactor-pytest-fixtures.md-->
1010
<!--@include: ./optional-to-none-union.md-->
11-
<!--@include: ./recursive-rewrite-type.md-->
11+
<!--@include: ./recursive-rewrite-type.md-->
12+
<!--@include: ./rewrite-sqlalchemy-mapped-column.md-->
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
## Rewrite SQLAlchemy `mapped_column` with Type Annotations <Badge type="tip" text="Has Fix" />
2+
3+
* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiYShudWxsYWJsZT1UcnVlKSIsInJld3JpdGUiOiIxMjMiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6ImtleXdvcmRfYXJndW1lbnQiLCJjb25maWciOiJpZDogcmVtb3ZlLW51bGxhYmxlLWFyZ1xubGFuZ3VhZ2U6IHB5dGhvblxucnVsZTpcbiAgcGF0dGVybjogJFggPSBtYXBwZWRfY29sdW1uKCQkJEFSR1MpXG4gIGFueTpcbiAgICAtIHBhdHRlcm46ICRYID0gbWFwcGVkX2NvbHVtbigkJCRCRUZPUkUsIFN0cmluZywgJCQkTUlELCBudWxsYWJsZT1UcnVlLCAkJCRBRlRFUilcbiAgICAtIHBhdHRlcm46ICRYID0gbWFwcGVkX2NvbHVtbigkJCRCRUZPUkUsIFN0cmluZywgJCQkTUlELCBudWxsYWJsZT1UcnVlKVxucmV3cml0ZXJzOlxuLSBpZDogZmlsdGVyLXN0cmluZy1udWxsYWJsZVxuICBydWxlOlxuICAgIHBhdHRlcm46ICRBUkdcbiAgICBpbnNpZGU6XG4gICAgICBraW5kOiBhcmd1bWVudF9saXN0XG4gICAgYWxsOlxuICAgIC0gbm90OiBcbiAgICAgICAgcGF0dGVybjogU3RyaW5nXG4gICAgLSBub3Q6XG4gICAgICAgIHBhdHRlcm46XG4gICAgICAgICAgY29udGV4dDogYShudWxsYWJsZT1UcnVlKVxuICAgICAgICAgIHNlbGVjdG9yOiBrZXl3b3JkX2FyZ3VtZW50XG4gIGZpeDogJEFSR1xuXG50cmFuc2Zvcm06XG4gIE5FV0FSR1M6XG4gICAgcmV3cml0ZTpcbiAgICAgIHJld3JpdGVyczogW2ZpbHRlci1zdHJpbmctbnVsbGFibGVdXG4gICAgICBzb3VyY2U6ICQkJEFSR1NcbiAgICAgIGpvaW5CeTogJywgJ1xuZml4OiB8LVxuICAkWDogTWFwcGVkW3N0ciB8IE5vbmVdID0gbWFwcGVkX2NvbHVtbigkTkVXQVJHUykiLCJzb3VyY2UiOiJtZXNzYWdlID0gbWFwcGVkX2NvbHVtbihTdHJpbmcsIGRlZmF1bHQ9XCJoZWxsb1wiLCBudWxsYWJsZT1UcnVlKVxuXG5tZXNzYWdlID0gbWFwcGVkX2NvbHVtbihTdHJpbmcsIG51bGxhYmxlPVRydWUpXG5cbl9tZXNzYWdlID0gbWFwcGVkX2NvbHVtbihcIm1lc3NhZ2VcIiwgU3RyaW5nLCBudWxsYWJsZT1UcnVlKVxuXG5tZXNzYWdlID0gbWFwcGVkX2NvbHVtbihTdHJpbmcsIG51bGxhYmxlPVRydWUsIHVuaXF1ZT1UcnVlKVxuXG5tZXNzYWdlID0gbWFwcGVkX2NvbHVtbihcbiAgU3RyaW5nLCBpbmRleD1UcnVlLCBudWxsYWJsZT1UcnVlLCB1bmlxdWU9VHJ1ZSlcblxuIyBTaG91bGQgbm90IGJlIHRyYW5zZm9ybWVkXG5tZXNzYWdlID0gbWFwcGVkX2NvbHVtbihTdHJpbmcsIGRlZmF1bHQ9XCJoZWxsb1wiKVxuXG5tZXNzYWdlID0gbWFwcGVkX2NvbHVtbihTdHJpbmcsIGRlZmF1bHQ9XCJoZWxsb1wiLCBudWxsYWJsZT1GYWxzZSlcblxubWVzc2FnZSA9IG1hcHBlZF9jb2x1bW4oSW50ZWdlciwgZGVmYXVsdD1cImhlbGxvXCIpIn0=)
4+
5+
### Description
6+
7+
[SQLAlchemy 2.0](https://docs.sqlalchemy.org/en/20/orm/declarative_tables.html) recommends using type annotations with `Mapped` type for modern declarative mapping. The `mapped_column()` construct can derive its configuration from [PEP 484](https://peps.python.org/pep-0484/) type annotations.
8+
9+
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]`.
10+
11+
The key technique demonstrated here is using **rewriters** to selectively filter arguments. The rewriter:
12+
1. Matches each argument inside the `argument_list`
13+
2. Excludes the `String` type argument
14+
3. Excludes the `nullable=True` keyword argument
15+
4. Keeps all other arguments
16+
17+
### YAML
18+
```yaml
19+
id: remove-nullable-arg
20+
language: python
21+
rule:
22+
pattern: $X = mapped_column($$$ARGS)
23+
any:
24+
- pattern: $X = mapped_column($$$BEFORE, String, $$$MID, nullable=True, $$$AFTER)
25+
- pattern: $X = mapped_column($$$BEFORE, String, $$$MID, nullable=True)
26+
rewriters:
27+
- id: filter-string-nullable
28+
rule:
29+
pattern: $ARG
30+
inside:
31+
kind: argument_list
32+
all:
33+
- not:
34+
pattern: String
35+
- not:
36+
pattern:
37+
context: a(nullable=True)
38+
selector: keyword_argument
39+
fix: $ARG
40+
41+
transform:
42+
NEWARGS:
43+
rewrite:
44+
rewriters: [filter-string-nullable]
45+
source: $$$ARGS
46+
joinBy: ', '
47+
fix: |-
48+
$X: Mapped[str | None] = mapped_column($NEWARGS)
49+
```
50+
51+
### Example
52+
53+
<!-- highlight matched code in curly-brace {lineNum} -->
54+
```python {1,3,5,7-8}
55+
message = mapped_column(String, default="hello", nullable=True)
56+
57+
message = mapped_column(String, nullable=True)
58+
59+
_message = mapped_column("message", String, nullable=True)
60+
61+
message = mapped_column(String, nullable=True, unique=True)
62+
63+
message = mapped_column(
64+
String, index=True, nullable=True, unique=True)
65+
66+
# Should not be transformed
67+
message = mapped_column(String, default="hello")
68+
69+
message = mapped_column(String, default="hello", nullable=False)
70+
71+
message = mapped_column(Integer, default="hello")
72+
```
73+
74+
### Diff
75+
<!-- use # [!code --] and # [!code ++] to annotate diff -->
76+
```python
77+
message = mapped_column(String, default="hello", nullable=True) # [!code --]
78+
message: Mapped[str | None] = mapped_column(default="hello") # [!code ++]
79+
80+
message = mapped_column(String, nullable=True) # [!code --]
81+
message: Mapped[str | None] = mapped_column() # [!code ++]
82+
83+
_message = mapped_column("message", String, nullable=True) # [!code --]
84+
_message: Mapped[str | None] = mapped_column("message") # [!code ++]
85+
86+
message = mapped_column(String, nullable=True, unique=True) # [!code --]
87+
message: Mapped[str | None] = mapped_column(unique=True) # [!code ++]
88+
89+
message = mapped_column( # [!code --]
90+
String, index=True, nullable=True, unique=True) # [!code --]
91+
message: Mapped[str | None] = mapped_column( # [!code ++]
92+
index=True, unique=True) # [!code ++]
93+
```
94+
95+
### Contributed by
96+
Inspired by [discussion #2319](https://github.com/ast-grep/ast-grep/discussions/2319)

0 commit comments

Comments
 (0)