flask

文章目录

flask基础(必知)

debug模式以及 app.config.from_object() , app.config.from_pyfile() 方法

debug模式 调试环境 flask会根据代码的变动自动的重启程序服务器 以上两个方法是从文件中读取配置信息的

    #以下是app.config.from_object()的代码

    def from_object(self, obj):
        if isinstance(obj, string_types):
            obj = import_string(obj)
        for key in dir(obj):
            if key.isupper():
                self[key] = getattr(obj, key)

string_type的值为(str,)

isinstance()方法补充: 在Python中 的isinstance方法是判断对象与类之间的关系
,假设该obj对象是string_types的实例返回True反正返回False


#以下是python中的isinstance的代码注释
def isinstance(x, A_tuple): # real signature unknown; restored from __doc__
    """
    Return whether an object is an instance of a class or of a subclass thereof.
    
    A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to
    check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B)
    or ...`` etc.
    """
    pass

可以看出isinstance(x, (A, B, ...))isinstance接受两个参数,object tuple 元组中包含着类 用来验证结果 当然dir内置方法也是重要的


def dir(p_object=None): # real signature unknown; restored from __doc__
    """
    dir([object]) -> list of strings
    
    If called without an argument, return the names in the current scope.
    Else, return an alphabetized list of names comprising (some of) the attributes
    of the given object, and of attributes reachable from it.
    If the object supplies a method named __dir__, it will be used; otherwise
    the default dir() logic is used and returns:
      for a module object: the module's attributes.
      for a class object:  its attributes, and recursively the attributes
        of its bases.
      for any other object: its attributes, its class's attributes, and
        recursively the attributes of its class's base classes.
    """
    return []

dir()方法返回一个对象中的所有属性的属性名,以下是 dir(int) 的返回结果


['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']


返回的结果都是以__开头结尾的名称,而且都是小写 同样的模块中也有类似的属性,因此flask的配置文件中的属性都是大写的原因在此

而app.config.from_pyfile() 方法则采用了exec的方法来生成配置文件中的选项

自定义url过滤器及url过滤器


提到url过滤器不得不提的是flask中的传递参数的方式

1,根据data的方式来传递参数该方法的弊端是url权重较低根据data的方式来传递参数的方式url如下

`http://127.0.0.1:5000/search/?book_name='活着'&page=2`的方式来传递参数优点在于该方式的post提交方式并不会将信息暴露在url中

2,根据url来传递参数

例如 `http://127.0.0.1:5000/search/活着/2`的方式来传递参数
该方式的传递参数url权重较高,但是参数的接受较为繁琐以下介绍flask(werkzeug)中内置的正则过滤器

    1 int 过滤器
        可以将接受的整形参数匹配并将匹配到的参数转换成为int类型的参数
        使用方式  app.route('/home/<int:page>/') 
        
    2 str过滤器
        可以将匹配的字符串参数
        使用方式  app.route('/home/<str:page>/')
    
    3 path过滤器
        可以将匹配的字符串参数(可以匹配/符号)
        使用方式  app.route('/home/<path:path>/')
        
        在flask中的static文件url就是由该过滤器来匹配的
        在flask启动的时候会自动的建立一个/static/<path:file_name>
        `假设你没有配置app的static_url选项,那么该处的static文件的url默认为这个`
        
    4 any过滤器 (同时匹配多个url)
        使用的方式 app.route(/path/<any(image,video):file_type>)/该方式的url过滤器可以匹配多种的url
        
        
        
    5 ×××自定义url过滤器
        from werkzeug.routing import BaseConverter
            
        class tel(BaseConverter):
            regex = '\d{11}' # 此处是要匹配的正则
            
            def to_python(self,value):
                reutrn value # 在此处的返回值将作为参数传递到视图函数中
                
            def to_url(self,value):
                return url_quote(value, charset=self.map.charset)
                # 在此处的返回值决定了url_for 反转url的结果 以及@app.route('')中的url
                
        app.url_map.converter['名字'] = 自定义的converter类

flask的路由映射


在flask中的url映射路由分发是由装饰器来完成的,例如

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    reutrn 'hello'
    
    
if __name__ == '__main__':
    app.run()
    
以上是pycharm为我们生成的代码可以看出flask中是基于装饰器的形式来进行路由映射路由分发的


Flask() 包含了10个参数


`import_name`:该参数决定了app的位置,flask用来区分各个app之间的关系


`static_url_path`: 该参数决定了在程序启动的时候flask生成的获取static静态文件的url

`static_folder`: 该参数决定了flask静态文件的寻找路径

`template_folder`: 该参数决定了模板文件的存放路径

关于app.route()方法详解


route() 方法是一个有参装饰器 该装饰器负责url_rule 与 view_function 之间的绑定关系

def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

但是为什么这个有参装饰器只有两层函数包裹


因为在route返回的函数中  decorator() 方法并没有增加改动函数的结构
而是取出了一个 endpoint 参数来注册url 后面的url_for 方法会详细讲解此处的endpoint参数

在route的更深的add_url_rule() 方法中注册了url与视图函数之间的关系,下面讲解add_url_rule方法的源码

add_url_rule()方法的详细讲解

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    
    if endpoint:
        assert '.' not in endpoint, "Blueprint endpoints should not contain dots"
    if view_func and hasattr(view_func, '__name__'):
        assert '.' not in view_func.__name__, "Blueprint view function name should not contain dots"
    self.record(lambda s:s.add_url_rule(rule, endpoint, view_func, **options))

可以看出在上方的add_url_rule()方法中if endpoint:assert '.' not in endpoint语句讲明了我们在
使用route传递endpoint参数的时候不能够传递包含有 . 的字符串来进行别名,假设未指定endpoint flask就会以function.__name__
作为endpoint名 然后 做完语法校验之后添加url_rule

url 反转与重定向


url 反转 `url_for()` 方法

    在flask中url_for() 为flask提供了路由之间的高可扩展性,不仅仅在视图函数中可以使用同样的在jinja2模板中使用
    以下是url_for()源码的简单解析 上源码

def url_for(endpoint, **values):    
    appctx = _app_ctx_stack.top
    reqctx = _request_ctx_stack.top
	# 获取appctx的栈顶元素 与 reqctx的栈顶元素   request的LocalStack  与 app的LocalStack的栈顶元素
    if appctx is None:
        raise RuntimeError(
            'Attempted to generate a URL without the application context being'
            ' pushed. This has to be executed when application context is'
            ' available.'
        )

    # If request specific information is available we have some extra
    # features that support "relative" URLs.
    if reqctx is not None:
        url_adapter = reqctx.url_adapter
        blueprint_name = request.blueprint
		#如果获取到的request元素不是空的话  获取当前的app中的所有url 与 reuqest的请求的蓝图名字
        if endpoint[:1] == '.':
            #假设url_for 的endpoint是简写的话 使用当前request请求的蓝图名字来作为url的寻找对象
            if blueprint_name is not None:
                endpoint = blueprint_name + endpoint
            else:
                endpoint = endpoint[1:]
			#拼接蓝图与url  以寻找相应的视图函数
        external = values.pop('_external', False)
		
    # Otherwise go with the url adapter from the appctx and make
    # the URLs external by default.
    else:
        url_adapter = appctx.url_adapter

        if url_adapter is None:
            raise RuntimeError(
                'Application was not able to create a URL adapter for request'
                ' independent URL generation. You might be able to fix this by'
                ' setting the SERVER_NAME config variable.'
            )

        external = values.pop('_external', True)
		###假如request栈顶返回是空,抛出异常
    anchor = values.pop('_anchor', None)
    method = values.pop('_method', None)
    scheme = values.pop('_scheme', None)
    appctx.app.inject_url_defaults(endpoint, values)

    # This is not the best way to deal with this but currently the
    # underlying Werkzeug router does not support overriding the scheme on
    # a per build call basis.
    old_scheme = None
    if scheme is not None:
        if not external:
            raise ValueError('When specifying _scheme, _external must be True')
        old_scheme = url_adapter.url_scheme
        url_adapter.url_scheme = scheme

    try:
        try:
            rv = url_adapter.build(endpoint, values, method=method,
                                   force_external=external)
        finally:
            if old_scheme is not None:
                url_adapter.url_scheme = old_scheme
    except BuildError as error:
        # We need to inject the values again so that the app callback can
        # deal with that sort of stuff.
        values['_external'] = external
        values['_anchor'] = anchor
        values['_method'] = method
        values['_scheme'] = scheme
        return appctx.app.handle_url_build_error(error, endpoint, values)

    if anchor is not None:
        rv += '#' + url_quote(anchor)
    return rv

url_for() 源码之前的必备知识
    了解Local对象 LocalStack 对象, flask的基本工作原理
    
    Local 是werkzeug中的一个线程隔离对象 from werkzeug.local import Local
    
    上源码
    
    
    class Local(object):
    
    __slots__ = ('__storage__', '__ident_func__')
    #此处的__slots__属性是限制为对象的添加属性必须为 '__storage__', '__ident_func__' 中的一个
    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)
        
        # 为了保证线程之间的数据安全与相互隔离 字典是一个很好的选择
        # 线程的ident唯一标示作为字典的键 ,要隔离的数据作为值
        # 假设有两个id为 1与id为2 的线程要修改一个int类型值为1 的变量
        # 线程隔离的对象中的字典结构为 {'1':1,'2':1} 再根据ident来获取字典中相对应的值
        # 该数据结构应当用于线程之间都需要修改但是不需要共享的一个值,可以使用线程隔离的方式来进行操作,来解决线程锁效率问题
        # 例如mysql数据库中的大量的线程数据插入的时候,需要执行commit() 方法的时候,假设commit时不加锁执行,那么数据库链接器就会抛出大量异常,
        # 例如 pymysql.err.InterfaceError: (0, '') 数据库链接断开 各种各样的异常,虽然使用Local对象能够避免commit执行带来的线程安全问题
        # 当然数据插入不必每一次都执行commit() 方法也可以在数据插入完成的时候再去执行commit 或者插入一定数据的时候执行commit 一种解决问题的思路

    def __iter__(self):
        return iter(self.__storage__.items())

        # iter()方法 返回一个迭代对象自动的调用__iter__ ,同样的__iter__也可以是一个生成器 使用yield来返回一个生成器 

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)
        
        `暂时不知道`

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)
        `调用ident方法得到线程的id 得到键值pop出值 释放内存`
        
        
        
    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
    	##通过线程的ident来对字典取值
    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}
		## 通过线程的ident等来设置值
    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
    	## 通过线程的ident来设置值




flask中的上下文对象与LocalStack对象

文章目录


线程隔离对象

自定义response类 与 response返回


在flask中的response的返回对象中 只能够包含 str tuple Response对象 在返回的时候假设返回类型都不是这三种类型将会调用 
app.response_class这个类中的force_type(cls,response,evniron)方法来尝试建立Response对象


定义Response方法的三种方式

1, 实例化Response

from werkzeug.wrapper import Response

Response('返回内容',状态码,headers) 


2, 使用make_response() 方法

from flask import make_response
# 302response跳转实例
# 定义字典 
header = {'Location':'跳转地址'}

response = make_response('返回内容',302)
response.headers = header

3,直接返回

return '返回内容',状态码



自定义response类让flask能够支持 字典返回类类型的数据

由于flask只接受视图函数返回元组,response与str类型的数据,并不能够接受字典等其他类型数值

以下是自定义 app.response_class 类使flask能够接受 dict的返回类型

from flask import Response,jsonify

class response(Response):
    # 重写force_type()方法
    @classmethod
    def force_type(cls,response,evniron):
        if isinstance(response,dict):
            response = jsonify(response)
            
        return super(Response,cls).force_type(response,evniron)
        
        
        
#将 app.response_class替换成为自己定义的response类

app.response_cls = response


# s重点 force_type方法必须返回一个response对象 调用父类的force_type的时候返回值是return到 自定义的Response类中的
# 代码中应当尽量避免这种错误  即下面的这一段代码必须有返回值
            return super(Response,cls).force_type(response,evniron)
        

jinjia2模板

使用方式


配置静态模板的位置通过实例化app的时候 传入 template_folder 参数定义模板文件的存放位置
例如  `app = Flask(__name__, template_folder = 'path')` 

使用静态模板 from flask import render_template


@app.route('/')
def index():
    return render_template('index.html',**{变量名:值,。。。。})
    #或者 return render_template('index.html',变量名=值, 变量名=值)

jinjia2模板中的标签


{% %} 逻辑语句 例如 {% block %}{% endblock %}  {% for i  in gen %}{% endfor %} 等

{{ }} 渲染语句 如 {{ a }} 该方法会将a的值渲染到页面上
 

jinjia2模板常用 标签 以及 过滤器


`常用过滤器及标签`

floor `过滤器`  向下求整  ceil `过滤器` 向上求整

max `过滤器` 求最大值 min `过滤器` 求最小值

default `过滤器` 使用形式  {{ a|default('this is default value') }} default接受两个参数 第一个是默认值的值 第二个是是否开启检测空值的选项
`default补充`: default的默认执行条件并不是当值为空的时候 而是当值不存在的时候, 当检测值为空的时候可以使用 `{{ a or 'this is default value' }}` 这样的形式来替代default  

first `过滤器` 返回容器类型的第一个元素 last 获取最后一个

format `过滤器` 格式化字符串 使用形式: {{ 'my name is %s'|format('long') }} 输出结果: my name is long

length `过滤器` 返回字符串等序列类型的长度

join `过滤器` 形同 python 中的 '/'.join(['','','',]) 将一个列表中的值以指定的字符串拼接默认使用 '' 

replace `过滤器` 形同 python中的replace 

truncate `过滤器` 截取指定长度字符串 使用方式 {{ a|truncate(length=20) }} 不足长度使用原来的字符串 超过长度最后一位使用 ... 来代替

striptags `过滤器` 将字符串中的标签截取舍弃 {{ '<h1>aaa</h1><h2>bbb<h2>'|striptags }} 输出结果 aaabbb
  



`转义标签及过滤器`
escape `过滤器` 使用形式 {{ a|escape }} 该方法是将 类似html代码 js代码 转义成为 字符串在浏览器输出 假设有一段js 代码 `<script>alert('test')</script>` 那么使用escape之后在浏览器并不会当成js代码来处理 而是将其转移成为了普通语句

safe `过滤器` 使用形式 {{ a|safe }} 该方法是将js html等代码 原样输出 不做任何转义

{% autoescape off %}{% endautoescape %} 在此标签范围之内的任何语句都得不到转义



自定义过滤器


使用app核心对象的 `@app.template_filter()`  方法来注册过滤器


例如: 

@app.template_filter()
def replace(value,old,new):
    return value.replace(old,new)

template_filter() 方法接受

jinja2中的宏定义


jinja2中的宏与python中的函数相同,不过jinja中的宏是不具备返回值的 只是负责渲染 
宏的内部就是封装了一层jinja2模板语法与html

定义关键字 macor

定义方式 :
{% macro input(name='', value='',type='input') %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

以上宏代码用于生成input标签

调用方式:
{{ input(参数) }}


宏在实际的开发环境中是单独放置的 这就意味着需要将宏从外部导入


宏的导入 

1 import... as...导入方式及调用方式

{% import 'test.html' as test %}

{{ test.input() }}

2 from ... import ... 导入方式以及调用方式

{% from 'test.html' import input %}
{{ input() }}


3 from ... import ... as...调用方式以及导入方式

{% from 'test.html' import input as test %}
{{ test() }}


假设macro中需要主html中的变量 可以使用 {% from 'test.html' import input with context %}

jinja2中的模板继承 include方法 以及重载父模板block块中的html代码


当html模板中包含有大量的重复小段代码的时候可以将该小段代码封装到另一个html文件中

通过 {% include 'nav.html' %} 来导入加载文件,include方法不需要像是 macro那样 使用with context 来访问外部变量


block语法  

在父模板中定义 block 块  

{% block content %}

{% endblock %}


子模板中使用block

1,继承父模板  
{% extends 'test.html' %}


2,重写父模板中的block块

{% block content %}

重写的内容

{% endblock %}

3,继承父模板中的html内容

使用super方法


{% block content %}

super()
重写的内容

{% endblock %}

这样在父模板中的block块中的内容就可以被子模板继承

set with 关键字


set关键字: 定义一个变量该变量全局可用
使用方式
{% set a = 1 %}


with关键字 定义一个局部变量 只在with标签内可以访问

{% with a = 1 %}
    a变量只能在with标签内使用
{% endwith %}

for循环


for 循环在jinja中的使用与python相同


{% for i in [1,2,3,4] %}
    
{% else %}
    else标签内的内容只在for循环内容为空的时候执行

{% endfor %}


关于for循环中的属性 

{{ loop.index }} 获取下标 从1开始

{{ loop.index0 }} 获取下标 从0开始

{{ loop.first }} 是否是第一次循环

{{ loop.last }} 是否是最后一次循环

视图类



#在flask中也提供了像是django那样的视图类来进行cbv fbv

# cbv的两种模式



#1, 标准视图类继承自 View类  重写 dispatch_request() 方法

from flask import views,request


class index(views.View):


    def dispatch_request(self):
        if request.method == 'GET':
            return 'get'
        if request.method == 'POST':
            return 'post'

    # 在普通类视图当中get 与 post 并不是分开的 所有的请求都会到 dispath_request当中
    # 在普通视图类当中重点并不在视图方法之间的解耦合,而是在与 视图当中的一个个的代码重用,
    # 当多个视图函数序言返回或者需要做同一种操作的时候 可以使用标准视图类 来解决代码的重用性,而不是使用装饰器,
    # 解决代码的复杂性,易读性 
    
#2, 函数视图  继承自 MethodView 重写get post 或者dispatch_request()方法 来做额外的操作

class index(views.MethodView):

    def get(self):
        return 'get'
        
        
    def post(self):
        return 'post'
        
        
    # 解决大量代码逻辑判断之间的代码凌乱 逻辑判断提高代码之间的复用性
    # 应用场景 : 在fbv应用大量的逻辑判断的时候,代码之间的耦合性较高,复用性底的时候
    
    
#视图类注册url 注册路由    使用 add_url_rule('url',index.as_view('endpoint名用于路由反转'))


  
视图类中的装饰器
    当类中的函数属性 经过装饰器装饰时候 会失去原有的特性例如__name__ __model__ __doc__等,即function类型 与类函数属性中的__开头结尾的属性不相同,
    但是 在functool中的 wraps 函数解决了这个问题 上源码  在这里不全部讲解源码  只讲解一部分

from functools import wraps


WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
    # from the wrapped function when updating __dict__
    wrapper.__wrapped__ = wrapped
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

'''

以上代码是wraps中较为核心的一段代码, 在这段代码中wraps 将 ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
中的属性替换到了装饰函数上,不做过多讲解  略
'''

flask蓝图


#1, 蓝图的意义
#  蓝图实现了url之间 模块之间的解耦和  方便管理代码 实现路由分发

#2,蓝图的基本使用

### 蓝图的使用方式与app核心对象相同

from flask import Blueprint

home = Blueprint('蓝图名字',__name__,url_prefix='/honme',static_folder='静态文件的文件路径',template_folder='模板文件夹的文件路径')


## url_prefix 指定 蓝图的头url 相等于 django中的 include 方法使用 实现路由分发
## template_folder 指定模板文件的路径
## static_folder  制定静态文件的文件路径 例如 js文件 css文件  图片等文件

### 然后使用 app核心对象 的 register_buleprint() 方法来注册 蓝图

from flask import Flask

app = Flask(__name__)

app.register_blueprint(home)


蓝图中的url_for反转 static文件夹查找 template文件查找

在flask中的模板查找优先级是从项目根目录的templates文件夹中寻找的 即app核心对象的templates的文件文件夹中寻找


假如蓝图中设置的static_folder 静态文件夹的路径  那么寻找静态文件夹的方式即 {{ url_for( 'static' filename='filepath') }} 这种导入静态文件的放是就应该加上蓝图的名字
{{ url_for( 'home.static' filename='filepath') }}  实际上的原理就是在home下寻找一个名叫static的endpoint对应的视图函数   
在 flask 中的静态路由   static 不管是 BulePrint(蓝图在指定static_folder才会生成static视图函数与url_rule) 还是 app核心对象都会生成一个endpoint为static的url与视图函数的对应关系

在url_for 的反转中可以使用假设反转的url在同一个app中或者蓝图中 使用 url_for('.endpontName')的方式也可以来进行路由反转详情请见源码
 
利用蓝图设置子域名


from flask import Blueprint,Flask

# 1,在实例化BluePrint的时候传递 subdomain参数 

home = Blueprint('home',__name__,subdomain='子域名')

# 2, 在app核心对象中配置 SERVER_NAME 参数 
app = Flask(__name__)
app.config['SERVER_NAME'] = 'baidu.com'

# 如果没有域名的话 可以在host文件中修改host来达到域名的效果


flask中的sqlalchemy


#使用方式     数据库环境是mysql


#sqlalchemy 的基本使用方式

from sqlalchemy import create_engine

host = '数据库ip'
port = '数据库的端口'
username = '用户名'
passwd = '密码'
database = '连接的数据库'
charset = '编码'

db_url = 'mysql+pymysql://{username}:{passwd}@{host}:{port}/{database}?{charset}'.format(username=username,passwd=passwd,host=host,
                                                                                      port=port,database=database,charset=charset)

#必须将url格式化成以上的结构

# 创建链接器
client = create_engine(db_url)

#得到游标
with client.connect() as f:
    #执行sql语句
    result = f.execute('sql语句')
    #取出结果
    resutl = result.fetchall()
                                                                                      

sqlalchemy基本的对象关系映射

from sqlalchemy import *
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base

host = '127.0.0.1'
port = '3306'
username = 'root'
passwd = '123456'
database = 'video'
charset = 'utf8'


db_url = 'mysql+pymysql://{}:{}@{}:{}/{}?{}'.format(username,passwd,host,port,database,charset)

client = create_engine(db_url)

Base = declarative_base(client)

class Person(Base):
    __tablename__ = 'person'
    
    id = Column(Integer,autoincrement=True,primary_key=True)
    name = Column(String(30),nullable=False)
    age = Column(Integer,nullable=False)

Base.metadata.create_all()

sqlalchemy中的约束


`nullable`  是否为空  默认为True

`primary_key` 是否是主键 默认为False

`autoincrement` 是否为自增长键

`name` 该字段在数据库中的映射 数据库中的字段名就是name指定的名字

`default` 默认值 

sqlalchemy中的数据类型

`String`: 字符串类型 对应的是mysql中的varchar类型

`Integer INT INTEGER`: 整形 对应的是mysql中的int类型

`DECIMAL Float` 浮点型 对应的是数据库中的 浮点型 长整形  使用方式 DECIMAL(总长度,浮点型长度)

`Boolean` 布尔型 对应的是 数据库中的布尔类型 boolean 

`Date` 日期类型 date 只存储 年月日 使用datetime.datetime().data()来插入数据 
 
`Datatime` 时间日期类型 存储年月日时分秒  使用datetime.datatime.now()插入数据

`Time` 存储时间 时分秒  使用 datetime.datetime().time() 插入数据

`Enum` 枚举类型 对应mysql中的 enum类型  Column(Enum('python','java','goland'))



自定义枚举类型

class choice(enum.Enum):
    python = 'Python'
    
使用方式 
Column(choice)

sqlalchemy 的简单的增删改查

# sqlalchemy 的增删改查是建立在 session会话上的 

# sqlalchemy 中提供了一个方法sessionmaker(bind=数据库链接对象) 改方法会产生一个工厂类

# 提到工厂类不得不提的是设计模式中两个经典的设计模式, 工厂设计模式 代理设计模式
 
'''
理解`产品对象` `工厂类` `面向对象多态`

产品对象: 单一的对象 具体指某一种事物,具有特定的描述,统一的特征,

工厂类: 对于对象笼统的描述 具有产生对象的方法,工厂类大多都是抽象基类,静态工厂不限


具体代码如下 : 可能我实现的工厂代理模式与代理不怎么完善 之前学的java的设计模式忘的七七八八了  望原谅

'''

from abc import ABCMeta
from abc import *



class Base(metaclass=ABCMeta):


    @abstractmethod
    def say(self):
        pass


    @abstractmethod
    def see(self):
        pass

    @staticmethod
    def get_dog(name):
        return Dog(name)




class Dog(Base):

    def __init__(self,name):
        self.name = name

    def say(self):
        print('狗在叫')

    def see(self):
        print('狗在看')


a = Base.get_dog('zhoule')


a.say()

#以上代码中Base用于产生Dog 使用者并不需要关心具体的代码中的具体方法的使用方式只需要
#查看Base中的抽象方法,调用抽象基类中的方法批量产生对象


#同样的也可以使用getattr 来实现工厂模式

#代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。
# 在某些情况下,一个对象不适合或者不能直接引用另一个对象,
# 而代理对象可以在客户端和目标对象之间起到中介的作用。



#具体的sqlalchemy的session回话机制


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


host = '127.0.0.1'
port = '3306'
username = 'root'
passwd = '123456'
database = 'video'
charset = 'utf8'


db_url = 'mysql+pymysql://{}:{}@{}:{}/{}?{}'.format(username,passwd,host,port,database,charset)

client = create_engine(db_url)

Base = declarative_base(client)

class Person(Base):
    __tablename__ = 'person'
    
    id = Column(Integer,autoincrement=True,primary_key=True)
    name = Column(String(30),nullable=False)
    age = Column(Integer,nullable=False)
    
    def __init__(self,name,age):
        self.name = name
        self.age = age
        

Base.metadata.create_all()


Base = declarative_base()

sessionMaker = sessionmaker(bind=client)

session = sessionMaker()
person = Person('long',18)

session.add(person)

# 当需要插入多个值的时候需要使用 add_all() 方法 add_all() 方法接受一个列表作为参数


#当执行完插入的时候数据并没有写入导数据库 当前中间出现异常可以使用 session.rollback() 方法回滚
session.commit()
session.rollback()
# 以上是增


session.query(Person).filter_by(name='long')

#filter_by方法不需要跟  Person.  但是 filter() 需要

session.query(Person).filter(Person.name == 'long')

# filter() 方法会根据条件类筛选 但是需要加 类名.属性名 来进行筛选


#修改数据需要将对象查询出来 

update_data = session.query(Person).get(1)  
#get方法是根据主键 来获取值的 只能够传递主键的值来进行筛选 返回的是Query对象可以直接通过.的方法来修改


update_data.name = 'le'

session.commit()
# 通过.的属性的方式修改完数据并不能提交到数据库中  需要执行 commit() 方法来执行 数据的修改


#删除对象
#查询对象 

delete_data = session.query(Person).get(1)

session.delete(delete_data)

session.commit()

#提交数据的修改


#具体的查询使用  in_ 方法
session.query(Person).filter(Person.id.in_([1,2]))

# not in 

session.query(Person).filter(~Person.id.in_([1,2]))

# and_ 与

session.query(Person).filter(and_(Person.id<5,Person.id>1))

# or_ 或

session.query(Person).filter(or_(Person.id==1,person.id==5))

# not_ 取反 异

session.query(Person).filter(not_(Person.id==2))

#is_

session.query(Person).filter(Person.name.is_(None))

# func中的聚合函数

#计数
session.query(func.count(Person.id))

#最大值
session.query(func.max(Person.id))

#最小值

session.query(func.min(Person.id))

#平均值

session.query(func.avg(person.id))

#query的查询操作

#仅仅查询需要的值
session.query(Person.name,Person.age)


常用的外键约束


`CASCADE`  级联删除
`NOACION`  无动作与RESTRICT相同
`SET NULL` 设置null
`RESTRICT` 不允许删除

sqlalchemy一对多


from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy import func,Integer,String,ForeignKey,Table,Column,Text
from sqlalchemy.orm import sessionmaker,relationship


# 建立数据库连接


username = 'root'
passwd = '123456'
host = '127.0.0.1'
port = '3306'
database = 't1'
charset = 'utf8'



url = 'mysql+pymysql://{}:{}@{}:{}/{}?{}'.format(username,passwd,host,port,database,charset)


client = create_engine(url)


Base = declarative_base(bind=client)

session = sessionmaker(bind=client)()


class Author(Base):
    __tablename__ = 'author'
    
    id = Column(Integer,autoincrement=True,primary_key=True)
    name = Column(String(30),nullable=False,index=True)
    
    
    
class Article(Base):
    __tablename__ = 'article'
    
    id = Column(Integer,autoincrement=True,primary_key=True)
    title = Column(String(255),nullable=False)
    content = Column(Text)
    
    author_id = Column(Integer,ForeignKey('author.id'),nullable=False)
    
    
    # 在此处的author表示与Author表关联 backref表示Author中反向查询的名字 
    author = relationship('author',backref='articles')


# 插入

#建立文章的映射对象
A1 = Article(title='从零开始的异世界生活',content='这部剧太好看额')
A2 = Article(tiele='亮剑',content='我的意大利炮呢')

# 作者的映射对象
author = Author(name='long')


#绑定关系
author.article.append(A1)
author.article.append(A2)

# 添加改动
session.add(author)

#确认改动
session.commit()


# 第二种添加数据的方式

# 先将作者对象查询出来


long = session.query(Author).filter(Author.name=='long')

long.article.add(A1)



session.commit()


# 移除外键关联,

long.article = [i for i in filter(lambda x: False if x.title == '亮剑' else True,long.article)]


session.commit()

#ForeignKey()的外键的backref默认的类型是一个列表的子类操作外键就是在操作列表、


一对一



from sqlalchemy import create_engine,Column,String,Integer,ForeignKey,Column
from sqlalchemy.orm import relationship,sessionmaker,backref
from sqlalchemy.ext.declarative import declarative_base

username = 'root'
passwd = '123456'
host = '127.0.0.1'
port = '3306'
database = 't1'
charset = 'utf8'


url = 'mysql+pymysql://{}:{}@{}:{}/{}@{}'.format(username,passwd,host,port,database,charset)


client = create_engine(url)
Base = declarative_base(bind=client)
session = sessionmaker(bind=client)()


class user(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(30),index=True,nullable=False)
    passwd = Column(String(30),nullable=False)
    
    #同样的也可以在两张表中设置
    # extend = relationship('user_extends',uselist=False)
    
class user_extends(Base):
    __tablename__ = 'user_extends'
    id = Column(Integer,autoincrement=True,primary_key=True)
    age = Column(Integer,nullable=True)
	user_id = Column(Integer,ForeginKey('user.id',ondelete='SET NULL'))
    user = relationship('user',backref=backref('user_extend',uselist=False)) 
    # user = relationship('user')
   
Base.create_all()


#查询
u1 = user(username='long',passwd='123456')
ue1 = user_extends(age=1)

u1.user_extend = u1

session.add(u1)
session.commit()


#一对一的查询 

session.query(user).age
session.query(user_extrnds).username



#多对多的表的两种建立方式

# 1 使用类的方式来建立


# 建立数据库链接  
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship,sessionmaker

username = 'root'
passwd = '123456'
host = '127.0.0.1'
port = '3306'
database = 't1'
charset = 'utf8'


url = 'mysql+pymysql://{}:{}@{}:{}/{}?{}'.format(username,passwd,host,port,database,charset)



client = create_engine(url)
Base = declarative_base(bind=client)
session = sessionmaker(bind=client)()

class article(Base):
    __tablename__ = 'article'
    
    id = Column(Integer,primary_key=True,autocrement=True)
    title = Column(String(30),nullable=True)
    content = Column(Text)
    
   
class tag(Base):
    __tablename__ = 'tag'
    id = Column(Integer,primary_key=True,autocrement=True)
    tagname = Column(String(30),nullable=False)
    article = relationship('article',backref='tag',secondary='tag_article')
    
#多对多的实现需要使用第三张表


class tag_article(Base):
    __tablename__ = 'tag_article'
    articles = Column(Integer,ForeignKey('article.id'))
    tags = Column(Integer,ForeignKey('tag.id'))
	
    
    
#使用Table类实现建表  
tag_article = Table(
	'tag_article',#表名字
    Base.meta,
    Column('tag',ForeignKey('tag',ondelete="CASCADE")),
    Column('article',ForeignKey('artice',ondelete="CASCADE")),
)  
#提交修改   操作列表

t1 = tag(tagname='test')
a1 = article(title='',content='')
t1.tags.append(a1)

session.add(t1)
session.commit()


cascade的参数介绍 (relationship参数)

`delete` : 在删除父表的数据的时候子表也会删除
`delete_orphan`  在解除关联操作的时候   被关联的数据的时候数据也会被删除
`expunge`  删除的时候 被关联的数据也会被删除  但是删除是在session中删除  expunge操作是在session中进行的  
`save_update` 在添加的时候被关联的数据也会被添加
`merge` session  形同字典的update操作根据主键寻找记录  更新记录   在更新记录的时候更新关联对象




实例代码

from sqlalchemy import * 
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship,backref



host = '127.0.0.1'
port = '3306'
user_name = 'root'
passwd='123456'
database = 't1'
charset = 'utf8'
url = 'mysql+pymysql@://{}:{}@{}:{}/{}?{}'.format(user_name,passwd,host,port,database,charset)
client = create_engine()
Base = declarative_base()
session = sessionmaker(bind=client)

# 建立表的时候建立order_by 的规则

class author(Base):
    __tablename__ = 'author'
    id = Column(Integer,autoincrement=True,primary_key=True)
    name = Column(String(30),nullable=False)
    
    __mapper_args__ = {
        'order_by': id
        #'order_by': -id倒序
    }
    
    

class age(Base):
    id = Column(Integer,autoincrement=True,primary_key=True)
    name = Column(String(30),nullable=False)
    
    author = relationship('author',backref=backref('ages',order_by=id,lazy='dynamic'),lazy='dynamic')
    
    #在外键当中添加order_by关联


#在查询的时候需要使用 dynamic 缓存机制来进行查询
session.query(author).order_by(author.id).all()

session.(age).author.order_by('-id').all()


子查询(subquery) 与 limit offset join group_by having


 a=session.query(author.id.lable('id')).filter(author.id.in_([1,2])).subquery()
 session.query(article).filter(article.id.in_(a))  
 
 # 上面就是简单的子查询语句的形式
 
 
 limit 与 offset 切片操作
 
 session.query(article).order_by(article.praise).offset(5).limit(5).all()  
 
 #取第五条到第十条的数据
 
 
 group_by 与 having
 
 
 session.query(article.author,func.count(author.id)).group_by(article.author).having(func.count(article.id) > 5)
 取文章中的文章数量大于5 的作者与其文章数量
 
 join 连表操作
 
 session.query(author,article.title).join(article,author.id == article.author_id).all()
 
 查询所有文章的名字和作者信息  join连表操作可以不用外键


alembic使用

安装alembic     pip install alembic 


使用方式    

1,建立  alembic 迁移脚本库


alembic init alembic 


2,配置数据信息

在alembic.ini中配置数据库url链接
在env文件中配置metadata信息


3,生成迁移文件

alembic revision --autogenerate -m '描述'


4 映射数据库
alembic upgrade heads


5版本回滚

alembic downgrade  版本号码

flask script 的基本使用


安装flask_script   pip install flask_script

建立manage.py 文件

from flask_script import Mannger

manage = Mannger(app)



#使用装饰器的形式进行装饰

@manage.commond
def run():
	print('启动服务器')
	
	
#使用option的方式来进行装饰 第一个参数的名字是简写形式  第二个是全名的形式

@manage.option('-n','--name',dist='接受的参数名字')
def test(参数名字):
	prin(参数)




if __name__ == '__main__':
	manage.run()


flask_migrate使用

安装flask_migrate   pip install flask_migrate

使用:

1 在flask_script 的文件中导入  flask_migrate

from flask_migrate import Migrate,MigrateCommond,Manager
Migrate(app=app)
manage = Manage(app=app,db=db)
manage.add_commond('db',MigrateCommond)



if __name__ == '__main__':
	manage.run()

WTForms表单使用


WTForm简单的使用方式


from WTForms import Form
frm WTForms import * 

class RegisterForm(Form):
	username = StringField(validators=[input_required('非空'),Length(max=最大长度,min='最小长度',message='错误信息')])
	firstpasswd = StringFiedld(lable='该参数是在html生成代码的时候lable的名字',validators=[input_required(message='非空'),Length(max=最大长度,min='最小长度',message='错误信息')])
	secondpasswd = StringField(lable='该参数是在html生成代码的时候lable的名字',validators=[input_required(),Length(max=最大值.min='最小长度',message=‘错误信息’),EqualTo('firstpasswd',message='错误信息')])


### 在此处的lable参数不传递默认使用字段名字
	
	
	
	
	
forms = RegisterForm(request.form)

if forms.validate():
	print('验证通过')
	
else:
	print(forms.errors)



## 常用的过滤器验证器与字段

Length  number_range  input_required  Email  UUID 	URL 

Length          长度与StringField字段相结合使用    参数  min max message 
number_range    与IntegerField字段结合使用  参数  min max message
input_required  非空 参数 message
Email          	检测Email                  参数 message
UUID            检测是否为UUID

补充 EqualTO  是否与某个字段的值相同  参数  fieldname 类型为字符串



使用WTForms的HTML生成


代码实例(python)

@app.route('/test/',methods = ['GET','POST'])
def test():
	if request.method == 'GET':
		forms = Register()
		return render_template('register.html',forms = forms)
	else:
		forms = RegisterForm(request.form)
		if forms.validate():
			return jsonify({'error':0,'message':'登陆成功'})
		else:
			message = {'error':1,'message':forms.errors}
			reutrn jsonify(message)
			
			
GET形式中的html

<html>
<meta charset='utf-8'>
</meta>

    <head>
        <title>
        register
        </title>
    </head>
    <body>
        <form action='{{ url_for('register') }}' method='POST'>
            {{ forms.username() }}
            {{ forms.firstpasswd() }}
            {{ forms.secondpasswd() }}
            <input type='submit' />
        </form>
    </body>
</html>




flask的文件上传


##flask实现文件上传的功能要比django的简单的多  

### 文件上传的简单的注意事项   html中的注意事项

<form action='{{ url_for('upload') }}' method='POST' enctype='multipart/form-data'>
	<input type='file' name='file' />
	<input type='submit'/>
</form>
{# 在form中必须声明enctype 为 multipart/form-data类型flask中的视图函数才能够接受参数 #}



#app中的函数代码示例

from flask import Flask,request,render_template,Response
from werkzeug.utils import secore_filename
from flask.helper import send_from_directory
app = Flask(__name__)

@app.route('/uploade/')
def upload():
	if request.method == 'GET':
		return render_template('upload.html')
		
	if request.method == 'POST':
		file = request.files.get('file')
		filename = secore_filename(file.filename)
		file.save('存储文件的文件夹')
		reutrn 文件访问路径或者返回信息提示上传成功
		
@app.route('/image/<path:filepath>')
def image():
	try:
		return send_form_directory('路径','文件名')
	except FileNoFondError as ex:
		return Response('找不到文件',404)




使用flask_wtf 的方式来验证文件

cookie的使用


代码实例


from flask import request,FLask,Response

app = Flask(__name__)

@app.route('/set_cookie/')
def index():
	response = Response('set cookie')
	response.set_cookie('key','value',max_age='cookie使用秒数',expires=datetime.timedelta(day=2,hours=16))
	### 在此处设置的 max_age = cookie的过期时间  当连个都设置的时候默认使用 expires  expires对低版本的浏览器支持较好  max_age的方式对IE8以下的浏览器不支持   expires在http2.0中已经宣布废弃
	return response
@app.route('/check_cookie/')
def index1():
	co = request.cookie.get('key')
	return co or '没有cookie'
	
	
@app.route('/delete_cookie/')
def index2():
	response = Response('clean cookie')
	response.delete_cookie('key')
	return response
if __name__ == '__main__':	
	app.run()


session的使用

session 只是一种思路  并不是局限于具体而固定的一种形式 而是一种解决问题的方案的体现   直接使用cookie的明文传递方式并不安全,并且cookie的传递信息最大只能传递大约4kb大小 4000多个字节
但是session的  server存储方式能够解决大量需要传递的信息  session的一种将数据存放在服务器的方式可以有效的解决普通的cookie的传递的信息大小带来的问题,但是会加大服务器的压力,

session的加密形式  将信息加密存储到cookie中,提高了安全性但是由于cookie的4kb容量问题  减少了cookie的信息携带量,该方式也较为安全,其实说session是存储到服务器上的键值对是错误的,session也可以以加密的形式放在cookie中







session的操作



session的操作就与python中的字典相同


from flask import Flask,request,Response
from datetime import timedelta



app = Flask(__name__)

@app.route('/set_session/')
def index():
	session['key'] = 'value'
	return Response('set_session')
	
@app.route('/get_session/')
def index1():
	ses = session.get('key')
	return Response(ses | request is not have session)
	
	
@app.route('/clear_session/')
def index2():
	session.clear()
	return response('session is cleaned suessed')
	
	
if __name__ = '__main__':
	app.run()
	
同样的session同样支持  setdefault  get  update  pop  del 等操作字典方式来操作session


session的过期时间的设置


PERMANENT_SESSION_TIMELIFE = timedelta(days=2,hour=16)   # session的过期时间默认为 31天
'SESSION_COOKIE_NAME':                  'session',       # session在浏览器中的名字  默认为session
'SESSION_COOKIE_DOMAIN':                None,            # session的path生效域名
'SESSION_COOKIE_PATH':                  None,            # session的生效路径
'SESSION_COOKIE_HTTPONLY':              True,            # 是否只使用http的方式来传替session
'SESSION_COOKIE_SECURE':                False,
'SESSION_COOKIE_SAMESITE':              None,
'SESSION_REFRESH_EACH_REQUEST':         True,
	
更多详情请看  app.config 中的default配置


设置session的持久化  


session.permanent = True


csrf 攻击原理 与csrf_token的使用

在flask中的csrf是不存在的需要使用 flask_wtf 来实现对csrf攻击的防范

1,什么是csrf攻击(跨站脚本攻击)?
​ csrf攻击与xss攻击的区别在与xss攻击是在站内利用富文本的漏洞上传一些恶意脚本,盗取用户的cookie,或在站内大量的发布广告,跳转等js脚本,虽然xss也有用户信息被盗取的危险

​ 但是在csrf跨站脚本攻击中的攻击发起并不是存在于站内的脚本 通俗说就是钓鱼网站通过js,让浏览器发起向目标网站的静默提交以达到非法操作的目的,虽然服务器能够在站内防范xss

​ 的方式来防止这种攻击但是在站外服务器根本无法分辨是不是用户允许的提交,这时就应该使用csrf验证来实现通过在用户的session中设置随机字符串和在网页中设置随机字符串,下一次

​ 提交表单的时候将随机字符串提交到服务器,服务器以此来字符串分辨是否是该用户session中的id ,csrf验证字符串必须存在于请求的数据或或者请求头当中,由于浏览器的同源策略,使

​ 恶意网站并不能够使用与访问其他网站的cookie中的session数据,而且csrf验证的字符串除了是随机字符串而且还是加密形式存放到session中的

flask中的csrf验证
#flask框架本身是不具备csrf防御的功能的快速开发需要使用第三方插件


#在代码中的使用
from flask_wtf.csrf improt CSRFProtect

CSRFProtect(app)

#在模板中的使用

{{ csrf_tokent }}


<form action='' method='post'>
	<input type='hide' value={{ csrf_token() }} name='csrf_token'>
</form>

#在ajax当中的使用
1,提交表单(csrf_token在提交的数据当中)
$.ajax({
    url:'提交地址',
    method:'post',
    data:{csrf_token:'使用js获取到的csrftoken'}
    success:funcion(data){执行的代码},
    field:funcion(data):{失败执行的代码}
})

2,在提交的header中使用

$.ajaxSetup(
	{
        headers:{
            X-CSRFToken:'获取到的值'
        }
	}
)
同样的   csrf_token 的值也可以从cookie中获取, 使用jquery的$.cookie方法获取cookie中的csrf_token




使用flask_wtf表单来验证上传的文件


from flask_wtf.file import FileRequired,FileAllowed


FileRequired  文件是否非空  
FileAllowed   文件上传的类型




flask的 线程隔离对象 app上下文, request上下文, app线程隔离栈 request线程隔离栈 g 线程对象



什么是线程隔离对象 , ? 

线程隔离对象就是在数据某种需要下在线程当中隔离访问  即每一个一个线程具有属于当前线程的一份数据  线程与线程之间的隔离数据互不干扰
flask中的线程隔离对象的实现是基于字典来实现的,与  `from threading import local` 中的local不同的是flask中的线程隔离对象是使用来自werkzeug中的Local功能更加丰富 
在werkzeug中的Local对象 除了核心的  __setattr__ __getattr__ __delattr__ 之外  还具有 释放当前线程的内存数据,即线程结束后删除线程在线程隔离对象中残留的数据  
也实现了获取当前线程隔离对象中所有的线程的数据方法,相当于对threading中的local对象进行了进一步的封装


线程隔离栈 LocalStack

Localstack中封装维护着一个Local对象 在Local对象中封装维护着一个列表  LocalStack利用列表的append与[-1]切片操作实现了push pop 的一个栈结构的对象以及 property装饰器装饰的top方法该方法永远指向LocalStack中的栈顶元素


flask中的线程隔离对象
current_app:指向app栈的栈顶元素
request:指向request栈的栈顶元素
session:指向request栈 的栈顶元素中的session属性
g: 可扩展的线程隔离对象  用于开发者快速的解决问题  场景用途在于多个方法之间的参数传递

from flask import request,session,current_app,g

关于之前各种工具的总结


自定义路由匹配器
from werkzeug.routing import BaseConverter

自定义response
from flask import Response
app.response_class = '自定义‘

自定义模板过滤器
@app.template_filter

自定义宏
{% macro  %}
{% endmacro %}





钩子函数



	在flask中的钩子函数与django的middlewear相似但是有所不同的是在flask中的钩子函数更加简单
	flask的钩子函数使用装饰器的形式来进行中间件的实现,以下是常用的钩子函数装饰器   源码稍后讲解
	
	
	from flask import Flask
	
	app = Flask(__name__)
	
	@app.first_before_request(self,f)
	在部署项目的时候  只有第一次请求才会生效的钩子函数本身不接收任何的参数,假设该函数具有返回值,则后面跳过所有的函数包括视图函数直接执行after_request中的函数
	
	@app.before_request(self,f)
	执行于first的后面 每次请求都会执行before_request,可以在before中做referrer效验  session验证等同样的  该函数具有返回值会跳过后面所有的函数进行after_request函数
	
	@app.context_processor(self,f)
	该函数必须具有返回值  且该返回值必须是一个字典,字典中的返回值在所有的模板文件中都可以使用  该函数必须具有返回值 且返回值为字典的形式   该函数会在before_request函数后执行
	并且值得注意的是假设返回的值并不需要渲染模板的话该方法并不会执行,只要使用模板,该方法一定会执行,尽管在模板中有时候使用不到
	
	@app.errorhandler(self,f,错误代码)
	该方法装饰的函数接收一个参数  该参数是一个Exceptopn对象  对象中包含了视图函数内错误代码抛出的异常,可以使用该方法返回特定的状态码页面
	
	@app.teardown_appcontext(self,f)
	该方法的装饰函数会接收一个参数,该参数是一个异常信息,假设没有异常 那么该参数是none  被该方法装饰的函数无论视图函数会不会抛出异常都会被执行
	
	@app.after_request(self,f)
	被该方法装饰的参数会接收一个response对象,被该函数装饰的函数必须返回一个Response对象或者 字符串 元组 
	
	
	以上的装饰器执行按从上倒下的顺序执行   first_before_request   brfore_request  context_processor errorhandler  teardown_appcontext after_request
	
	在此处更值得注意的是在视图函数抛出异常的时候@app.context_processor() 装饰器的方法并不会一定会执行,因为在执行视图函数的时候抛出异常,后续钩子函数未调用模板的话该方法并不会执行
	
	
	
	关于钩子函数源码的简单讲解:
	
	在装饰器中的代码
	
	self.first_before_request_funcs.append(fn)
	在装饰器中的存储中间件即钩子函数只有first_before_request是列表而其他方法存储的方式都为字典  使用setdefault的方式获取键值是None的钩子函数列表  使用setdefault的方式添加钩子函数
    
   

以下代码是flask执行钩子函数的代码

before_request方法


    def preprocess_request(self):
        """Called before the request is dispatched. Calls
        :attr:`url_value_preprocessors` registered with the app and the
        current blueprint (if any). Then calls :attr:`before_request_funcs`
        registered with the app and the blueprint.

        If any :meth:`before_request` handler returns a non-None value, the
        value is handled as if it was the return value from the view, and
        further request handling is stopped.
        """

        bp = _request_ctx_stack.top.request.blueprint

        funcs = self.url_value_preprocessors.get(None, ())
        if bp is not None and bp in self.url_value_preprocessors:
            funcs = chain(funcs, self.url_value_preprocessors[bp])
        for func in funcs:
            func(request.endpoint, request.view_args)

        funcs = self.before_request_funcs.get(None, ())
        if bp is not None and bp in self.before_request_funcs:
            funcs = chain(funcs, self.before_request_funcs[bp])
        for func in funcs:
            rv = func()
            if rv is not None:
                return rv
	'''
	在代码中不难看出flask操作app线程隔离栈  使用LocalStack 的top方法获取栈顶的request上下文对象  取出reuqest对象中的请求参数来验证该请求的是否是蓝图下的url 
	
	`if bp is not None and bp in self.url_value_preprocessors`这段代码中   bp 为栈顶request上下文的blueprint请求参数  
	
	'''


flask中的信号



flask中的信号是由第三方工具blinker来完成的  可以使用信号来进行日志的记录,用户行为的监控

blinker 的安装  pip install blinker

信号的使用

1  声明namespace

from blinker import Namespace
long = Namespace()

2 声明信号对象
login = long.single('登录')


3 需要绑定的函数
def test(blinker ,user_name):
	print()


4 监听信号
login.connect(test)


5 发出信号信息
login.send(user_name='long')



`信号的基本应用场景`: 日志文件的记录,监控分析类爬虫行为,并分析行为进行反爬取,对用户的登录行为分析,
对于短时间多个不同ip进行登录,对用户的账号进行风险管控,防止用户因木马被盗取私人信息



flask中的内置信号量

from flask import before_template_render,template_rendered,request_start,request_finished,request_tearing_down,app_context_down,appcontext_pushed,appcontext_poped,message_flashed

## 以上是flask中内置的信号量从较为陌生的讲起

1, message_flashed
	该信号量的触发是在进行调用flask中的flash方法执行





memcached 使用安装


安装
sudo apt-get install memcached 

pip install python_memcached


启动参数介绍

-m memcached在内存中占用的最大空间以M为单位
-l 启动的ip
-p 启动的端口默认为11211
-u 设置启动的用户
-d 设置是否为后台运行 假设没有-d参数 memcached程序会一直在控制台中阻塞
-c 客户段允许的最大链接数
-R 该参数表示同一个客户端能够连续处理的最大请求数




memcached的使用

1,在控制台中使用memcached 
telnet 127.0.0.1 11211




2,链接到memcached中的时候会提示以下信息

Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.




3,telnet命令行的基本使用


设置值 set

set key[键名] 0[是否压缩] 60[过期时间 s] 3[字符长度]
aaa[设置的值]







获取值 get

get key[键名]



获取值  gets
与get不同的是gets会返回设置值的顺序 是否压缩 长度
实例如下

代码实例1537885606984

可以看出在上方的gets命令中会返回值的 是否压缩 长度 设置的顺序






添加值 add  假设所设的键值已经存在则设置失败

add key[键名] 0[是否压缩] 60[过期时间] 3[字符长度]
aaa[设置的值]








追加   append    将值添加到指定的键值后面(字符串拼接)

append key[键名] 0[是否压缩] 60[过期时间] 3[字符长度]
aaa[设置的值]





在数据首部插入 perpend

perpend key[键名] 0[是否压缩] 60[过期时间] 3[字符长度]
aaa[设置的值]






删除值 delete

delete key[键名]





清空所有值  flush_all

flush_all 



###############################################################################################################################################

memcached中的运算操作

准备操作
set a 0 300 1
1


加  incr  incr操作只能是数字与数字时间 假如是字符串 则会报错

	incr a[键名] 1[值]  



减 decr
	decr a[键名] 1[值] 
	
	
	
	
##################################################################################################################################################
其他

stats  查看


redis的安装与使用

1 安装与启动

	sudo apt-get install redis-server
	
	sudo service redis-server start
	或者
	redis-server -h 地址 -p 端口
    
    
3 进入redis
	redis-cli -h host -p port
	
	
4 
	
	set key value EX time #  设置 值 与过期时间
	
	setex key time value  # 设置值与过期时间
	
	setrange key offset value # 替换字符串 offset参数设置被替换字符串的开始被替换的位置 假设key为空 则偏移位置使用 /x00替代
	
	setnx key value Ex time # 设置值 假设key存在不做动作
	
	mset key1 value1 key2 value2 # mset可以同时设置多个值
	
	msetnx key1 value1 key2 value2 # msetnx #同时设置多个值 key存在不做动作
	
	
	
	
	
	
	
	
	
	get key  #获取值
	
	getrange key start end #获取字符串指定位置的子字符串  注意该命令的获取值是闭区间的 即是顾头顾尾 
	
	getset key value  #获取旧值 设置新值假设值不存在返回错误
	
	mget key1 key2 key3 #同时获取多个值
	
	
	del key
	
	incr decr 自增1 自减1
	
	incrby incrbyfloat decrby  加减操作 
	
	append  追加
	




[http://redisdoc.com/index.html]?redis document

列表操作


	添加值
	
	lpush rpush  在列表的左侧右侧添加值 假设key没有值 则会创建一个列表
	lpop rpop    在列表的左侧或者右侧删除并获取一个值,假设key没有值 返回nil
    
    blpush brpush blpop brpop 阻塞获取 假设给定的key不存在值则阻塞到其他客户端给定值 用法与非阻塞的相同
    lpushx rpushx 只有在key中存在值的时候才会执行动作
    
    
    
    
    
    
    lindex lrange 获取列表中的值 
    
    lindex username 1        获取key为username下标为1的值
    lrange username 0 -1     获取key为username 下标从0到-1的值  即所有的值  lrange就是切片
    
    
    
    
    
    
    
    
    lset  修改设置列表中的值
    
    lset username 1 long  	 将username中下标为1的数字修改为long  假设索引超出列表的索引范围报错
    
    

	lrem 删除列表中的值   lrem key count value
	
	lrem username 0 long     删除列表中 所有为long的值
	lrem username 3 long     删除列表中 从下标3 开始到尾部的 所有为long的值
	lrem username -1 long    删除列表中 从下标为-1 开始到头部的 所有为long的值
	
	
    count > 0: 从头往尾移除值为 value 的元素。
    count < 0: 从尾往头移除值为 value 的元素。
    count = 0: 移除所有值为 value 的元素。

	其他操作
	
	llen 长度
	


集合操作


添加

sadd user long   向集合中添加一个值
spop user        随机删除集合中的一个值
sdiffstore a key1 key2  取key1 key2 集合中的差集 并添加到 a中
sinterstore a key1 key2 取key1 key2 集合中的交集 并添加到a中
sunionstore a key1 key2      取key1 key2 集合中的并集 并添加到a中


查询

smembers key1                 查询key1集合所有的值
sdiff key1 key2				  查询key1 key2 的差集
sunion key1 key2			  查询key1 key2 的并集
sinter key1 key2			  查询key1 key2 的交集
srandmember key1	          随机获取一个集合中的元素
sismember key1 'a'            查询是否是集合中的元素
scard key1                    查询集合中的个数


修改

smove key1 key2 'a'        讲 key1中的'a' 移动到key2中 假设key1或者key2不存在终止操作  key2中包含了'a'则只会删除key1中的值


删除

srem key1 'a'      删除key1中的为'a'的值

哈希操作


##哈希操作就是在key中设置另外一个key

新加值

hset hmset hsetnx 


hset key field value                        设置值 假设key存在field存在覆盖值
hmset key field1 value1 field2 value2       设置多个假设存在则覆盖
hsetnx key field1 value1                    设置多个值假设field不存在的时候才会执行动作


hgetall key                   获取所有
                
hget key field                获取指定字段值

hmget key field1 field2       获取多个值
 
hlen key                      获取哈希中的Field个数

hstrlen key field             获取哈希中字段的字符长度

hvals key                     获取哈希中的值

hkeys key                     获取哈希中的field

hdel key field                删除字段

hexists key field             字段是否存在

hincrby key field value       加运算

hincrfloat key field value    浮点加运算

发布与订阅





使用 subscribe 频道名称来订阅频道,使用该命令后控制台会阻塞



使用publish 频道名字 信息





python中使用 redis模块来操作redis数据库

使用PIL自动生成图形验证码


from PIL import Image, ImageFont, ImageDrow

image = Image.new('RGB',(宽,高),颜色(0~255))
drow = ImageDrow.Drow(image,模式:RGB)
font = ImageFont.truetype('字体文件路径',大小)

drow.line((点一x,点一y,点二x,点二y),颜色(0~255,0~255,0~255)) 
drow.point((x,y),颜色(0~255,0~255,0~255))
drow.text((x,y),文本,实例化的font对象)


image.save(io对象, '模式')



alembic插件的使用方式

1, 初始化生成迁移文件

	alembic init dir_name   初始化alembic 


2, 配置
	alembic.ini 配置文件
	
3, 生成迁移文件
	alembic revision --autogenerate 
	
4, 将迁移文件转移到数据库
	alembic upgrade heads
	
5, 数据库版本降级
	alembic downgrade 版本号
	


celery 的基本使用

什么是celery
celery是一个异步的消息队列,用于提升代码的执行效率,但是在celery中的执行的任务容易丢失失败

celery的角色
task   broker   worker     backend
任务    中间人   异步线程   任务结果的后台存储



redis的安装  
pip install redis

celery的安装
pip install celery

pool的安装 
pip install solo

实例化 Celery对象


from celery import Celery

celery = Celery(__name__ ,backends='redis://127.0.0.1:6379/0',broker:'redis://127.0.0.1:6379/0')

@celery.task
def test():
	print('任务')
	


if __name__ == '__main__':
	test.dlay()


启动celery

通过终端命令开启celery 
celery -A 模块名字.实例化对象 -pool solo worker --loglevel=info

ajax实现文件上传以及读取文件内容


由于使用的是jquery来发送的请求  首先导入jquery


1,获取input中的文件对象
file = $('#file').prop('files')[0]

2,实例化FormDate对象

data = new FormDate()

data.set('file',file)


3,发送ajax请求

$.ajax(
	{
        url:'',
        type:'POST',
        processData:False;
        contenType:false,
        dataType:'json',
        data:data,
        suessce:function(datas){
            
        },
        error:function(){
            
        }
	}
)


读取文件使用 FileReader类来实现对文件的读取

reader = new FileReader();
reader.readAsText('此处的参数是使用prop获取到的');
reader.onload = function(){
	文件读取完成之后的回调函数
};


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值