-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
'DeclarativeMeta' is not defined #14
Comments
Some further testing shows that this error was introduced since 1.4.3 as running the last example above with 1.4.2 shows the following errors.
|
hi - to the poster: I don't have a fix for this and it remains to be seen if one is possible without changing how the plugin works. this will workaround for now:
for mypy people @bryanforbes @ilevkivskyi: I have no idea why this error is being shown, even if I explicitly add the name to the symbol table, it still shows it as not found:
what's the purpose of |
failing test in https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/2682 |
as_declarative wasn't supported in 1.4.2 and support was added in 1.4.3. the above test shows that the declarative class wasn't recognized at all which is also a bug that's been fixed. |
OK this seems to fix it, but per mypy devs I shouldn't have to do this? is this wrong? why wouldn't a name with GDEF need to be in self.globals? diff --git a/lib/sqlalchemy/ext/mypy/plugin.py b/lib/sqlalchemy/ext/mypy/plugin.py
index c8fbcd6a2..7e60ffa68 100644
--- a/lib/sqlalchemy/ext/mypy/plugin.py
+++ b/lib/sqlalchemy/ext/mypy/plugin.py
@@ -201,6 +201,10 @@ def _make_declarative_meta(
info = target_cls.info
info.declared_metaclass = info.metaclass_type = declarative_meta_instance
+ sym = SymbolTableNode(GDEF, declarative_meta_typeinfo)
+ api.add_symbol_table_node("DeclarativeMeta", sym)
+ api.globals["DeclarativeMeta"] = sym
+
def _base_cls_decorator_hook(ctx: ClassDefContext) -> None:
|
I did some more testing, and don't know if it is related. But in our code base we use Mixins to add parent columns to several tables. It is a pattern which we use for a while now and keeps our code clean. But doing this seems to generate another mypy error.
This error is fixable by removing the I have not been able to find a workaround, but reproducing code is as follows: # models/__init__.py
from typing import TYPE_CHECKING
from sqlalchemy import Column, Integer
from sqlalchemy.orm import Mapped, as_declarative, declared_attr
if TYPE_CHECKING:
from sqlalchemy.orm.decl_api import DeclarativeMeta
@as_declarative()
class Base(object):
@declared_attr
def __tablename__(self) -> Mapped[str]:
return self.__name__.lower()
id = Column(Integer, primary_key=True)
from .user import User
from .address import Address
__all__ = ['User', 'Address']
# models/user.py
from typing import List, TYPE_CHECKING
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import Mapped, relationship
from sqlalchemy.orm.decl_api import declared_attr
from sqlalchemy.orm.relationships import RelationshipProperty
from . import Base
if TYPE_CHECKING:
from .address import Address
class User(Base):
name = Column(String)
addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
class HasUser:
@declared_attr
def user_id(self) -> "Column[Integer]":
return Column(
Integer,
ForeignKey(User.id, ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
)
@declared_attr
def user(self) -> RelationshipProperty[User]:
return relationship(User)
# models/address.py
from typing import TYPE_CHECKING
from . import Base
from .user import HasUser
if TYPE_CHECKING:
from .user import User
from sqlalchemy import Integer, Column
from sqlalchemy.orm import RelationshipProperty
class Address(Base, HasUser):
pass Also, if we don't have the TYPE_CHECKING block in address.py the following errors show.
Unfortunately I'm not really familiar with either mypy and the mypy plugin system, otherwise I could deepdive a bit more. But it feels that things are not "re-exported" correctly. Please let me know if there is a good starting point in helping out, I absolutely love typing in Python and would love to see it working for SqlAlchemy too. If this was supposed to be opened as a new issue, my apologies, but it seemed somehow related. |
yes it's likely. mypy's behavior is extremely difficult to predict as it arrives at the same result through many different codepaths depending on how the files are laid out, what was already cached in .mypy_cache or not, incremental mode, etc. We have a similar set of fixes for mypy crashes in this regard coming through in https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/2680 .
OK great, this will be added to what I'm doing in the above review as I need to greatly expand the test suite to accommodate for multi-file setups as well as rerunning files for incremental mode tests.
wrt mixins, my current thinking is that the mypy plugin will need a hint for mixins for cases where they are by themselves in a file and arent recognized as part of a mapped hierarchy. once i get that in with some tests it will probably look like:
I just learned how to do mypy plugins in the past month and at the moment I can't point people towards an "easy" way to do it, you start out by reading the source code to the example plugins at https://github.com/python/mypy/tree/master/mypy/plugins , and then basically experimenting with print statements and pdb. I spend a lot of time inside of mypy's source finding out how things work, but it's a very large and interconnected system with multiple passes and phases that I only have a vague idea how they work. getting the plugin right now to work as well as it does (which is barely) was one of the more difficult programming tasks I've done in some years.
it's all good because this is all super -alpha stuff and we're still just getting the baseline functionality into the thing. |
You won't have to do this for
Calling
To be honest, I'm not really sure. I didn't design the API and find this part of the semantic analyzer API fairly confusing. |
I think this is going to have to be the case unless we want to scan all classes for declarative properties (I'd rather not do that). There may be an advantage to using a decorator, though: I don't think the plugin will need to build the properties while scanning the class hierarchies for declarative information since it will already be built and serialized into |
I did some digging into the plugin system and it looks like diff --git a/lib/sqlalchemy/ext/mypy/plugin.py b/lib/sqlalchemy/ext/mypy/plugin.py
index c8fbcd6a2..f35aa08e6 100644
--- a/lib/sqlalchemy/ext/mypy/plugin.py
+++ b/lib/sqlalchemy/ext/mypy/plugin.py
@@ -119,6 +119,18 @@ def _queryable_getattr_hook(ctx: AttributeContext) -> Type:
def _fill_in_decorators(ctx: ClassDefContext) -> None:
for decorator in ctx.cls.decorators:
+ if (
+ isinstance(decorator, nodes.CallExpr)
+ and isinstance(decorator.callee, nodes.NameExpr)
+ and decorator.callee.name == "as_declarative"
+ ):
+ declarative_meta_sym: SymbolTableNode = ctx.api.modules[
+ "sqlalchemy.orm.decl_api"
+ ].names["DeclarativeMeta"]
+ declarative_meta_typeinfo: TypeInfo = declarative_meta_sym.node
+ sym = SymbolTableNode(GDEF, declarative_meta_typeinfo)
+ ctx.api.add_symbol_table_node("__sa_DeclarativeMeta", sym)
+ continue
# set the ".fullname" attribute of a class decorator
# that is a MemberExpr. This causes the logic in
# semanal.py->apply_class_plugin_hooks to invoke the
@@ -189,7 +201,7 @@ def _make_declarative_meta(
].names["DeclarativeMeta"]
declarative_meta_typeinfo: TypeInfo = declarative_meta_sym.node
- declarative_meta_name: NameExpr = NameExpr("DeclarativeMeta")
+ declarative_meta_name: NameExpr = NameExpr("__sa_DeclarativeMeta")
declarative_meta_name.kind = GDEF
declarative_meta_name.fullname = "sqlalchemy.orm.decl_api.DeclarativeMeta"
declarative_meta_name.node = declarative_meta_typeinfo |
it seems really simple and tempting to just put the names in api.globals whenever we want and be done with it. we're a plugin, we should be able to say "no really, just have this name around for us". that said is the idea that when we call add_symbol_table_node within the mro hook, we happen to be in the right scope such that the names get put into api.globals (or somewhere else ?) in any case? |
I totally understand, but it's not a public API, so at some point
That's exactly right. In
|
we can go with this adjustment, but for future plugin authors I would propose mypy needs to have dedicated API to add global symbols as this is a common need for the kinds of things "plugins" do. it's really not reasonable that writing a mypy plugin needs to depend on this level of undocumented internals, I know it's a very hard problem in this case but this is quite poor support for third parties. |
I agree. I wonder if @JukkaL, @hauntsaninja, or @ilevkivskyi could comment on what it would take to get an API like that into the mypy plugin API. |
I've got fixes for these names in sqlalchemy/sqlalchemy@1c8e221 . |
I'm using My code in @declarative_mixin
class DeliveryRefundPolicyMixin:
@declared_attr
def default_carrier(cls) -> Mapped["Carrier"]:
return relationship("Carrier", primaryjoin="Carrier.id==%s.default_carrier_id" % cls.__name__)
class ProductBase(AbstractBase, DeliveryRefundPolicyMixin):
__abstract__ = True models.py not using |
where are you getting the error? in mixin.py or in models.py? |
In models.py |
the issue seems different I think the solution here is to use if TYPE_CHECKING:
from module.to.carrier import Carrier |
Yes, that's right. That way the error goes away. But the Carrier is not used in model.py so it conflicts with libraries like flake8. Is this normal? |
I think it's currently required for how the plugin works. In any case this issue seems different from this one, so if you could open a new issue it would be great |
@CaselIT Thanks, i just opened new issue. |
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:
When trying to use it as following it shows the error:
main mypy --config mypy.ini --strict simple-test.py simple-test.py: error: Name 'DeclarativeMeta' is not defined
mypy config
Versions
The text was updated successfully, but these errors were encountered: