sqlalchemy / sqlalchemy2-stubs Goto Github PK
View Code? Open in Web Editor NEWPEP-484 typing stubs for SQLAlchemy 1.4
License: MIT License
PEP-484 typing stubs for SQLAlchemy 1.4
License: MIT License
Describe the bug
The typing of relationship
's order_by
parameter doesn't allow passing multiple 'order by' clauses.
Expected behavior
The typing should reflect the runtime behaviour, which allows passing multiple clauses to order the relationship.
I noticed other order_by
parameters/functions seems to just take Any
as the clauses, but potentially all could be updated to something like:
Union[Literal[False], str, Column, Callable[[], Column], List[Union[str, Column]]]
(with or without the Literal[False]
, as appropriate.)
To Reproduce
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship, declarative_base
from typing import List, Optional
Base = declarative_base()
class Foo(Base):
__tablename__ = "foo"
id = Column(Integer(), primary_key=True)
bar_id: Optional[int] = Column(ForeignKey("bar.id"))
a = Column(Integer())
b = Column(Integer())
class Bar(Base):
__tablename__ = "bar"
id = Column(Integer(), primary_key=True)
foos: List[Foo] = relationship(Foo, uselist=True, order_by=[Foo.a, Foo.b])
Error
order_by.py:21: error: Argument "order_by" to "RelationshipProperty" has incompatible type "List[Mapped[Optional[int]]]"; expected "Union[Literal[False], str, Column[Any], Callable[[], Column[Any]]]"
Found 1 error in 1 file (checked 1 source file)
Versions.
Additional context
requirements:
greenlet==1.0.0
mypy==0.812
mypy-extensions==0.4.3
SQLAlchemy==1.4.11
sqlalchemy2-stubs==0.0.1a4
typed-ast==1.4.3
typing-extensions==3.7.4.3
Describe the bug
The attribute sqlalchemy.orm.mapperlib
is missing
Versions.
When Index
outside table
error: Argument 3 to "Index" has incompatible type "ColumnElement[Any]"; expected "ColumnClause[Any]"
When Index
is inline
error: Argument 2 to "Index" has incompatible type "str"; expected "ColumnClause[Any]"
Describe the bug
Columns with a UserDefinedType
raise an error
error: Value of type variable "_TE" of "Column" cannot be <UserDefinedType>
Expected behavior
UserDefinedTypes
are handled like builtin types.
To Reproduce
from typing import Callable
from sqlalchemy import Column, Integer, MetaData
import sqlalchemy.types as types
from sqlalchemy.dialects.postgresql.base import PGDialect
from sqlalchemy.orm import declarative_base, Mapped
#########
# SETUP #
#########
class CITEXT(types.Concatenable, types.UserDefinedType):
"""Case Insensitive Text Type"""
def literal_processor(self, dialect: PGDialect) -> Callable:
def process(value: str) -> str:
value = value.replace("'", "''")
if dialect.identifier_preparer._double_percents:
value = value.replace("%", "%%")
return "'%s'" % value
return process
def get_col_spec(self) -> str:
return "CITEXT"
def bind_processor(self, dialect: PGDialect) -> Callable:
def process(value: str) -> str:
return value
return process
def result_processor(self, dialect: PGDialect, coltype: int) -> Callable:
def process(value: str) -> str:
return value
return process
#########
# USAGE #
#########
Base = declarative_base(metadata=MetaData())
class User(Base):
id = Column(Integer(), primary_key=True)
email: Mapped[str] = Column(CITEXT) # error: Value of type variable "_TE" of "Column" cannot be "CITEXT"
Error
# error: Value of type variable "_TE" of "Column" cannot be "CITEXT"
Versions.
Context
This appears to be happening because Column
expects its type to be a subclass of TypeEngine
but the stub for UserDefinedType
is not
In contrast, the definition in sqlalchemy proper is
class UserDefinedType(util.with_metaclass(VisitableCheckKWArg, TypeEngine)):
If its as simple as adding TypeEngine
as a base I'd be happy to open a PR, but I'm guessing something to do with the metaclasses is a problem for mypy?
Describe the bug
Mypy tells me that __version__ doesn't exist for sqlalchemy, but it's just missing in the __init__.pyi
Expected behavior
__version__ should be defined
To Reproduce
import sqlalchemy
print(sqlalchemy.__version__)
Versions.
Describe the bug
mypy config
$cat mypy.ini
[mypy]
plugins = sqlalchemy.ext.mypy.plugin
I'm trying annotate flask-sqlalchemy library in out
directory and when I run mypy like
MYPYPATH="out" prun mypy example.py
. Here prun
is alias to poetry run
. the command produces following output
example.py contains no flask sqlalchemy imports
Error constructing plugin instance of SQLAlchemyPlugin
Traceback (most recent call last):
File "/Users/user/Library/Caches/pypoetry/virtualenvs/flask-sqlalchemy-KzL4aHKU-py3.9/bin/mypy", line 8, in <module>
sys.exit(console_entry())
File "/Users/user/Library/Caches/pypoetry/virtualenvs/flask-sqlalchemy-KzL4aHKU-py3.9/lib/python3.9/site-packages/mypy/__main__.py", line 11, in console_entry
main(None, sys.stdout, sys.stderr)
File "mypy/main.py", line 90, in main
File "mypy/build.py", line 179, in build
File "mypy/build.py", line 228, in _build
File "mypy/build.py", line 472, in load_plugins
File "mypy/build.py", line 451, in load_plugins_from_config
File "mypy/build.py", line 491, in take_module_snapshot
NotADirectoryError: [Errno 20] Not a directory: '/Users/user/Library/Caches/pypoetry/virtualenvs/flask-sqlalchemy-KzL4aHKU-py3.9/lib/python3.9/site-packages/SQLAlchemy-1.4.18-py3.9-macosx-11-x86_64.egg/sqlalchemy/ext/mypy/plugin.py'
Expected behavior
The mypy shouldn't complain sqlalchemy plugin is invalid.
To Reproduce
Please try to provide a Minimal, Complete, and Verifiable example.
See also Reporting Bugs on the website, and some example issues.
Please do not use Flask-SQLAlchemy or any other third-party extensions or dependencies in test cases. The test case must illustrate the problem without using any third party SQLAlchemy extensions. Otherwise, please report the bug to those projects first.
# Insert code here
from sqlalchemy import orm
Error
poetry run mypy example.py
Python 2.7 will no longer be supported in the next feature release of Poetry (1.2).
You should consider updating your Python version to a supported one.
Note that you will still be able to manage Python 2.7 projects by using the env command.
See https://python-poetry.org/docs/managing-environments/ for more information.
Error constructing plugin instance of SQLAlchemyPlugin
Traceback (most recent call last):
File "/Users/user/Library/Caches/pypoetry/virtualenvs/flask-sqlalchemy-KzL4aHKU-py3.9/bin/mypy", line 8, in <module>
sys.exit(console_entry())
File "/Users/user/Library/Caches/pypoetry/virtualenvs/flask-sqlalchemy-KzL4aHKU-py3.9/lib/python3.9/site-packages/mypy/__main__.py", line 11, in console_entry
main(None, sys.stdout, sys.stderr)
File "mypy/main.py", line 90, in main
File "mypy/build.py", line 179, in build
File "mypy/build.py", line 228, in _build
File "mypy/build.py", line 472, in load_plugins
File "mypy/build.py", line 451, in load_plugins_from_config
File "mypy/build.py", line 491, in take_module_snapshot
NotADirectoryError: [Errno 20] Not a directory: '/Users/user/Library/Caches/pypoetry/virtualenvs/flask-sqlalchemy-KzL4aHKU-py3.9/lib/python3.9/site-packages/SQLAlchemy-1.4.18-py3.9-macosx-11-x86_64.egg/sqlalchemy/ext/mypy/plugin.py'
# Copy error here. Please include the full stack trace.
Versions.
Additional context
I tried a different versions of mypy like 0.812
and 0.902
. The error remains same. Not sure where I should report here or in mypy repository, starting here.
Have a nice day!
Describe the bug
Running mypy . --show-traceback
I am getting this stacktrace:
version: 0.812
Traceback (most recent call last):
File "mypy/semanal.py", line 4835, in accept
File "mypy/nodes.py", line 950, in accept
File "mypy/semanal.py", line 1048, in visit_class_def
File "mypy/semanal.py", line 1125, in analyze_class
File "mypy/semanal.py", line 1134, in analyze_class_body_common
File "mypy/semanal.py", line 1194, in apply_class_plugin_hooks
File "/Users/alex/.pyenv/versions/3.8.2/envs/my-app/lib/python3.8/site-packages/sqlalchemy/ext/mypy/plugin.py", line 159, in _base_cls_hook
decl_class._scan_declarative_assignments_and_apply_types(ctx.cls, ctx.api)
File "/Users/alex/.pyenv/versions/3.8.2/envs/my-app/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 123, in _scan_declarative_assignments_and_apply_types
_scan_declarative_assignment_stmt(cls, api, stmt, cls_metadata)
File "/Users/alex/.pyenv/versions/3.8.2/envs/my-app/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 380, in _scan_declarative_assignment_stmt
python_type_for_type = _infer_type_from_decl_column(
File "/Users/alex/.pyenv/versions/3.8.2/envs/my-app/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 714, in _infer_type_from_decl_column
python_type_for_type = _extract_python_type_from_typeengine(
File "/Users/alex/.pyenv/versions/3.8.2/envs/my-app/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 989, in _extract_python_type_from_typeengine
assert False, "could not extract Python type from node: %s" % node
AssertionError: could not extract Python type from node: TypeInfo(
Name(sqlalchemy.sql.sqltypes.Boolean)
Bases(sqlalchemy.sql.type_api.Emulated, sqlalchemy.sql.type_api.TypeEngine[builtins.bool], sqlalchemy.sql.sqltypes.SchemaType)
Mro(sqlalchemy.sql.sqltypes.Boolean, sqlalchemy.sql.type_api.Emulated, sqlalchemy.sql.type_api.TypeEngine, sqlalchemy.sql.visitors.Traversible, sqlalchemy.sql.sqltypes.SchemaType, sqlalchemy.sql.base.SchemaEventTarget, builtins.object)
Names(
__init__
__visit_name__ (builtins.str)
bind_processor
create_constraint (Any)
literal_processor
name (Any)
native (builtins.bool)
python_type
result_processor))
Expected behavior
mypy runs without dying.
To Reproduce
I haven't yet figured out a stripped down example. Working on it
Versions.
sqlalchemy2-stubs==0.0.1a4
sqlalchemy[postgresql]==1.4.2
Additional context
Have a nice day!
it's only in Python 3.10, the ability to make our own types that are like tuples:
https://www.python.org/dev/peps/pep-0646/
what we would want is:
stmt: Select[User, str, int] = select(User, Address.name, Address.id).join_from(User, Address)
result: Result[User, str, int] = session.execute(stmt)
for row: Row[User, str, int] in result:
# ...
that is, every ReturnsRows class as well as Result and Row become some kind of covariant variadic generic. is that every word ?
Describe the bug
total = query.order_by(None).count()
items = query.limit(10).offset(10).all()
mypy --strict test.py
error: "order_by" of "Query" does not return a value
error: "limit" of "Query" does not return a value
Expected behavior
This is a valid usage of Query
. The doc states that ORM Query
is considered legacy in v2, it is still usable.
To Reproduce
from typing import Any
from sqlalchemy.orm import Query
def paginate(query: Query) -> Any:
total = query.order_by(None).count()
items = query.limit(10).offset(10).all()
return [total, items]
Error
mypy --strict test.py
test.py:7: error: "order_by" of "Query" does not return a value
test.py:8: error: "limit" of "Query" does not return a value
Found 2 errors in 1 file (checked 1 source file)
Versions.
❯ pip list
Package Version
----------------- -------
greenlet 1.0.0
mypy 0.812
mypy-extensions 0.4.3
pip 21.0.1
setuptools 54.2.0
SQLAlchemy 1.4.11
sqlalchemy2-stubs 0.0.1a4
typed-ast 1.4.3
typing-extensions 3.7.4.3
Have a nice day and thanks for the amazing work!
Describe the bug
When functions are called from the sqlalchemy.func
import they are all of type sqlalchemy.sql.functions.Function[sqlalchemy.sql.sqltypes.NullType]
.
Expected behavior
Have the same type when importing from:
sqlalchemy.func
sqlalchemy.sql.functions
To Reproduce
Please try to provide a Minimal, Complete, and Verifiable example.
See also Reporting Bugs on the website, and some example issues.
Please do not use Flask-SQLAlchemy or any other third-party extensions or dependencies in test cases. The test case must illustrate the problem without using any third party SQLAlchemy extensions. Otherwise, please report the bug to those projects first.
from sqlalchemy import func
from sqlalchemy.sql import functions
reveal_type(func.now())
reveal_type(functions.now())
Error
column_server_default_ticket_81.py:8: note: Revealed type is 'sqlalchemy.sql.functions.Function[sqlalchemy.sql.sqltypes.NullType]'
column_server_default_ticket_81.py:9: note: Revealed type is 'sqlalchemy.sql.functions.now[sqlalchemy.sql.sqltypes.DateTime]'
Have a nice day!
On 1.3 + Dropbox Stubs, mypy allowed me to assign None
to this field:
my_int = Column(Integer, nullable=True, index=True)
however on 1.4 + the new stubs, I need to annotate:
my_int: Column[Optional[int]] = Column(Integer, nullable=True, index=True
otherwise it complains about assigning None
to an int
Describe the bug
The annotation for one of sessionmaker's overloads seems to be missing the **kw: Any
parameter, causing the code below to fail.
Expected behavior
Mypy shouldn't complain when calling sessionmaker
with the future=True
flag.
To Reproduce
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///', future=True)
Session = sessionmaker(engine, future=True)
Error
foo.py:6: error: No overload variant of "sessionmaker" matches argument types "Engine", "bool"
foo.py:6: note: Possible overload variants:
foo.py:6: note: def [_TSessionMakerType] __init__(self, bind: Union[Connection, Engine, None] = ..., class_: None = ..., autoflush: bool = ..., autocommit: bool = ..., expire_on_commit: bool = ..., info: Optional[Mapping[Any, Any]] = ...) -> sessionmaker[Session]
foo.py:6: note: def [_TSessionMakerType] __init__(self, bind: Union[Connection, Engine, AsyncConnection, AsyncEngine, None], class_: Type[_TSessionMakerType], autoflush: bool = ..., autocommit: bool = ..., expire_on_commit: bool = ..., info: Optional[Mapping[Any, Any]] = ..., **kw: Any) -> sessionmaker[_TSessionMakerType]
foo.py:6: note: def [_TSessionMakerType] __init__(self, *, bind: Union[Connection, Engine, AsyncConnection, AsyncEngine, None] = ..., class_: Type[_TSessionMakerType], autoflush: bool = ..., autocommit: bool = ..., expire_on_commit: bool = ..., info: Optional[Mapping[Any, Any]] = ..., **kw: Any) -> sessionmaker[_TSessionMakerType]
Found 1 error in 1 file (checked 1 source file)
Versions.
Have a nice day!
Describe the bug
Non nullable columns are treated as optional.
Expected behavior
Non nullable columns are treated as non optional.
To Reproduce
from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy.orm import declarative_base
Base = declarative_base(metadata=MetaData())
class U(Base):
__tablename__ = "x"
id = Column(Integer, primary_key=True)
my_int = Column(Integer, nullable=False, index=True)
def x(my_int: int):
pass
u1 = U(my_int = 2)
x(u1.my_int)
Error
error: Argument 1 to "x" has incompatible type "Optional[int]"; expected "int"
Versions.
Additional context
Trying to test on
Seems to work when you manually type my_int: int = Column(Integer, nullable=False, index=True)
Have a nice day!
Describe the bug
The Session.no_autoflush
property is annotated as returning None
.
Expected behavior
The Session.no_autoflush
property should be annotated as returning a context manager that yields a Session
.
To Reproduce
# no_autoflush.py
from sqlalchemy.orm import Session
def test(session: Session):
with session.no_autoflush:
pass
Error
$ mypy no_autoflush.py
no_autoflush.py:5: error: "None" has no attribute "__enter__"
no_autoflush.py:5: error: "None" has no attribute "__exit__"
Found 2 errors in 1 file (checked 1 source file)
Versions
AsyncConnection.begin()
returns AsyncTransaction
, like one would expect:
however, AsyncEngine.begin()
returns _trans_ctx
:
This leads to following errors, when user declares argument type to AsyncTransaction
.
error: Argument "bar" to "foo" has incompatible type "_trans_ctx"; expected "AsyncTransaction"
Describe the bug
mypy
is complaining about unpacking Row
object, even though it works.
To Reproduce
test_table = Table(
"test_table",
metadata,
Column("i", UUID(as_uuid=True), nullable=False, primary_key=True),
Column("x", UUID(as_uuid=True), index=True),
Column("y", UUID(as_uuid=True), index=True),
)
query = test_table.select()
async with engine.connect() as connection:
result = await connection.execute(query)
row = result.fetchone()
if row:
my_dict = {**row}
Error
Argument after ** must be a mapping, not "Row"
Versions.
Good Morning,
I was hoping my issue would be solved by #39 , sadly it isn't: you might want to have another look.
Describe the bug
Create a query using filter or options.
You will get
error: "filter" of "Query" does not return a value
or
error: "options" of "Query" does not return a value
Expected behavior
No mypy message.
To Reproduce
Not a complete example, but I guess it's clear:
session.query(User).filter(User.name == "John").options(joinedload(User.address)).all()
Versions.
Have a nice day!
U2 :D
Describe the bug
Calling scoped_session returns Any which is should not be the case
Expected behavior
A concrete type should be returned
To Reproduce
session_factory = sessionmaker(bind=create_engine("foo", future=True))
reveal_type(scoped_session(session_factory)())
Error
Revealed type is "Any"
Versions.
This issue is a placeholder for now so I don't forget, I'll write some examples of problems we cannot easily type using stubs only in the coming days.
Describe the bug
Introduced by #40, sql.functions
are no longer able to be assigned to ORM attributes. I couldn't find documented cases of directly assigning functions to attributes. Is it even a supported use-case? Or should update()
always be used.
Expected behavior
Type checking to pass, as it works at runtime
To Reproduce
from sqlalchemy import Column, DateTime
from sqlalchemy import Integer
from sqlalchemy.orm import registry
from sqlalchemy.sql.functions import now
mapper_registry: registry = registry()
@mapper_registry.mapped
class A:
__tablename__ = "a"
id = Column(Integer, primary_key=True)
date_time = Column(DateTime())
a = A()
a.date_time = now()
Error
test/ext/mypy/files/function_now.py:15: error: Incompatible types in assignment (expression has type "now[<nothing>]", variable has type "Optional[datetime]")```
Have a nice day!
Describe the bug
TypeDecorator.cache_ok
is missing in the stubs
Expected behavior
TypeDecorator.cache_ok
is present
To Reproduce
from sqlalchemy.types import TypeDecorator
class MyType(TypeDecorator):
cache_ok = True
Versions.
async and scoped session use a proxy approach to adding methods from Session, so these need to be stubbed explicitly.
Is your feature request related to a problem? Please describe.
Currently the package on pypi is published as a tarball. Installing usually requires first building the wheel and then installing
Describe the solution you'd like
Publish the package to pypi as a wheel
I think this should be Literal[False] | str
.
In most cases a type only superclass can avoid repeating the definitions in one or more classes.
Examples: Session|ScopedSession|AsyncSession; Connection|AsyncConnection
Describe the bug
If columns are created with functions wrapping sqlalchemy.Column
(e.g. NotNullColumn = functools.partial(sqlalchemy.Column, nullable=False)
) they are not recognised by the type checker in places such as initialisation.
Expected behavior
A function wrapping the creation of a sqlalchemy.Column
object, should be treated the same by the type checker as the constructor of sqlalchemy.Column
.
To Reproduce
from sqlalchemy import Column, Integer, orm
Base = orm.declarative_base()
def MyColumn(*args, **kwargs) -> Column:
return Column(*args, **kwargs)
class Foo(Base):
bar = MyColumn(Integer)
foo = Foo(bar=1)
Error
example.py:11: error: Unexpected keyword argument "bar" for "Foo"
Found 1 error in 1 file (checked 1 source file)
Versions.
Describe the bug
Using hybrid_property leads to several errors
Expected behavior
No errors are returned
To Reproduce
from sqlalchemy import Column, Date, Time
from sqlalchemy.ext.hybrid import hybrid_property
from datetime import datetime
class DummyTable:
create_date = Column("create_date", Date)
create_time = Column("create_time", Time)
@hybrid_property
def create_timestamp(self) -> datetime:
"""create time property"""
return datetime.combine(self.create_date, self.create_time)
@create_timestamp.setter
def create_timestamp(self, value:datetime) -> None:
"""create time setter"""
self.create_date = value.date()
self.create_time = value.time()
DummyTable().create_timestamp = datetime.now()
Error
src/x.py:14:6: error: Name "create_timestamp" already defined on line 9 [no-redef]
@create_timestamp.setter
^
src/x.py:21:1: error: Cannot assign to a method [assignment]
DummyTable().create_timestamp = datetime.now()
^
src/x.py:21:33: error: Incompatible types in assignment (expression has type "datetime", variable has type "Callable[..., Any]") [assignment]
DummyTable().create_timestamp = datetime.now()
Versions.
Describe the bug
#73 limits Column.server_default
based on A FetchedValue instance, str, Unicode or text() construct representing the DDL DEFAULT value for the column, however more documentation shows that it can also accept ColumnElement
.
Expected behavior
Column(Boolean, nullable=False, server_default=true())
to type check.
Versions.
Additional context
Have a nice day!
Describe the bug
It appears that the stubs are missing the metadata
attribute on Table
objects. When running mypy, I get error: "Table" has no attribute "metadata"; maybe "tometadata" or "to_metadata"? [attr-defined]
.
Expected behavior
The metadata
field on Table
objects should be defined, most likely as Optional[MetaData]
.
To Reproduce
from sqlalchemy import MetaData, Table, Column, String
meta = MetaData()
table = Table('table', meta, Column('column', String))
assert table.metadata is meta
Error
`error: "Table" has no attribute "metadata"; maybe "tometadata" or "to_metadata"? [attr-defined]`.
Versions.
Describe the bug
We have a statically typed dictionary that has values of type Cast[Decimal]
& it seems to be valid, since we cast column value to FLOAT
. But the type annotation expects two type arguments. I think it uses this stub: https://github.com/sqlalchemy/sqlalchemy2-stubs/blob/master/sqlalchemy-stubs/sql/elements.pyi#L333
whereas it's for a method cast
on ColumnElement
.
Expected behavior
Static typing using Cast[Decimal]
works fine :P
To Reproduce
from uuid import uuid4, UUID
from typing import Dict
from sqlalchemy.sql.elements import Cast
from decimal import Decimal
from sqlalchemy import FLOAT, cast
class X:
y: Optional[float]
x =x()
x.y = 2.0
payload: Dict[UUID, Cast[Decimal]] = {
uuid4(): cast(x.y, FLOAT)
}
Error
error: "Cast" expects 2 type arguments, but 1 given
Versions.
Additional context
Maybe we should actually not use cast
in this case? We are using it because y
is an Optional[float]
& not float
, but we are 100% sure that it's not None
?
Have a nice day!
Describe the bug
When using methods of Select
decorated with @_generative
, type checkers will assume they return None
.
This is expected behavior as they are annotated as returning None
, but in fact they don't.
Expected behavior
The annotations should represent the actual behavior, ie returning a Select
instance.
Compare to the dropbox/sqlalchemy-stubs annotations for Select
.
To Reproduce
Register the plugin with mypy and run it against the following code:
from sqlalchemy.sql.selectable import Select
def bar(stmt: Select):
return stmt.offset(5).limit(5)
Error
test.py:4: error: "offset" of "GenerativeSelect" does not return a value
Versions.
Describe the bug
Mypy outputs "Module service.models.Person is not valid as a type" and then crashes with the following traceback:
version: 0.902
Traceback (most recent call last):
File "mypy/semanal.py", line 4872, in accept
File "mypy/nodes.py", line 950, in accept
File "mypy/semanal.py", line 1056, in visit_class_def
File "mypy/semanal.py", line 1134, in analyze_class
File "mypy/semanal.py", line 1143, in analyze_class_body_common
File "mypy/semanal.py", line 1203, in apply_class_plugin_hooks
File "/opt/venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/plugin.py", line 199, in _base_cls_hook
decl_class._scan_declarative_assignments_and_apply_types(ctx.cls, ctx.api)
File "/opt/venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 90, in _scan_declarative_assignments_and_apply_types
_scan_for_mapped_bases(cls, api, cls_metadata)
File "/opt/venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 476, in _scan_for_mapped_bases
_scan_declarative_assignments_and_apply_types(
File "/opt/venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 90, in _scan_declarative_assignments_and_apply_types
_scan_for_mapped_bases(cls, api, cls_metadata)
File "/opt/venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 476, in _scan_for_mapped_bases
_scan_declarative_assignments_and_apply_types(
File "/opt/venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/decl_class.py", line 51, in _scan_declarative_assignments_and_apply_types
info = util._info_for_cls(cls, api)
File "/opt/venv/lib/python3.8/site-packages/sqlalchemy/ext/mypy/util.py", line 211, in _info_for_cls
assert sym and isinstance(sym.node, TypeInfo)
AssertionError:
sym
seems to be None. Also, Person
is a class, not a module. It is imported in init.py from person.py.
I am using from __future__ import annotations
to avoid circular import issues when typing relationships in declarative models (they are in different files and reference each other).
Expected behavior
To not crash and to know that Person is a class.
To Reproduce
I am not sure how to reproduce it. It seems to happen only to one out of several similar models.
Versions.
Have a nice day!
Describe the bug
Synchronize_session argument in update and delete methods is currently not acceting a bool
Expected behavior
Accepts a bool.
To Reproduce
session.query(User).update({"name": John}, synchronize_session=False)
Error
error: Argument "synchronize_session" to "update" of "Query" has incompatible type "bool"; expected "str"
Versions.
Additional context
Have a nice day!
It's saturday, guys!
Describe your question
When creating a Base class with @as_declarative the following errors shows up using mypy error: Name 'DeclarativeMeta' is not defined
. Am I doing something wrong or is there a better way?
Example - please use the Minimal, Complete, and Verifiable guidelines if possible
Using the example as provided in the documentation, it works without a problem:
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user: Mapped[User] = relationship(User, back_populates="addresses")
main mypy --config mypy.ini --strict simple-test.py
Success: no issues found in 1 source file
When trying to use it as following it shows the error:
@as_declarative()
class Base(object):
id = Column(Integer, primary_key=True)
class User(Base):
__tablename__ = "user"
name = Column(String)
addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
class Address(Base):
__tablename__ = "address"
user_id: int = Column(ForeignKey("user.id"))
user: Mapped[User] = relationship(User, back_populates="addresses")
main mypy --config mypy.ini --strict simple-test.py
simple-test.py: error: Name 'DeclarativeMeta' is not defined
mypy config
[mypy]
plugins = sqlalchemy.ext.mypy.plugin
Versions
Describe the bug
Receiving an error when using @declared_attr
decorator on __tablenane__
class method.
Expected behavior
Not receiving an error.
To Reproduce
from uuid import UUID, uuid4
from sqlalchemy import Column
from sqlalchemy.orm import as_declarative, declared_attr
from sqlalchemy_utils import Timestamp, UUIDType, generic_repr
@as_declarative()
@generic_repr
class BaseModel(Timestamp):
__name__: str
uuid: UUID = Column(
UUIDType(native=True),
primary_key=True,
default=uuid4,
unique=True,
index=True,
)
@declared_attr
def __tablename__(cls) -> str:
return f"{cls.__name__}s"
Error
[SQLAlchemy Mypy plugin] Can't infer type from @declared_attr on function '__tablename__'; please specify a return type from this function that is one of: Mapped[<python type>], relationship[<target class>], Column[<TypeEngine>], MapperProperty[<python type>]
Versions.
Additional context
It also happens for columns with UUID (same example) if you don't specify a type on left hand side. It happens when using UUIDType
from sqlalchemy_utils
or UUID
from sqlalchemy.dialects.postgresql
.
Have a nice day!
Describe the bug
AsyncEngine._trans_ctx
is typed as returning itself on __aenter__
.
Expected behavior
AsyncEngine._trans_ctx
should be typed as returning AsyncConnection on __aenter__
.
To Reproduce
Please try to provide a Minimal, Complete, and Verifiable example.
See also Reporting Bugs on the website, and some example issues.
Please do not use Flask-SQLAlchemy or any other third-party extensions or dependencies in test cases. The test case must illustrate the problem without using any third party SQLAlchemy extensions. Otherwise, please report the bug to those projects first.
from sqlalchemy.ext import asyncio
async def test() -> None:
database = asyncio.create_async_engine("", future=True)
trans_ctx = database.begin()
reveal_type(trans_ctx)
async with trans_ctx as connection:
await connection.execute(...)
Error
PS C:\Users\Lucina\PycharmProjects\PTF> mypy test1.py
test1.py: note: In function "test":
test1.py:8:17: note: Revealed type is 'sqlalchemy.ext.asyncio.engine.AsyncEngine._trans_ctx'
test1.py:10:15: error: "_trans_ctx" has no attribute "execute" [attr-defined]
await connection.execute(...)
^
Found 1 error in 1 file (checked 1 source file)
Versions.
Additional context
This feels similar to #93 except that issue seems to focus on the return type of AsyncEngine.begin() without actually raising the fact that the typed behaviour of AsyncEngine._trans_ctx is erroneous.
Have a nice day!
On SQLA 1.3 + the Dropbox stubs, I use Base = declarative_base(metadata=meta)
for my models' base class and everything works fine. When I upgrade to 1.4 + the new stubs, using that Base
for my models generates the following mypy warning:
api/models.py:1121: error: Variable "api.database.Base" is not valid as a type
api/models.py:1121: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
So first I tried:
from sqlalchemy.orm import registry
....
mapper_registry = registry(metadata=meta)
Base = mapper_registry.generate_base()
however I get the same warning. I do get an additional warning:
error: Module 'sqlalchemy.orm' has no attribute 'registry'
mapper_registry = registry(metadata=meta)
class Base(metaclass=DeclarativeMeta):
__abstract__ = True
registry = mapper_registry
metadata = mapper_registry.metadata
however I then get:
mypy . --show-traceback
api/database.py:16: error: Module 'sqlalchemy.orm' has no attribute 'registry'
./api/models.py:34: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.rtfd.io/en/latest/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.812
Traceback (most recent call last):
File "mypy/semanal.py", line 4835, in accept
File "mypy/nodes.py", line 950, in accept
File "mypy/semanal.py", line 1048, in visit_class_def
File "mypy/semanal.py", line 1125, in analyze_class
File "mypy/semanal.py", line 1134, in analyze_class_body_common
File "mypy/semanal.py", line 1194, in apply_class_plugin_hooks
File "/Users/alex/.pyenv/versions/3.8.2/envs/api/lib/python3.8/site-packages/sqlalchemy/ext/mypy/plugin.py", line 193, in _base_cls_hook
_add_globals(ctx)
File "/Users/alex/.pyenv/versions/3.8.2/envs/api/lib/python3.8/site-packages/sqlalchemy/ext/mypy/plugin.py", line 184, in _add_globals
util.add_global(ctx, "sqlalchemy.orm.attributes", "Mapped", "__sa_Mapped")
File "/Users/alex/.pyenv/versions/3.8.2/envs/api/lib/python3.8/site-packages/sqlalchemy/ext/mypy/util.py", line 79, in add_global
lookup_sym: SymbolTableNode = ctx.api.modules[module].names[
KeyError: 'Mapped'
./api/models.py:34: : note: use --pdb to drop into pdb
Describe the bug
mypy
complaining about &
and |
operators in where clauses.
To Reproduce
test_table = Table(
"test_table",
metadata,
Column("i", UUID(as_uuid=True), nullable=False, primary_key=True),
Column("x", Integer, index=True),
Column("y", Integer, index=True),
Index(
"ix_xy_unique",
"x",
"y",
unique=True,
),
)
select([test_table]).where((test_table.c.x == 2) & (test_table.c.x == 2))
Error
Unsupported left operand type for | ("ClauseElement")
Unsupported left operand type for & ("ClauseElement")
Versions.
Additional context
Have a nice day!
I installed the the type stubs and configured the mypy plugin as directed in the documentation
My database is defined like this:
@as_declarative()
class Base:
def __repr__(self):
name = self.__class__.__name__
attrs = (
"%s=%r" % (attr, getattr(self, attr))
for attr in self._sa_class_manager.keys()
if not (attr[-2:] == "id" or isinstance(getattr(self, attr), list))
)
return name + "(%s)" % ", ".join(attrs)
class Source(Base):
__tablename__ = "sources"
id = sa.Column(sa.Integer, primary_key=True)
address = sa.Column(sa.String, index=True)
match_id = sa.Column(sa.Integer, sa.ForeignKey("matches.id"))
match = relationship("Match", back_populates="sources")
class Match(Base):
__tablename__ = "matches"
id = sa.Column(sa.Integer, primary_key=True)
original_id = sa.Column(sa.Integer, sa.ForeignKey("original.id"))
romanized_id = sa.Column(sa.Integer, sa.ForeignKey("romanized.id"))
count = sa.Column(sa.Integer)
original = relationship("Original", back_populates="matches")
romanized = relationship("Romanized", back_populates="matches")
sources = relationship("Source", back_populates="match")
sa.Index("matches_idx", Match.original_id, Match.romanized_id, unique=True)
class Standard(Base):
__tablename__ = "standards"
id = sa.Column(sa.Integer, primary_key=True)
st = sa.Column(sa.String, index=True, unique=True)
class Original(Base):
__tablename__ = "original"
id = sa.Column(sa.Integer, primary_key=True)
form = sa.Column(sa.String, index=True, unique=True)
matches = relationship("Match", back_populates="original")
class Romanized(Base):
__tablename__ = "romanized"
id = sa.Column(sa.Integer, primary_key=True)
form = sa.Column(sa.String, index=True)
standard_id = sa.Column(sa.Integer, sa.ForeignKey("standards.id"))
standard = relationship(Standard)
matches = relationship("Match", back_populates="romanized")
sa.Index("standard_form", Romanized.form, Romanized.standard_id, unique=True)
When I run mypy, I get these errors:
$ mypy deromanize
deromanize/cacheutils.py:201: error: Need type annotation for 'match'
deromanize/cacheutils.py:211: error: Need type annotation for 'original'
deromanize/cacheutils.py:212: error: Need type annotation for 'romanized'
deromanize/cacheutils.py:213: error: Need type annotation for 'sources'
deromanize/cacheutils.py:230: error: Need type annotation for 'matches'
deromanize/cacheutils.py:239: error: Need type annotation for 'standard'
deromanize/cacheutils.py:240: error: Need type annotation for 'matches'
Found 7 errors in 1 file (checked 6 source files)
This does not match the documented error message:
test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
collection for ORM mapped expression assigned to attribute 'user'
if both 'uselist' and 'collection_class' arguments are absent from the
relationship(); please specify a type annotation on the left hand side.
Found 1 error in 1 file (checked 1 source file)
Furthermore, when I attempt to add annotations, that doesn't work either:
class Source(Base):
__tablename__ = "sources"
id = sa.Column(sa.Integer, primary_key=True)
address = sa.Column(sa.String, index=True)
match_id = sa.Column(sa.Integer, sa.ForeignKey("matches.id"))
match: 'Match' = relationship("Match", back_populates="sources")
deromanize/cacheutils.py:201: error: Incompatible types in assignment (expression has type "RelationshipProperty[<nothing>]", variable has type "Match")
Am I doing something wrong or is this a bug?
Currently the session.query
returns Query[Any]
in all cases.
Some overloads could probably be added:
def query(self, model: T, ...) -> Query[T]: ...
def query(self, model: Union[TypeEngins, Type[TypeEngine]], ...) -> Query[Any]: ...
def query(self, model: T, model2: T2) -> Query[Tuple[T, T2]] # not sure it's actually correct
def query(self, thing: TableClause)->Query[tuple]
I'm not sure if these above are possible with python, or 100% correct
Is your feature request related to a problem? Please describe.
Currently https://github.com/dropbox/sqlalchemy-stubs allows specifying a type on a query eg Query[MyModel]
whereas doing that in this library yields: error: "Query" expects no type arguments, but 1 given
Describe the solution you'd like
Allow type arguments on Query
I was just trying out the new stubs on an existing project if mine, and it looks like you did some awesome work there - I can get rid of a lot of workarounds. Thanks! Nevertheless, there's one issue I wanted to share with you.
Describe the bug
There are two ways to pass the first parameter of a relationship: by class or by str. When using the class directly everything works as expected, the type is detected when using uselist
or relationsship
. And I don't find any false positives afterwards.
When I use a str to configure my relationship things get pretty messy. First, I need to type the annotations manually, I wasn't able to find any way to configure it such that auto-detection works.
[SQLAlchemy Mypy plugin] Can't infer type from ORM mapped expression assigned to attribute 'addresses'; please specify a Python type or Mapped[<python type>] on the left hand side.
Second, even when annotating it manually, I'll have to cast it every single time I use it (when it's some kind of Iterable as shown in the example below).
error: "List?[Address?]" has no attribute "__iter__" (not iterable)
error: "List?[Address?]" has no attribute "append"
Expected behavior
Type detection works for relationship independent of type of first argument.
To Reproduce
from __future__ import annotations
from typing import List
from sqlalchemy import Integer, Column, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: List[Address] = relationship("Address", back_populates="user", uselist=True)
def clone(self) -> User:
new_user = User(name=self.name)
for existing_addr in self.addresses:
new_user.addresses.append(
Address(address=existing_addr.address)
)
return new_user
class Address(Base):
__tablename__ = "addresses"
id = Column(Integer, primary_key=True)
address = Column(String)
user_id = Column(Integer, ForeignKey("users.id"))
user = relationship(User, back_populates="addresses", uselist=False)
Versions.
Have a nice day!
You too ;)
Following this, I'm getting the following error:
setup.cfg:2: error: Error importing plugin "sqlalchemy.ext.mypy.plugin": No module named '_contextvars'
Found 1 error in 1 file (errors prevented further checking)
setup.cfg:
[mypy]
plugins = sqlalchemy.ext.mypy.plugin
Expected behavior
mypy should work in a pipenv virtualenv :)
Versions.
Describe the bug
When use @property
and pass value in __init__
, mypy
show error: Unexpedted keyword argument "<property>" for "<entity>"
To Reproduce
from hashlib import md5
import sqlalchemy as sa
from sqlalchemy import orm
Base = orm.declarative_base()
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
nickname = sa.Column(sa.Unicode(20), nullable=False)
pwd_hash = sa.Column(sa.Unicode(200))
@property
def password(self):
raise AttributeError('Cannot read password.')
@password.setter
def password(self, value: str):
self.pwd_hash = md5(value.encode('utf-8')).hexdigest()
if __name__ == '__main__':
user = User(nickname='kenny', password='Aa1234') # <-- mypy: Unexpected keyword argument "password" for "User"
Error
app\__init__.py:25: error: Unexpected keyword argument "password" for "User"
Versions.
Have a nice day!
I cannot find the right typing for Array of Enums. It is also doesn't work with Array of Strings, so I haven't any workaround :(
Example
import sqlalchemy as sa
import enum
from typing import List
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class TokenType(str, enum.Enum):
a = "a"
class Model(Base):
token_types: List[TokenType] = sa.Column(
sa.ARRAY(sa.Enum(TokenType)),
nullable=False,
)
Complete stack trace, if applicable
app/models.py:305: error: [SQLAlchemy Mypy plugin] Left hand assignment
'token_types: "List[TokenType]"' not compatible with ORM mapped expression of
type "Mapped[List[_TE]]" [misc]
token_types: List[TokenType] = sa.Column(
Versions
Describe the bug
mypy reports an error when datetime_col.desc()
is used as an Index
second argument; datetime_col
(without desc
) works as intended.
Expected behavior
mypy does not produce an error
To Reproduce
from sqlalchemy import Column, DateTime, Integer, Index
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Model(Base):
id = Column(Integer, primary_key=True)
created_at = Column(DateTime)
__tablename__ = 'model'
__table_args__ = (
# error: Argument 2 to "Index" has incompatible type "ClauseElement"; expected "ColumnClause[Any]" [arg-type]
Index('created_at_idx', created_at.desc()),
)
Error
error: Argument 2 to "Index" has incompatible type "ClauseElement"; expected "ColumnClause[Any]" [arg-type]
Versions.
Have a nice day!
Describe the bug
I'd like to switch from the dropbox/sqlalchemy stubs to the official stubs, however when the declarative base is defined as attribute of an instance, it is not recognized as valid base class.
The Dropbox stubs give similar errors regarding the subclassing, however the types are still correctly inferred.
Expected behavior
Type definitions to be picked up as the builtin types: test.py:20: note: Revealed type is 'Union[builtins.int, None]'
To Reproduce
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, String
from sqlalchemy.orm.decl_api import DeclarativeMeta, declarative_base
db = DB()
class DB():
def __init__(self):
self.Base = declarative_base()
class A(db.Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
a = A()
reveal_type(a.id)
Error
test2.py:12: error: Name 'db.Base' is not defined
test2.py:17: note: Revealed type is 'sqlalchemy.sql.schema.Column[sqlalchemy.sql.sqltypes.Integer*]'
Found 1 error in 1 file (checked 1 source file)
Versions.
Additional context
The problem seems similar to one described on StackOverflow. However, I expected that replacing the direct subclassing with a typed variable would resort the problem.
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, String
from sqlalchemy.orm.decl_api import DeclarativeMeta, declarative_base
db = DB()
class DB():
def __init__(self):
self.Base = declarative_base()
Base: DeclarativeMeta = db.Base
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
a = A()
reveal_type(a.id)
Have a nice day!
Describe the bug
Following the mypy support instructions here and trying variations from here, I cannot find a way to get type checking to work for both the fields as clause elements and the dataclass constructor. Removing _mypy_mapped_attrs
results in a usable constructor, but unusable fields for constructing a query.
Expected behavior
That the dataclass constructor would be type checked according to the actual field type annotations. Instead, the presence of _mypy_mapped_attrs
causes mypy to expect Mapped
values to be passed to the constructor, so creating an instance does not pass type checking.
To Reproduce
from dataclasses import dataclass, field
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import registry
mapper_registry: registry = registry()
@mapper_registry.mapped
@dataclass
class Foo:
__tablename__ = "foo"
__table_args__ = {"schema": "public"}
__sa_dataclass_metadata_key__ = "sa"
uuid: str = field(
metadata={
"sa": Column(
UUID,
primary_key=True,
nullable=False,
)
}
)
_mypy_mapped_attrs = [uuid]
foo = Foo(
uuid="123",
)
[mypy]
plugins = sqlalchemy.ext.mypy.plugin
Error
mypy_test.py:31: error: Argument "uuid" to "Foo" has incompatible type "str"; expected "Mapped[str]"
Versions.
Additional context
Alternative mapping that produces the same error:
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
from sqlalchemy import Column, Table
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import registry
mapper_registry: registry = registry()
@mapper_registry.mapped
@dataclass
class Foo:
__table__ = Table(
"foo",
mapper_registry.metadata,
Column(
UUID,
primary_key=True,
nullable=False,
),
)
uuid: str = field()
if TYPE_CHECKING:
_mypy_mapped_attrs = [uuid]
foo = Foo(
uuid="123",
)
Describe the bug
When using joinedload the way it is described in the current documentation I get the following error:
"LoaderOption" not callable
Expected behavior
No error.
To Reproduce
Import joinedload as folows:
from sqlalchemy.orm import joinedload
And then use it in a query:
users = session.query(User).options(joinedload(User.address)).all()
Versions.
Have a nice day!
Again: you too :)
sqlalchemy/sqlalchemy@97d9226 implements new methods:
:meth:`_asyncio.AsyncSession.in_nested_transaction`,
:meth:`_asyncio.AsyncSession.get_transaction`,
:meth:`_asyncio.AsyncSession.get_nested_transaction`.
:func:`_asyncio.async_object_session`,
:func:`_asyncio.async_session` as well as a new
:class:`_orm.InstanceState` attribute :attr:`_orm.InstanceState.asyncio_session`
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.