Dataset基于SQLAlchemy的便利工具

数据库 开发
数据集使得数据库中的数据读取和写入数据就像阅读和编写JSON文件一样简单。Dataset对于操作JSON、CSV文件、NoSQL非常好用。

[[206839]]

数据集使得数据库中的数据读取和写入数据就像阅读和编写JSON文件一样简单。

Dataset对于操作JSON、CSV文件、NoSQL非常好用。

  1. import dataset 

连接MySQL数据库:

  1. db = dataset.connect('mysql://username:password@10.10.10.10/ctf?charset=utf8'

用户名:username,密码:password,数据库地址(地址+端口):10.10.10.10,database名: ctf

连接SQLite数据库:

  1. db = dataset.connect('sqlite:///ctf.db'

连接PostgreSQL数据库:

  1. db = dataset.connect('postgresql://scott:tiger@localhost:5432/mydatabase'

一定要注意指定字符编码

  1. table = db['city'] #(选择city表) 
  2.  
  3. user = table('name') # 找出表中'name'列属性所有数据 
  4.  
  5. res = db.query('select name from table limit 10') # 如果不需要查看全部数据的话***用limit,因为全部数据的载入非常非常耗时间 
  6.  
  7. for x in res: 
  8.  
  9. print x['name'] # 选name字段的数据 
  10.  
  11. table.insert(dict(name='John Doe', age=37)) 
  12.  
  13. table.insert(dict(name='Jane Doe', age=34, gender='female')) 
  14.  
  15. john = table.find_one(name='John Doe' 

在数据库中查找是否有同时满足多个条件的数据:table.find_one(属性1=属性值1, 属性2=属性值2, …)

注:find_one速度很慢

插入数据

dataset会根据输入自动创建表和字段名

  1. table = db['user'
  2.  
  3. # 或者table = db.get_table('user'
  4.  
  5. table.insert(dict(name='John Doe', age=46, country='China')) 
  6.  
  7. table.insert(dict(name='Jane Doe', age=37, country='France', gender='female')) 
  8.  
  9. # 主键id自动生成  

更新数据

  1. table.update(dict(name='John Doe', age=47), ['name']) 
  2.  
  3. # 第二个参数相当于sql update语句中的where,用来过滤出需要更新的记录  

事务操作

事务操作可以简单的使用上下文管理器来实现,出现异常,将会回滚

  1. with dataset.connect() as tx: 
  2.  
  3. tx['user'].insert(dict(name='John Doe', age=46, country='China')) 
  4.  
  5. # 相当于: 
  6.  
  7. db = dataset.connect() 
  8.  
  9. db.begin() 
  10.  
  11. try: 
  12.  
  13. db['user'].insert(dict(name='John Doe', age=46, country='China')) 
  14.  
  15. db.commit() 
  16.  
  17. except
  18.  
  19. db.rollback() 
  20.  
  21. # 也可以嵌套使用: 
  22.  
  23. db = dataset.connect() 
  24.  
  25. with db as tx1: 
  26.  
  27. tx1['user'].insert(dict(name='John Doe', age=46, country='China')) 
  28.  
  29. with db as tx2: 
  30.  
  31. tx2['user'].insert(dict(name='Jane Doe', age=37, country='France', gender='female'))  

从表获取数据

  1. users = db['user'].all() 
  2.  
  3. for user in db['user']: 
  4.  
  5. # print(user['age']) 
  6.  
  7. # chinese_users = user.find(country='China'
  8.  
  9. john = user.find_one(name='John Doe' 

获取非重复数据

  1. db['user'].distinct('country'

删除记录

  1. table.delete(place='Berlin'

执行SQL语句

  1. result = db.query('SELECT country, COUNT(*) c FROM user GROUP BY country'
  2.  
  3. for row in result: 
  4.  
  5. print(row['country'], row['c'])  

导出数据

  1. result = db['users'].all() 
  2.  
  3. dataset.freeze(result, format='json', filename='users.json' 

JSON

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,非常易于人阅读和编写。

  1. import json 

json.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本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了

  1. from functools import wraps 
  2.  
  3. def logged(func): 
  4.  
  5. @wraps(func) 
  6.  
  7. def with_logging(*args,**kwargs): 
  8.  
  9. print func.__name__ + "was called" 
  10.  
  11. return func(*args,**kwargs) 
  12.  
  13. return with_logging 
  14.  
  15.   
  16.  
  17. @logged 
  18.  
  19. def f(x): 
  20.  
  21. """does some math""" 
  22.  
  23. return x + x * x 
  24.  
  25.   
  26.  
  27. print f.__name__ # prints 'f' 
  28.  
  29. print f.__doc__ # prints 'does some math'  

web框架采用flask

  1. from flask import Flask 

引入Flask类,Flask类实现了一个WSGI(Web Server Gateway Interface)应用

  1. app = Flask(__name__) 

app是Flask的实例,它接收包或者模块的名字作为参数,但一般都是传递__name__

  1. @app.route('/'
  2.  
  3. def hello_world(): 
  4.  
  5. return 'Hello World!'  

使用app.route装饰器会将URL和执行的视图函数的关系保存到app.url_map属性上。处理URL和视图函数的关系的程序就是路由,这里的视图函数就是hello_world

  1. if __name__ == '__main__'
  2.  
  3. 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$hash

password: 明文密码

method: 哈希的方式(需要是hashlib库支持的),格式为

pbpdf2:<method>[: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

  1. rom flask import session 
  2.  
  3. user = db['users'].find_one(username=username) 
  4.  
  5. session['user_id'] = user['id' 

由于使用了session,所以需要设置一个secret_key用来做一些模块的hash

Flask Web Development 中的内容:

SECRET_KEY配置变量是通用密钥,可在Flask和多个第三方扩展中使用。如其名所示,加密的强度取决于变量值的机密度。不同的程序要使用不同的密钥,而且要保证其他人不知道你所用的字符串。

SECRET_KEY的作用主要是提供一个值做各种HASH, 是在其加密过程中作为算法的一个参数(salt或其他)。所以这个值的复杂度也就影响到了数据传输和存储时的复杂度。

flask 变量规则

要给URL添加变量部分,你可以把这些特殊的字段标记为<variable_name>, 这个部分将会作为命名参数传递到你的函数。规则可以用<converter:variable_name>指定一个可选的转换器

  1. @route('/hello/<name>'
  2.  
  3. def index(name): 
  4.  
  5. return '<b>Hello {{name}}</b>!'  

数据库查询

对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函数是用来找出记录集中***值的记录

  1. 对于left join,不管on后面跟什么条件,左表的数据全部查出来,因此要想过滤需把条件放到where后面
  2. 对于inner join,满足on后面的条件表的数据才能查出,可以起到过滤作用。也可以把条件放到where后面

在使用left jion时,on和where条件的区别如下:

  1. on条件是在生成临时表时使用的条件,它不管on中的条件是否为真,都会返回左边表中的记录。
  2. 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 cno

2、GROUP BY子句的作用对象是查询的中间结果表

分组方法:按指定的一列或多列值分组,值相等的为一组。

使用GROUP BY子句后,SELECT子句的列名列表中只能出现分组属性(比如:sno)和集函数(比如:count())

select sno,count(cno) from sc group by sno

3、多个列属性进行分组

select cno,grade,count(cno) from sc group by cno,grade

4、使用HAVING短语筛选最终输出结果

只有满足HAVING短语指定条件的组才输出。

HAVING短语与WHERE子句的区别:作用对象不同。

1、WHERE子句作用于基表或视图,从中选择满足条件的元组。

2、HAVING短语作用于组,从中选择满足条件的组

select sno from sc group by sno having count(cno)>3

 

select sno,count(cno) from sc where grade>60 group by sno having count(cno)>3

MySQL的左连接、右连接、等值连接

1.左连接(left join )

  1. select m.columnname……,n.* columnname….. 
  2.  
  3. from left_table m left join right_table n on m.columnname_join=n.columnname_join and n.columnname=xxx 
  4.  
  5. where m.columnname=xxx…..  

ON是连接条件,用于把2表中等值的记录连接在一起,但是不影响记录集的数量。若是表left_table中的某记录,无法在表right_table找到对应的记录,则此记录依然显示在记录集中,只是表right_table需要在查询显示的列的值用NULL替代;

ON连接条件中表n.columnname=xxx用于控制right_table表是否有符合要求的列值还是用NULL替换的方式显示在查询列中,不影响记录集的数量;

WHERE字句控制记录是否符合查询要求,不符合则过滤掉

2.右连接(right join)

  1. select m.columnname……,n.* columnname….. 
  2.  
  3. from left_table m right join right_table n on m. columnname_join=n. columnname_join and m. columnname=xxx 
  4.  
  5. where n.columnname=xxx…..  

3.等值连接

  1. select m.columnname……,n.* columnname….. 
  2.  
  3. from left_table m [innerjoin right_table n on m. columnname_join=n. columnname_join 
  4.  
  5. where m.columnname=xxx….. and n.columnname=xxx….  

或者

  1. select m.columnname……,n.* columnname….. 
  2.  
  3. from left_table m , right_table n 
  4.  
  5. where m. columnname_join=n. columnname_join and 
  6.  
  7. m.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

  1. {% for i in names %} 
  2.  
  3. <tr> 
  4.  
  5. <td>{{ loop.index }}</td> //当前是第x条 
  6.  
  7. <td>{{ i.name }}</td> 
  8.  
  9. </tr> 
  10.  
  11. {% endfor %}  

flask 重定向和错误

可以用redirect()函数把用户重定向到其它地方。放弃请求并返回错误代码,用abort()函数。

  1. from flask import abort, redirect, url_for 
  2.  
  3. @app.route('/'
  4.  
  5. def index(): 
  6.  
  7. return redirect(url_for('login')) 
  8.  
  9. @app.route('/login'
  10.  
  11. def login(): 
  12.  
  13. abort(401) 
  14.  
  15. this_is_never_executed()  

默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面,可以使用errorhandler()

装饰器:

  1. from flask import render_template 
  2.  
  3. @app.errorhandler(404) 
  4.  
  5. def page_not_found(error): 
  6.  
  7. return render_template('page_not_found.html'), 404  

注意 render_template()调用之后的 404 。这告诉Flask,该页的错误代码是404 ,即没有找到。默认为200,也就是一切正常。

flask CSRF防护机制

  1. @app.before_request 
  2.  
  3. def csrf_protect(): 
  4.  
  5. if request.method == "POST"
  6.  
  7. token = session.pop('_csrf_token', None) 
  8.  
  9. if not token or token != request.form.get('_csrf_token'): 
  10.  
  11. abort(403) 
  12.  
  13. def some_random_string(): 
  14.  
  15. return hashlib.sha256(os.urandom(16).hexdigest()) 
  16.  
  17. def generate_csrf_token(): 
  18.  
  19. if '_csrf_token' not in session: 
  20.  
  21. session['_csrf_token'] = some_random_string() 
  22.  
  23. return session['_csrf_token' 

在flask的全局变量里面注冊 上面那个生成随机token的函数

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

在网页的模板是这么引入的

  1. <form method=post action=""
  2.  
  3. <input name=_csrf_token type=hidden value="{{ csrf_token() }}" 

flask上下文处理器

Flask 上下文处理器自动向模板的上下文中插入新变量。上下文处理器在模板渲染之前运行,并且可以在模板上下文中插入新值。上下文处理器是一个返回字典的函数,这个字典的键值最终将传入应用中所有模板的上下文:

  1. @app.context_processor 
  2.  
  3. def inject_user(): 
  4.  
  5. return dict(user=g.user 

上面的上下文处理器使得模板可以使用一个名为user值为g.user的变量。不过这个例子不是很有意思,因为g在模板中本来就是可用的,但它解释了上下文处理器是如何工作的。

变量不仅限于值,上下文处理器也可以使某个函数在模板中可用(由于Python允许传递函数):

  1. @app.context_processor 
  2.  
  3. def utility_processor(): 
  4.  
  5. def format_price(amount, currency=u'€'): 
  6.  
  7. return u'{0:.2f}{1}.format(amount, currency) 
  8.  
  9. 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

  1. @app.before_request 
  2.  
  3. def before_request(): 
  4.  
  5. g.requestId = gen_requestId() 
  6.  
  7. logger.info("Start Once Access, and this requestId is %s" % g.requestId)  

2、请求之后添加响应头与记录日志

每次返回数据中,带上响应头,包含API版本和本次请求的requestId,以及允许所有域跨域访问API, 记录访问日志 

  1. @app.after_request 
  2.  
  3. def add_header(response): 
  4.  
  5. response.headers["X-SaintIC-Media-Type"] = "saintic.v1" 
  6.  
  7. response.headers["X-SaintIC-Request-Id"] = g.requestId 
  8.  
  9. response.headers["Access-Control-Allow-Origin"] = "*" 
  10.  
  11. logger.info(json.dumps({ 
  12.  
  13. "AccessLog": { 
  14.  
  15. "status_code": response.status_code, 
  16.  
  17. "method": request.method, 
  18.  
  19. "ip": request.headers.get('X-Real-Ip', request.remote_addr), 
  20.  
  21. "url": request.url, 
  22.  
  23. "referer": request.headers.get('Referer'), 
  24.  
  25. "agent": request.headers.get("User-Agent"), 
  26.  
  27. "requestId": str(g.requestId), 
  28.  
  29.  
  30.  
  31. )) 
  32.  
  33. return response  

basicConfig方法可以满足你在绝大多数场景下的使用需求,但是basicConfig有一个很大的缺点。调用basicConfig其实是给root logger添加了一个handler(FileHandler ),这样当你的程序和别的使用了 logging的第三方模块一起工作时,会影响第三方模块的logger行为。这是由logger的继承特性决定的

  1. logging.basicConfig(level=logging.DEBUG, 
  2.  
  3. format='%(asctime)s %(levelname)s %(message)s'
  4.  
  5. datefmt='%a, %d %b %Y %H:%M:%S'
  6.  
  7. filename='logs/pro.log'
  8.  
  9. filemode='w'
  10.  
  11.   
  12.  
  13. logging.debug('dddddddddd' 

MySQL字符编码

除了设置数据库的之外,由于dataset默认创建数据库和表的字符集不是utf8,所以需要自己设置,否则会中文乱码,所以需要修改表的字符集

  1. my.cnf 
  2.  
  3. [client] 
  4.  
  5. default-character-set=utf8 
  6.  
  7. [mysqld] 
  8.  
  9. character-set-server=utf8 
  10.  
  11. collation-server=utf8_general_ci 
  12.  
  13. default-storage-engine=INNODB  

表的字符集 

  1. show create table tasks; 
  2.  
  3. alter table tasks convert to character set utf8;   
责任编辑:庞桂玉 来源: w2n1ck
相关推荐

2016-09-23 13:34:15

PythonORMsqlalchemy

2015-04-14 09:38:35

软件开发人员便利工具

2015-07-07 09:26:08

SaaS亚马逊云服务

2019-11-20 08:50:16

PythonORM工具包SQLAlchemy

2017-12-15 10:36:04

华为云

2019-07-11 15:24:30

开发者技能工具

2012-02-20 09:55:54

虚拟化虚拟化技术虚拟服务器

2023-05-18 07:58:27

2013-01-06 09:09:57

BYOD网络设备

2023-05-17 10:16:04

ORM工具包SQLAlchemy

2009-09-08 17:27:18

LINQ to Dat

2010-09-16 14:37:54

DataSetXML

2023-07-04 08:28:27

2009-09-15 13:59:18

LINQ to Dat

2009-09-08 17:57:54

LINQ to Dat

2009-09-14 19:58:47

DataSet和Dat

2016-05-12 13:48:05

DockerAPI

2024-08-05 09:58:24

2009-08-12 15:43:02

操作C# Datase

2022-06-14 14:03:10

架构实践模式
点赞
收藏

51CTO技术栈公众号