Python Web 框架工具包 werkzeug

大家好,我是知秋君,一个会写博客吟诗的知秋码农。今天说一说Python Web 框架工具包 werkzeug,希望能够帮助大家进步!!! 一、什么是werkzeug  werkzeug 官方的介绍说是一个 WSGI 工具包,不是一个web服务器,也不是一个web框架,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response

大家好,我是知秋君,一个会写博客吟诗的知秋码农。今天说一说Python Web 框架工具包 werkzeug,希望能够帮助大家进步!!!

一、什么是werkzeug 

werkzeug 官方的介绍说是一个 WSGI 工具包,不是一个web服务器,也不是一个web框架,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response 等等;例如我最常用的 Flask 框架就是一 Werkzeug 为基础开发的。

Werkzeug 教程基础中文文档

二、Werkzeug实现密码校验功能

使用Werkzeug实现密码散列,最关键的是使用其中security模块的generater_password_hash()和check_password_hash()。

from werkzeug.security import generate_password_hash,check_password_hash class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True)#id列 username = db.Column(db.String(64), unique=True, index=True) #username列 role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) #role_id列 password_hash = db.Column(db.String(128)) #密码hash列 @property def password(self): #访问password属性 raise AttributeError('password is not a readable attribute') # 对保存到数据库中的密码进行加盐 # 什么是盐? # 通过一个随机字符串或指定字符串与原有字符串进行混淆处理,等到一个新的字符串,这个过程称为加盐处理。 @password.setter def password(self,password): #设置password属性 self.password_hash = generate_password_hash(password) #默认加盐了 # 输入密码时进行校验 def verify_password(self,password):#验证密码 return check_password_hash(self.password_hash,password) def __repr__(self): return '<user r="">' % self.username</user>
只听到从知秋君办公室传来知秋君的声音:

飘飘何所似,天地一沙鸥。有谁来对上联或下联?

1、generate_password_hash(password, method=pbkdf2:sha1, slat_length=8)

method指定哈希变化的算法,默认sha1,salt_length指定‘盐的长度’,默认是8位。password是必须指定的。

返回计算得出的128位哈希值

2、check_password_hash(hash,password)

hash是密码散列值(一般存于数据库),password是输入需比较的密码。

返回的是True或者False

三、local 模块

1、简介

local模块中,Werkzeug实现了类似Python标准库中threading.local的功能。threading.local是线程局部变量,也就是每个线程的私有变量,具有线程隔离性,可以通过线程安全的方式获取或者改变线程中的变量。参照threading.local,Werkzeug实现了比thread.local更多的功能。

总结起来: 以上文档解释了对于“并发”问题,多线程并不是唯一的方式,在Python中还有“协程”。“协程”的一个显著特点在于是一个线程执行,一个线程可以存在多个协程。也可以理解为:协程会复用线程。对于WSGI应用来说,如果每一个线程处理一个请求,那么thread.local完全可以处理,但是如果每一个协程处理一个请求,那么一个线程中就存在多个请求,用thread.local变量处理起来会造成多个请求间数据的相互干扰。

对于上面问题,Werkzeug库解决的办法是local模块。local模块实现了四个类:

  • Local
  • LocalStack
  • LocalProxy
  • LocalManager

本文重点介绍前两个类的实现。

2、Local类

Local类能够用来存储线程的私有变量。在功能上这个threading.local类似。与之不同的是,Local类支持Python的协程。在Werkzeug库的local模块中,Local类实现了一种数据结构,用来保存线程的私有变量,对于其具体形式,可以参考它的构造函数:

此代码由一叶知秋网-知秋君整理
class Local(object):     __slots__ = ('__storage__', '__ident_func__')     def __init__(self):         object.__setattr__(self, '__storage__', {})         object.__setattr__(self, '__ident_func__', get_ident)

从上面类定义可以看出,Local类具有两个属性:__storage____ident_func__。从构造函数来看,__storage__是一个字典,而__ident_func__是一个函数,用来识别当前线程或协程。

1. __ident_func__

关于当前线程或协程的识别,local模块引入get_ident函数。如果支持协程,则从greenlet库中导入相关函数,否则从thread库中导入相关函数。调用get_ident将返回一个整数,这个整数可以确定当前线程或者协程。

try:     from greenlet import getcurrent as get_ident except ImportError:     try:         from thread import get_ident     except ImportError:         from _thread import get_ident

2. __storage__

__storage__是一个字典,用来存储不同的线程/协程,以及这些线程/协程中的变量。以下是一个简单的多线程的例子,用来说明__storage__的具体结构。

此代码由一叶知秋网-知秋君整理
import threading from werkzeug.local import Local l = Local() l.__storage__ def add_arg(arg, i):     l.__setattr__(arg, i) for i in range(3):     arg = 'arg' + str(i)     t = threading.Thread(target=add_arg, args=(arg, i))     t.start() l.__storage__

上面的例子,具体分析为:

  • 首先,代码创建了一个Local的实例l,并且访问它的__storage__属性。由于目前还没有数据,所以l.__storage__的结果为{};
  • 代码创建了3个线程,每个线程均运行add_arg(arg, i)函数。这个函数会为每个线程创建一个变量,并对其赋值;
  • 最后,再次访问l.__storage__。这次,l实例中将包含3个线程的信息。其结果为:
{20212: {'arg0': 0}, 20404: {'arg1': 1}, 21512: {'arg2': 2}}

从以上结果可以看出,__storage__这个字典的键表示不同的线程(通过get_ident函数获得线程标识数值),而值表示对应线程中的变量。这种结构将不同的线程分离开来。当某个线程要访问该线程的变量时,便可以通过get_ident函数获得线程标识数值,进而可以在字典中获得该键对应的值信息了。

3、LocalStack类

LocalStack类和Local类类似,但是它实现了栈数据结构。

LocalStack类初始化的时候,便会创建一个Local实例,这个实例用于存储线程/协程的变量。与此同时,LocalStack类还实现了pushpoptop等方法或属性。调用这些属性或者方法时,该类会根据当前线程或协程的标识数值,在Local实例中对相应的数值进行操作。以下还是以一个多线程的例子进行说明:

from werkzeug.local import LocalStack, LocalProxy import logging, random, threading, time # 定义logging配置 logging.basicConfig(level=logging.DEBUG,     format='(%(threadName)-10s) %(message)s', ) # 生成一个LocalStack实例_stack _stack = LocalStack() # 定义一个RequestConetxt类,它包含一个上下文环境。 # 当调用这个类的实例时,它会将这个上下文对象放入 # _stack栈中去。当退出该上下文环境时,栈会pop其中 # 的上下文对象。 class RequestConetxt(object):     def __init__(self, a, b, c):         self.a = a         self.b = b         self.c = c     def __enter__(self):         _stack.push(self)     def __exit__(self, exc_type, exc_val, exc_tb):         if exc_tb is None:             _stack.pop()     def __repr__(self):         return '%s, %s, %s' % (self.a, self.b, self.c) # 定义一个可供不同线程调用的方法。当不同线程调用该 # 方法时,首先会生成一个RequestConetxt实例,并在这 # 个上下文环境中先将该线程休眠一定时间,之后打印出 # 目前_stack中的信息,以及当前线程中的变量信息。 # 以上过程会循环两次。 def worker(i):     with request_context(i):         for j in range(2):             pause = random.random()             logging.debug('Sleeping %0.02f', pause)             time.sleep(pause)             logging.debug('stack: %s' % _stack._local.__storage__.items())             logging.debug('ident_func(): %d' % _stack.__ident_func__())             logging.debug('a=%s; b=%s; c=%s' % (LocalProxy(lambda: _stack.top.a),                            LocalProxy(lambda: _stack.top.b),                            LocalProxy(lambda: _stack.top.c)) )     logging.debug('Done') # 调用该函数生成一个RequestConetxt对象 def request_context(i):     i = str(i+1)     return RequestConetxt('a'+i, 'b'+i, 'c'+i) # 在程序最开始显示_stack的最初状态 logging.debug('Stack Initial State: %s' % _stack._local.__storage__.items()) # 产生两个线程,分别调用worker函数 for i in range(2):     t = threading.Thread(target=worker, args=(i,))     t.start() main_thread = threading.currentThread() for t in threading.enumerate():     if t is not main_thread:         t.join() # 在程序最后显示_stack的最终状态 logging.debug('Stack Finally State: %s' % _stack._local.__storage__.items())

以上例子的具体分析过程如下:

  • 首先,先创建一个LocalStack实例_stack,这个实例将存储线程/协程的变量信息;
  • 在程序开始运行时,先检查_stack中包含的信息;
  • 之后创建两个线程,分别执行worker函数;
  • worker函数首先会产生一个上下文对象,这个上下文对象会放入_stack中。在这个上下文环境中,程序执行一些操作,打印一些数据。当退出上下文环境时,_stack会pop该上下文对象。
  • 在程序结束时,再次检查_stack中包含的信息。

运行上面的测试例子,产生结果如下:

(MainThread) Stack Initial State: [] (Thread-1  ) Sleeping 0.31 (Thread-2  ) Sleeping 0.02 (Thread-2  ) stack: [(880, {'stack': [a1, b1, c1]}), (13232, {'stack': [a2, b2, c2]})] (Thread-2  ) ident_func(): 13232 (Thread-2  ) a=a2; b=b2; c=c2 (Thread-2  ) Sleeping 0.49 (Thread-1  ) stack: [(880, {'stack': [a1, b1, c1]}), (13232, {'stack': [a2, b2, c2]})] (Thread-1  ) ident_func(): 880 (Thread-1  ) a=a1; b=b1; c=c1 (Thread-1  ) Sleeping 0.27 (Thread-2  ) stack: [(880, {'stack': [a1, b1, c1]}), (13232, {'stack': [a2, b2, c2]})] (Thread-2  ) ident_func(): 13232 (Thread-2  ) a=a2; b=b2; c=c2 (Thread-2  ) Done (Thread-1  ) stack: [(880, {'stack': [a1, b1, c1]})] (Thread-1  ) ident_func(): 880 (Thread-1  ) a=a1; b=b1; c=c1 (Thread-1  ) Done (MainThread) Stack Finally State: []

注意:

  • 当两个线程在运行时,_stack中会存储这两个线程的信息,每个线程的信息都保存在类似{'stack': [a1, b1, c1]}的结构中(注:stack键对应的是放入该栈中的对象,此处为了方便打印了该对象的一些属性)。
  • 当线程在休眠和运行中切换时,通过线程的标识数值进行区分不同线程,线程1运行时它通过标识数值只会对属于该线程的数值进行操作,而不会和线程2的数值混淆,这样便起到线程隔离的效果(而不是通过锁的方式)。
  • 由于是在一个上下文环境中运行,当线程执行完毕时,_stack会将该线程存储的信息删除掉。在上面的运行结果中可以看出,当线程2运行结束后,_stack中只包含线程1的相关信息。当所有线程都运行结束,_stack的最终状态将为空。

四、wrappers模块

1、简介

Werkzeug库中的wrappers模块主要对requestresponse进行封装。request包含了客户端发往服务器的所有请求信息,response包含了web应用返回给客户端的所有信息。wrappers模块对请求和响应的封装简化了客户端、服务器和web应用通信的流程。本文主要介绍wrappers模块中重要的类。

2、BaseRequest

BaseRequest是一个非常基础的请求类,它可以和其他的“混合”类结合在一起构建复杂的请求类。只要传递一个环境变量environ(由WSGI服务器根据请求产生),便可以构造一个BaseRequest实例。其构造函数如下:

def __init__(self, environ, populate_request=True, shallow=False):     self.environ = environ     if populate_request and not shallow:         self.environ['werkzeug.request'] = self     self.shallow = shallow

初始化后,形成的实例request便具有了一些属性可以访问,这些属性只能以“只读”的方式访问。例如:

  • url_charset
  • want_form_data_parsed
  • stream
  • args
  • data
  • form
  • values
  • files
  • cookies
  • headers
  • path
  • full_path
  • script_root
  • url
  • base_url
  • url_root
  • host_url
  • host
  • access_route
  • remote_addr

BaseRequest中还有两个类方法比较常用:

from_values(cls, *args, kwargs)**

@classmethod def from_values(cls, *args, **kwargs):     """Create a new request object based on the values provided.  If     environ is given missing values are filled from there.  This method is     useful for small scripts when you need to simulate a request from an URL.     Do not use this method for unittesting, there is a full featured client     object (:class:`Client`) that allows to create multipart requests,     support for cookies etc.     This accepts the same options as the     :class:`~werkzeug.test.EnvironBuilder`.     .. versionchanged:: 0.5        This method now accepts the same arguments as        :class:`~werkzeug.test.EnvironBuilder`.  Because of this the        `environ` parameter is now called `environ_overrides`.     :return: request object     """     from werkzeug.test import EnvironBuilder     charset = kwargs.pop('charset', cls.charset)     kwargs['charset'] = charset     builder = EnvironBuilder(*args, **kwargs)     try:         return builder.get_request(cls)     finally:         builder.close()

这个类方法可以根据提供的参数构建一个请求。

application(cls, f)

@classmethod def application(cls, f):     """Decorate a function as responder that accepts the request as first     argument.  This works like the :func:`responder` decorator but the     function is passed the request object as first argument and the     request object will be closed automatically::         @Request.application         def my_wsgi_app(request):             return Response('Hello World!')     :param f: the WSGI callable to decorate     :return: a new WSGI callable     """     #: return a callable that wraps the -2nd argument with the request     #: and calls the function with all the arguments up to that one and     #: the request.  The return value is then called with the latest     #: two arguments.  This makes it possible to use this decorator for     #: both methods and standalone WSGI functions.     def application(*args):         request = cls(args[-2])         with request:             return f(*args[:-2] + (request,))(*args[-2:])     return update_wrapper(application, f)

这个类方法是一个装饰器,可以用来装饰WSGI可调用对象或函数。

以上属性和方法的具体用法可以参考 Request——werkzeug文档。

3、BaseResponse

BaseResponse类是一个响应类,用它可以封装一个response对象。response对象最大的特点是它是一个WSGI应用。

在之前介绍WSGI规范的文章中曾介绍过Web服务器网关,它简化了服务器和web应用之间的通信过程,它要求服务器和web应用要遵循WSGI规范进行开发。对于web应用而言,应用应该实现一个函数或者一个可调用对象,这样WSGI服务器可以通过调用myWebApp(environ, start_response)从web应用获得响应内容。

response响应对象就是这样一个WSGI应用对象。在其实现过程中有一个__call__方法,可以实现对一个response对象的调用。代码如下:

def __call__(self, environ, start_response):     """Process this response as WSGI application.     :param environ: the WSGI environment.     :param start_response: the response callable provided by the WSGI                            server.     :return: an application iterator     """     app_iter, status, headers = self.get_wsgi_response(environ)     start_response(status, headers)     return app_iter

这样,我们就可以很清楚地理解WSGI应用的实现过程。下面是一个非常简单的WSGI应用。

from werkzeug.wrappers import Request, Response def application(environ, start_response):     request = Request(environ)     response = Response("Hello %s!" % request.args.get('name', 'World!'))     return response(environ, start_response)

上面的小例子的实现步骤分析:

  1. 根据传入web应用的environ构造请求对象request
  2. web应用构造响应对象response
  3. 调用响应对象response。调用过程中产生三个值:app_iterstatusheaders,其中statusheaders作为参数传递给函数start_response用于生成响应报文首行的相关信息,而app_iter作为响应的内容(它是一个可迭代对象)返回给WSGI网关
  4. WSGI网关将返回的信息组成响应首行、响应首部、响应主体等,形成响应报文发回给客户端。

BaseResponse类中还有一些属性和方法,以下属性和方法的具体用法可以参考Response——werkzeug文档。

  • 属性

    • status_code
    • status
    • data
    • is_stream
    • is_sequence
    • ······
  • 方法

    • call_on_close(func)
    • close()
    • freeze()
    • force_type() 类方法
    • from_app() 类方法
    • set_data()
    • get_data()
    • _ensure_sequence()
    • make_sequence()
    • iter_encoded()
    • calculate_content_length()
    • set_cookie()
    • delete_cookie()
    • get_wsgi_headers(environ)
    • get_app_iter(environ)
    • get_wsgi_response(environ)
    • __call__(environ, start_response)
    • ······

4、Mixin类

BaseRequest类和BaseResponse类是请求和响应最基础的类。wrappers模块中还提供了一些Mixin类,用于扩展请求类和响应类。

有关请求类的Mixin类主要有:

  • AcceptMixin类 ——请求报文中关于客户端希望接收的数据类型的类。
  • ETagRequestMixin类 ——请求报文中关于Etag和Cache的类。
  • UserAgentMixin类 ——请求报文中关于user_agent的类。
  • AuthorizationMixin类 ——请求报文中关于认证的类。
  • CommonRequestDescriptorsMixin类 ——通过这个类可以获取请求首部中的相关信息。

有关响应类的Mixin类主要有:

  • ETagResponseMixin类 ——为响应增加Etag和Cache控制的类。
  • ResponseStreamMixin类 ——为响应可迭代对象提供一个“只写”的接口的类。
  • CommonResponseDescriptorsMixin类 ——通过这个类可以获取响应首部中的相关信息。
  • WWWAuthenticateMixin类 ——为响应提供认证的类。

5、RequestResponse

终于讲到Request类和Response类了。

Request类继承自BaseRequest类,并且结合一些请求相关的Mixin类,具体如下:

class Request(BaseRequest, AcceptMixin, ETagRequestMixin,               UserAgentMixin, AuthorizationMixin,               CommonRequestDescriptorsMixin)

Response类继承自BaseResponse类,并且结合一些响应相关的Mixin类,具体如下:Python

class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin,                CommonResponseDescriptorsMixin,                WWWAuthenticateMixin)

至此,可以从wrappers模块中引入Request类和Response用于构建请求对象和响应对象。

五、routing模块

Werkzeug库的routing模块的主要功能在于URL解析。对于WSGI应用来讲,不同的URL对应不同的视图函数,routing模块则会对请求信息的URL进行解析并匹配,触发URL对应的视图函数,以此生成一个响应信息。routing模块的解析和匹配功能主要体现在三个类上:RuleMapMapAdapter

1、Rule

Rule类继承自RuleFactory类。一个Rule的实例代表一个URL模式,一个WSGI应用可以处理很多不同的URL模式,这也就是说可以产生很多不同的Rule实例。这些Rule实例最终会作为参数传递给Map类,形成一个包含所有URL模式的对象,通过这个对象可以解析并匹配请求对应的视图函数。

关于Rule类有一些常用的方法:

  • empty() ——在实际情况中,Rule实例会和一个Map实例进行绑定。通过empty()方法可以将Rule实例和Map实例解除绑定。
  • get_empty_kwargs() ——在empty()方法中调用,可以获得之前Rule实例的参数,以便重新构造一个Rule实例。
  • get_rules(map) ——这个方法是对RuleFactory类中get_rules方法的重写,返回Rule实例本身。
  • refresh() ——当修改Rule实例(URL规则)后可以调用该方法,以便更新Rule实例和Map实例的绑定关系。
  • bind(map, rebind=False) ——将Rule实例和一个Map实例进行绑定,这个方法会调用complie()方法,会给Rule实例生成一个正则表达式。
  • complie() ——根据Rule实例的URL模式,生成一个正则表达式,以便后续对请求的path进行匹配。
  • match(path) ——将Rule实例和给定的path进行匹配。在调用complie()方法生成的正则表达式将会对path进行匹配。如果匹配,将返回这个path中的参数,以便后续过程使用。如果不匹配,将会由其他的Rule实例和这个path进行匹配。

注意: 在对给定的URL进行匹配的过程中,会使用一些Converters。关于Converters的信息后续加以介绍。

2、Map

通过Map类构造的实例可以存储所有的URL规则,这些规则是Rule类的实例。Map实例可以 通过后续的调用和给定的URL进行匹配。

关于Map类有一些常用的方法:

  • add(rulefactory) ——这个方法在构造Map实例的时候就会调用,它会将所有传入Map类中的Rule实例和该Map实例建立绑定关系。该方法还会调用Rule实例的bind方法。
  • bind方法 ——这个方法会生成一个MapAdapter实例,传入MapAdapter的包括一些请求信息,这样可以调用MapAdapter实例的方法匹配给定URL。
  • bind_to_environ方法 ——通过解析请求中的environ信息,然后调用上面的bind方法,最终会生成一个MapAdapter实例。

3、MapAdapter

MapAdapter类执行URL匹配的具体工作。关于MapAdapter类有一些常用的方法:

  • dispatch方法 ——该方法首先会调用MapAdapter实例的match()方法,如果有匹配的Rule,则会执行该Rule对应的视图函数。
  • match方法 ——该方法将会进行具体的URL匹配工作。它会将请求中的url和MapAdapter实例中的所有Rule进行匹配,如果有匹配成功的,则返回该Rule对应的endpoint和一些参数rvendpoint一般会对应一个视图函数,返回的rv可以作为参数传入视图函数中。

4、示例

为了说明routing模块的工作原理,这里使用Werkzeug文档中的一个例子,稍加改动后如下所示:

from werkzeug.routing import Map, Rule, NotFound, RequestRedirect, HTTPException url_map = Map([     Rule('/', endpoint='blog/index'),     Rule('/<int:year>/', endpoint='blog/archive'),     Rule('/<int:year>/<int:month>/', endpoint='blog/archive'),     Rule('/<int:year>/<int:month>/<int:day>/', endpoint='blog/archive'),     Rule('/<int:year>/<int:month>/<int:day>/<slug>',          endpoint='blog/show_post'),     Rule('/about', endpoint='blog/about_me'),     Rule('/feeds/', endpoint='blog/feeds'),     Rule('/feeds/<feed_name>.rss', endpoint='blog/show_feed') ]) def application(environ, start_response):     urls = url_map.bind_to_environ(environ)     try:         endpoint, args = urls.match()     except HTTPException, e:         return e(environ, start_response)     start_response('200 OK', [('Content-Type', 'text/plain')])     return  ['Rule points to %r with arguments %r' % (endpoint, args)] if __name__ == '__main__':     from werkzeug.serving import run_simple     run_simple('localhost', 4000, application)

这里我们使用werkzeug自带的服务器模块构造了一个Web服务器,并且设计了一个简单的WSGI应用——application。这个Web服务器可以根据URL的不同返回不同的结果。关于服务器的构造这里不再赘述,以下部分简单对URL Routing过程进行分析:

1. 设计URL模式

设计URL模式的过程就是构造Rule实例的过程。上面的例子中我们构造了8个Rule实例,分别对应8个不同的URL模式。每个Rule实例还对应一个endpoint,这个endpoint可以和视图函数进行对应,以便访问某个URL时,可以触发与之对应的视图函数。下面的例子展示了endpoint和视图函数的对应关系。

from werkzeug.wrappers import Response from werkzeug.routing import Map, Rule def on_index(request):     return Response('Hello from the index') url_map = Map([Rule('/', endpoint='index')]) views = {'index': on_index}

2. 构造Map实例

构造Map实例时,会调用它的add(rulefactory)方法。这个方法会在Map实例和各个Rule实例之间建立绑定关系,并通过调用Rule实例的bind()方法为每个Rule实例生成一个正则表达式。

例如,对于'/about'这个URL,它对应的正则表达式为:

1

'^\\|\\/about$'

对于'/<int:year>/<int:month>/<int:day>/'这个URL,它对应的正则表达式为:

1

'^\\|\\/(?P<year>\\d+)\\/(?P<month>\\d+)\\/(?P<day>\\d+)(?<!/)(?P<__suffix__>/?)$'

3. 构造MapAdapter实例

在设计WSGI应用时,上述例子通过url_map.bind_to_environ(environ)构建了一个MapAdapter实例。这个实例将请求的相关信息和已经创建好的Map实例放在一起,以便进行URL匹配。

进行URL匹配的过程是通过调用MapAdapter实例的match()方法进行的。实质上,这个方法会将请求中的path传入到所有Rule实例的match(path)方法中,经过正则表达式的匹配来分析path是否和某个Rule实例匹配。如果匹配则返回对应的endpoint和其他的参数,这可以作为参数传入视图函数。

4. 访问URL可得相关结果

之后,访问URL可以得到相对应的结果。

例如,访问http://localhost:4000/2017/,可以得到:

Rule points to 'blog/archive' with arguments {'year': 2017}

访问http://localhost:4000/2017/3/20/,可以得到:

Rule points to 'blog/archive' with arguments {'month': 3, 'day': 20, 'year': 2017}

访问http://localhost:4000/about,可以得到:

Rule points to 'blog/about_me' with arguments {}

六、utils模块

utils是一个工具模块,很多方法都用在responserequest模块中。当设计中间件的时候也需要调用某些方法。

secure_filename() 函数处理文件名,该函数只返回ASCII字符,非ASCII字符会被过滤掉。中文文件名需要转换成英文,可以使用pypinyin库 来转换。

 

知秋君
上一篇 2024-07-03 15:32
没有下一篇了

相关推荐