Im trying to create a new DataBase with SQLAlchemy using FastAPI but when i run the server i get a empty DB file without tables.
database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./fastapi-test.db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
models.py
from db.database import Base
from sqlalchemy import Column, Integer, String
class DbUser(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String)
email = Column(String)
password = Column(String)
main.py
from fastapi import FastAPI
from routers import blogs_get, blog_post
from db import models
from db.database import engine
app = FastAPI()
app.include_router(blogs_get.router)
app.include_router(blog_post.router)
#app.get('/')
def index():
return 'Hello World'
models.Base.metadata.create_all(engine)
I already try to rewrite all the model. but i think i have an issue importing the Base component from Database
I'm writing a site for a Flask,and I made a mistake when I want to connect migrations, shows this error
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Users.roles - there are no foreign keys
linking these tables via secondary table 'сайт.role_users'. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or
specify 'primaryjoin' and 'secondaryjoin' expressions.
Here is my code:
from flask import Flask, render_template, request, flash, \
redirect, url_for
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_migrate import Migrate
from flask_security import RoleMixin
import os
css_file = 'static/css/main.css'
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///lib.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
db = SQLAlchemy(app)
manager = LoginManager(app)
migrate = Migrate(app, db)
role_users = db.Table('role_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')),
)
class Users(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(75), nullable=False, unique=True)
password = db.Column(db.String(80), nullable=False)
roles = db.relationship('Roles', secondary=role_users, backref=db.backref('users',
lazy='dynamic'))
def __repr__(self):
return f'<User> -----> {self.login}'
class Roles(db.Model, RoleMixin):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), unique=True, nullable=False)
def __repr__(self):
return f'<Role> ----> {self.title}'
Although there were no such errors during the first migration
.......
Help solve this problem
I am attempting to use FastApi + SqlAlchemy And am having an issue with Parameters. This is the Error my Post parameter is causing. What am i doing wrong?
ERROR MESSAGE
fastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that <class 'app.models.Post'> is a valid pydantic field type
Code
from fastapi import FastAPI, Response, HTTPException
from fastapi.params import Body, Depends
from sqlalchemy.orm import Session
from starlette import status
from . import models
from .database import engine, get_db
from .models import Post
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
#app.get("/posts")
def get_posts(db: Session = Depends(get_db)):
posts = db.query(models.Post).all()
return {"data": posts}
#app.post("/posts", status_code = status.HTTP_201_CREATED)
def create_posts(post: Post, db: Session = Depends(get_db)):
new_post = models.Post(title=post.title, content=post.content, published=post.published)
# db.add(new_post)
# db.commit()
# db.refresh(new_post)
return {"data": new_post}
Post Type
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import text
from sqlalchemy.sql.sqltypes import TIMESTAMP
from .database import Base
class Post(Base):
__tablename__ = "postsalchemy"
id = Column(Integer, primary_key=True, nullable=False)
title = Column(String, nullable=False)
content = Column(String, nullable=False)
published = Column(Boolean, server_default='TRUE', nullable=False)
created_at = Column(TIMESTAMP(timezone=True),
nullable=False, server_default=text('now()'))
FastAPI uses Pydantic library to validate both HTTP request and HTTP response body. ( BTW Pydantic is a library that will provide you validation of data structure against a given schema )
For that purpose, you must create a Pydantic Model class that corresponds to your sqlalchemy Post class.
Please read the docs here to learn more about response model.
FYI, here is how to do with your example :
from fastapi import FastAPI, Response, HTTPException
from fastapi.params import Body, Depends
from sqlalchemy.orm import Session
from starlette import status
from . import models
from .database import engine, get_db
from .models import Post
from pydantic import BaseModel
from datetime import datetime
from typing import Dict, List
# Define your pydantic response model
class PostResponse(BaseModel):
id: int
title: str
content: str
published: bool
created_at: datetime
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Set PostResponse as a response model in the route decorator
#app.get("/posts", response_model=Dict[str, List[PostResponse]])
def get_posts(db: Session = Depends(get_db)):
posts = db.query(models.Post).all()
return {"data": posts}
#app.post("/posts", status_code = status.HTTP_201_CREATED, response_model=Dict[str, PostResponse])
def create_posts(post: Post, db: Session = Depends(get_db)):
new_post = models.Post(title=post.title, content=post.content, published=post.published)
# db.add(new_post)
# db.commit()
# db.refresh(new_post)
return {"data": new_post}
EXTRA : You might be also interested in SQLModel library which is from the same author of FastAPI and is a combination between SQL Alchemy and Pydantic library and basically allow you to avoid duplication (and declare your model only once)
Using automap_base from sqlalchemy.ext.automap to map my tables.
Not able to shema.dumps(result);
getting
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Decimal is not JSON serializable
Tried using JSON custom decoders, but no use.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import Session
from sqlalchemy.ext.automap import automap_base
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
db = SQLAlchemy(app)
ma = Marshmallow(app)
engine = db.engine
session = Session(engine)
Base = automap_base()
Base.prepare(engine, reflect=True)
MyTable = Base.classes.my_table
class MyTableSchema(ma.ModelSchema):
class Meta:
model = MyTable
#app.route("/")
def api():
all_rows = session.query(MyTable).all()
schema = MyTableSchema(many=True)
response = schema.dumps(all_rows)
return response
if __name__ == '__main__':
app.run(debug=True)
An easy workaround to transform an SQLAlchemy result object in JSON is using simplejson.
You just need to import it (import simplejson) and it works.
Using your example:
import simplejson
...
#app.route("/")
def api():
all_rows = session.query(MyTable).all()
response = simplejson.dumps(all_rows)
Add .data to response like below
def api():
all_rows = session.query(MyTable).all()
schema = MyTableSchema(many=True)
response = schema.dumps(all_rows)
return response.data
I defined table name users_table and run db.create_all() to create the table, but get error "no such table user_table" on commit for updating user info.
How I test :
(under /project) python3 manage.py shell
>>> u = User(email='foo#bar.com', username='foobar', password='player')
>>> db.create_all()
>>> db.session.add(u)
>>> db.session.commit() # with following error message
Traceback (most recent call last):
File "C:\...\Python\Python36-32\lib\site-packages\sqlalchemy\engine\base.py", line 1182, in _execute_context
context)
File "C:\...\Python\Python36-32\lib\site-packages\sqlalchemy\engine\default.py", line 470, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: no such table: users_table
...
...
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users_table
/project/app/_init_.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import config
db = SQLAlchemy()
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app)
return app
/project/app/models.py:
import os
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash
from flask import Flask
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'users_table'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
def __repr__(self):
return '<User %r>' % self.username
#property
def password(self):
raise AttributeError('Password is not a readable attribute')
#password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
project/config.py:
import os
basedir = os.path.abspath(os.path.dirname(\__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'fhuaioe7832of67^&*T#oy93'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
#staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
config = {
'development': DevelopmentConfig,
'default': DevelopmentConfig,
}
project/manage.py:
import os
from app import create_app, db
from app.models import User
from flask_script import Manager, Shell
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
def make_shell_context():
return dict(app=app, db=db, User=User)
manager.add_command("shell", Shell(make_context=make_shell_context))
if __name__ == '__main__':
manager.run()
I just got done setting up a Flask app and I dealt with this kind of problem.
I strongly suspect the problem here is that the instance of db that you are creating in __init__.py is unaware of the contents of models.py, including the User class. The db object in __init__.py is a totally separate object from the db you are creating in models.py. So when you run db.create_all() in __init__.py, it is checking the list of tables that it knows about and isn't finding any. I ran into this exact issue.
What I discovered is that the models (like User) are registered with the particular db object that is listed in the model's class definition (e.g. class User(db.Model):).
So basically my understanding is that the way to fix this is to run db.create_all() using the same instance of db that is being used to define the models. In other words, run db.create_all() from within models.py.
Here's my code so you can see how I have it set up:
app.py:
#!flask/bin/python
import os
from flask import Flask
class CustomFlask(Flask):
jinja_options = Flask.jinja_options.copy()
jinja_options.update(dict(
variable_start_string='%%', # Default is '{{', I'm changing this because Vue.js uses '{{' / '}}'
variable_end_string='%%',
))
app = CustomFlask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
import yaml
if os.environ['SERVER_ENVIRONMENT'] == 'PRODUCTION':
config_filename = "production.yaml"
elif os.environ['SERVER_ENVIRONMENT'] == 'LOCAL':
config_filename = "local.yaml"
else:
config_filename = "local.yaml"
base_directory = path = os.path.dirname(os.path.realpath(__file__))
with open(base_directory + "/config/" + config_filename) as config_file:
config = yaml.load(config_file)
db_config = config['database']
SQLALCHEMY_DATABASE_URI = "mysql+mysqlconnector://{username}:{password}#{hostname}/{databasename}".format(
username=db_config['username'],
password=db_config['password'],
hostname=db_config['hostname'],
databasename=db_config['databasename'],
)
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_POOL_RECYCLE"] = 299
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
db.app = app
def clear_the_template_cache():
app.jinja_env.cache = {}
app.before_request(clear_the_template_cache)
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.init_app(app)
#login_manager.user_loader
def load_user(email):
from models import User
return User.query.filter_by(email=email).first()
if __name__ == '__main__':
from routes import web_routes
app.register_blueprint(web_routes)
from api import api
app.register_blueprint(api)
# To get PyCharm's debugger to work, you need to have "debug=False, threaded=True"
#app.run(debug=False, threaded=True)
app.run(debug=True)
models.py:
from app import db
import datetime
from werkzeug.security import generate_password_hash, \
check_password_hash
class Song(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(80))
datetime_created = db.Column(db.DateTime, default=datetime.datetime.utcnow())
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
lines = db.relationship('Line', cascade="all,delete", backref=db.backref('song', lazy='joined'), lazy='dynamic')
is_deleted = db.Column(db.Boolean, default=False)
class Line(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
song_id = db.Column(db.Integer, db.ForeignKey('song.id'))
spans_of_time = db.relationship('SpanOfTime', cascade="all,delete", backref=db.backref('line', lazy='joined'), lazy='dynamic')
class SpanOfTime(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
line_id = db.Column(db.Integer, db.ForeignKey('line.id'))
starting_64th = db.Column(db.Integer) # I'm assuming the highest-granularity desired will be a 1/64th note-length.
length = db.Column(db.Integer) # I guess this'll be in 1/64th notes, so a 1/16th note will be '4'.
content = db.Column(db.String(80))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(80), primary_key=True, unique=True)
display_name = db.Column(db.String(80), default="A Rhymecraft User")
password_hash = db.Column(db.String(200))
datetime_subscription_valid_until = db.Column(db.DateTime, default=datetime.datetime.utcnow() - datetime.timedelta(days=1))
datetime_joined = db.Column(db.DateTime, default=datetime.datetime.utcnow())
songs = db.relationship('Song', cascade="all,delete", backref=db.backref('user', lazy='joined'), lazy='dynamic')
def __init__(self, email, password):
self.email = email
self.set_password(password)
def __repr__(self):
return '<User %r>' % self.email
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return str(self.email)
def init_db():
db.create_all()
# Create a test user
new_user = User('a#a.com', 'aaaaaaaa')
new_user.display_name = 'Nathan'
db.session.add(new_user)
db.session.commit()
new_user.datetime_subscription_valid_until = datetime.datetime(2019, 1, 1)
db.session.commit()
if __name__ == '__main__':
init_db()
Very simple solution: in the app.py or main.py you can just add these lines of code for fixing this issue:
#app.before_first_request
def create_tables():
db.create_all()
In your case, require to add following code into __init__.py:
from models import User, Role
#app.shell_context_processor
def make_shell_context():
return dict(db=db, User=User, Role=Role)
then you do your previous works, it's all work.
I run into the same problem, after doing a YT tutorial. I solved it by adding this code at the end of my __init__.py
from .models import User, Post
with app.app_context():
db.create_all()
Sidenote: Most tutorials don't use the with app.app_context(): . I think there was a update in flask, which is why this is needed. This caused errors in my code and maybe it helps someone who reads this.
I would like to mention that it was the flask tutorial from "corey schafer" after "part 6 - user authentication", and the error appeared when i ran some tests. just in case anyone else is doing the exact same tutorial and therfore finds it easier to identify my answer as helpful. I am not mentioning the creater for advertisement. I hope this is ok.
Create a folder named "instance" in the root directory and move your database file to that folder.