Mypy plugin and stubs for SQLAlchemy

Overview

mypy logo

Mypy plugin and stubs for SQLAlchemy

Build Status Checked with mypy

This package contains type stubs and a mypy plugin to provide more precise static types and type inference for SQLAlchemy framework. SQLAlchemy uses some Python "magic" that makes having precise types for some code patterns problematic. This is why we need to accompany the stubs with mypy plugins. The final goal is to be able to get precise types for most common patterns. Currently, basic operations with models are supported. A simple example:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)

user = User(id=42, name=42)  # Error: Incompatible type for "name" of "User"
                             # (got "int", expected "Optional[str]")
user.id  # Inferred type is "int"
User.name  # Inferred type is "Column[Optional[str]]"

Some auto-generated attributes are added to models. Simple relationships are supported but require models to be imported:

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from models.address import Address

...

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    address = relationship('Address')  # OK, mypy understands string references.

The next step is to support precise types for table definitions (e.g. inferring Column[Optional[str]] for users.c.name, currently it is just Column[Any]), and precise types for results of queries made using query() and select().

Installation

Install latest published version as:

pip install -U sqlalchemy-stubs

Important: you need to enable the plugin in your mypy config file:

[mypy]
plugins = sqlmypy

To install the development version of the package:

git clone https://github.com/dropbox/sqlalchemy-stubs
cd sqlalchemy-stubs
pip install -U .

Development Setup

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

git submodule update --init --recursive
pip install -r dev-requirements.txt

Then, to run the tests, simply:

pytest

Development status

The package is currently in alpha stage. See issue tracker for bugs and missing features. If you want to contribute, a good place to start is help-wanted label.

Currently, some basic use cases like inferring model field types are supported. The long term goal is to be able to infer types for more complex situations like correctly inferring columns in most compound queries.

External contributions to the project should be subject to Dropbox Contributor License Agreement (CLA).


Copyright (c) 2018 Dropbox, Inc.

Comments
  • Typed queries

    Typed queries

    Goal of this PR is to type orm's Query objects. This will allow to type-check stuff like:

    session.query(Employee)  # -> Query[Employee]
    session.query(Employee).get(123)  # -> Employee
    session.query(Employee, Invoice)  # -> Query[Tuple[Employee, Invoice]]
    session.query(Employee, Invoice).all()  # -> List[Tuple[Employee, Invoice]]
    
    opened by rafales 19
  • pre-commit mypy additional dependencies when pre-commit-config.yaml is inside child directory

    pre-commit mypy additional dependencies when pre-commit-config.yaml is inside child directory

    - repo: https://github.com/pre-commit/mirrors-mypy
       rev: v0.782
       hooks:
         - id: mypy
           args: [--strict-optional, --ignore-missing-imports, --follow-imports=skip]
           additional_dependencies:
             - pydantic
             - sqlalchemy-stubs
    

    Currently my .pre-commit-config.yaml looks like the above. However, adding sqlalchemy-stubs to the additional_dependencies doesn't do anything to change the behaviour. Adding sqlmypy to setup.cfg works just fine however.

    If someone on the team or @asottile can help, that'd be much appreciated.

    opened by JitPackJoyride 11
  • mypy 0.930 breaks the sqlmypy plugin with an INTERNAL ERROR

    mypy 0.930 breaks the sqlmypy plugin with an INTERNAL ERROR

    Upgrading to mypy 0.930 triggered the following INTERNAL ERROR issue:

    % mypy --config-file pkg/tsa-repo-db-client/setup.cfg -p tsa.repo.db --show-traceback
    ***/pkg/tsa-repo-db-client/tsa/repo/db/model.py:11: error: INTERNAL ERROR -- Please try using mypy master on Github:
    https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
    Please report a bug at https://github.com/python/mypy/issues
    version: 0.930
    Traceback (most recent call last):
      File "mypy/semanal.py", line 5132, in accept
      File "mypy/nodes.py", line 1140, in accept
      File "mypy/semanal.py", line 2110, in visit_assignment_stmt
      File "mypy/semanal.py", line 2392, in apply_dynamic_class_hook
      File "***/miniconda3/envs/tsa37/lib/python3.7/site-packages/sqlmypy.py", line 203, in decl_info_hook
        obj = ctx.api.builtin_type('builtins.object')
    AttributeError: 'SemanticAnalyzer' object has no attribute 'builtin_type'
    ***/pkg/tsa-repo-db-client/tsa/repo/db/model.py:11: : note: use --pdb to drop into pdb
    

    Considering the announcement in https://github.com/python/mypy/issues/6617 , I assume this should be solved here ...

    Workaround: keep mypy pinned on version 0.921

    opened by plankthom 10
  • Use list for relationship(uselist=True) instead of iterable

    Use list for relationship(uselist=True) instead of iterable

    Right now relationship(uselist=True) uses typing.Iterable, which breaks most of our code.

    Relationship configuration is described in greater detail here: https://docs.sqlalchemy.org/en/13/orm/collections.html

    Right now this PR just replaces typing.Iterable with list.

    What I plan to do in this PR:

    • [x] default to list when using uselist=True
    • [ ] use collection_class argument to customize collection class if present (simple type)

    What can be done later:

    • support dynamic loader and query_class parameters (when #81 gets accepted)
    • support dict-like collections (attribute_mapped_collection, column_mapped_collection, mapped_collection)
    opened by rafales 9
  • Data type for Numeric column should be Decimal, not float

    Data type for Numeric column should be Decimal, not float

    The official SQLAlchemy documentation for the column type Numeric says:

    This type returns Python decimal.Decimal objects by default, unless the Numeric.asdecimal flag is set to False, in which case they are coerced to Python float objects.

    https://docs.sqlalchemy.org/en/13/core/type_basics.html#sqlalchemy.types.Numeric

    Currently this is specified in sqltypes.pyi as TypeEngine[float] but by default the inner data type for Numeric should be Decimal, not float. That would be more in line with

    • what the SQLAlchemy documentation says.
    • how SQLAlchemy actually behaves.

    The current approach reports typing errors even though the code is correct (due to the float/Decimal confusion) . Example to reproduce:

    from decimal import Decimal
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import Column, Integer, Numeric
    
    Base = declarative_base()
    
    
    class Transaction(Base):
        __tablename__ = 'transactions'
        id = Column(Integer, primary_key=True)
        amount = Column(Numeric, nullable=False)
    
    
    user = User(id=42, amount=Decimal(42))
    

    This code is 100% correctly typed, but since the sqlalchemy-stubs use float instead of Decimal we receive a typing-error message that we shouldn't get:

    mypy-sa-test.py:14: error: Incompatible type for "amount" of "User" (got "Decimal", expected "float")
    
    opened by martinstein 8
  • Type relationship as list

    Type relationship as list

    :sparkles: Type relationship as list.

    Currently, the plugin types relationships with uselist as Iterable[X], but these relationships actually expose a list with list semantics, including .append(), assignments, etc.

    This PR updates the plugin to type those relationships with List[X] instead of Iterable[X].

    Ref: https://docs.sqlalchemy.org/en/13/orm/relationship_api.html#sqlalchemy.orm.relationship.params.uselist

    Ref of usage with assignment: https://docs.sqlalchemy.org/en/13/orm/tutorial.html#working-with-related-objects Ref of usage with append: https://docs.sqlalchemy.org/en/13/orm/tutorial.html#building-a-many-to-many-relationship


    Note: I'm by no means expert in mypy internals and its plugins, I'm not 100% sure the usage of ctx.api.named_generic_type('builtins.list', [new_arg]) is correct here, but I see that seems to be how it's used by mypy: https://github.com/python/mypy/blob/master/mypy/plugins/ctypes.py#L158

    opened by tiangolo 7
  • Typed queries 2 (reduced)

    Typed queries 2 (reduced)

    First, thanks for making these stubs and plugin! It works great. I'm using it with Pyright and that gives me autocompletion in VS Code for SQLAlchemy stuff, that never worked before. :sparkles:


    This PR includes a reduced version of the changes by @rafales in #81 .

    I removed the plugin method hook and its tests to reduce the scope of the PR, to make it easier to manage and merge, as just the type annotations are a great improvement.

    I applied @ilevkivskyi 's code review from that PR here as well.


    There were some unresolved questions/discussions in that PR, so I removed any changes related to those sections.

    My objective is just to reduce the scope of the changes to the minimum that would be easily acceptable and postpone any additional improvement for future PRs.

    opened by tiangolo 7
  • Release v0.2?

    Release v0.2?

    Hello! Thanks for building this plugin and stubs! It's great to see more support for type-checked Python.

    We hit a wall and needed to #type: ignore our ForeignKeyConstraint calls to get them to pass type checking. We found that a fix had been merged a couple months ago (#56), but is not yet released. We were deciding between either submoduling this repo so we could use the fix which is on master or just #type: ignoreing it, neither is really great. Could you guys please put out a v0.2 release for this library so we can clean that up? Thanks :)

    opened by MattF-NSIDC 7
  • Latest version seems to break mypy

    Latest version seems to break mypy

    when running mypy on my project, I get this error: Error importing plugin sqlmypy

    When debugging a little bit, the problem seems to be here:

        def get_dynamic_class_hook(self, fullname: str) -> CB[DynamicClassDefContext]:
      File "/usr/lib/python3.5/typing.py", line 546, in __getitem__
        "Cannot subscript an existing Union. Use Union[u, t] instead.")
    TypeError: Cannot subscript an existing Union. Use Union[u, t] instead.
    
    opened by mehdigmira 7
  • Implement plugins to detect declarative bases

    Implement plugins to detect declarative bases

    We currently can only detect declarative bases defined using @as_declarative, a plugin for detecting declarative_base() depends on https://github.com/python/mypy/issues/5508

    enhancement priority-normal topic-plugins 
    opened by ilevkivskyi 7
  • Unexpected keyword argument

    Unexpected keyword argument

    Hello, With the latest release of mypy and the master branch of sqlalchemy-stubs, this code causes mypy to raise a "error: Unexpected keyword argument "name" for "User"" Is there smth I can do to avoid these errors ?

    from sqlalchemy import CheckConstraint, Column, Integer, String
    from sqlalchemy.ext.declarative import as_declarative
    
    @as_declarative()
    class Base:
        pass
    
    
    class User(Base):
        __tablename__ = 'users'
        id = Column(Integer, primary_key=True)
        name = Column(String)
    
    
    user = User(name="xx")
    
    opened by mehdigmira 6
  • overlaps keyword is not included in typing checks

    overlaps keyword is not included in typing checks

    error: Unexpected keyword argument
    "overlaps" for "RelationshipProperty"  [call-arg]
    

    I get this error when running typing on code that includes the overlaps keyword on a relationship.

    opened by justin-hu-dev 0
  • add delete method on scoped_session

    add delete method on scoped_session

    for this sort of code:

    user = UserModel.query.filter_by(username="awesomeuser").first()
    db.session.delete(user)  # type: ignore, until now
    db.session.commit()
    

    not sure if it's a new issue in some version of sqlalchemy. i'm on SQLAlchemy-1.4.42 and Flask-SQLAlchemy-3.0.2.

    opened by burnettk 1
  • declarative_base available from sqlalchemy.orm in 1.4

    declarative_base available from sqlalchemy.orm in 1.4

    https://docs.sqlalchemy.org/en/14/orm/extensions/declarative/

    Changed in version 1.4: The vast majority of the Declarative extension is now integrated into the SQLAlchemy ORM and is importable from the sqlalchemy.orm namespace.

    Using the old location (ext.declarative) gives a deprecation warning when SQLALCHEMY_WARN_20 enabled.

    opened by philbudne 1
  • Hints do not work with imported Base

    Hints do not work with imported Base

    There are no hints in PyCharm for models, which inherit declarative base from another module. Here is example:

    # base.py
    import re
    
    from sqlalchemy import Column, Integer, DateTime
    from sqlalchemy.ext.declarative import declared_attr
    from sqlalchemy.sql import func
    from sqlalchemy.ext.declarative import declarative_base
    
    
    class Base:
        @declared_attr
        def __tablename__(cls):
            return cls.__name__.lower()
    
        id = Column(Integer, primary_key=True, index=True)
        created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
    
    
    Base = declarative_base(cls=Base)
    
    # models.py
    from base import Base
    from sqlalchemy import Integer, String, Column
    
    
    class SomeModel(Base):
        id = Column(Integer, primary_key=True)
        name = Column(String)
        description = Column(String)
    

    изображение

    opened by Phinnik 0
  • sql-alchemy 1.4 - using is_not on column leads to mypy warning, while using isnot does not lead to the warning

    sql-alchemy 1.4 - using is_not on column leads to mypy warning, while using isnot does not lead to the warning

    In the sqlalchemy version 1.4, the method isnot was renamed to is_not, and the old method was left for backward compatibility.

    (see https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.ColumnOperators.is_not)

    However, if one try to use is_not on the column expression

    (something like:

    ...
    .where(and_(pch_alias.c.New_Id.is_not(None))))
    

    ) then one gets following warning in mypy:

    error: "Column[Any]" has no attribute "is_not" However, if one is using older isnot construct, then mypy produces no warning.

    As is_not now is a recommended method, I think stubs should function with it without warnings.

    opened by serhiy-yevtushenko 1
Owner
Dropbox
Dropbox
open source tools to generate mypy stubs from protobufs

mypy-protobuf: Generate mypy stub files from protobuf specs We just released a new major release mypy-protobuf 2. on 02/02/2021! It includes some back

Dropbox 527 Jan 3, 2023
A plugin for flake8 integrating Mypy.

flake8-mypy NOTE: THIS PROJECT IS DEAD It was created in early 2017 when Mypy performance was often insufficient for in-editor linting. The Flake8 plu

Łukasz Langa 103 Jun 23, 2022
Pymxs, the 3DsMax bindings of Maxscript to Python doesn't come with any stubs

PyMXS Stubs generator What Pymxs, the 3DsMax bindings of Maxscript to Python doe

Frieder Erdmann 19 Dec 27, 2022
A plugin for Flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle.

flake8-bugbear A plugin for Flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycode

Python Code Quality Authority 869 Dec 30, 2022
Pylint plugin for improving code analysis for when using Django

pylint-django About pylint-django is a Pylint plugin for improving code analysis when analysing code using Django. It is also used by the Prospector t

Python Code Quality Authority 544 Jan 6, 2023
❄️ A flake8 plugin to help you write better list/set/dict comprehensions.

flake8-comprehensions A flake8 plugin that helps you write better list/set/dict comprehensions. Requirements Python 3.6 to 3.9 supported. Installation

Adam Johnson 398 Dec 23, 2022
Flake8 plugin that checks import order against various Python Style Guides

flake8-import-order A flake8 and Pylama plugin that checks the ordering of your imports. It does not check anything else about the imports. Merely tha

Python Code Quality Authority 270 Nov 24, 2022
flake8 plugin that integrates isort

Flake8 meet isort Use isort to check if the imports on your python files are sorted the way you expect. Add an .isort.cfg to define how you want your

Gil Forcada Codinachs 139 Nov 8, 2022
Flake8 plugin to find commented out or dead code

flake8-eradicate flake8 plugin to find commented out (or so called "dead") code. This is quite important for the project in a long run. Based on eradi

wemake.services 277 Dec 27, 2022
A Pylint plugin to analyze Flask applications.

pylint-flask About pylint-flask is Pylint plugin for improving code analysis when editing code using Flask. Inspired by pylint-django. Problems pylint

Joe Schafer 62 Sep 18, 2022
flake8 plugin to run black for checking Python coding style

flake8-black Introduction This is an MIT licensed flake8 plugin for validating Python code style with the command line code formatting tool black. It

Peter Cock 146 Dec 15, 2022
A plugin for Flake8 that checks pandas code

pandas-vet pandas-vet is a plugin for flake8 that provides opinionated linting for pandas code. It began as a project during the PyCascades 2019 sprin

Jacob Deppen 146 Dec 28, 2022
flake8 plugin to catch useless `assert` statements

flake8-useless-assert flake8 plugin to catch useless assert statements Download or install on the PyPI page Violations Code Description Example ULA001

null 1 Feb 12, 2022
Optional static typing for Python 3 and 2 (PEP 484)

Mypy: Optional Static Typing for Python Got a question? Join us on Gitter! We don't have a mailing list; but we are always happy to answer questions o

Python 14.4k Jan 8, 2023
The strictest and most opinionated python linter ever!

wemake-python-styleguide Welcome to the strictest and most opinionated python linter ever. wemake-python-styleguide is actually a flake8 plugin with s

wemake.services 2.1k Jan 1, 2023
coala provides a unified command-line interface for linting and fixing all your code, regardless of the programming languages you use.

"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." ― John F. Woods coala provides a

coala development group 3.4k Dec 29, 2022
Automated security testing using bandit and flake8.

flake8-bandit Automated security testing built right into your workflow! You already use flake8 to lint all your code for errors, ensure docstrings ar

Tyler Wince 96 Jan 1, 2023
Easy saving and switching between multiple KDE configurations.

Konfsave Konfsave is a config manager. That is, it allows you to save, back up, and easily switch between different (per-user) system configurations.

null 42 Sep 25, 2022
A framework for detecting, highlighting and correcting grammatical errors on natural language text.

Gramformer Human and machine generated text often suffer from grammatical and/or typographical errors. It can be spelling, punctuation, grammatical or

Prithivida 1.3k Jan 8, 2023