I'm trying to build a REST API application in Python 3.4, using Flask, SQLAlchemy and Marshmallow.
In my model I have a User class with a one to many relationship with a MailAddress class.
If I run a GET request, I manage to read data from the DB, and the data is correctly returned as a JSON string.
Instead, if I run a POST request with the JSON serialization of my User object with some MailAddresses, I get this error:
File "X:\test\...\site-packages\sqlalchemy\orm\collections.py", line 785, in bulk_replace
constants = existing_idset.intersection(values or ())
File "X:\test\...\site-packages\sqlalchemy\util\_collections.py", line 612, in intersection
result._members.update(self._working_set(members).intersection(other))
TypeError: unhashable type: 'dict'
I've tried adding a __hash__ function to my model classes (as suggested at sqlalchemy: TypeError: unhashable type creating instance, sqlalchemy) but that didn't help.
Here is a complete code example that shows this problem:
from flask import Flask, request
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from marshmallow import fields
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
class Config(object):
SQLALCHEMY_DATABASE_URI = '<CONNECTION STRING HERE>'
SQLALCHEMY_TRACK_MODIFICATIONS = False
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
ma = Marshmallow(app)
# Model
class MailAddress(db.Model):
__tablename__ = 'mail_addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
mail_type = Column(String(200), nullable=False)
mail = Column(String(200), nullable=False)
def __init__(self, mail, mail_type):
self.mail = mail
self.mail_type = mail_type
class MailAddressSchema(ma.ModelSchema):
class Meta:
model = MailAddress
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
mail_addresses = relationship('MailAddress', backref='user')
def __init__(self, name, mail_addresses):
self.name = name
self.mail_addresses = mail_addresses
def __hash__(self):
return hash(self.name)
class UserSchema(ma.ModelSchema):
mail_addresses = fields.Nested(MailAddressSchema, many = True, only=('mail', 'mail_type'))
class Meta:
model = User
# Routes
user_schema = UserSchema()
#app.route('/api/v0/user', methods=['GET'])
def user_get():
users = db.session.query(User).all()
return user_schema.jsonify(users, many = True), 200
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance = user_schema.make_instance(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201
# Main
if __name__ == '__main__':
app.run('localhost', 5555)
Is there something I'm missing?
Use load instead make_instance
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance, errors = user_schema.load(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201
Related
I have an issue that was already mentioned here
https://youtrack.jetbrains.com/issue/PY-44557
But I couldn't find a solution yet. I'm using Python Flask with SQLAlchemy to create an API service.
Here are my imports:
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String, Float
import os
from flask_marshmallow import Marshmallow
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+os.path.join(basedir, 'dealers.db')
db = SQLAlchemy(app)
ma = Marshmallow(app)
Here is my data model:
class Car(db.Model):
__tablename__ = 'cars'
car_id = Column(Integer, primary_key=True)
car_name = Column(String)
car_type = Column(String)
class CarSchema(ma.Schema):
class Meta:
fields = ('car_id', 'car_name', 'car_type')
car_schema = CarSchema()
cars_schema = CarSchema(many=True)
I'm having an issue/Warning with my UDF when I use .query
def cars():
car_list = Car.query.all() # *Unresolved attribute reference 'query' for class 'Car'
result = cars_schema.dump(car_list)
return jsonify(result)
I've found out how to make a hacky and partial solution for this issue using type-hinting stubs.
Create abstract python class derived from db.Model in a separate file and a "stub"-file with a same name
Example:
abstract.py:
class AbstractBaseModel(db.Model):
__abstract__ = True
abstract.pyi:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import orm
from sqlalchemy.ext.declarative import DeclarativeMeta
db: SQLAlchemy = SQLAlchemy()
class _FBase(db.Model):
query: orm.Query
class AbstractBaseModel(_FBase, DeclarativeMeta):
...
Then make models deriving from abstract.AbstractBaseModel instead of db.Model
e.g:
from abstract import AbstractBaseModel
class Car(AbstractBaseModel):
__tablename__ = 'cars'
car_id = Column(Integer, primary_key=True)
car_name = Column(String)
car_type = Column(String)
I've also created a sample project for testing this workaround
Modify class Car(db.Model): to be:
class Car(db.Model):
__tablename__ = 'cars'
car_id = db.Column(db.Integer, primary_key=True)
car_name = db.Column(db.String) # it is better to give string length for example db.String(100)
car_type = db.Column(db.String)
where you should define Columns to be related to db
I'm following a tutorial and using the below code. I'm also using Postman to view the status of the server for http://localhost:5000/planets , but I'm getting 500 INTERNAL SERVER ERROR, when I should see my JSON data of the planets I created.
In the command line I also see: AttributeError: 'list' object has no attribute 'data'
I feel it might have to do with the line that has: return jsonify(result.data) but I'm not sure.
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String, Float
import os
from flask_marshmallow import Marshmallow
from marshmallow import Schema
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///C:/Users/krist/Py3/flask2/planets.db'
db = SQLAlchemy(app)
ma = Marshmallow(app)
#app.cli.command('db_create')
def db_create():
db.create_all()
print("DB Created")
#app.cli.command('db_seed')
def deb_seed():
mercury = Planet(planet_name='Mercury',
planet_type='Class D',
home_star='Sol',
mass=3.25e23,
radius=1516,
distance=35.98e6)
venus = Planet(planet_name='Venus',
planet_type='Class K',
home_star='Sol',
mass=8.95e24,
radius=3516,
distance=67.98e6)
earth = Planet(planet_name='Earth',
planet_type='Class M',
home_star='Sol',
mass=5.97e24,
radius=3916,
distance=92.96e6)
db.session.add(mercury)
db.session.add(venus)
db.session.add(earth)
test_user = User(first_name='William',
last_name='Hershel',
email='test#test.com',
password='p#ssw0rd')
db.session.add(test_user)
db.session.commit()
print("DB Seeded")
#app.route('/planets', methods=['GET'])
def planets():
planets_list = Planet.query.all()
result = planets_schema.dump(planets_list)
return jsonify(result.data)
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
email = Column(String, unique=True)
password = Column(String)
class Planet(db.Model):
__tablename__ = 'planets'
planet_id = Column(Integer, primary_key=True)
planet_name = Column(String)
planet_type = Column(String)
home_star = Column(String)
mass = Column(Float)
radius = Column(Float)
distance = Column(Float)
class UserSchema(ma.Schema):
class Meta:
fields = ('id', 'first_name', 'last_name', 'email', 'password')
class PlanetSchema(ma.Schema):
class Meta:
fields = ('planet_id', 'planet_name', 'planet_type', 'home_star', 'mass', 'radius', 'distance')
user_schema = UserSchema()
users_schema = UserSchema(many=True)
planet_schema = PlanetSchema()
planets_schema = PlanetSchema(many=True)
if __name__ == '__main__':
app.run()
Instead of
result = planets_schema.dump(planets_list)
return jsonify(result.data)
Try
result = planets_schema.dump(planets_list)
return jsonify(result)
Why this works:
Here you are querying the Planet Mapper to return a list of Planet ORM objects
planets_list = Planet.query.all()
Then the Marshmallow schema is used to marshal, or transform the ORM object into a python dictionary object. This is the basic principle of marshaling - transforming data from one format into another when the data is about to be transmitted or stored. So in this case you transform you data from a list of SQLAlchemy ORM objects into a list of Python dictionary objects.
result = planets_schema.dump(planets_list)
Now you have result (which could more aptly be names results that contains a list of dictionary objects.
Then you are attempting to access the data variable on this list object. However Python lists have no data variable, so you get an error.
return jsonify(result.data)
The jsonify method from flask accepts a list of dictionaries as input, so simply modifying this line to the below should work:
return jsonify(result)
I am trying to deserialize a deep structure with marshmallow. For example:
hour = {
'day': {
'name': 'monday'
}
}
loaded_hour, error = HoursSerializationSchema().load(hour) # this works
new_practitioner_at_location = {
'hours': [
hour
]
}
loaded, error = PractitionerToServiceLocationSerializationSchema().load(new_practitioner_at_location) # this fails
When I try to deserialize the new_practitioner_at_location I get the following (occurs when the serializer is working on the 'day' key):
AttributeError: 'dict' object has no attribute '_sa_instance_state'
Note that the same schema works to deserialize the same data structure (hour) when that structure is not nested inside the new_practitioner_at_location.
self-contained script showing the problem:
from sqlalchemy import Column, Integer, ForeignKey, String
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declarative_base
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_migrate import Migrate
base = declarative_base()
class HoursDay(base):
__tablename__ = 'HoursDay'
uid = Column(Integer, primary_key=True)
name = Column(String)
hour_id = Column(Integer, ForeignKey('Hours.uid'))
hour = relationship("Hours", back_populates="day")
def __init__(self, **kwargs):
super().__init__(**kwargs)
class Hours(base):
__tablename__ = 'Hours'
uid = Column(Integer, primary_key=True)
practitioner_at_location_id = Column(Integer, ForeignKey('PractitionerToServiceLocation.uid'))
practitioner_at_location = relationship('PractitionerToServiceLocation', back_populates="hours")
day = relationship(HoursDay, uselist=False, back_populates="hour")
def __repr__(self):
return f'<Hours {self.uid}>'
class PractitionerToServiceLocation(base):
"""
A practitioner practices at a number of service locations.
"""
__tablename__ = 'PractitionerToServiceLocation'
uid = Column(Integer, primary_key=True)
hours = relationship("Hours", back_populates="practitioner_at_location")
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __repr__(self):
return f'<PractitionerToServiceLocation {self.uid}>'
app = Flask(__name__)
app.config.from_object(os.environ['APP_SETTINGS'])
db = SQLAlchemy(app, model_class=base)
ma = Marshmallow(app)
migrate = Migrate(app, db)
from marshmallow import fields
class HoursDaySerializationSchema(ma.ModelSchema):
class Meta:
model = HoursDay
class HoursSerializationSchema(ma.ModelSchema):
class Meta:
model = Hours
day = fields.Nested(HoursDaySerializationSchema)
class PractitionerToServiceLocationSerializationSchema(ma.ModelSchema):
class Meta:
model = PractitionerToServiceLocation
hours = fields.Nested('HoursSerializationSchema', many=True)
if __name__ == "__main__":
hour = {
'day': {
'name': 'monday'
}
}
loaded_hour, error = HoursSerializationSchema().load(hour) # this works
new_practitioner_at_location = {
'hours': [
hour
]
}
loaded, error = PractitionerToServiceLocationSerializationSchema().load(new_practitioner_at_location) # this fails
print('hi')
Update:
I think that what is happening is that marshmallow is not attempting to deserialize the HoursDay object when trying to deserialize the new_practitioner_at_location dict. If I remove the backpopulates behavior from the HoursDay.hour field then you can see it just assign the unserialized data struct to the field. This makes no sense at all to me, especially since it works when you just deserialize the hour dict directly instead of embedding it inside new_practitioner_at_location.
Any help would be appreciated.
This is a simple typo-like bug:
class PractitionerToServiceLocationSerializationSchema(ma.ModelSchema):
class Meta:
model = PractitionerToServiceLocation
hours = fields.Nested('HoursSerializationSchema', many=True)
You are defining hours inside class Meta, but it needs to be in your schema itself:
class PractitionerToServiceLocationSerializationSchema(ma.ModelSchema):
class Meta:
model = PractitionerToServiceLocation
hours = fields.Nested('HoursSerializationSchema', many=True)
I'm building a Flask-Restful API using python and sqlalchemy, and I'm trying to join two tables from different databases. It appears that I'm only able to search for tables in one database at a time. Am I missing something?
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, jsonify, request
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://username:password#host:8000/database1'
app.config['SQLALCHEMY_BINDS'] = {
'database2': 'mysql://username:password#host:8000/database2'
}
db = SQLAlchemy(app)
db.create_all(bind='database2')
class Table1(db.Model):
__tablename__ = "table1"
__table_args__ = {'schema':'database1'}
location_id = db.Column(db.Integer, primary_key=True)
def __init__(self, location_id):
self.location_id = location_id
def __repr__(self):
return '{}'.format(self.location_id)
class Table2(db.Model):
__bind_key__ = "database2"
__tablename__ = "table2"
__table_args__ = {'schema':'database2'}
other_id = db.Column(db.Integer, primary_key=True)
location_id = db.Column(db.Integer, db.ForeignKey('database1.table1.location_id'))
def __init__(self, other_id, location_id):
self.other_id = other_id
self.location_id = location_id
def __repr__(self):
return '{}'.format(self.other_id)
#app.route('/', methods=['GET'])
def returnRes():
session = db.session
q = session.query(table1).join(table2, table1.location_id==table2.location_id).all()
return str(q)
In my browser, I am getting the error:
'sqlalchemy.exc.ProgrammingError: (_mysql_exceptions.ProgrammingError) (1146, "Table 'database1.table2' doesn't exist").
Both tables indeed exist, as when I change my query to
q = session.query(table2).join(table1, table2.location_id==table1.location_id).all()
I get an error that database2.table1 doesn't exist.
I'm using python==3.6.1, Flask==0.11.1 and Flask-SQLAlchemy==2.1
Adding a data base schema argument to my table classes and adding a foreign key fixed this problem. I found the answer at this link: https://github.com/mitsuhiko/flask-sqlalchemy/issues/172
I've updated the question to reflect the answer in case it helps anyone else.
I'm not sure if the binds are redundant, but I've left them in because they don't seem to interfere with anything.
You need to add Schema declarations. It is strange that Flask-SQLAlchemy doesn't mention this at all in their documentation.
The Foreign Keys are unnecessary for this to work.
class Table1(db.Model):
__tablename__ = "table1"
__table_args__ = {'schema':'database1'}
#...
def __init__(self, location_id):
self.location_id = location_id
def __repr__(self):
return '{}'.format(self.location_id)
class Table2(db.Model):
__bind_key__ = "database2"
__tablename__ = "table2"
__table_args__ = {'schema':'database2'}
#...
so long as your config file looks like this
SQLALCHEMY_DATABASE_URI = 'postgres:////path/to/database1'
SQLALCHEMY_BINDS = {
'database2': 'mysqldb:////path/to/database2'
}
I need to create a dB for testing purposes and I would like initialize it with "some" users.
The "intended" code is:
import os
from flask import Flask
from flask.ext.script import Manager, Shell
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
manager = Manager(app)
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(64), unique = True)
email = db.Column(db.String(64), unique = True)
def __init__(self, user = None, email = None):
name = self.__tablename__
db.drop_all()
db.create_all()
for i in range(100):
user = User('user%d' % i, 'someone#example.com')
db.session.add(user)
db.session.commit()
def __repr__(self):
return '<User %r>' % self.name
The call usr_db = User() generates RuntimeError: maximum recursion depth exceeded.
The question is , when during construction the class is "ready" to be called recursively to avoid the error above.
I think creating test users inside the model class is the wrong way to do it. This is going to recursively call __init__() if you instantiate a User() instance inside __init__(). A better place would be inside a unittest setUp() method.
However, if you really want to do this, you could guard against instantiating the users more than once like this:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(64), unique = True)
email = db.Column(db.String(64), unique = True)
_test_users_initialised = False
def _initialise_test_users(self):
if self._test_users_initialised:
return
self.__class__._test_users_initialised = True
for i in range(100):
user = User('user%d' % i, 'someone#example.com')
db.session.add(user)
def __init__(self, user = None, email = None):
name = self.__tablename__
db.drop_all()
db.create_all()
self._initialise_test_users()
db.session.commit()