Day 26 of self-taught Python - Tornado framework

Day 26 of self-taught Python - Tornado framework


Tornado is a python-based web development framework, similar to Django and Flask. Tornado is a high-performance framework that supports asynchronous web services , and its performance is 1.5 times that of Flask. Tornado was designed with performance factors in mind, aiming to solve the C10K problem. This design makes it a very high-performance framework that can handle severe network traffic.

Due to its high performance, it can be used for instant feedback and real-time communication scenarios, such as scalable social applications, real-time analysis engines (because Tornado is based on long connections, it can perform real-time data analysis and synchronization), RESTful API, and WebSocket (such as online customer service, live video broadcast, etc.) etc.

Tornado Chinese documentation
Tornado official English documentation

Installation and basic reference

pip install tornado
from tornado.web import Application
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler

Create, configure, initialize applications and simply run services

A simple Tornado application service includes four main steps: creating an application, configuring the application, setting routing and processors, and starting the service .

Create app

Use Application to create an application object. Its parameter is a list of tuples, each tuple specifies a route and processor.

from tornado.web import Application
# 创建应用
app = Application([('/', IndexHandler)])

Set up the app

After the app is created, settings need to be made, such as binding ports, setting access addresses, etc.

# 设置监听
app.listen(5000, '127.0.0.1')

Another way to set up

tornado supports settings by adding parameters in the command line using options.

# manager.py
from tornado.options import define, parse_command_line, options
# 设置命令行输入的参数
define('port', default=5000, type=int, help='bind socket port')
define('host', default='127.0.0.1', type=str, help='host ip address')
# 解析命令行的参数
parse_command_line()
# 创建 app
app = make_app()	# 使用 make_app 函数来创建app对象和注册路由及处理器
# 在监听中使用获取的参数
app.listen(options.port, options.host)
# 在服务运行前输出提示
print('starting web server at http://%s:%s' % (options.host, options.port))
# 运行服务
IOLoop.current().start()

This way you can see the parameter prompts on the command line

python manager.py --help

Use parameters

python manager.py --host='127.0.0.1' --port=5000

Some other configurations

Additional settings can be added when creating the app object and registering routes and handlers.

from tornado.web import Application

app = Application([('/', IndexHandler), ('/hello', HelloHandler)], **settings)

The settings here are a dictionary containing some setting information.

import os

settings = {
    
    
    # 设置模板路径
    'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
    # 设置静态文件路径
    'static_path': os.path.join(os.path.dirname(__file__), "static"),
    # 设置防跨域请求,默认为 false,即不防御
    'xsrf_cookies': True,
    # 设置登陆路径,未登陆用户在操作时跳转会用到这个参数:
    # #默认为 @tornado.web.authenticated
    # 'login_url': '/login',
    # 设置调试模式,默认为 false
    'debug': True,
    # 设置cookie密钥,默认为字符串"secure cookies"
    'cookie_secret': "alen#!2ODFog45G45(*&(';//?}dfj423$%^FS",
    # 设置是否自动编码,用以兼容之前的APP,默认为未设置
    'autoescape': None,
    # 设置gzip压缩:
    'gzip': True,
    # 设置静态路径,默认是 '/static/'
    'static_url_prefix': '/static/',
    # 设置静态文件处理类,默认是 tornado.web.StaticFileHandler
    # 'static_handler_class' : tornado.web.StaticFileHandler,
    # 设置日志处理函数
    # 'log_function' : your_fun,
}

About debug mode

The debug mode has some disadvantages: it only senses changes to the .py file, and changes to the template will not be loaded. Some special errors, such as import errors, will directly take the service offline, and you will have to manually restart it at that time. Also, the debug mode is incompatible with the multi-process mode of HTTPServer. In debug mode, you must set the HTTPServer.start parameter to a number no greater than 1

Set up route processor

When creating the app object before, you need to specify the route and processor

from tornado.web import Application
# 创建应用
app = Application([('/', IndexHandler)])

The route processor is a custom class that inherits from RequestHandler

from tornado.web import RequestHandler
# 设置路由处理器
class IndexHandler(RequestHandler):
    # 处理 get 请求,其他请求可以写对应的方法
    def get(self):
        # 向客户端发送响应数据
        self.write('<h3>Hello, Tornado!</h3>')

Start service

Tornado supports multi-threading and multi-process, and its service is started by adding the application to the IO event loop.

from tornado.ioloop import IOLoop
# 启动服务,因为会进行阻塞,所以先输出信息
print('starting http://127.0.0.1:5000')
IOLoop.current().start()	# 获取当前循环,启动服务

Start the service with HTTPServer

If you use the HTTPServer service, for example, you need to use a multi-process service, you need to create http_server and add a listener (no longer use the app to listen)

from tornado.web import Application
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

app = Application([
	('/', IndexHandler)
], **settings)
http_server = HTTPServer(app)
http_server.listen(5000, '127.0.0.1')
IOLoop.instance().start()

Execute a simple Tornado service

from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

class IndexHandler(RequestHandler):
    # 处理 get 请求,其他请求可以写相应的方法
    def get(self):
        # 向客户端发送响应数据
        self.write('<h3>Hello, Tornado!</h3>')

if __name__ == '__main__':
    # 创建应用
    app = Application([
        ('/', IndexHandler), ('/hello', IndexHandler)
    ])
    # 设置监听
    app.listen(5000, '127.0.0.1')
    # 启动服务
    print('starting http://127.0.0.1:5000')
    IOLoop.current().start()  # 获取当前循环,启动服务

After the code is written, just execute it directly. Can be accessed via browser test.

Tornado application structure

The application structure of Tornado is similar to flask and is managed by manager.py, which is the startup entrance of the program. Applications are placed in each app folder, models store data model classes, and utils stores other tools. The app directory contains static to store static resources, templates to store template files, and views to store view classes. In addition, tornado supports custom UI modules, which can be stored independently in a file or folder in the app directory.

request entry point

Each request tornado will create a corresponding processor object, which is then processed by the corresponding method. Before that, methods such as initializing object instances and some specific methods will be used as the entry point.

initialize initialization method

On each request, the initialize() method is executed before all request methods are called.

prepare preprocessing method

This method is called for preprocessing after initialization and before calling the processing method. Mainly used to verify parameters, permissions, read cache, etc.

on_finish completion method

After all methods are executed, the on_finish() method will be executed. Generally used to release resources.

Handle request

When tornado receives a request, it will create a corresponding processor object and use the corresponding processing method to process it, so all request information will be in this processor object. By setting breakpoints for debugging, you can find that request is a member of the processor object, and you can obtain the required data by calling this member object.

What information is included in the request object

Commonly used in request objects are

  • arguments: request parameters (including parameters in body_arguments and query_arguments), dictionary type
  • body: request body, byte type
  • body_arguments: parameters of the request body (text parameters), dictionary type
  • cookies: cookie information, dictionary (actually SimpleCookie) type
  • files: file information, dictionary type
  • headers: request headers, dictionary (actually HTTPHeaders) type
  • host: request host, i.e. ip + port
  • host_name: host name, i.e. ip without port number
  • method: request method
  • path: request path (without ip, port number, parameters, etc.)
  • protocol: request protocol, http or https, etc.
  • query: query string, that is, the parameters in the url
  • query_arguments: request parameters (query parameters), dictionary type
  • remote_ip: remote IP
  • uri: request resource address, path plus query string

Get request parameters

If you don't care whether the parameters come from a get request or a post request, you can use self.request.argumentsget parameters. Note that it is a dictionary, and the dictionary values ​​are bytecodes. You can also use the method self.get_argument()to get the required parameters and return a string. If you need to get the values ​​of multiple parameters with the same name, you can use self.get_arguments()the method to return a list of strings.

# 使用方法获取参数
from tornado.web import RequestHandler

class HelloHandler(RequestHandler):
    def get(self):
        uid = self.get_argument('uid')		# 返回值为字符串
        name = self.get_arguments('name')[0]	# 返回值为字符串的列表
		print(uid, name)
        self.write('<h2>Hello, Tornado!</h2>')
# 从请求对象中获取参数
from tornado.web import RequestHandler

class HelloHandler(RequestHandler):
    def get(self):
		req: HTTPServerRequest = self.request	# 先获取请求对象
		args = req.arguments				# arguments 成员是一个字典,键为参数名,值为参数值,是字节型
		uid = req.arguments.get('uid')		# 返回值为字节型列表
		name = req.arguments.get('name')	# 返回值为字节型列表
		print(args, type(args))
		print(uid, name)
        self.write('<h2>Hello, Tornado!</h2>')

Get get parameters (query parameters)

You can get the get parameter self.request.query_argumentsfrom , please note that it is a dictionary, and the dictionary value is bytecode. You can also use the method self.get_query_argument()to obtain the required parameters and return a string. If you need to get the values ​​of multiple parameters with the same name, you can use self.get_query_arguments()the method to return a list of strings.

If you want to get parameters from the request object, you can use self.request.query_arguments. Please note that the value obtained is of byte type.

Get post parameters (text parameters)

self.request.body_argumenstPost parameters can be obtained from . Note that it is a dictionary, and the dictionary values ​​are bytecodes. You can also use the method sefl.get_body_argument()to obtain the required parameters and return a string. If you need to get the values ​​of multiple parameters with the same name, you can use self.get_body_arguments()the method to return a list of strings.

If you want to get parameters from the request object, you can use self.request.body_arguments. Please note that the value obtained is of byte type.

Get parameters in path routing

Add parameters to the URL, for example http://127.0.0.1:5000/page/112, 112 can be a parameter.

from tornado.web import Application, RequestHandler

class PageHandler(RequestHandler):
	# 方法中添加参数,接收路由中的参数数据
    def get(self, page, name):
        self.write('page = %s <br>name = %s' % (page, name))

# 创建app对象时,可以通过注册路由获取参数
app = Application([
	('/', IndexHandler),
    (r'/page/(\d+)/(\w+)', PageHandler)	# 注册路由时,使用正则的方式可以获得参数
], **settings)

You can also specify the name of the group, so that the values ​​will be passed by name instead of in order.

from tornado.web import Application, RequestHandler

class PageHandler(RequestHandler):
	# 方法中添加参数,接收路由中的参数数据
    def get(self, page, name):
        self.write('page = %s <br>name = %s' % (page, name))

# 创建app对象时,可以通过注册路由获取参数
app = Application([
	('/', IndexHandler),
    (r'/page/(?P<name>\d+)/(?P<page>\w+)', PageHandler)	# 注册路由时,使用正则的方式可以获得参数
], **settings)

It should be noted that when routing path parameters are used, the processor's processing method must add the incoming parameters, otherwise an error will be reported.

Get json data

The json data is passed in as the request body (text), so use self.request.bodyto obtain it. Note that what is obtained is byte type and needs to be converted into a dictionary for use.

from tornado.web import RequestHandler

class ApiHandler(RequestHandler):
	def post(self):
		# 获取 json 数据
		bytes = self.request.body		# 字节类型
		# 判断数据类型
		content_type = self.request.headers.get('Content-Type')
		if content_type.startswith('application/json'):
			json_str = bytes.decode('utf-8')		# 解码成字符串
			json_data = json.loads(json_str)		# 反序列化为 json 对象(字典)
			self.write('upload 成功')
		else:
			self.write('upload data 必须是 json 格式')

Read cookies and headers

Same as getting parameter data, you can also get cookies by using the request object and the corresponding method. However, it is not recommended to get it from the request object, because the self.request.cookies.get()cookie obtained by using is http.cookies.Morselan object, and self.get_cookie()the string can be obtained directly by using the method. But all cookies can be obtained through the request object.

You can use self.request.headersto get all request header information, and use self.request.headers.get()to get the required request header data.

Cookie Vulnerabilities and Security Cookies

Since there are many ways to intercept cookies in the browser and forge cookie data, Tornado provides secure cookies to use cryptographic signatures to verify whether cookies have been illegally tampered with.

You can use tornado's set_secure_cookie()and get_secure_cookie()functions to set and obtain browser cookies to prevent malicious tampering. However, in order to use these functions, the cookie_secret parameter must be set in the settings. Please note that secure cookies are signed rather than encrypted, so they are not completely secure.

Read file information and content

self.request.filesContains uploaded file information, the type is dictionary type. You can get the corresponding file object and use FileStorage.save()to save the file

from tornado.web import RequestHandler

class UploadHandler(RequestHandler):
	def post(self):
		# 注意提交的 form 中文件字段的名称必须相同
		upload_file = self.request.files.get('upload', None)
		if not upload_file:
			self.write('没有上传文件')
		# 判断文件类型是否需要
		elif upload_file.content_type.startswith('image/'):
			self.write('上传文件只支持图片类型')
		# 符合要求,保存图片
		else:
			# 创建文件名
			filename = uuid.uuid4().hex + os.path.splitext(upload_file.filename)[-1]
			# 上传文件保存路径
			filepath = os.path.join(settings['static_url_prefix'], filename)
			# 服务端保存上传文件
			upload_file.save(filepath)
			# 返回保存成功信息
			self.write('文件保存成功')

Handle response

Unlike flask and others, tornado has multiple ways of returning response. It will only return objects set by methods such as write and set_coonkie. Therefore, it does not matter the order in which these methods are called, and all settings will take effect.

Return simple information

You can use self.write()the method to return simple html information

from tornado.web import RequestHandler

class HelloHandler(RequestHandler):
    def get(self):
		# 直接返回html信息
        self.write('<h2>Hello, Tornado!</h2>')

Return to template page

tornado uses self.render()the method to return to the template page

class IndexHandler(RequestHandler):
	def get(self):
		self.render('index.html')

Return data to template page

The method of tornado returning data to the template page is somewhat similar to flask, by self.render()adding parameters in the method.

<!-- test.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
    <p>
        a = {
   
   { aa }}<br>
        b = {
   
   { bb }}<br>
        data = {
   
   { dd }}
    </p>
    {% for k,v in dd.items() %}
        <p>{
   
   { k }} = {
   
   { v }}</p>
    {% end %}
</body>
</html>
class TestHandler(RequestHandler):
    def get(self):
        a = '数据a'
        b = 2
        data = {
    
    
            '参数1': '1111',
            '参数2': '2222',
            'aaa': 333
        }
        self.render('test.html', aa=a, bb=b, dd=data)

Return redirect

Use self.redirect()the method to redirect. There is no conflict between redirection and self.wirte()other methods. In actual use, things set by other methods after redirection are actually useless.

Return json

You can use self.write()the method to write directly to the serialized json object

from tornado.web import RequestHandler
import json

class JsonHandler(RequestHandler):
    def post(self):
        data = {
    
    
            'msg': 'Hello, Tornado!',
            'status': 'OK'
        }
        self.write(json.dumps(data))	# 将字典对象序列化
        # 因为 json.dumps() 方法返回的响应,其 content-type 类型还是 text/html,所以需要重设响应头
        self.set_header('Content-Type','application/json;charset=utf-8')

Custom cookies and headers

You can use self.set_cookie(key, value)to set cookies, use self.clear_cookie()to delete a cookie, and use to self.clear_all_cookies()delete all cookies.

Use self.set_header(key, value)to set request headers. self.clear_header()Request headers can be removed using the method.

Solving cross-domain request issues

Currently, there are two commonly used solutions for cross-domain requests: JSONP and CORS. The most widely used method is CORS.

The RequestHandler class inherited by the processor object comes with a function set_default_headers()that can uniformly set response headers for all requests. This method is called after the routing processing method is called, and the Response is returned after setting the response header, so cross-domain CORS information can be added inside this method.

class LoginHandler(RequestHandler):
	# 所有的请求方法执行完成后,默认设置响应头信息
	def set_default_handlers(self):
		# 设置响应头以解决跨域问题
		self.set_header('Access-Control-Allow-Origin', '*')		# 设置允许的源域名(即从哪个域访问本域)
		self.set_header('Access-Control-Allow-Headers', 'Content-Type,x-requested-with')	# 允许跨域访问时重设哪些属性
		self.set_header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE')	# 允许跨域请求的方法
		self.set_header('Access-Control-Allow-Credentials', 'true')		# 允许跨域传输 cookie
		self.set_header('Access-Control-Expose-Headers', "cos1,cos2")	# 允许请求端接收自定义的响应头
		self.set_header('Access-Control-Max-Age', 10)	# 设置预检缓存时间,时间内发送请求无需预检(option)
	
	# 跨域请求时,会被客户端请求,用来表示服务器是否支持跨域请求
	def options(self):
		# 默认会返回200,所以可以不写这条语句,但是必须有此方法
		self.set_status(200)

template language

Tornado supports the jinja2 template language like flask, but there are still some minor changes.

variable data

tornado uses double curly braces to fill variables and expressions

<!-- 后端传入数据
	name = '张三',
	value = 447
	age = 18 -->

<p>
	姓: {
   
   { name[0:1] }}<br>
	名: {
   
   { name[1:] }}<br>
	年龄: {
   
   { age }}<br>
	得分: {
   
   { value }}<br>
</p>

You can also use some complex expressions, functions, and methods. It should be noted that the final output is a string.

For the use of variables such as dictionaries and objects

Different from flask, tornado does not support "dot" syntax. Instead, it uses Python's dictionary access syntax (dict[key]) to use dictionary variables, and for objects, it still uses "dot" to obtain its attributes.

pipe character filter

It should be noted that tornado does not support pipe character filters, and the required operations can be processed using the corresponding functions and methods.

control flow

The control flow statement is placed inside {% %} and can support if, for, while, and try statements like the corresponding Python statement. However, unlike jinja2 supported by flask and others, the end statement is {% end %}, not {% endif %} or {% endfor %}.

In a control flow statement, you can even use{% set variable name = value %}to set variables, but in most cases, you end up using UI modules to do more complex partitioning.

In addition, when using if to determine whether a variable has a value, please note that if the variable is not passed in, the template syntax of flask and other jinja2 will default to a null value, and tornado will report an error. Therefore, variables used in the front end must be passed in in the back end, even if the value is None.

Using functions in templates

In addition to using many Python functions and methods in stream statements, tornado also provides some convenient template functions

  • escape(s): Replace special characters such as &, <, >, etc. in the string s with corresponding HTML characters
  • url_escape(s): Use urllib.quote_plus to replace the characters in the string s into URL-encoded form
  • json_encode(val): Encode val into JSON format (call json.dumps()method)
  • squeeze(s): Filter the string s and replace multiple consecutive whitespace characters with one space

Use static resources

Using static_url()the template method, you can add the incoming file path that needs to be referenced to the path of static_url_prefix set when creating the app, making it easier to change and reference static resources.

<link rel="stylesheet" href="{
     
     { static_url('stytle.css') }}">

Blocks and Inheritance (Master)

The definition block and inheritance master are roughly the same as the template syntax of jinja2 such as flask. The difference is that {% end %} is used instead of {% endblock %}.

UI module

Tornado's UI module is similar to flask's macro. The difference is that the macro is defined in the template, while the UI module uses the processor class in python.

UI modules must be registered in the app settings when used. The ui_modules field is added to the settings. The value is a dictionary. The dictionary key is the module name and the dictionary value is the class.

# settings.py

settings = {
    
    
	'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
	'static_path': os.path.join(os.path.dirname(__file__), "static"),
	'debug': True,
	# 注册 UI 模块
	'ui_modules' :{
    
    
		'Hello': HelloModule
	}
}
<!-- 引入 UI 模块 -->
{% extends 'base.html' %}
{% block content %}
	<!-- 引入和调用 UI 模块 -->
	{% module Hello() %}
{% end %}
from tonado.web import UIModule
# UI 模块类
class HelloModule(tornado.web.UIModule):
	def render(self):
		retrun '<h1>Hello, World!</h1>'

UI module in depth

The purpose of the UI module is not to render strings directly, but to render a dynamically rendered template into another template as UI. For example

<!-- 需要访问的模板页面 -->
{% extends "main.html" %}

{% block body %}
<h2>推荐读的书有</h2>
    {% for book in books %}
        {% module Book(book) %}
    {% end %}
{% end %}

This template page calls the UI processing module named Book and passes in the parameter book object.

from tonado.web import UIModule
# UI 处理模块
class BookModule(tornado.web.UIModule):
    def render(self, book):
    	# render_string() 方法可以显示的渲染模板文件为字符串,然后就可以返回给调用者了
        return self.render_string('modules/book.html', book=book)

The UI processing module actually renders another template book.html and returns the rendered data to the caller.

<!-- UI 处理模块渲染的模板 book.html -->
<div class="book">
    <h3 class="book_title">{
   
   { book["title"] }}</h3>
    <img src="{
     
     { book["image"] }}" class="book_image"/>
</div>

Embed JS and CSS

When using UI modules, you can embed JS and CSS into the calling module. Overriding methods embedded_javascript()and embedded_css()can embed the corresponding code, for example

class BookModule(tornado.web.UIModule):
    def render(self, book):
        return self.render_string(
            "modules/book.html",
            book=book,
        )

    def embedded_javascript(self):
        return "document.write(\"hi!\")"

When the module is called, "document.write("hi!")" will be surrounded by <script> tags and inserted into the <body> tag. You can also wrap CSS rules in <style> and add them to the <head> tag.

def embedded_css(self):
    return ".book {background-color:#F5F5F5}"

For more flexibility, you can override html_body()the method to add complete HTML tags inside the <body>

def html_body(self):
    return "<script>document.write(\"Hello!\")</script>"

Although direct embedding is useful, for more rigor and simplicity, it is recommended to add a stylesheet file or script file: override css_files()method and javascript_files()method

def css_files(self):
    return "/static/css/newreleases.css"

def javascript_files(self):
    return "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"

Be careful not to include a method that requires something from elsewhere, such as a JS function that relies on other files, as it may not render in the desired order.

ORM data model

tornado can use the SQLAlchemy library to build an ORM data model.

SQLAlchemy official site (English)
pip install sqlalchemy

SQLAlchemy supports common databases such as PostgreSQL, MySQL/MariaDB, SQLite, Oracle, and MSSQLServer. The specific and required connection strings can be found here

Supported databases and connection methods

Create and configure data models

First use create_engine()the method to create the database engine. It should be noted that in the syntax of the database connection string , the first two items are the database type and driver, followed by the user name and password, then the database host and port, and finally the database name and character set.

For specific supported databases and required connection strings, please view the documentation.

Then generate the database connection class, create the session object and generate the parent class of all data model classes

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 创建数据库引擎
engine = create_engine('mysql+mysqldb://user:[email protected]:3306/learn_tornado?charset=utf8')
# 生成数据库连接类
DbSession = sessionmaker(bind=engine)
# 创建会话对象
session = DbSession()
# 生成所有模型类的父类
Base = declarative_base(bind=engine)

Create data model class

Create data model classes based on base classes. It should be noted that all model classes must declare table names.

from sqlalchemy import Column, Integer, String, Text, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import null

# 数据模型类
class Menu(Base):
    # 定义数据表名称
    __tablename__ = 'menu'
    # 定义各字段
    id = Column(Integer, primary_key=True, autoincrement=True)  # 整型,主键,自增
    title = Column(String(20), unique=True, nullable=False)  # 字符型,长度20,唯一,可以为空
    url = Column(String(50), unique=True)  # 字符型,长度50,唯一
    note = Column(Text)  # 文本型
    # 整型,外键绑定 menu 表的 id 字段,名称为 parent_id_fk,默认值 0
    # 注:default 是使用 sqlalchemy 插入数据时传递给数据库的默认值,server_default 是数据库定义的默认值,仅支持字符串
    # 注:默认空时可以省略
    parent_id = Column(Integer, ForeignKey('menu.id', name='parent_id_fk'), default=null(), server_default=null())
    # 定义关系,关联到 Menu 类中,可以根据 parent 属性进行反查
    # 此项只是类的一个属性,不会生成数据库字段
    childs = relationship('Menu')

Data model relationships

You can use relationship()functions to declare relationships and make convenient calls. For example for two models

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address", backref="user")


class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))

If there is no relationship, the relationship data can only be called like this

# 通过给定的 User.name 来获取该 user 的 address 数据
def get_addresses_from_user(user_name):
    user = session.query(User).filter_by(name=user_name).first()
    addresses = session.query(Address).filter_by(user_id=user.id).all()
    return addresses

If relationship is defined as in the example, you can call it like this

# 定义了 address = relationship('Address')
# 注意:参数指向的是需要关联的类名
def get_addresses_from_user(user_name):
    user = session.query(User).filter_by(name=user_name).first()
    return user.addresses

It should be noted that this can only be called in the forward direction, not in the reverse direction, that is, the data of the Address class is obtained through user.address, not the other way around. If you need to call reversely, you need to use the backref parameter.

# 定义了 addresses = relationship('Address', backref='user')
# backref 参数值为反向调用使用的属性名称
def get_user_from_email(e_mail):
    address = session.query(Address).filter_by(email=e_mail).first()
    return addresses.user

Create and delete tables

After defining the data model class and determining the database table structure, you can create and delete tables.

# 注意使用创建的基类 Base 来创建和删除表
# 创建表
Base.metadata.create_all()
# 删除表
Base.metadata.drop_all()

CURD operation

CURD is create, update, read, delete, that is, add, delete, modify, and check.

Add data

Adding data is to create objects based on model classes, and each object is a piece of data. Then just add the object to the session and submit it.

# 创建数据对象
m1 = Menu()
# 给数据对象添加数据
m1.title = '用户管理'
m1.note = '管理使用者账户'
# 将数据对象添加到会话
session.add(m1)
# 提交会话
session.commit()

You can also add data in batches

 # 直接添加数据对象列表到会话,并在创建对象的同时添加数据
 session.add_all([
 	Menu(title='订单管理'),
 	Menu(title='会员管理', url='/user1', parent_id=1)
 	Menu(title='派件员管理', url='user2', parent_id=1)
 	Menu(title='合作商管理', url='user3', parent_id=1)
 	Menu(title='订单统计', url='user4', parent_id=2)
 ])
 # 提交会话
 session.commit()

Query data

Use the method to query. The parameter of this method is the data class (table) or field that needs to be returned. A query objectsession.query() will be returned , so you can add filtering, sorting and other conditions through chain calls. The query object can also be used as a list of data objects , or use and and other methods to obtain specific data objects (or use the method to convert the query object into a real list of data objects).get()first()all()

# 查询所有数据
session.query(Menu).all()
# 按照主键查询
session.query(Menu).get(2)

# 使用 filter 方法,此方法可以对参数进行比对筛选( where 方法与此方法相同)
session.query(Menu).filter(Menu.id == 1).first()
session.query(Menu).filter(Menu.parent_id == null()).all()
# 在 filter 方法的参数中使用方法
session.query(Menu).filter(Menu.url.startswith('/')).all()
session.query(Menu).filter(Menu.url.endswith('3')).all()
session.query(Menu).filter(Menu.url.contains('user')).all()
session.query(Menu).filter(Menu.url.like('/%s%3')).all()	# 条件中 % 作为通配符使用
session.query(Menu).filter(Menu.url.notlike('%user%')).all()
# 组合查询
session.query(Menu).filter(Menu.url.startswith('/'), Menu.url.endswith('2')).all()
# 条件与或非
from sqlalchemy import and_, or_, not_
session.query(Menu).filter(and_(Menu.url.startswith('/'), Menu.parent_id == 1)).all()
session.query(Menu).filter(or_(Menu.url.endswith('3'), Menu.parent_id == 2)).all()
session.query(Menu).filter(not_(Menu.url.endswith('3'))).all()
# 条件在集合内
session.query(Menu).filter(Menu.id.in_([1, 3, 4])).all()
# 条件在某区间内(闭合区间)
session.query(Menu).filter(Menu.id.between(2, 5)).all()

# 使用 filter_by 方法,效果同 filter 方法,此方法参数为 **kwargs,是按照参数值进行筛选
session.query(Menu).filter_by(parent_id=1, id=5).first()

In addition, the query object can also perform operations such as sorting and paging.

# 排序,默认使用 asc() 升序,也可以 desc() 降序
session.query(Menu.id, Menu.title).order_by(Menu.id.desc()).all()
# 有优先级的排序
session.query(Menu.id, Menu.title, Menu.parent_id).order_by(Menu.parent_id.asc(),Menu.id.desc()).all()
# 对查询结果去重
session.query(Menu.parent_id).distinct().all()
# 分页,忽略前2条,显示3条
session.query(Menu).offset(2).limit(3).all()
Aggregation query

update data

After obtaining the data object, directly change the value of its member (field), and then update the session, you can complete the modification and update data.

menu = session.query(Menu).get(5)
menu.title = '合作伙伴'
session.commit()

delete data

Similarly, after obtaining the data object, you can delete the data

menu = session.query(Menu).get(5)
session.delete(menu)
session.commit()

other

If you need to use a null value when inserting or updating data (note that the database null value is different from python's None), you can use sqlalchemy.sql.null()the method to obtain a database null value.

Synchronous and asynchronous services

The tornado package provides synchronous and asynchronous request services. Of course, other request libraries can also be used.

Both synchronous and asynchronous requests can send the Request object directly. If it is a get method, you can simply send only the url.

Initiate a synchronization request

tornado provides a class for sending synchronous requests

client = tornado.httpclient.HTTPClient()	# 创建客户端对象
response = client.fetch(url)		# 发送请求,并获取 response

If https needs to verify the certificate, you can add the parameter validate_cert=False to not perform verification.

Initiate an asynchronous request

Different from HTTPClient, tornado provides another class for sending asynchronous requests

client = tornado.httpclient.AsyncHTTPClient()		# 创建异步客户端
client.fetch(url, callback)		# 发送请求,并将 response 传入 callback 回调函数处理

have to be aware of is

  • callback must receive response object
  • The method that initiates an asynchronous request needs to be @tornado.web.asynchronousmodified to indicate that the connection will not be closed.
  • The callback function needs to call self.finish()manual relationship connection

Coroutine mode

You can also use await to wait asynchronously and continue execution (coroutine mode) instead of using a callback function.

client = tornado.httpclient.AsyncHTTPClient()		# 创建异步客户端
response = await client.fetch(url)			# 异步发送请求,并等待响应

What needs to be noted with this method is that

  • @tornado.web.asynchronousStill needed
  • The method that initiates an asynchronous request is decorated with async, and then the asynchronous request uses the await keyword
  • What is returned is a response object

async generator

Somewhat similar to the coroutine method

client = tornado.httpclient.AsyncHTTPClient()		# 创建异步客户端
response = yield client.fetch(url)			# 异步发送请求,并等待响应
  • In addition @tornado.web.asynchronousto , you also need to use @tornado.web.gen.coroutinemodification
  • What is returned is a response object

WebSockets

WebSockets is a newly proposed client-server communication protocol in the HTML5 specification. It provides two-way communication over a persistent connection between client and server. The protocol itself uses the new ws:// URL format, but it is implemented over standard HTTP. By using HTTP and HTTPS ports, it avoids various issues introduced when connecting to a site from a network behind a web proxy. The HTML5 specification not only describes the protocol itself, but also describes the browser API required to write client code using WebSockets.

WebSocket module for Tornado

Tornado provides a WebSocketHandler class in the websocket module. This class provides hooks for WebSocket events and methods that communicate with connected clients. When a new WebSocket connection is opened, the open method is called, and the on_message and on_close methods are called when the connection receives a new message and the client is closed respectively.

When using WebSocket in tornado, write a class that inherits from WebSocketHandler and perform corresponding processing through the open, on_message, on_connection_close and on_close methods.

from tornado.web import RequestHandler
from tornado.websocket import WebSocketHandler

class LoginChatroomHandler(RequestHandler):
    def get(self):
        self.write("""
        <form method='post'>
            <input name='name'>
            <button>登录</button>
        </form>
        """)

    def post(self):
        name = self.get_body_argument('name', '匿名')
        self.set_secure_cookie('username', name)
        self.render('chatRoom.html')

class MessageHandler(WebSocketHandler):
    # 所有的处理器实例(即获取所有与客户端通信的长连接)列表
    online_clients = []

    def open(self):
        # 建立连接时被调用,表示客户端请求连接
        # 相当于 socket 里的 server.accept()
        # 向客户端发送成功连接的消息
        data = {
    
    
            'host': self.request.remote_ip,
            'name': self.get_secure_cookie('username').decode(),
            'status': 'connect',
            'msg': ''
        }
        # 将当前实例对象添加到处理器实例列表中
        self.online_clients.append(self)
        self.send_message(data)

    def send_message(self, data):
        # 发送消息给每个连接的客户端
        for client in self.online_clients:
            client.write_message(data)

    def on_message(self, message):
        # 连接建立,等待接收信息
        # 接收到消息,进行处理,并向客户端返回需要的数据
        data = {
    
    
            'host': self.request.remote_ip,
            'name': self.get_secure_cookie('username').decode(),
            'status': 'OK',
            'msg': message
        }
        self.send_message(data)

    def on_connection_close(self):
        # 客户端断开连接
        # 从客户端连接列表中删除此连接
        self.online_clients.remove(self)
        data = {
    
    
            'host': self.request.remote_ip,
            'name': self.get_secure_cookie('username').decode(),
            'status': 'connection_close',
            'msg': ''
        }
        self.send_message(data)

    def on_close(self):
        # 服务端断开连接
        # 一般用于释放资源
        pass
// 根据 id 获取 dom
function $(id) {
    
    
    return document.getElementById(id)
}

window.onload = function (event) {
    
    
    // 创建 socket 对象
    var socket = new WebSocket('ws://127.0.0.1:5000/charRoom/message');
    // 发送请求建立连接
    socket.onopen = function (ev) {
    
    
        console.log('---------onopen---------')
        console.log(ev)
    };

    // 连接建立完成,等待接收服务端发送的消息
    socket.onmessage = function (ev) {
    
    
        console.log('---------onmessage-------------')
        console.log(ev)
        let data = JSON.parse(ev.data)
        if (data.status === 'connect') {
    
    
            $('message_body').innerHTML += data.host + '连接成功,' + data.name + '进入聊天室<br>'
        } else if (data.status === 'OK') {
    
    
            $('message_body').innerHTML += data.name + '(' + data.host +') 说 : ' + data.msg + '<br>'
        } else if (data.status === 'connection_close'){
    
    
            $('message_body').innerHTML += data.host + '断开连接,' + data.name + '退出聊天室<br>'
        }
    };

    // 当接收到错误信息
    socket.onerror = function (ev) {
    
    
        console.log('---------onerror-------------')
        console.log(ev)
    };

    // 绑定事件
    $('submit').onclick = function () {
    
    
        submit_msg(socket);
    };
    $('quit').onclick = function (){
    
    
        quit(socket);
    };
}

// 提交消息事件
function submit_msg(socket) {
    
    
    let msg = $('msg').value;
    // 向服务器发送消息
    socket.send(msg)
    $('msg').value = ''
    $('msg').focus()
}
// 退出聊天室事件
function quit(socket){
    
    
    // 断开连接
    socket.close();
    // 跳转页面
    window.location="/chatRoom";
}

Reference article

Source code analysis of Tornado

Guess you like

Origin blog.csdn.net/runsong911/article/details/126717049