Dataset基於SQLAlchemy的便利工具

Dataset基於SQLAlchemy的便利工具

作者: w2n1ck

資料集使得資料庫中的資料讀取和寫入資料就像閱讀和編寫JSON檔案一樣簡單。

dataset對於操作JSON、CSV檔案、NoSQL非常好用。

import dataset

連線MySQL資料庫:

db = dataset。connect(‘mysql://username:password@10。10。10。10/ctf?charset=utf8’)使用者名稱:username,密碼:password,資料庫地址(地址+埠):10。10。10。10,database名: ctf連線SQLite資料庫:db = dataset。connect(‘sqlite:///ctf。db’)連線PostgreSQL資料庫:db = dataset。connect(‘postgresql://scott:tiger@localhost:5432/mydatabase’)

一定要注意指定字元編碼

table = db[‘city’] #(選擇city表)user = table(‘name’) # 找出表中‘name’列屬性所有資料res = db。query(‘select name from table limit 10’) # 如果不需要檢視全部資料的話最好用limit,因為全部資料的載入非常非常耗時間for x in res:print x[‘name’] # 選name欄位的資料table。insert(dict(name=‘John Doe’, age=37))table。insert(dict(name=‘Jane Doe’, age=34, gender=‘female’))john = table。find_one(name=‘John Doe’)

在資料庫中查詢是否有同時滿足多個條件的資料:table。find_one(屬性1=屬性值1, 屬性2=屬性值2, …)

注:find_one速度很慢

插入資料

dataset會根據輸入自動建立表和欄位名

table = db[‘user’]# 或者table = db。get_table(‘user’)table。insert(dict(name=‘John Doe’, age=46, country=‘China’))table。insert(dict(name=‘Jane Doe’, age=37, country=‘France’, gender=‘female’))# 主鍵id自動生成

更新資料

table。update(dict(name=‘John Doe’, age=47), [‘name’])# 第二個引數相當於sql update語句中的where,用來過濾出需要更新的記錄

事務操作

事務操作可以簡單的使用上下文管理器來實現,出現異常,將會回滾

with dataset。connect() as tx:tx[‘user’]。insert(dict(name=‘John Doe’, age=46, country=‘China’))# 相當於:db = dataset。connect()db。begin()try:db[‘user’]。insert(dict(name=‘John Doe’, age=46, country=‘China’))db。commit()except:db。rollback()# 也可以巢狀使用:db = dataset。connect()with db as tx1:tx1[‘user’]。insert(dict(name=‘John Doe’, age=46, country=‘China’))with db as tx2:tx2[‘user’]。insert(dict(name=‘Jane Doe’, age=37, country=‘France’, gender=‘female’))

從表獲取資料

users = db[‘user’]。all()for user in db[‘user’]:# print(user[‘age’])# chinese_users = user。find(country=‘China’)john = user。find_one(name=‘John Doe’)

獲取非重複資料

db[‘user’]。distinct(‘country’)

刪除記錄

table。delete(place=‘Berlin’)

執行SQL語句

result = db。query(‘SELECT country, COUNT(*) c FROM user GROUP BY country’)for row in result:print(row[‘country’], row[‘c’])

匯出資料

result = db[‘users’]。all()dataset。freeze(result, format=‘json’, filename=‘users。json’)

JSON

JSON(JavaScript Object Notation) 是一種輕量級的資料交換格式,非常易於人閱讀和編寫。

import jsonjson。dumps 將 Python 物件編碼成 JSON 字串json。loads 將已編碼的 JSON 字串解碼為 Python 物件

MySQL資料庫

分類表-categories,包括類別web,reversing,crypto(加解密),mic等

題目表-tasks,包括題目id,題目名,flag,分值,檔案&地址,題目等級,題目詳細描述

flag表-flag,包括題目id,使用者id,得分,時間戳

使用者表-users,包括使用者id,使用者名稱,密碼

題目分類表-cat_task,包括題目id,題目類別id

flag表中每條資料由於是有題目ID task_id和使用者ID user_id來共同確認的,所以採用複合主鍵:primary key (task_id,user_id)

聯合主鍵和複合主鍵的區別

python裝飾器

Decorator透過返回包裝物件實現間接呼叫,以此插入額外邏輯

https://www。zhihu。com/question/26930016

wraps本身也是一個裝飾器,它能把原函式的元資訊複製到裝飾器函式中,這使得裝飾器函式也有和原函式一樣的元資訊了

from functools import wrapsdef logged(func):@wraps(func)def with_logging(*args,**kwargs):print func。__name__ + “was called”return func(*args,**kwargs)return with_logging@loggeddef f(x):“”“does some math”“”return x + x * xprint f。__name__ # prints ‘f’print f。__doc__ # prints ‘does some math’

web框架採用flask

from flask import Flask

引入Flask類,Flask類實現了一個WSGI(Web Server Gateway Interface)應用

app = Flask(__name__)

app是Flask的例項,它接收包或者模組的名字作為引數,但一般都是傳遞__name__

@app。route(‘/’)def hello_world():return ‘Hello World!’

使用app。route裝飾器會將URL和執行的檢視函式的關係儲存到app。url_map屬性上。處理URL和檢視函式的關係的程式就是路由,這裡的檢視函式就是hello_world

if __name__ == ‘__main__’:app。run(host=‘0。0。0。0’,port=9000)

使用這個判斷可以保證當其他檔案引用這個檔案的時候(例如from hello import app)不會執行這個判斷內的程式碼,也就是不會執行app。run函式。

執行app。run就可以啟動服務了。預設Flask只監聽虛擬機器的本地127。0。0。1這個地址,埠為5000。而我們對虛擬機器做的埠轉發埠是9000,所以需要制定host和port引數,0。0。0。0表示監聽所有地址,這樣就可以在本機訪問了。

伺服器啟動後,會呼叫werkzeug。serving。run_simple進入輪詢,預設使用單程序單執行緒的werkzeug。serving。BaseWSGIServer處理請求,實際上還是使用標準庫BaseHTTPServer。HTTPServer,透過select。select做0。5秒的while TRUE的事件輪詢。當我們訪問http://127。0。0。1:9000/,透過app。url_map找到註冊的/這個URL模式,就找到了對應的hello_world函式執行,返回hello world!,狀態碼為200。如果訪問一個不存在的路徑,如訪問http://127。0。0。1:9000/a,Flask找不到對應的模式,就會向瀏覽器返回Not Found,狀態碼為404

flask中jsonify的作用

jsonify的作用實際上就是將我們傳入的json形式資料序列化成為json字串,作為響應的body,並且設定響應的Content-Type為application/json,構造出響應返回至客戶端

效果等於json。dumps

jsonify的Content-Type欄位值為application/json

json。dumps的Content-Type欄位值為text/html

修改flask中靜態資料夾

修改的flask預設的static資料夾只需要在建立Flask例項的時候,把static_folder和static_url_path引數設定為空字串即可。

app = Flask(__name__, static_folder=”, static_url_path=”)

訪問的時候用url_for函式,res資料夾和static資料夾同一級:

url_for(‘static’, filename=’res/favicon。ico’)

werkzeug

werkzeug是一個WSGI工具包,可以作為一個Web框架的底層庫。它封裝好了很多Web框架的東西,例如 Request,Response等等。Flask框架就是一Werkzeug 為基礎開發的

generate_password_hash(password)

將使用者輸入的明文密碼加密成密文進行儲存

密碼加鹽雜湊函式。用來將明文密碼加密,返回加密後的密文,用來進行使用者註冊

函式定義:

werkzeug。security。generate_password_hash(password, method=‘pbkdf2:sha1’, salt_length=8)密文格式:method$salt$hashpassword: 明文密碼method: 雜湊的方式(需要是hashlib庫支援的),格式為pbpdf2:[:iterations]。引數說明:method:雜湊的方式,一般為SHA1,iterations:(可選引數)迭代次數,預設為1000。slat_length: 鹽值的長度,預設為8

check_password_hash(hash,password)

驗證經過generate_password_hash雜湊的密碼,將明文和密文進行比較,檢視是否一致,用來驗證使用者登入

函式定義:

werkzeug。security。check_password_hash(pwhash, password)pwhash: generate_password_hash生成的雜湊字串password: 需要驗證的明文密碼

flask中的session

rom flask import sessionuser = db[‘users’]。find_one(username=username)session[‘user_id’] = user[‘id’]

由於使用了session,所以需要設定一個secret_key用來做一些模組的hash

Flask Web Development 中的內容:

SECRET_KEY配置變數是通用金鑰,可在Flask和多個第三方擴充套件中使用。如其名所示,加密的強度取決於變數值的機密度。不同的程式要使用不同的金鑰,而且要保證其他人不知道你所用的字串。

SECRET_KEY的作用主要是提供一個值做各種HASH, 是在其加密過程中作為演算法的一個引數(salt或其他)。所以這個值的複雜度也就影響到了資料傳輸和儲存時的複雜度。

flask 變數規則

要給URL新增變數部分,你可以把這些特殊的欄位標記為, 這個部分將會作為命名引數傳遞到你的函式。規則可以用指定一個可選的轉換器

@route(‘/hello/’)def index(name):return ‘Hello {{name}}!’

資料庫查詢

對dataset的資料查詢,使用冒號來為變數傳參。

select f。task_id from flags f where f。user_id = :user_id”’,user_id=session[‘user_id’])

模板渲染

使用render_template方法來渲染模板。將模板名和你想作為關鍵字的引數傳入模板的變數

MySQL

IFNULL(expr1,expr2)

如果expr1不是NULL,IFNULL()返回expr1,否則它返回expr2。

IFNULL()返回一個數字或字串值,取決於它被使用的上下文環境。

max函式是用來找出記錄集中最大值的記錄

對於left join,不管on後面跟什麼條件,左表的資料全部查出來,因此要想過濾需把條件放到where後面

對於inner join,滿足on後面的條件表的資料才能查出,可以起到過濾作用。也可以把條件放到where後面

在使用left jion時,on和where條件的區別如下:

on條件是在生成臨時表時使用的條件,它不管on中的條件是否為真,都會返回左邊表中的記錄。

where條件是在臨時表生成好後,再對臨時表進行過濾的條件。這時已經沒有left join的含義(必須返回左邊表的記錄)了,條件不為真的就全部過濾掉。

order by的用法

使用order by,一般是用來,依照查詢結果的某一列(或多列)屬性,進行排序(升序:ASC;降序:DESC;預設為升序)。當排序列含空值時:ASC:排序列為空值的元組最後顯示。DESC:排序列為空值的元組最先顯示。可以把null值看做無窮大select * from s order by sno desc, sage asc

group by的用法

group by按照查詢結果集中的某一列(或多列),進行分組,值相等的為一組1、細化集函式(count,sum,avg,max,min)的作用物件:未對查詢結果分組,集函式將作用於整個查詢結果。對查詢結果分組後,集函式將分別作用於每個組。SELECT cno,count(sno) from sc group by cno2、GROUP BY子句的作用物件是查詢的中間結果表分組方法:按指定的一列或多列值分組,值相等的為一組。使用GROUP BY子句後,SELECT子句的列名列表中只能出現分組屬性(比如:sno)和集函式(比如:count())select sno,count(cno) from sc group by sno3、多個列屬性進行分組select cno,grade,count(cno) from sc group by cno,grade4、使用HAVING短語篩選最終輸出結果只有滿足HAVING短語指定條件的組才輸出。HAVING短語與WHERE子句的區別:作用物件不同。1、WHERE子句作用於基表或檢視,從中選擇滿足條件的元組。2、HAVING短語作用於組,從中選擇滿足條件的組select sno from sc group by sno having count(cno)>3select sno,count(cno) from sc where grade>60 group by sno having count(cno)>3

MySQL的左連線、右連線、等值連線

1.左連線(left join )

select m。columnname……,n。* columnname…。。from left_table m left join right_table n on m。columnname_join=n。columnname_join and n。columnname=xxxwhere m。columnname=xxx…。。

ON是連線條件,用於把2表中等值的記錄連線在一起,但是不影響記錄集的數量。若是表left_table中的某記錄,無法在表right_table找到對應的記錄,則此記錄依然顯示在記錄集中,只是表right_table需要在查詢顯示的列的值用NULL替代;

ON連線條件中表n。columnname=xxx用於控制right_table表是否有符合要求的列值還是用NULL替換的方式顯示在查詢列中,不影響記錄集的數量;

WHERE字句控制記錄是否符合查詢要求,不符合則過濾掉

2.右連線(right join)

select m。columnname……,n。* columnname…。。from left_table m right join right_table n on m。 columnname_join=n。 columnname_join and m。 columnname=xxxwhere n。columnname=xxx…。。

3.等值連線

select m。columnname……,n。* columnname…。。from left_table m [inner] join right_table n on m。 columnname_join=n。 columnname_joinwhere m。columnname=xxx…。。 and n。columnname=xxx…。或者select m。columnname……,n。* columnname…。。from left_table m , right_table nwhere m。 columnname_join=n。 columnname_join andm。columnname=xxx…。。 and n。columnname=xxx…。

ON是連線條件,不再與左連線或右連線的功效一樣,除了作為2表記錄匹配的條件外,還會起到過濾記錄的作用,若left_table中記錄無法在right_table中找到對應的記錄,則會被過濾掉;

WHERE字句,不管是涉及表left_table、表right_table上的限制條件,還是涉及2表連線的條件,都會對記錄集起到過濾作用,把不符合要求的記錄刷選掉;

jinja2獲取迴圈索引

jinja2獲取迴圈{% for i in n %}的索引使用loop。index{% for i in names %}{{ loop。index }} //當前是第x條{{ i。name }}{% endfor %}

flask 重定向和錯誤

可以用redirect()函式把使用者重定向到其它地方。放棄請求並返回錯誤程式碼,用abort()函式。

from flask import abort, redirect, url_for@app。route(‘/’)def index():return redirect(url_for(‘login’))@app。route(‘/login’)def login():abort(401)this_is_never_executed()

預設情況下,錯誤程式碼會顯示一個黑白的錯誤頁面。如果你要定製錯誤頁面,可以使用errorhandler()

裝飾器:

from flask import render_template@app。errorhandler(404)def page_not_found(error):return render_template(‘page_not_found。html’), 404

注意 render_template()呼叫之後的 404 。這告訴Flask,該頁的錯誤程式碼是404 ,即沒有找到。預設為200,也就是一切正常。

flask CSRF防護機制

@app。before_requestdef csrf_protect():if request。method == “POST”:token = session。pop(‘_csrf_token’, None)if not token or token != request。form。get(‘_csrf_token’):abort(403)def some_random_string():return hashlib。sha256(os。urandom(16)。hexdigest())def generate_csrf_token():if ‘_csrf_token’ not in session:session[‘_csrf_token’] = some_random_string()return session[‘_csrf_token’]

在flask的全域性變數裡面註冊 上面那個生成隨機token的函式

app。jinja_env。globals[‘csrf_token’] = generate_csrf_token

在網頁的模板是這麼引入的

flask上下文處理器

Flask 上下文處理器自動向模板的上下文中插入新變數。上下文處理器在模板渲染之前執行,並且可以在模板上下文中插入新值。上下文處理器是一個返回字典的函式,這個字典的鍵值最終將傳入應用中所有模板的上下文:

@app。context_processordef inject_user():return dict(user=g。user)

上面的上下文處理器使得模板可以使用一個名為user值為g。user的變數。不過這個例子不是很有意思,因為g在模板中本來就是可用的,但它解釋了上下文處理器是如何工作的。

變數不僅限於值,上下文處理器也可以使某個函式在模板中可用(由於Python允許傳遞函式):

@app。context_processordef utility_processor():def format_price(amount, currency=u‘€’):return u‘{0:。2f}{1}。format(amount, currency)return dict(format_price=format_price)上面的上下文處理器使得format_price函式在所有模板中可用:{{ format_price(0。33) }}

日誌記錄

handler = logging。FileHandler(‘flask。log’, encoding=’UTF-8′)

1、請求之前設定requestId並記錄日誌

每個URL請求之前,定義requestId並繫結到g

@app。before_requestdef before_request():g。requestId = gen_requestId()logger。info(“Start Once Access, and this requestId is %s” % g。requestId)

2、請求之後新增響應頭與記錄日誌

每次返回資料中,帶上響應頭,包含API版本和本次請求的requestId,以及允許所有域跨域訪問API, 記錄訪問日誌

@app。after_requestdef add_header(response):response。headers[“X-SaintIC-Media-Type”] = “saintic。v1”response。headers[“X-SaintIC-Request-Id”] = g。requestIdresponse。headers[“Access-Control-Allow-Origin”] = “*”logger。info(json。dumps({“AccessLog”: {“status_code”: response。status_code,“method”: request。method,“ip”: request。headers。get(’X-Real-Ip‘, request。remote_addr),“url”: request。url,“referer”: request。headers。get(’Referer‘),“agent”: request。headers。get(“User-Agent”),“requestId”: str(g。requestId),}}))return response

basicConfig方法可以滿足你在絕大多數場景下的使用需求,但是basicConfig有一個很大的缺點。呼叫basicConfig其實是給root logger添加了一個handler(FileHandler ),這樣當你的程式和別的使用了 logging的第三方模組一起工作時,會影響第三方模組的logger行為。這是由logger的繼承特性決定的

logging。basicConfig(level=logging。DEBUG,format=’%(asctime)s %(levelname)s %(message)s‘,datefmt=’%a, %d %b %Y %H:%M:%S‘,filename=’logs/pro。log‘,filemode=’w‘)logging。debug(’dddddddddd‘)

MySQL字元編碼

除了設定資料庫的之外,由於dataset預設建立資料庫和表的字符集不是utf8,所以需要自己設定,否則會中文亂碼,所以需要修改表的字符集

my。cnf[client]default-character-set=utf8[mysqld]character-set-server=utf8collation-server=utf8_general_cidefault-storage-engine=INNODB

表的字符集

show create table tasks;alter table tasks convert to character set utf8;

End。