2022-10-17
Posted by
最終更新: 2023-03-05
さて、今回はdbです
今回はsqlite3を使います
忘れないうちに設定をしておきましょう
やっと設定の出番が来ましたね
app.config.from_mapping( #アプリの設定
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
,SQLALCHEMY_TRACK_MODIFICATIONS = False
)
SQLALCHEMY_DATABASE_URIはdbの位置を表しています
SQLALCHEMY_TRACK_MODIFICATIONSは無くても良いですが無いと警告が出でるためFalseを設定しています
デフォルトだとNullでTrue又はFalseに設定したら警告は消えます
dbは以下のようにして定義します
db = SQLAlchemy(app)
ですがこれだけだと意味が有りません
モデルを作成する必要が有ります
modelはclassとして作成できます
今回のmodelはこんな感じです
class Url(db.Model): # type: ignore
id = db.Column(db.Integer, primary_key=True)
url = db.Column(db.String(4096), unique=True, nullable=False)
db.Columnで新しいColumnを作成します
db.Integerは整数の値をidに保存する事を表し
primary_keyは主キーである事を表します
db.Stringは文字列である事を表し
uniqueは重複する事が無い事
nullbleはNull値になる事が無い事を表します
したがって
idは整数であり主キーでもある
さてurlは次のうちどれがより適切に表現しているだろうか
info
urlを4096字に制限してるのはsafariの上限が4096字だからです
RFC7230なんて無かった
https://www.rfc-editor.org/rfc/rfc7230.txt (P21
今app.pyはこのような形になっていると思います
import os #pathの取得などに使う
import json #何かと使う
from flask import Flask # flaskを使うのに絶対必要
from flask import jsonify # jsonを送るのに使う
from flask import request # queryなどを取得するのに使う
from flask_cors import CORS, cross_origin#crosの設定
from flask_sqlalchemy import SQLAlchemy#databaseを使うために必要
basedir = os.path.abspath(os.path.dirname(__file__)) #実行folderのpathを取得、何かと使う
app = Flask(__name__, instance_relative_config=True) # アプリの作成
app.config.from_mapping( #アプリの設定
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db') #dbの位置
,SQLALCHEMY_TRACK_MODIFICATIONS = False #警告よけ
)
cors = CORS(app, responses={r"/*": {"origins": "*"}}) # cors error対策
db = SQLAlchemy(app) # dbの作成
class Url(db.Model): # type: ignore
id = db.Column(db.Integer, primary_key=True)
url = db.Column(db.String(4096), unique=True, nullable=False)
@app.route("/get/<id>")
def get_url(id):
return jsonify({"url":"https://example.com"})
@app.route("/create", methods=["POST"])
@cross_origin()
def create_url():
return jsonify({"url":"https://exmaple.com"})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000) # アプリの実行
ここで一回cmdでDBを操作してみましょう
まず仮想環境に入り
A:/abc>cd A:\abc\osaka\main\api
A:/abc/osaka/main/api>venv\Scripts\activate
(venv) A:\abc\osaka\main\api>
apple@apple ~ % cd /abc/osaka/main/api
apple@apple api % source venv/bin/activate
(venv) apple@apple api %
pythonを起動します
(venv) A:\abc\osaka\main\api>python
Python 3.9.4 ()
Type "help", "copyright", "credits" or "license" for more information.
>>>
(venv) apple@apple api % python
Python 3.11.2 (v3.11.2,) [Clang 13.0.0] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
起動出来たらappからdbとmodel(Url)を読み込みましょう
>>> from app import db, Url
そして色々と試す前にまずはdbを作らないといけません
さっき作ったじゃないかと思うかもしれないがあれは定義しただけで作った訳では無いのです
>>> db.create_all()
で作る
warning
複数のErrorが環境によっては出ます
これはflask sqlalchemyのバージョンが原因である可能性が有ります
Flask-SQLAlchemy==3.0.2で出る事を確認しました
Flask-SQLAlchemy==2.5.1では出ませんでした
versionの確認は>pip freeze でできます
>>>db
で実行結果が
<SQLAlchemy> の場合出ます
<SQLAlchemy engine=sqlite:///A:\abc\osaka\main\api\app.db>
などのengineが指定されてる場合は出ませんでした
追記 2023-03-05
sqlalchemyバージョンが原因でエラーが出ることを確認しました
sqlalchemyのversionが2.0になり一部の互換性が失われたためだと思います
SQLAlchemy==2.0.4では出ました
SQLAlchemy==1.4.41では出ませんでした
その際下記のようなErrorが出るかもしれません
>>> db
<SQLAlchemy>
>>> db.drop_all()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "A:\abc\osaka\main\api\venv\lib\site-packages\flask_sqlalchemy\extension.py", line 885, in drop_all
self._call_for_binds(bind_key, "drop_all")
File "A:\abc\osaka\main\api\venv\lib\site-packages\flask_sqlalchemy\extension.py", line 839, in _call_for_binds
engine = self.engines[key]
File "A:\abc\osaka\main\api\venv\lib\site-packages\flask_sqlalchemy\extension.py", line 628, in engines
app = current_app._get_current_object() # type: ignore[attr-defined]
File "A:\abc\osaka\main\api\venv\lib\site-packages\werkzeug\local.py", line 513, in _get_current_object
raise RuntimeError(unbound_message) from None
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.
errorが出た場合下記のコマンドを実行してみてください
pythonからはctrl + zで出てください
(venv) A:\abc\osaka\main\api>pip install Flask-SQLAlchemy==2.5.1
(venv) apple@var-apple api % pip install Flask-SQLAlchemy==2.5.1
時がたちsqlalchemyのversionが2.0になり、以下のようなエラーが出る可能性が出てきました
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/abc/osaka/main/api/app.py", line 18, in <module>
db = SQLAlchemy(app) # dbの作成
^^^^^^^^^^^^^^^
File "/abc/osaka/main/api/venv/lib/python3.11/site-packages/flask_sqlalchemy/__init__.py", line 758, in __init__
_include_sqlalchemy(self, query_class)
File "/abc/osaka/main/api/venv/lib/python3.11/site-packages/flask_sqlalchemy/__init__.py", line 112, in _include_sqlalchemy
for key in module.__all__:
^^^^^^^^^^^^^^
AttributeError: module 'sqlalchemy' has no attribute '__all__'. Did you mean: '__file__'?
出た場合は下記のコマンドを実行してみてください
(venv) A:\abc\osaka\main\api>pip install SQLAlchemy==1.4.41
(venv) apple@apple api % pip install SQLAlchemy==1.4.41
それでまだ出た場合は同じようにバージョンを指定し
これを全てinstallしてみてください
click==8.1.3
colorama==0.4.5
Flask==2.2.2
Flask-Cors==3.0.10
Flask-SQLAlchemy==2.5.1
greenlet==1.1.3.post0
importlib-metadata==5.0.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
six==1.16.0
SQLAlchemy==1.4.41
Werkzeug==2.2.2
zipp==3.9.0
これで出たら原因はもはや分かりません
google itしてください(googleは動詞)
リセットする場合は
>>>db.drop_all()
#消したら再度使うには作り直す必要がある
>>>db.create_all()
これでできます
試しにurlを保存してみましょう
>>>u = Url(url="https://example.org") #uをUrlとして定義
>>>db.session.add(u) #uをdbに追加する
>>>db.session.commit() #変更を保存
取得するには
>>>u = Url.query.get(1) #主キー(id)で取得
>>>u.id, u.url
#又は
>>>u = Url.query.all() #全て取得あまり使わない
>>>u = Url.query.first() #最初のを取得
>>>u = Url.query.filter_by(url = "https://example.org").first() #検索.firstは複数あった場合最初の一つで十分だから
値の変更もできる
>>>u = Url.query.get(1)
>>>u.id, u.url
>>>u.url = "http://example.com/about"
>>>db.session.add(u)
>>>db.session.commit()
>>>u.id, u.url
消すにも簡単
>>>u = Url.query.get(1)
>>>db.session.delete(u)
>>>db.session.commit()
これで一通り基本的な操作はできました
ほんとはMigrateなどもありますが複雑化する為今回は使用しません
まあそんな話は置いといサックっとdbを実装しましょう
一応dbはリセットしておいてください
因みに今はこうなっているはずです
import os #pathの取得などに使う
import json #何かと使う
from flask import Flask # flaskを使うのに絶対必要
from flask import jsonify # jsonを送るのに使う
from flask import request # queryなどを取得するのに使う
from flask_cors import CORS, cross_origin#crosの設定
from flask_sqlalchemy import SQLAlchemy#databaseを使うために必要
basedir = os.path.abspath(os.path.dirname(__file__)) #実行folderのpathを取得、何かと使う
app = Flask(__name__, instance_relative_config=True) # アプリの作成
app.config.from_mapping( #アプリの設定
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db') #dbの位置
,SQLALCHEMY_TRACK_MODIFICATIONS = False #警告よけ
)
cors = CORS(app, responses={r"/*": {"origins": "*"}}) # cors error対策
db = SQLAlchemy(app) # dbの作成
class Url(db.Model): # type: ignore
id = db.Column(db.Integer, primary_key=True)
url = db.Column(db.String(4096), unique=True, nullable=False)
@app.route("/get/<id>")
def get_url(id):
return jsonify({"url":"https://example.com"})
@app.route("/create", methods=["POST"])
@cross_origin()
def create_url():
return jsonify({"url":"https://exmaple.com"})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000) # アプリの実行
今回はdbのversion管理がめんどくさいので変更を加える度にdbをdropして作り直すようにします
if __name__ == '__main__':
db.drop_all()
db.create_all()
db.session.commit()
app.run(debug=True, host='0.0.0.0', port=5000) # アプリの実行
これで面倒な作業を致命的な欠陥と引き換えに得られました
(programを変更するたびにdataが飛ぶのでその都度対応してください
(次章ではちゃんとします
さぁ後は関数を二つ修正するだけです
get_url
はurlをdbから取得するように変更しましょう
簡単そうですね
作り終わったのが以下のものになります
@app.route("/get/<int:id>")
def get_url(id):
u = Url.query.get(id)
if not u:
return jsonify({"url":"http://127.0.0.1:3000/"}), 200
return jsonify({"url":u.url})
ここまで来たらもう理解できますね?
if not uはもし存在しない場合はNoneが返されるからです
無い場合は適当に返してもいいですが今回はurl作成pageに飛ぶようにしました
さぁ最後はcreate_url
を修正しましょう
まずは送られてきたurlを取得する必要が有ります
urlはrequest.data
にbyte形式で入っています
あまり美しくないコードですが動けば問題ありません以下のコードを使いましょう
url = json.loads(json.loads(request.data.decode('utf-8'))["body"])["url"]
後はdbに追加して返すだけです
@app.route("/create", methods=["POST"])
@cross_origin()
def create_url():
url = json.loads(json.loads(request.data.decode('utf-8'))["body"])["url"]
if not url:
return jsonify({"url":"oh"})
if Url.query.filter_by(url=url).first():
return jsonify({"url": "127.0.0.1:3000/" + str(Url.query.filter_by(url=url).first().id)})
u = Url(url=url)
db.session.add(u)
db.session.commit()
return jsonify({"url":"127.0.0.1:3000/" + str(u.id)})
最終的にこのような形になりました
import os #pathの取得などに使う
import json #何かと使う
from flask import Flask # flaskを使うのに絶対必要
from flask import jsonify # jsonを送るのに使う
from flask import request # queryなどを取得するのに使う
from flask_cors import CORS, cross_origin#crosの設定
from flask_sqlalchemy import SQLAlchemy#databaseを使うために必要
basedir = os.path.abspath(os.path.dirname(__file__)) #実行folderのpathを取得、何かと使う
app = Flask(__name__, instance_relative_config=True) # アプリの作成
app.config.from_mapping( #アプリの設定
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db') #dbの位置
,SQLALCHEMY_TRACK_MODIFICATIONS = False #警告よけ
)
cors = CORS(app, responses={r"/*": {"origins": "*"}}) # cors error対策
db = SQLAlchemy(app) # dbの作成
class Url(db.Model): # type: ignore
id = db.Column(db.Integer, primary_key=True)
url = db.Column(db.String(4096), unique=True, nullable=False)
@app.route("/get/<int:id>")
def get_url(id):
u = Url.query.get(id)
if not u:
return jsonify({"url":"http://127.0.0.1:3000/"}), 200
return jsonify({"url":u.url})
@app.route("/create", methods=["POST"])
@cross_origin()
def create_url():
url = json.loads(json.loads(request.data.decode('utf-8'))["body"])["url"]
if not url:
return jsonify({"url":"oh"})
if Url.query.filter_by(url=url).first():
return jsonify({"url": "127.0.0.1:3000/" + str(Url.query.filter_by(url=url).first().id)})
u = Url(url=url)
db.session.add(u)
db.session.commit()
return jsonify({"url":"127.0.0.1:3000/" + str(u.id)})
if __name__ == '__main__':
db.drop_all()
db.create_all()
db.session.commit()
app.run(debug=True, host='0.0.0.0', port=5000) # アプリの実行
これでできました早速使ってみましょう
ちゃんと動いているでしょうか
一応確認用のスクリプトを載せておく
info
urlに${id}がついてるのは内部的に...
MDを変換する限界かな
bodyとかheaderとかも同じだから気にしないで
多分動いているだろう
warning
ここまで飛ばしてきた人はいないと思うけど一応書いておこう
-
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.
-
もしこんな感じのErrorが出たら
以下のcommandを実行してみてください
>pip install Flask-SQLAlchemy==2.5.1
もしそれでも解決しなかった場合は一応
>python
>>>from app import db
>>>db.drop_all()
>>>db.create_all()
場合によってはdb.drop_all()でエラーが出るかもしれないが
多分大丈夫
後はお好みでcssを書いたり
今はidを直接10進数でやり取りしていますが効率を考え64進数に変換したり
アクセス数を取ったりしても良いかもしれません
最終的なコードはここに置いておきます
ではまた
このドキュメントどう?