Reputation: 41
I am basically trying to create an API using flask, flask-restx, marshmallow and sqlalchemy to retrieve info for a users fleet as well as create/add new vessels to it.
A user can have multiple vessels, a vessel can have multiple trips and a combination of vessel & trip has a specific fuel consumption.
While sending GET requests seems to return all the specified messages, sending a POST request returns the error above:
---ERROR---
Relevant Code:
---MODEL-----
A vessel model, including all of the possible fields for a new vessel and some helper functions to query from and save to the db.
from zeus import db
from typing import List
class VesselModel(db.Model):
__tablename__ = "vessels"
imo = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(50), nullable=False)
breadth_m = db.Column(db.Float(precision=2), nullable=False)
depth_m = db.Column(db.Float(precision=2), nullable=False)
draught_m = db.Column(db.Float(precision=2), nullable=False)
baseline_m = db.Column(db.Float(precision=2), nullable=False)
length_ovrl_m = db.Column(db.Float(precision=2), nullable=False)
length_subm_m = db.Column(db.Float(precision=2), nullable=False)
lpp_m = db.Column(db.Float(precision=2), nullable=False)
lwl_m = db.Column(db.Float(precision=2), nullable=False)
ap_m = db.Column(db.Float(precision=2), nullable=False)
fp_m = db.Column(db.Float(precision=2), nullable=False)
# user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), nullable=False)
# user = db.relationship('UserModel')
#trips = db.relationship("TripModel", lazy="dynamic", primaryjoin="VesselModel.imo == TripModel.vessel_imo")
# fuel_consumption_tn_c02 = db.relationship("FuelConsumptionModel", lazy="dynamic", primaryjoin="VesselModel.imo == FuelConsumptionModel.vessel_imo")
def __init__(self, imo, name, breadth_m, depth_m, draught_m, baseline_m, length_ovrl_m, length_subm_m, lpp_m, lwl_m, ap_m, fp_m, user_id):
self.imo = imo
self.name = name
self.breadth_m = breadth_m
self.depth_m = depth_m
self.draught_m = draught_m
self.baseline_m = baseline_m
self.length_ovrl_m = length_ovrl_m
self.length_subm_m = length_subm_m
self.lpp_m = lpp_m
self.lwl_m = lwl_m
self.ap_m = ap_m
self.fp_m = fp_m
# self.user_id = user_id
def __repr__(self):
return 'VesselModel(imo=%s, name=%s, breadth_m=%s, depth_m=%s, draught_m=%s, baseline_m=%s, length_ovrl_m=%s, length_subm_m=%s, lpp_m=%s, lwl_m=%s, ap_m=%s, fp_m=%s' % (self.imo, self.name, self.breadth_m, self.depth_m, self.draught_m, self.baseline_m, self.length_ovrl_m, self.length_subm_m, self.lpp_m, self.lwl_m, self.ap_m, self.fp_m)
def json(self):
return {'imo': self.imo, 'name': self.name, 'breadth_m': self.breadth_m, 'depth_m': self.depth_m, 'draught_m': self.draught_m, 'baseline_m': self.baseline_m, 'length_ovrl_m': self.length_ovrl_m, 'length_subm_m': self.length_subm_m, 'lpp_m': self.lpp_m, 'lwl_m': self.lwl_m, 'ap_m': self.ap_m, 'fp_m': self.fp_m}
@classmethod
def find_by_imo(cls, imo) -> "VesselModel":
return cls.query.filter_by(imo=imo).first()
@classmethod
def find_all(cls) -> "VesselModel":
return cls.query.all()
# @classmethod
# def find_fleet(cls, user_id) -> List["VesselModel"]:
# return cls.query.filter_by(user_id=user_id).all()
def save_to_db(self) -> None:
db.session.add(self)
db.session.commit()
def delete_from_db(self) -> None:
db.session.delete(self)
db.session.commit()
---SCHEMA---
from ma import ma
from models.User import UserModel
from models.Vessel import VesselModel
from schemas.Trip import TripSchema
class VesselSchema(ma.SQLAlchemyAutoSchema):
trips = ma.Nested(TripSchema, many=True)
class Meta:
model: VesselModel
load_instance = True
include_fk = True
---RESOURCE---
class Fleet(Resource):
@fleet_ns.doc('Get all the vessels in the fleet')
def get(self):
return fleet_schema.dumps(VesselModel.find_all()), 200
@fleet_ns.expect(vessel)
@fleet_ns.doc('Add a vessel')
def post(self):
vessel_json = request.get_json()
if not vessel_json:
return {"message": "No input data provided"}, 400
try:
vessel_data = vessel_schema.load(vessel_json)
except ValidationError as err:
print("Im here")
return err.messages, 422
vessel_data.save_to_db()
return vessel_schema.dump(vessel_data), 201
---app.py----
from pydoc import doc
from flask import Flask, Blueprint, jsonify
from flask_restx import Api
from ma import ma
from zeus import db
from resources.vessel import Vessel, Fleet, vessel_ns, fleet_ns
from resources.trip import Trip, TripList, trip_ns, trips_ns
from resources.fuelconsumption import Fuel_Consumption, fuelconsumption_ns
from marshmallow import ValidationError
app = Flask(__name__)
bluePrint = Blueprint('api', __name__, url_prefix='/api')
api = Api(bluePrint, doc='/doc', title='Promilist - Hermes API')
app.register_blueprint(bluePrint)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///olympus.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['PROPAGATE_EXCEPTIONS'] = True
api.add_namespace(vessel_ns)
api.add_namespace(fleet_ns)
api.add_namespace(trip_ns)
api.add_namespace(trips_ns)
api.add_namespace(fuelconsumption_ns)
@app.before_first_request
def create_tables():
db.create_all()
@api.errorhandler(ValidationError)
def handle_validation_error(error):
return jsonify(error.messages), 400
vessel_ns.add_resource(Vessel, '/<int:vessel_imo>', endpoint =
'vessel_characteristics_by_imo')
fleet_ns.add_resource(Fleet, "", endpoint = 'fleet_by_user')
trip_ns.add_resource(Trip, '/<int:trip_id>', endpoint = 'trip_by_id')
trips_ns.add_resource(TripList, '', endpoint = 'trips_by_vessel')
fuelconsumption_ns.add_resource(Fuel_Consumption, '/<int:vessel_imo>/<int:trip_id>',
endpoint = 'fuel_consumption_by_vessel_and_trip_combination')
db.init_app(app)
if __name__ == '__main__':
ma.init_app(app)
app.run(port=5000, debug=True)
----FILE TREE----
Upvotes: 0
Views: 1092
Reputation: 41
Finally found out what was causing the errors. Posting below in case anyone runs into the same issue.
Not sure if the syntax changed on Marshmallow 3x but when declaring the schema and assigning a model to it, the proper way to do so is by using (=) and not (:) as I have used above. Specifically:
---WRONG----
class VesselSchema(ma.SQLAlchemyAutoSchema):
trips = ma.Nested(TripSchema, many=True)
class Meta:
model: VesselModel
load_instance = True
include_fk = True
---RIGHT---
class VesselSchema(ma.SQLAlchemyAutoSchema):
trips = ma.Nested(TripSchema, many=True)
class Meta:
model= VesselModel #NEW
load_instance = True
include_fk = True
This mistake caused an 'Unknown Field' error when trying to execute the Schema.dump method. In order to solve that error, I put an unkown=EXCLUDE argument as suggested in multiple other solutions, which in turn caused the error above.
After fixing the (=)/(:) issue, I also discovered that I have to declare a session in the load method. This was the last small fix needed for the POST method to work.
Upvotes: 2