i have deployed my flask rest api app on cloud run, using this dockerfile:
# Python image to use.
FROM python:3.10-alpine
# Set the working directory to /app
WORKDIR /app
# copy the requirements file used for dependencies
#COPY requirements.txt .
# Copy the rest of the working directory contents into the container at /app
COPY . .
#RUN export PYTHONPATH=/usr/bin/python
# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt
#RUN pip install -r requirements.txt
# Run app.py when the container launches
#ENTRYPOINT ["python", "app.py"]
CMD gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app
When i try to call one of rest endpoint of app, i receive this error in my log:
"Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
worker.init_process()
File "/usr/local/lib/python3.10/site-packages/gunicorn/workers/gthread.py", line 92, in init_process
super().init_process()
File "/usr/local/lib/python3.10/site-packages/gunicorn/workers/base.py", line 134, in init_process
self.load_wsgi()
File "/usr/local/lib/python3.10/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
self.wsgi = self.app.wsgi()
File "/usr/local/lib/python3.10/site-packages/gunicorn/app/base.py", line 67, in wsgi
self.callable = self.load()
File "/usr/local/lib/python3.10/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
return self.load_wsgiapp()
File "/usr/local/lib/python3.10/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
return util.import_app(self.app_uri)
File "/usr/local/lib/python3.10/site-packages/gunicorn/util.py", line 359, in import_app
mod = importlib.import_module(module)
File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 883, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/app/app.py", line 12, in <module>
from resources.user import UserRegister, UserLogin, User, TokenRefresh, UserLogout
File "/app/resources/user.py", line 13, in <module>
from schemas.user import UserSchema
File "/app/schemas/user.py", line 7, in <module>
class UserSchema(ma.SQLAlchemyAutoSchema):
File "/usr/local/lib/python3.10/site-packages/marshmallow/schema.py", line 121, in __new__
klass._declared_fields = mcs.get_declared_fields(
File "/usr/local/lib/python3.10/site-packages/marshmallow_sqlalchemy/schema.py", line 91, in get_declared_fields
fields.update(mcs.get_declared_sqla_fields(fields, converter, opts, dict_cls))
File "/usr/local/lib/python3.10/site-packages/marshmallow_sqlalchemy/schema.py", line 130, in get_declared_sqla_fields
converter.fields_for_model(
File "/usr/local/lib/python3.10/site-packages/marshmallow_sqlalchemy/convert.py", line 154, in fields_for_model
field = base_fields.get(key) or self.property2field(prop)
File "/usr/local/lib/python3.10/site-packages/marshmallow_sqlalchemy/convert.py", line 193, in property2field
field_class = field_class or self._get_field_class_for_property(prop)
File "/usr/local/lib/python3.10/site-packages/marshmallow_sqlalchemy/convert.py", line 275, in _get_field_class_for_property
column = _base_column(prop.columns[0])
File "/usr/local/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 1329, in __getattr__
return self._fallback_getattr(key)
File "/usr/local/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 1298, in _fallback_getattr
raise AttributeError(key)
AttributeError: columns"
This is my app.py file:
import os
from flask import Flask, jsonify
from flask_restful import Api
from flask_jwt_extended import JWTManager
from marshmallow import ValidationError
from datetime import timedelta
from db import db, getconn
from ma import ma
from blocklist import BLOCKLIST
from resources.user import UserRegister, UserLogin, User, TokenRefresh, UserLogout
from resources.confirmation import Confirmation, ConfirmationByUser
app = Flask(__name__)
### Cloud Sql Google Parameter ###
app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql+pg8000://"
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
"creator" : getconn,
"max_overflow": 2,
"pool_timeout": 30,
"pool_size": 5,
"pool_recycle": 1800
}
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
#app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DATABASE_URL")
#app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["PROPAGATE_EXCEPTIONS"] = True
#app.secret_key = os.environ.get(
# "APP_SECRET_KEY"
#) # could do app.config['JWT_SECRET_KEY'] if we prefer
### Flask JWT Configuration Key ###
app.config["JWT_SECRET_KEY"] = os.environ["JWT_KEY"]
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
db.init_app(app)
ma.init_app(app)
api = Api(app)
@app.errorhandler(ValidationError)
def handle_marshmallow_validation(err):
return jsonify(err.messages), 400
jwt = JWTManager(app)
# This method will check if a token is blocklisted, and will be called automatically when blocklist is enabled
@jwt.token_in_blocklist_loader
def check_if_token_in_blocklist(jwt_header, jwt_payload):
return jwt_payload["jti"] in BLOCKLIST
@app.route("/api")
def user_route():
return "Welcome user API !"
api.add_resource(UserRegister, "/register")
api.add_resource(User, "/user/<int:user_id>")
api.add_resource(UserLogin, "/login")
api.add_resource(TokenRefresh, "/refresh")
api.add_resource(UserLogout, "/logout")
api.add_resource(Confirmation, "/user_confirm/<string:confirmation_id>")
api.add_resource(ConfirmationByUser, "/confirmation/user/<int:user_id>")
if __name__ == "__main__":
#app.run(port=5000, debug=True)
server_port = os.environ.get('PORT', '8082')
app.run(debug=True, port=server_port, host='0.0.0.0')
This is the requirements.txt file:
Flask==2.2.2
#Flask==2.1.3
requests==2.28.1
flask-sqlalchemy
ptvsd==4.3.2 # Required for debugging.
gunicorn
flask-smorest
python-dotenv
marshmallow
cloud-sql-python-connector
pg8000
flask-jwt-extended
passlib
datetime
uuid
requests
flask_restful
flask_marshmallow
marshmallow-sqlalchemy
Flask-SQLAlchemy
This is my user.py schema:
from marshmallow import pre_dump
from ma import ma
from models.user import UserModel
class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = UserModel
load_instance = True
load_only = ("password",)
dump_only = ("id", "confirmation")
@pre_dump
def _pre_dump(self, user: UserModel, **kwargs):
user.confirmation = [user.most_recent_confirmation]
return user
And this is my user model:
from requests import Response
from flask import request, url_for
from db import db
from libs.mailgun import Mailgun
from models.confirmation import ConfirmationModel
class UserModel(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False, unique=True)
password = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(80), nullable=False, unique=True)
created = db.Column(db.String(80), nullable=False, unique=True)
uuid_user = db.Column(db.String(200), nullable=False, unique=True)
confirmation = db.relationship(
"ConfirmationModel", lazy="dynamic", cascade="all, delete-orphan"
)
@property
def most_recent_confirmation(self) -> "ConfirmationModel":
# ordered by expiration time (in descending order)
return self.confirmation.order_by(db.desc(ConfirmationModel.expire_at)).first()
@classmethod
def find_by_username(cls, username: str) -> "UserModel":
return cls.query.filter_by(username=username).first()
@classmethod
def find_by_email(cls, email: str) -> "UserModel":
return cls.query.filter_by(email=email).first()
@classmethod
def find_by_id(cls, _id: int) -> "UserModel":
return cls.query.filter_by(id=_id).first()
def send_confirmation_email(self) -> Response:
subject = "Registration Confirmation"
link = request.url_root[:-1] + url_for(
"confirmation", confirmation_id=self.most_recent_confirmation.id
)
text = f"Please click the link to confirm your registration: {link}"
html = f"<html>Please click the link to confirm your registration: <a href={link}>link</a></html>"
return Mailgun.send_email([self.email], subject, text, html)
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()
Previously the app was on python 3.7 and worked fine, but the requirements.txt was different because it didn't need the modules for GCP and Cloud Sql connection. Any idea how to fix this error and what is the cause? Thanks
Looks like a marshmallow-sqlalchemy vs. sqlalchemy 2.0 issue.
I released a new marshmallow-sqlalchemy last week with sqlalchemy 2.0 support.
Please try with marshmallow-sqlalchemy.
If it doesn't work, edit your question to add a pip freeze.