I am running into this sqlachemy error that I haven't been able to understand:
sqlalchemy.exc.InvalidRequestError: SQL expression, column, or mapped entity expected - got '<class '__main__.JobRecord'>'
What does this error mean? What are possible causes?
This is the method that triggers the error:
#classmethod
def find_job_record_from_pk(cls, pk):
'''
return the job record with the given pk
'''
job_record = MlcDb.get_session().query(cls).filter(cls.pk == pk).first()
return job_record
Mapping:
#classmethod
def define_mapping(cls):
'''
SQLAlchemy mapping definition
'''
cls.mapper = mapper(cls, cls.table,
polymorphic_on = cls.table.c.item_type,
properties = {
'item_type': synonym('_JobRecord__item_type', map_column=True),
'version': synonym('_JobRecord__version', map_column=True),
'state': synonym('_JobRecord__state', map_column=True),
'date_created' : synonym( '_JobRecord__date_created', map_column=True)
}
)
In my case the error ocurred because I forgot to extend from "Base" in the class Definition.
Base on the given error it's probable that it was declared as
follows:
JobRecord:
pro1: Column()
instead of
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
JobRecord(Base): #here is the missing extend
pro1: Column()
The messages says that "the parameter is not recognized.
According to the documentation
Classes mapped using the Declarative system are defined in terms of a base class which maintains a catalog of classes and tables relative to that base - this is known as the declarative base class
And it's important to notice that:
Our application will usually have just one instance of this base in a commonly imported module. We create the base class using the declarative_base() function
Of course all this helps me in my particular case
I hope this is useful.
more info:
https://docs.sqlalchemy.org/en/13/orm/tutorial.html
try filter_by(pk = pk) instead
Related
I'm trying to get typing working similar how SQAlchemy's ORM model declaration works:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class MyClass(Base):
name = Column(String(30))
def __repr__(self):
return f"{self.name.zfill(3)}"
In this example you can see how name is declared as a Column(String(30)), but when the class is initialized, name can be used as self.name and will be a string. The part I'm most interested about is having mypy not throw any errors and an IDE recognize this and help with formatting, in this case PyCharm correctly assumes name is a string
I've been checking out their source code but I can't understand how they're making it work. As a very brief example, this would be ideally how types are defined:
from typing import Any, Optional, Type
class Attribute(object):
def __init__(self, attr_type: Optional[Type[Any]] = None):
self.attr_type = attr_type
class MyModel(object):
# This would be nice
name: str = Attribute()
# But this could work too
name2 = Attribute(str)
def __repr__(self):
return f"{self.name.zfill(3)}"
Although at the moment this small snippet makes mypy complain:
type_checker_test.py:11: error: Incompatible types in assignment (expression has type "Attribute", variable has type "str")
Found 1 error in 1 file (checked 1 source file)
Having class
from pony.orm import Required
class Place:
name = Required(str)
country = Required(str)
Is it possible to generate PonyORM entity from this definition with
from pony.orm import database
def initialize_database():
db=database()
adds_db_entity_connection(db, Place)
db.bind(provider="sqlite",filename=":memory:")
db.generate_mapping(check_tables=True, create_tables=True)
return db
I have tried creating these from dictionary like {"name": Required(str), "country":Required(str)} with
def bind_class(name, db, prop_dict):
return type(
name,
(
db.Entity,
),
prop_dict,
)
But this leads to
TypeError: Entity classes cannot contain __slots__ variable
I am aware of dynamic factory solution but this does not really apply to here. I have also tried adding db.Entity to Place.__bases__ but this did not work either as I suspect the act of inheritance is required to formulate database relation to the entities. Is the approach completely impossible?
mypy reports an error in the following code:
import enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Enum
Base = declarative_base()
class MyEnum(enum.Enum):
A = 1
B = 2
class MyTable(Base):
__tablename__ = 'my_table'
col = Column(Enum(MyEnum), nullable=False)
c = MyTable(col=MyEnum.A)
Following is the error:
a.py:16: error: Incompatible type for "col" of "MyTable" (got
"MyEnum", expected "str")
How do I make this error go away without adding a "type: ignore" ? I could also replace MyEnum.A with MyEnum.A.name to make the error go away. But this doesn't look clean and is also not suggested in sqlalchemy documentation.
I don't know what exactly makes this error go away but on my setup after some refactoring and mypy configuring this error disappeared.
I installed sqlalchemy-stubs
pip install sqlalchemy-stubs
and created setup.cfg file:
[mypy]
files = **/*.py
plugins =
sqlalchemy.ext.mypy.plugin
import enum
from sqlalchemy import Column, Enum
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class MyEnum(enum.Enum):
"""My enum."""
ONE = 1
TWO = 2
class MyTable(Base):
"""My table."""
__tablename__ = 'my_table'
col = Column(Enum(MyEnum), nullable=False)
table = MyTable(col=MyEnum.ONE)
Since sqlalchemy does not use type annotations, you have to introduce them yourself in your script. The dynamically created Base class is of type DeclarativeMeta. If you type annotate the variable Base, mypy does not show the error any more.
from sqlalchemy.orm.decl_api import DeclarativeMeta
Base: DeclarativeMeta = declarative_base()
Now the Base variable is properly type annotated. I think that the DeclarativeMeta class is not to be exposed in the API, so I'm not sure how sustainable this solution will be.
This is however not surprising and one may ask the question what the purpose of enforced static type checking in a dynamically typed language would be. But that's for another day.
I have a project where every table has some common fields, e.g., status, and I'd like to alias all of them. Is it possible to do this without manually adding the alias to each class? E.g., here's what I have now:
from core import foo_table, bar_table, Status
Base = declarative_base()
def CustomBase(object):
#property
def status(self):
return Status(self._status)
...
def Foo(Base, CustomBase):
__table__ = foo_table
_status = foo_table.c.status
...
def Bar(Base, CustomBase):
__table__ = bar_table
_status = bar_table.c.status
...
Ideally, I'd like to be able to set up my _status alias on CustomBase instead of in Foo and Bar, or set up my project so that the alias is added whenever a class extending CustomBase is loaded. Is this possible or am I trying to accomplish this in the wrong way? I know I can make it work if I rename the status field in my db or rename the status property in the CustomBase, but I'd prefer to avoid this if possible since they're both representations of the same thing, and there's no need to directly access in the enum value through the code.
Thanks!
Your best bet is probably to create a custom Column type that adapts Enum to translate to and from your own Status class. See here for a full reference. Below is a draft for your core module, the precise code depends a bit on your situation.
# core module
import sqlalchemy.types as types
class DBStatus (types.TypeDecorator):
impl = types.Enum
# what should happen with Status objects on the way into the table
def process_bind_param(self, value, dialect):
if value is None:
return value
return str(value) # if Status has a __str__ or __repr__ method
# what should happen with Enum objects on the way out of the table
def process_result_value(self, value, dialect):
if value is None:
return value
return Status(value)
foo_table = Table(
'foo',
MetaData(),
Column('status', DBStatus('OK', 'Error')),
# ...
)
After this you don't have to do anything special anymore in the module with the mappings:
# module with the mappings
Base = declarative_base()
class Foo (Base):
__table__ = foo_table
# ...
In fact it's so straightforward you might just as well use full declarative mapping, as far as the Status columns are concerned.
# everything in one module
class DBStatus (types.TypeDecorator):
# same as above
Base = declarative_base()
class Foo (Base):
status = Column(DBStatus('OK', 'Error'))
# ...
Edit
The problem was in importing. What I should have done was write: from SomeInterface import SomeInterface. Really I should write module name in lowercase someinterface.py as per Python styleguide (PEP 8).
I have a file model.py the defines all classes related to my DB as well as instantiates my Base.
# model.py
metadata = MetaData()
DeclarativeBase = declarative_base()
metadata = DeclarativeBase.metadata
class Bar(DeclarativeBase):
__tablename__ = 'Bar'
__table_args__ = {}
# column and relation definitions
The file model.py is autogenerated so I can't really touch it. What I did instead was create a file called modelaugmented.py where I add extra functionality to some of the model classes via inheritance.
# modelaugmented.py
from model import *
import SomeInterface
class BarAugmented(Bar, SomeInterface):
pass
# SomeInterface.py
class SomeInterface(object):
some_method(): pass
The problem I'm having is that for classes like BarAugmented, I'm getting the following error:
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I only get this error when SomeInterface is in a separate file instead of being inside modelaugmented.py.
I understand that the metaclass for SomeInterface and Bar are different. The problem is that I can't figure out how to resolve this problem. I tried the solution suggested in Triple inheritance causes metaclass conflict... Sometimes which works in the example given, but not in my case. Not sure if SqlAlchmey has anything to do with it.
class MetaAB(type(DeclarativeBase), type(SomeInterface)):
pass
class BarAugmented(Bar, SomeInterface):
__metaclass__ = MetaAB
But then I get the error:
TypeError: Error when calling the metaclass
bases multiple bases have instance lay-out conflict
Using SQLAlchemy 0.8 and Python 2.7.
Ok, there must be something I'm missing, because I created a similar file layout to yours (I think) and it works in my machine. I appreciate you kept your question short and simple, but maybe is missing some little detail that alters... something? Dunno... (maybe SomeInterface has an abc.abstract metaclass?) If you update your question, please let me know trough a comment to this answer, and I'll try to update my answer.
Here it goes:
File stack29A.py (equivalent to your model.py):
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
DSN = "mysql://root:foobar#localhost/so_tests?charset=utf8"
engine = create_engine(DSN)
Session = scoped_session(sessionmaker(bind=engine))
session = Session()
DeclarativeBase = declarative_base()
class Bar(DeclarativeBase):
__tablename__ = 'Bar'
_id = Column('_id', Integer, primary_key=True)
email = Column('email', String(50))
File stack29B.py (equivalent to your someinterface.py):
class SomeInterface(object):
def some_method(self):
print "hellou"
File stack29C.py (equivalent to your modelaugmented.py):
from stack29A import Bar
from stack29B import SomeInterface
class BarAugmented(Bar, SomeInterface):
pass
File stack29D.py (like a kind of main.py: table creator and sample):
from stack29C import BarAugmented
from stack29A import session, engine, DeclarativeBase
if __name__ == "__main__":
DeclarativeBase.metadata.create_all(engine)
b1 = BarAugmented()
b1.email = "foo#bar.baz"
b2 = BarAugmented()
b2.email = "baz#bar.foo"
session.add_all([b1, b2])
session.commit()
b3 = session.query(BarAugmented)\
.filter(BarAugmented.email == "foo#bar.baz")\
.first()
print "b3.email: %s" % b3.email
b3.some_method()
If I run the "main" file (stack29D.py) everything works as expected:
(venv_SO)borrajax#borrajax:~/Documents/Tests$ python ./stack29D.py
b3.email: foo#bar.baz
hellou