Create SQLAlchemy mysql connection string from input - mysql

I'm new to python. I am trying to create a connection string to a mysql, but from an input via a webform.
My first thought was to create a function to call to pass in the details. Here is what I have:
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import pymysql
app = Flask(__name__)
def db_connection(user, password, ep, db):
connection = f"mysql+pymysql://{user}:{password}#{ep}/{db}"
app.config['SQLALCHEMY_DATABASE_URI'] = connection
app.config['SQLALCHEMY_TRACK_MODIFICATION'] = False
db = SQLAlchemy(app)
class Lab(db.Model):
id = db.Column(db.Integer, primary_key=True)
store = db.Column(db.String(80), unique=True, nullable=False)
item = db.Column(db.String(120), unique=True, nullable=False)
quantity = db.Column(db.Integer, nullable=False)
db.create_all()
The details of the "user", "password", "ep" (endpoint/hostname), "db" for the db_connection function are passed in via filling out a webform.
I think i might be going about this the wrong way. The end result that I want is for my user to go to the webform, fill the details about the DB to connect to, click submit then the function(or what ever) would establish the connection with those details.
The form I have passes to an app.route to establish to call the above function
#app.route('/save_settings', methods=["GET", "POST"])
def save_settings():
ep_data = request.form['endpoint']
db_data = request.form['database']
user_data = request.form['username']
password_data = request.form['password']
db_connection(user=user_data, password=password_data, db=db_data, ep=ep_data)
When I try it, I get the following error:
AssertionError: A setup function was called after the first request was handled. This usually indicates a bug in the application where a module was not imported and decorators or other functionality was called too late.
To fix this make sure to import all your view modules, database models and everything related at a central place before the application starts serving requests.

Ok, after some playing around I seemed to have fixed it. I hope this helps others that were stuck on this too.
The mistakes I made:
the class does not come under the function
before the class needs to have: db = SQLAlchemy(app)
Here is my modified code:
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import pymysql
app = Flask(__name__)
db = SQLAlchemy(app)
def db_connection(user, password, ep, db):
connection = f"mysql+pymysql://{user}:{password}#{ep}/{db}"
app.config['SQLALCHEMY_DATABASE_URI'] = connection
app.config['SQLALCHEMY_TRACK_MODIFICATION'] = False
class Lab(db.Model):
id = db.Column(db.Integer, primary_key=True)
store = db.Column(db.String(80), unique=True, nullable=False)
item = db.Column(db.String(120), unique=True, nullable=False)
quantity = db.Column(db.Integer, nullable=False)

Related

Interaction between pydantic models/schemas in fastAPI Tutorial

I follow the FastAPI Tutorial and am not quite sure what the exact relationship between the proposed data objects is.
We have the models.py file:
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
and the schemas.py file:
from typing import List, Union
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: Union[str, None] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
Those classes are then used to define db queries like in the crud.py file:
from sqlalchemy.orm import Session
from . import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
and in the FastAPI code main.py:
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
#app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
#app.get("/users/", response_model=List[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
#app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
#app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
#app.get("/items/", response_model=List[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Now from what I understand:
the models data classes define the sql tables
the schemas data classes define the api that FastAPI uses to interact with the database
they must be convertible into each other so that the set-up works
What I don't understand:
in crud.create_user_item I expected the return type to be schemas.Item, since that return type is used by FastAPI again
according to my understanding the response model of #app.post("/users/{user_id}/items/", response_model=schemas.Item) in the main.py is wrong, or how can I understand the return type inconsistency?
however inferring from the code, the actual return type must be models.Item, how is that handled by FastAPI?
what would be the return type of crud.get_user?
I'll go through your bullet points one by one.
The models data classes define the sql tables
Yes. More precisely, the ORM classes that map to actual database tables are defined in the models module.
the schemas data classes define the api that FastAPI uses to interact with the database
Yes and no. The Pydantic models in the schemas module define the data schemas relevant to the API, yes. But that has nothing to do with the database yet. Some of these schemas define what data is expected to be received by certain API endpoints for the request to be considered valid. Others define what the data returned by certain endpoints will look like.
they must be convertible into each other so that the set-up works
While the database table schemas and the API data schemas are usually very similar, that is not necessarily the case. In the tutorial however, they correspond quite neatly, which allows succinct CRUD code, like this:
db_item = models.Item(**item.dict(), owner_id=user_id)
Here item is a Pydantic model instance, i.e. one of your API data schemas schemas.ItemCreate containing data you decided is necessary for creating a new item. Since its fields (their names and types) correspond to those of the database model models.Item, the latter can be instantiated from the dictionary representation of the former (with the addition of the owner_id).
in crud.create_user_item I expected the return type to be schemas.Item, since that return type is used by FastAPI again
No, this is exactly the magic of FastAPI. The function create_user_item return an instance of models.Item, i.e. the ORM object as constructed from the database (after calling session.refresh on it). And the API route handler function create_item_for_user actually does return that same object (of class models.Item).
However, the #app.post decorator takes that object and uses it to construct an instance of the response_model you defined for that route, which is schemas.Item in this case. This is why you set this in your schemas.Item model:
class Config:
orm_mode = True
This allows an instance of that class to be created via the .from_orm method. That all happens behind the scenes and again depends on the SQLAlchemy model corresponding to the Pydantic model with regards to field names and types. Otherwise validation fails.
according to my understanding the response model [...] is wrong
No, see above. The decorated route function actually deals with the schemas.Item model.
however inferring from the code, the actual return type must be models.Item
Yes, see above. The return type of the undecorated route handler function create_item_for_user is in fact models.Item. But its return type is not the response model.
I assume that to reduce confusion the documentation example does not annotate the return type of those route functions. If it did, it would look like this:
#app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
) -> models.Item:
return crud.create_user_item(db=db, item=item, user_id=user_id)
It may help to remember that a decorator is just syntactic sugar for a function that takes a callable (/function) as argument and returns a function. Typically the returned function actually internally calls the function passed to it as argument and does additional things before and/or after that call. I could rewrite the route above like this and it would be exactly the same:
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
) -> models.Item:
return crud.create_user_item(db=db, item=item, user_id=user_id)
create_item_for_user = app.post(
"/users/{user_id}/items/", response_model=schemas.Item
)(create_item_for_user)
what would be the return type of crud.get_user?
That would be models.User because that is the database model and is what the first method of that query returns.
db.query(models.User).filter(models.User.id == user_id).first()
This is then again returned by the read_user API route function in the same fashion as I explained above for models.Item.
Hope this helps.

Incomplete json structure - no count/next/previous/results field

like many times before, im trying to send data from my Django backend to my Ionic mobile app.
This time, however, for some reason, the .jsons im parsing are coming out incomplete.
A complete .json:
{"count":1,"next":null,"previous":null,"results":[{"codigo":"qwe","area":"ewq","especies":[],"id":1}]}
My incomplete .json:
[{"nome_ficheiro":"1520529086252.jpg","id":26,"especie":"Ameijoa Branca","zona":"L6","data":"09/06/2018"}]
IONIC is struggling with identifying what I'm parsing as a .json, which makes sense since there is no "results" field.
Here are the relevant snippets of my django code:
Views.py (both Views are doing the same thing! This is just me trying out different approaches!)
class resultUploadViewSet(viewsets.ViewSet):
def list(self, request, nome_ficheiro):
queryset = labelResult.objects.all()
nome = nome_ficheiro
answer = queryset.filter(nome_ficheiro=nome)
serializer = resultSerializer(answer, many=True)
return Response(serializer.data)
class resultUploadView(APIView):
serializer_class = resultSerializer
def get(self, request, nome_ficheiro):
queryset = labelResult.objects.all()
nome = nome_ficheiro
answer = queryset.filter(nome_ficheiro=nome)
serializer = self.serializer_class(answer, many=True)
return Response(serializer.data)
Models.py
class labelResult(models.Model):
nome_ficheiro = models.CharField(max_length=120)
especie = models.CharField(max_length=120)
zona = models.CharField(max_length=120)
data = models.CharField(max_length=120)
Urls.py
urlpatterns = [
url(r'results/(?P<nome_ficheiro>.+)/$', resultUploadViewSet.as_view({'get': 'list'})),
url(r'results1/(?P<nome_ficheiro>.+)/$', resultUploadView.as_view())]
Serializers.py
class resultSerializer(serializers.ModelSerializer):
class Meta:
model = labelResult
fields = ('nome_ficheiro','id','especie','zona', 'data')
Any idea why my .jsons are coming out incomplete?
you should use ListAPIView so the pagination is applied.
http://www.django-rest-framework.org/api-guide/generic-views/#listapiview
more on pagination you can find here:
http://www.django-rest-framework.org/api-guide/pagination/

Django DRF TypeError: object is not iterable, how to approach this?

Wondering what I'm doing wrong here. I'm running DRF with a React frontend. Trying to get one serializer to GET and POST a user's selected stations.
The AJAX POST works great, but the get on a page's initial load no longer works, and Django tells me: TypeError: 'Station' object is not iterable.
From reading around I'm vaguely aware that I shouldn't be declaring station with many = True, as this is causing the exception to be thrown. But I can't find any other way to get it working, it mangles the JSON POST request if I remove this option and stops the data validating.
The create is using custom code and is working fine.
Should I be trying to get this to work seamlessly or should I just hack it/make a different serializer to do the POST? Am I doing this in a logical way or have I got it all back-to-front?
models.py
class Station(models.Model):
network = models.ForeignKey(Network, on_delete=models.CASCADE)
name = models.CharField(db_column='name', max_length=256) # Field name made lowercase.
latitude = models.FloatField()
longitude = models.FloatField()
users = models.ManyToManyField('Account', through='UserStations')
class Meta:
managed = True
def __str__(self):
return self.name
class UserStations(models.Model):
station = models.ForeignKey(Station, on_delete=models.CASCADE)
user = models.ForeignKey(Account, on_delete=models.CASCADE)
serializers.py
class UserStationListSerializer(serializers.ModelSerializer):
class Meta:
model = Station
# fields = ('id', 'name')
fields = '__all__'
class UserStationsSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
#many = kwargs.pop('many', True)
print args
super(UserStationsSerializer, self).__init__(*args, **kwargs)
station = UserStationListSerializer(many=True, required = False, read_only=False)
views.py
class UserStationList(generics.ListCreateAPIView):
queryset = UserStations.objects.all()
serializer_class = UserStationsSerializer
Urls.py
from django.conf.urls import url
from . import views
from .views import StationList, AccountViewSet
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken.views import obtain_auth_token
router = DefaultRouter()
router.register(r'users', AccountViewSet)
urlpatterns = router.urls
urlpatterns += [
url(r'^$', views.StationList.as_view(), name='station-list'),
url(r'user-station-list/', views.UserStationList.as_view(), name='user-station-list'),
url(r'^obtain-auth-token/$', obtain_auth_token),
url(r'station-list/', views.StationList.as_view(), name='user-station-list'),
]

Automap multiple databases with Flask-SQLAlchemy

I have an app currently working fine Automapping one database, but I need to access another database now as well. I tried to follow the Flask-SQLAlchemy documentation here: http://flask-sqlalchemy.pocoo.org/2.1/binds/, but it doesn't seem to work with the automap_base.
The only changes I made were creating SQLALCHEMY_BINDS and adding the __bind_key__ in models.py. The error I get is
sqlalchemy.exc.ArgumentError: Mapper Mapper|Table2|table2 could not assemble any primary key columns for mapped table 'table2'
However, both tables have a primary key column, and if I get rid of SQLALCHEMY_BINDS, set the URI to that of db2, and only have table2 in models.py, everything works fine.
I'm clearly doing something wrong, but I have no idea what it is. It looks like Flask is still looking for table2 in db1. I think my problem is that some change needs to be made to __init__.py as well, but I don't know what that would be.
config.py
SQLALCHEMY_DATABASE_URI = 'mysql://user#host/db1'
SQLALCHEMY_BINDS = {
'otherDB': 'mysql://user#host/db2',
}
__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.automap import automap_base
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
db.Model = automap_base(db.Model)
from app import models
db.Model.prepare(db.engine, reflect=True)
models.py
class Table1(db.Model):
__tablename__ = 'table1'
class Table2(db.Model):
__bind_key__ = 'otherDB'
__tablename__ = 'table2'
Automap is a extension of sqlalchemy to reflect an existing database into a new model. It has not been baked into flask-sqlalchemy. Plz see the issue here. You can connect to multiple databases with Flask-SQLAlchemy like this:
__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
from app import models
if __name__ == "__main__":
# show the table schema
print m3.Table1.__table__.c
print m3.Table2.__table__.c
models.py
db.reflect() # reflection to get table meta
class Table1(db.Model):
__tablename__ = 'table1'
class Table2(db.Model):
__tablename__ = 'table2'
__bind_key__ = 'otherDB'

Render server side JSON in Django REST Framework

I'm trying to make bulk data downloads by serializing my entire database as JSON. The drf documentation on serializers has a section that says you can simply do:
from rest_framework.renderers import JSONRenderer
serializer = CommentSerializer(comment)
json = JSONRenderer().render(serializer.data)
Unfortunately, this doesn't work for HyperLinked relationships. When you try to do it with them, you get something like:
AssertionError: HyperlinkedIdentityField requires the request in the serializer context. Add context={'request': request} when instantiating the serializer.
So, I figured out I can add context attribute, like:
r = Request(request=HttpRequest())
context = dict(request=r)
serializer = CommentSerializer(comment, context=context)
json = JSONRenderer().render(serializer.data)
Which then returns the error:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "opinioncluster-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
I know this API works properly when it's called from the browser, but I can't get past this when I call it as above. Something that's automatic from the browser doesn't happen when you render it manually.
Any ideas?
First edit
Here's another strategy that seemed promising because it would add the path to my request object:
r = Request(request=RequestFactory().get(reverse('comment-list', kwargs={'version': 'v3'})))
context = dict(request=r)
serializer = CommentSerializer(comment, context=context)
json = JSONRenderer().render(serializer.data)
That returns the same problem as if I hadn't defined a path to the request:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "opinioncluster-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
Second Edit
As I said in my comments, I'm fairly certain my serializers and views aren't to blame, since they work perfectly fine via the browser. Nevertheless, here they are. If you're truly generous, the full serializers, filters, and codebase is online.
View:
class OpinionClusterViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = OpinionCluster.objects.all()
serializer_class = OpinionClusterSerializer
filter_class = OpinionClusterFilter
ordering_fields = (
'date_created', 'date_modified', 'date_filed', 'citation_count',
'date_blocked',
)
Serializer:
class OpinionClusterSerializer(DynamicFieldsModelSerializer,
serializers.HyperlinkedModelSerializer):
absolute_url = serializers.CharField(source='get_absolute_url',
read_only=True)
panel = serializers.HyperlinkedRelatedField(
many=True,
view_name='judge-detail',
read_only=True,
)
non_participating_judges = serializers.HyperlinkedRelatedField(
many=True,
view_name='judge-detail',
read_only=True,
)
docket = serializers.HyperlinkedRelatedField(
many=False,
view_name='docket-detail',
read_only=True,
)
sub_opinions = serializers.HyperlinkedRelatedField(
many=True,
view_name='opinion-detail',
read_only=True,
)
class Meta:
model = OpinionCluster
This management command creates a MockRequest to be used in a non-browser environment and should allow you to create your JSON:
from django.core.management.base import BaseCommand
from django.http import HttpRequest
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request
from comments.models import Comment
from comments.serializers import CommentSerializer
class MockRequest(HttpRequest):
def __init__(self):
super(MockRequest, self).__init__()
self.setup_host()
def setup_host(self):
# Required to give absolute urls in output
self.META['HTTP_HOST'] = 'localhost:8000'
class Command(BaseCommand):
help = 'Export JSON'
def handle(self, *args, **options):
request = MockRequest()
serializer_context = {
'request': Request(request),
}
comment = Comment.objects.first()
serializer = CommentSerializer(comment, context=serializer_context)
json = JSONRenderer().render(serializer.data)
print(json)
To remove the hard coded domain you could use the Sites framework to power the host name:
def setup_host(self):
from django.contrib.sites.models import Site
site = Site.objects.get_current()
self.META['HTTP_HOST'] = site.domain
After digging quite deeply into the code, I finally figured this out:
r = RequestFactory().request()
r.version = 'v3'
r.versioning_scheme = URLPathVersioning()
context = dict(request=r)
renderer = JSONRenderer()
json_str = renderer.render(
serializer(item, context=context).data,
accepted_media_type='application/json; indent=2',
)
This seems to work, but the HyperLinkRelated values in the JSON that is serialized have the server set to testserver. I could get around that by setting:
r.META['SERVER_NAME']
But I also need to set r.scheme to https, which doesn't seem to be possible (I get an error that r.scheme cannot be set).
I'm pretty close though, so this is going to have to serve as an answer for now.