Flask 自建擴充套件

lghgo發表於2022-03-07
  • 自建擴充套件介紹

    • Flask擴充套件分兩類
      1. 純功能, 如: Flask-Login 提供使用者認證
      2. 對已有的庫和工具包裝(簡化繼承操作,並提供有用的功能,更方便)
        如: Flask-SQLAlchemy 包裝了 SQLAlchemy
    • 涉及的 python 包
      1. setuptools
      2. wheel
      3. twine: 釋出python 包 (釋出到 PyPI 後才能使用 pippipenv 安裝)
      4. readme_renderer: 將 mdrsttxt 文字 渲染成.html
    • 命名:
      1. 擴充套件的名稱: Flask-<功能/第三方庫名> 或 <功能/第三方庫名>-Flask
      2. 擴充套件的包名: flask_<功能/第三方庫名> (小寫加下劃線)
  • 擴充套件類實現

    • 編寫擴充套件類(以 Flask-Share 為例)
      • 使用擴充套件步驟: 匯入擴充套件類 - 例項化 - 傳入 app 初始化
        from flask_share import Share
        share = Share()  # extensions.py 中統一例項化所有擴充套件
        share.init_app(app) # 在工廠函式中統一初始化所有擴充套件
        # 也可以一步到位
        # share = share(app) 
        
      • 新建擴充套件類 (flask_share/__init__.py)
        class Share(object):
          def __inti__(self, app=None):
            self.init_app(app)
            
          def init_app(self, app):
            # 相容 0.7 以前版本
            if not hasattr(app, 'extensions'): 
              app.extensions={}
              
            # 在 app 應用中儲存所有擴充套件例項, 可驗證擴充套件是否完成例項化
            app.extensions['share'] = self
            
            # 擴充套件類新增到模板上下文中
            app.jinja_env.globals['share'] = self
            # app.context_processor(lambda:{'share': self})
            
            # 擴充套件配置, 初始化後新增到 app.config 中, 以 SHARE_ 開頭避免衝突
            app.config.setdefault('SHARE_SITES', 'weibo,wechat,douban,facebook,twitter,google,linkedin,qq,qzone')
            app.config.setdefault('SHARE_MOBILESITES','weibo,douban,qq,qzone')
            app.config.setdefault('SHARE_HIDE_ON_MOBILE', False)
            app.config.setdefault('SHARE_SERVER_LOCAL', False) # 是否使用內建資源
        
    • 實現擴充套件功能
      • 載入靜態資源
        class Share(object):
          @staticmethod
          def load(css_url=None, js_url=None):
            
            if current_app.config('SHARE_SERVE_LOCAL'):# 使用本地進入條件
              css_url = url_for('share.static', filename='css/share.min.css')
              js_url = url_for('share.static', filename='js/share.min.js')
            
            if css_url is None:
              css_url = 'https://cdn.bootcss.com/social.share.js/1.0.16/css/share.min.css'
            if js_url is None:
              js_url = 'https://cdn.bootcss.com/social-share.js/1.0.16/js/social-share.min.js'
            return Markup('''<link rel="stylesheet" href="%s">\n
            	<script src="%s"></script>'''% (css_url, js_url))
          
          def init_app(self, app):
            # app.static_url_path 的引用是為了和使用者設定一致
            blueprint = Blueprint('share', __name__, static_folder='static',
                                 static_url_path='/share'+ app.static_url_path)
            app.register_blueprint(blueprint)
            
            
          
        
      • 建立前端分享元件
        class Share(object):
          @staticmethod
          def create( title='', sites=None, mobile_sites=None,align='left',addtion_class=''):
            if sites is None:
              sites = current_app.config['SHARE_SITES']
            if mobile_sites is None:
              mobile_sites = current_app.config['SHARE_MOBILE_SITES']
              
            return Markup('''
            	<div class="social-share %s" data-sites="%s" data-mobile-site="%s"align="%s">
                %s</div>'''%(addition_class, sites, mobile_sites,align, title ))
            
        
      • 在模板中使用
        {{ share.create('分享到:') }}
        
  • 開源釋出準備

      1. 新增文件字串與註釋後的完整程式碼
      """
          Flask-Share 
          # ~~~~~~~~~~~~~~ 
          Create social share component in Jinja2 tempalte based on share.js. 
          :copyright: (c) 2017 by Gavin Li. 
          :license: MIT, see LICENSE for more details. 
      """
      
      import re 
      from flask import current_app, url_for, Markup, Blueprint, request
      
      class Share(object):
          
        @staticmethod
        def load(css_url=None, js_url=None):
          """ Load share.js resourse.
          
          :param css_url: if set, will be used as css url
          :param js_url: if set, will be used as js url
          :param serve_local: if set to True, the local resource will be used
          """
        
        @staticmethod
        def create( title='', sites=None, mobile_sites=None,align='left',addtion_class=''):
          """ Create a share component.
          
          :param title: the prompt displayed on the left of the share component.
          :param sites: a string that consist of sites, separate by comma.
          :param mobile_sites: a string that consist of sites, separate by comma.
          	supported site name: weibo, wechat, douban, facebook, twitter, google, linkedin, qq, qzone."
              for example: weibo,wechat, qq.
          :param mobile_sites: the sites displayed on mobile.
          :param align: the align of the share component,default to '`left`'.
          :param addition_class: the style class added to the share component.
          """
         
      
      1. 編寫 README 與文件
      • 小專案 直接用 README概括所有的必需的說明
      • 大專案 比較複雜的,多檔案組織文件內容
        將專案部署到 Read the Docs
        Sphinx + Github + Readthedocs的工作流編寫和部署文件
      1. 定義 python 包的後設資料:(setup.py)
      """
      	Flask-Share
          
          Create social share component in Jinja2 template based on share.js.
          :copyright: (c) 2022 by Gavin li.
          :license: MIT, see LICENSE for more details.
      """
      form os import path
      from codecs import open
      form setuptools import setup
      
      basedir = path.abspath(path.dirname(__file__))
      
      # Get the long description from the README file
      with open(path.join(basedir,'README.md'), encoding='utf-8') as f:
        long_description = f.read()
        
      setup(
        name='Flask-Share', # 包名稱
        version='0.1.0',  # 版本
        url='https://github.com/lghpython/flask-share',
        license='MIT', 
        author='xxx'
        author_email='xx@xx.com',
        description='xxx',
        long_description=long_description,
        long_description_content_type='text/markdown', # 預設渲染格式為 rst
        platforms='any',
        packages=['flask_share'], # 包含的包列表,包括子包,可用find_pakages()
        zip_safe=False,
        test_suite='test_flask_share', 測試包或模組
        include_package_data=True, 
        install_requires=['Flask'],  # 安裝依賴
        keywords='flask extension development', # 專案關鍵詞
        classifiers=[ # 分類詞, 在 PyPI 中設定分類
          'DevelopmentStatus::3-Alpha',
          'Environment::WebEnvironment',
          'IntendedAudience::Developers',
          'License::OSIApproved::MITLicense',
          'ProgrammingLanguage::Python',
          'ProgrammingLanguage::Python::2',
          'ProgrammingLanguage::Python::2.7',
          'ProgrammingLanguage::Python::3',
          'ProgrammingLanguage::Python::3.3',
          'ProgrammingLanguage::Python::3.4',
          'ProgrammingLanguage::Python::3.5',
          'ProgrammingLanguage::Python::3.6',
          'Topic::Internet::WWW/HTTP::DynamicContent',
          'Topic::SoftwareDevelopment::Libraries::PythonModules']
        ],
      )
      
      1. 指定打包其他檔案: MANIFEST.in
        需要在 setup()方法中設定: include_package_data=True
      graft flask_share/static
      include LICENSE test_flask_share.py
      # exclude 用來排除匹配檔案
      # recursive-include 遞迴匹配
      # recursive-exclude 遞迴排除匹配
      # graft 目錄 包含目錄下所有
      # prune 目錄 配出目錄下所有
      
      1. 編寫單元測試
      import unittest
      
      from flask import Flask, render_template_string, current_app
      from flask_share import Share
      
      class ShareTestCase(unittest.TestCase):
        
        def setUp(self):
          self.mobile_agent={{'HTTP_USER_AGENT':'Mozilla/5.0(iPhone;CPUiPhoneOS9_1likeMacOSX)\
          	AppleWebKit/601.1.46(KHTML,likeGecko)Version/9.0Mobile/13B143Safari/601.1'}}
      	app = Flask(__name__)
          app.testing=True
          self.share=Share(app)
          
          @app.route('/')
          def index():
            return render_template_string('{{share.load() }}\n {{share.create() }}')
          # 推送上下文
          self.context=app.app_context()
          self.context.push()
          self.client - app.test_client()
          
        def tearDown(self):
          self.context.pop()
          
        def test_create_on_mobile(self):
          current_app.config['SHARE_HIDE_ON_MOBILE'] = True
          response = self.client.get('/', environ_base=self.mobile_agent)
          data = response.get_data(as_text=True)
          self.assertIn('social-share.min.js', data)
          self.assertNotIn('<div class="socail-share"', data))
       
      
      1. setup.cfg
  • 釋出到 PyPI

    • 建立 PyPI 賬號
      • 註冊訪問
      • 方便訪問: 建立 .pypirc檔案, 放置$HOME/.pypirc(win) 或~/.pypir(mac linux) 明文密碼限制訪問許可權
        [distutils]
        index-servers=
        	pypi
            
        [pypi]
        username: 使用者名稱
        password: 密碼
        
    • setuptools 打包
      • 建立 Source Distributions 包
        python setup.py sdist
        
      • 建立 Wheel 包
        python setup.py bdist_wheel
        
      • 合併命令
        python setup.py sdist bdist_wheel
        
    • twine 上傳
      • 安裝 twine
        pipenv install twine --dev
        
      • 上傳
        twine upload dist/*
        
  • 編寫良好的擴充套件

    • 命名規範(Flask-Foo 或 Foo-Flask)
    • 使用相對寬鬆的開源許可證(MIT/BSD)
    • 支援工廠模式(新增 initi_app() 方法)
    • 支援同時執行多程式例項( 使用 current_app 獲取程式例項)
    • 包含 setup.py指令碼,並列出所有安裝依賴(必需)
    • 包含單元測試
    • 編寫文件並線上釋出
    • 上傳到 PyPI

相關文章