问题
User.id
was of type postgresql.UUID
Message.sender_id
was of type postgresql.UUID
with foreignkey to User.id
.
Changed my type to sqlalchemy_util.UUIDType
.
I had a problem for serializing my foreign key so I set my own JSONEncoder
Now everything is working properly except when creating a Message
(other classes with the same configuration does not have the problem).
test_message.py
def test_create_message(client, db, admin_user, admin_headers):
# test bad data
data = {
'message': 'foobar',
'title': 'title',
}
rep = client.post(
'/api/v1/messages',
json=data,
headers=admin_headers
)
assert rep.status_code == 422
data['sender'] = admin_user.id.hex
data['recipient'] = admin_user.id.hex
rep = client.post(
'/api/v1/messages',
json=data,
headers=admin_headers
)
assert rep.status_code == 201
data = rep.get_json()
message = db.session.query(Message).filter_by(id=data['message']['id']).first()
assert message.message == 'foobar'
assert message.title == 'title'
models/message.py
from sqlalchemy_utils import UUIDType
from supervisor.extensions import db
from .notification import Notification
class Message(Notification):
"""Basic message model
"""
__tablename__ = 'message'
__repr_attrs__ = ['sender', 'message']
id = db.Column(
db.Integer,
db.ForeignKey('notification.id', ondelete='CASCADE'),
primary_key=True
)
sender_id = db.Column(
UUIDType(binary=False),
db.ForeignKey('user.id', ondelete='CASCADE'),
)
sender = db.relationship(
'User',
lazy='joined',
)
message = db.Column(db.Text, nullable=False)
def __init__(self, **kwargs):
kwargs.setdefault('type', 'message')
super().__init__(**kwargs)
models/user.py
class User(BaseModel):
"""Basic user model
"""
__tablename__ = 'user'
__repr_attrs__ = ['email']
id = db.Column(UUIDType(binary=False), default=uuid.uuid4, primary_key=True)
email = db.Column(db.String(128), unique=True, nullable=False)
name = db.Column(db.String(128))
photo_url = db.Column(db.Text)
active = db.Column(db.Boolean, default=True)
company_id = db.Column(
db.Integer,
db.ForeignKey('company.id', ondelete='CASCADE'),
nullable=False
)
admin_id = db.Column(
db.Integer,
db.ForeignKey('company.id'),
nullable=True
)
sites = db.relationship(
'Site',
secondary='users_sites',
lazy='joined',
)
tel_office = db.Column(db.String(25))
title = db.Column(db.String(128))
supplies = db.relationship(
'Supply',
lazy='joined',
backref='contact'
)
notifications = db.relationship(
'Notification',
lazy='joined',
backref='recipient',
foreign_keys='[Notification.recipient_id]',
cascade='all,delete-orphan',
passive_deletes=True,
)
models/base.py
from sqlalchemy_mixins import AllFeaturesMixin, ReprMixin
from supervisor.extensions import db
class BaseModel(db.Model, AllFeaturesMixin, ReprMixin):
__abstract__ = True
__repr__ = ReprMixin.__repr__
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
output of the test suite
_______________________________________________________________________________________________ test_create_message _______________________________________________________________________________________________
client = <FlaskClient <Flask 'supervisor'>>, db = <SQLAlchemy engine=postgresql+psycopg2://postgres:***@/rework-product-test?host=/cloudsql/flowlity:europe-west1:supervisor-db-test>
admin_user = <User #a7b838c3a8e64d118b13b1b28ab41f5f 'admin@flowlity....'>
admin_headers = {'authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1Njc0Mzg0MDgsIm5iZiI6MTU2NzQzODQwOCwianRpIjo...yZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.Zd5pOLAR5YSHDugU52E4qN-xQmXoqyJZTR_LNEbsUYM', 'content-type': 'application/json'}
def test_create_message(client, db, admin_user, admin_headers):
# test bad data
data = {
'message': 'foobar',
'title': 'title',
}
rep = client.post(
'/api/v1/messages',
json=data,
headers=admin_headers
)
assert rep.status_code == 422
data['sender'] = admin_user.id.hex
data['recipient'] = admin_user.id.hex
rep = client.post(
'/api/v1/messages',
json=data,
> headers=admin_headers
)
tests/test_message.py:35:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.tox/py37/lib/python3.7/site-packages/werkzeug/test.py:1039: in post
return self.open(*args, **kw)
.tox/py37/lib/python3.7/site-packages/flask/testing.py:227: in open
follow_redirects=follow_redirects,
.tox/py37/lib/python3.7/site-packages/werkzeug/test.py:993: in open
response = self.run_wsgi_app(environ.copy(), buffered=buffered)
.tox/py37/lib/python3.7/site-packages/werkzeug/test.py:884: in run_wsgi_app
rv = run_wsgi_app(self.application, environ, buffered=buffered)
.tox/py37/lib/python3.7/site-packages/werkzeug/test.py:1119: in run_wsgi_app
app_rv = app(environ, start_response)
.tox/py37/lib/python3.7/site-packages/flask/app.py:2463: in __call__
return self.wsgi_app(environ, start_response)
.tox/py37/lib/python3.7/site-packages/flask_socketio/__init__.py:46: in __call__
start_response)
.tox/py37/lib/python3.7/site-packages/engineio/middleware.py:74: in __call__
return self.wsgi_app(environ, start_response)
.tox/py37/lib/python3.7/site-packages/flask/app.py:2449: in wsgi_app
response = self.handle_exception(e)
.tox/py37/lib/python3.7/site-packages/flask_restful/__init__.py:269: in error_router
return original_handler(e)
.tox/py37/lib/python3.7/site-packages/flask_cors/extension.py:161: in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
.tox/py37/lib/python3.7/site-packages/flask/app.py:1866: in handle_exception
reraise(exc_type, exc_value, tb)
.tox/py37/lib/python3.7/site-packages/flask/_compat.py:38: in reraise
raise value.with_traceback(tb)
.tox/py37/lib/python3.7/site-packages/flask/app.py:2446: in wsgi_app
response = self.full_dispatch_request()
.tox/py37/lib/python3.7/site-packages/flask/app.py:1951: in full_dispatch_request
rv = self.handle_user_exception(e)
.tox/py37/lib/python3.7/site-packages/flask_restful/__init__.py:269: in error_router
return original_handler(e)
.tox/py37/lib/python3.7/site-packages/flask_cors/extension.py:161: in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
.tox/py37/lib/python3.7/site-packages/flask/app.py:1820: in handle_user_exception
reraise(exc_type, exc_value, tb)
.tox/py37/lib/python3.7/site-packages/flask/_compat.py:38: in reraise
raise value.with_traceback(tb)
.tox/py37/lib/python3.7/site-packages/flask/app.py:1949: in full_dispatch_request
rv = self.dispatch_request()
.tox/py37/lib/python3.7/site-packages/flask/app.py:1935: in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
.tox/py37/lib/python3.7/site-packages/flask_restful/__init__.py:458: in wrapper
resp = resource(*args, **kwargs)
.tox/py37/lib/python3.7/site-packages/flask/views.py:89: in view
return self.dispatch_request(*args, **kwargs)
.tox/py37/lib/python3.7/site-packages/flask_restful/__init__.py:573: in dispatch_request
resp = meth(*args, **kwargs)
.tox/py37/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py:103: in wrapper
return fn(*args, **kwargs)
supervisor/api/resources/message.py:51: in post
message = schema.load(request.json)
.tox/py37/lib/python3.7/site-packages/marshmallow_sqlalchemy/schema.py:216: in load
return super(ModelSchema, self).load(data, *args, **kwargs)
.tox/py37/lib/python3.7/site-packages/marshmallow/schema.py:684: in load
data, many=many, partial=partial, unknown=unknown, postprocess=True
.tox/py37/lib/python3.7/site-packages/marshmallow/schema.py:799: in _do_load
unknown=unknown,
.tox/py37/lib/python3.7/site-packages/marshmallow/schema.py:639: in _deserialize
index=index,
.tox/py37/lib/python3.7/site-packages/marshmallow/schema.py:483: in _call_and_store
value = getter_func(data)
.tox/py37/lib/python3.7/site-packages/marshmallow/schema.py:632: in <lambda>
val, field_name, data, **d_kwargs
.tox/py37/lib/python3.7/site-packages/marshmallow/fields.py:329: in deserialize
output = self._deserialize(value, attr, data, **kwargs)
.tox/py37/lib/python3.7/site-packages/marshmallow_sqlalchemy/fields.py:109: in _deserialize
self.session.query(self.related_model), value
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <class 'supervisor.models.user.User'>, entities = (), kwargs = {}
def query(self, *entities, **kwargs):
"""Return a new :class:`.Query` object corresponding to this
:class:`.Session`."""
> return self._query_cls(entities, self, **kwargs)
E AttributeError: type object 'User' has no attribute '_query_cls'
.tox/py37/lib/python3.7/site-packages/sqlalchemy/orm/session.py:1544: AttributeError
---------------------------------------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------------------------------------
{'message': 'foobar', 'title': 'title'}
{'message': 'foobar', 'recipient': 'a7b838c3a8e64d118b13b1b28ab41f5f', 'sender': 'a7b838c3a8e64d118b13b1b28ab41f5f', 'title': 'title'}
回答1:
As said by SuperShoot,
My ModelSchema was wrongly instanciate:
class MessageSchema(ma.ModelSchema):
class Meta:
model = Message
sqla_session = db.Session
The answer was to correct
sqla_session = db.Session
to
sqla_session = db.session
来源:https://stackoverflow.com/questions/57759801/attributeerror-type-object-user-has-no-attribute-query-cls