Skip to content

Commit 7bd2ace

Browse files
authored
Merge pull request #2 from dropbox/master
Reset
2 parents c0d5592 + 66cbc2b commit 7bd2ace

File tree

8 files changed

+266
-45
lines changed

8 files changed

+266
-45
lines changed

.travis.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
script: |
1818
set -e
1919
pytest
20-
- name: "run direct typecheck"
20+
- name: "run typecheck on stubs"
2121
python: 3.6
2222
script: |
2323
set -e
@@ -29,6 +29,11 @@ jobs:
2929
python: 3.6
3030
script: |
3131
flake8 sqlalchemy-stubs
32+
- name: "run typecheck on plugin"
33+
python: 3.6
34+
script: |
35+
set -e
36+
MYPYPATH=external/mypy python3 -m mypy --disallow-untyped-defs sqlmypy.py
3237
3338
3439
before_install: |

README.md

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ Mypy plugin and stubs for SQLAlchemy
66
[![Build Status](https://travis-ci.org/dropbox/sqlalchemy-stubs.svg?branch=master)](https://travis-ci.org/dropbox/sqlalchemy-stubs)
77
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
88

9-
This package contains [type stubs](https://www.python.org/dev/peps/pep-0561/) and soon a
10-
mypy plugin to provide more precise static types
11-
and type inference for [SQLAlchemy framework](http://docs.sqlalchemy.org/en/latest/).
12-
SQLAlchemy uses some Python "magic" that
13-
makes having precise types for some code patterns problematic. This is why we need to
14-
accompany the stubs with mypy plugins. The final goal is to be able to get precise types
15-
for most common patterns. A simple example:
16-
9+
This package contains [type stubs](https://www.python.org/dev/peps/pep-0561/) and a
10+
[mypy plugin](https://mypy.readthedocs.io/en/latest/extending_mypy.html#extending-mypy-using-plugins)
11+
to provide more precise static types and type inference for
12+
[SQLAlchemy framework](http://docs.sqlalchemy.org/en/latest/). SQLAlchemy uses some
13+
Python "magic" that makes having precise types for some code patterns problematic.
14+
This is why we need to accompany the stubs with mypy plugins. The final goal is to
15+
be able to get precise types for most common patterns. Currently, basic operations
16+
with models are supported. A simple example:
1717
```python
1818
from sqlalchemy.ext.declarative import declarative_base
1919
from sqlalchemy import Column, Integer, String
@@ -25,11 +25,33 @@ class User(Base):
2525
id = Column(Integer, primary_key=True)
2626
name = Column(String)
2727

28-
user: User
28+
user = User(id=42, name=42) # Error: Incompatible type for "name" of "User"
29+
# (got "int", expected "Optional[str]"
2930
user.id # Inferred type is "int"
30-
User.name # Inferred type is "Column[str]"
31+
User.name # Inferred type is "Column[Optional[str]]"
32+
```
33+
34+
Some auto-generated attributes are added to models. Simple relationships
35+
are supported but require models to be imported:
36+
```python
37+
from typing import TYPE_CHECKING
38+
if TYPE_CHECKING:
39+
from models.address import Address
40+
41+
...
42+
43+
class User(Base):
44+
__tablename__ = 'users'
45+
id = Column(Integer, primary_key=True)
46+
name = Column(String)
47+
address = relationship('Address') # OK, mypy understands string references.
3148
```
3249

50+
The next step is to support precise types for table definitions (e.g.
51+
inferring `Column[Optional[str]]` for `users.c.name`, currently it is just
52+
`Column[Any]`), and precise types for results of queries made using `query()`
53+
and `select()`.
54+
3355
## Installation
3456

3557
To install the latest version of the package:
@@ -45,6 +67,12 @@ stable version as:
4567
pip install -U sqlalchemy-stubs
4668
```
4769

70+
*Important*: you need to enable the plugin in your mypy config file:
71+
```
72+
[mypy]
73+
plugins = sqlmypy
74+
```
75+
4876
## Development Setup
4977

5078
First, clone the repo and cd into it, like in _Installation_, then:

sqlalchemy-stubs/engine/interfaces.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ from .result import ResultProxy
44
from ..sql.compiler import Compiled as Compiled, TypeCompiler as TypeCompiler
55

66
class Dialect(object):
7+
@property
8+
def name(self) -> str: ...
79
def create_connect_args(self, url): ...
810
@classmethod
911
def type_descriptor(cls, typeobj): ...

sqlalchemy-stubs/sql/schema.pyi

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ from .. import util
1111
from ..engine import Engine, Connection, Connectable
1212
from ..engine.url import URL
1313
from .compiler import DDLCompiler
14+
from .expression import FunctionElement
1415
import threading
1516

1617
_T = TypeVar('_T')
@@ -90,25 +91,25 @@ class Column(SchemaItem, ColumnClause[_T]):
9091
def __init__(self, name: str, type_: Type[TypeEngine[_T]], *args: Any, autoincrement: Union[bool, str] = ...,
9192
default: Any = ..., doc: str = ..., key: str = ..., index: bool = ..., info: Mapping[str, Any] = ...,
9293
nullable: bool = ..., onupdate: Any = ..., primary_key: bool = ..., server_default: Any = ...,
93-
server_onupdate: FetchedValue = ..., quote: Optional[bool] = ..., unique: bool = ...,
94+
server_onupdate: Union[FetchedValue, FunctionElement] = ..., quote: Optional[bool] = ..., unique: bool = ...,
9495
system: bool = ..., comment: str = ...) -> None: ...
9596
@overload
9697
def __init__(self, type_: Type[TypeEngine[_T]], *args: Any, autoincrement: Union[bool, str] = ...,
9798
default: Any = ..., doc: str = ..., key: str = ..., index: bool = ..., info: Mapping[str, Any] = ...,
9899
nullable: bool = ..., onupdate: Any = ..., primary_key: bool = ..., server_default: Any = ...,
99-
server_onupdate: FetchedValue = ..., quote: Optional[bool] = ..., unique: bool = ...,
100+
server_onupdate: Union[FetchedValue, FunctionElement] = ..., quote: Optional[bool] = ..., unique: bool = ...,
100101
system: bool = ..., comment: str = ...) -> None: ...
101102
@overload
102103
def __init__(self, name: str, type_: TypeEngine[_T], *args: Any, autoincrement: Union[bool, str] = ...,
103104
default: Any = ..., doc: str = ..., key: str = ..., index: bool = ..., info: Mapping[str, Any] = ...,
104105
nullable: bool = ..., onupdate: Any = ..., primary_key: bool = ..., server_default: Any = ...,
105-
server_onupdate: FetchedValue = ..., quote: Optional[bool] = ..., unique: bool = ...,
106+
server_onupdate: Union[FetchedValue, FunctionElement] = ..., quote: Optional[bool] = ..., unique: bool = ...,
106107
system: bool = ..., comment: str = ...) -> None: ...
107108
@overload
108109
def __init__(self, type_: TypeEngine[_T], *args: Any, autoincrement: Union[bool, str] = ...,
109110
default: Any = ..., doc: str = ..., key: str = ..., index: bool = ..., info: Mapping[str, Any] = ...,
110111
nullable: bool = ..., onupdate: Any = ..., primary_key: bool = ..., server_default: Any = ...,
111-
server_onupdate: FetchedValue = ..., quote: Optional[bool] = ..., unique: bool = ...,
112+
server_onupdate: Union[FetchedValue, FunctionElement] = ..., quote: Optional[bool] = ..., unique: bool = ...,
112113
system: bool = ..., comment: str = ...) -> None: ...
113114
def references(self, column: Column[Any]) -> bool: ...
114115
def append_foreign_key(self, fk: ForeignKey) -> None: ...
@@ -260,7 +261,7 @@ class ForeignKeyConstraint(ColumnCollectionConstraint):
260261
use_alter: bool = ...
261262
match: Optional[str] = ...
262263
elements: List[ForeignKey] = ...
263-
def __init__(self, columns: Sequence[str], refcolumns: Sequence[Union[str, Column[Any]]], name: Optional[str] = ...,
264+
def __init__(self, columns: SequenceType[str], refcolumns: SequenceType[Union[str, Column[Any]]], name: Optional[str] = ...,
264265
onupdate: Optional[str] = ..., ondelete: Optional[str] = ..., deferrable: Optional[bool] = ...,
265266
initially: Optional[str] = ..., use_alter: bool = ..., link_to_name: bool = ..., match: Optional[str] = ...,
266267
table: Optional[Table] = ..., info: Optional[Mapping[str, Any]] = ..., **dialect_kw: Any) -> None: ...
@@ -325,12 +326,12 @@ class MetaData(SchemaItem):
325326
@property
326327
def sorted_tables(self) -> List[Table]: ...
327328
def reflect(self, bind: Optional[Connectable] = ..., schema: Optional[str] = ..., views: bool = ...,
328-
only: Optional[Union[Sequence[str], Callable[[str, MetaData], bool]]] = ..., extend_existing: bool = ...,
329+
only: Optional[Union[SequenceType[str], Callable[[str, MetaData], bool]]] = ..., extend_existing: bool = ...,
329330
autoload_replace: bool = ..., **dialect_kwargs: Any) -> None: ...
330331
def append_ddl_listener(self, event_name: str, listener: Callable[[str, MetaData, Connection], None]) -> None: ...
331-
def create_all(self, bind: Optional[Connectable] = ..., tables: Optional[Sequence[Table]] = ...,
332+
def create_all(self, bind: Optional[Connectable] = ..., tables: Optional[SequenceType[Table]] = ...,
332333
checkfirst: bool = ...) -> None: ...
333-
def drop_all(self, bind: Optional[Connectable] = ..., tables: Optional[Sequence[Table]] = ...,
334+
def drop_all(self, bind: Optional[Connectable] = ..., tables: Optional[SequenceType[Table]] = ...,
334335
checkfirst: bool = ...) -> None: ...
335336

336337
class ThreadLocalMetaData(MetaData):

sqlalchemy-stubs/sql/type_api.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Optional, Union, TypeVar, Generic, Type, Callable, ClassVar, Tuple, Mapping, overload
1+
from typing import Any, Optional, Union, TypeVar, Generic, Type, Callable, ClassVar, Tuple, Mapping, overload, Text as typing_Text
22
from .. import util
33
from .visitors import Visitable as Visitable, VisitableType as VisitableType
44
from .base import SchemaEventTarget as SchemaEventTarget
@@ -91,7 +91,7 @@ class TypeDecorator(SchemaEventTarget, TypeEngine[_T]):
9191
def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]: ...
9292
def __getattr__(self, key: str) -> Any: ...
9393
def process_literal_param(self, value: Optional[_T], dialect: Dialect) -> Optional[str]: ...
94-
def process_bind_param(self, value: Optional[_T], dialect: Dialect) -> Optional[str]: ...
94+
def process_bind_param(self, value: Optional[_T], dialect: Dialect) -> Optional[typing_Text]: ...
9595
def process_result_value(self, value: Optional[Any], dialect: Dialect) -> Optional[_T]: ...
9696
def literal_processor(self, dialect: Dialect) -> Callable[[Optional[_T]], Optional[str]]: ...
9797
def bind_processor(self, dialect: Dialect) -> Callable[[Optional[_T]], Optional[str]]: ...

0 commit comments

Comments
 (0)