• Python包管理不同方式的区别

    Python包管理不同方式的区别

    学习Python已经有一段时间,经常会遇到安装各种包的问题,一会 setup.py, 一会 easy_install,一会又是pip,还有一些概念比如distutilssetuptools等等,搞不清楚谁是谁,什么时候应该用什么,今天就把这些概念 澄清一下。

    distutils

    distutils是Python标准库的一部分,其初衷是为开发者提供一种方便的打包方式, 同时为使用者提供方便的安装方式。

    Read More ...
  • web.py 源代码分析之 web.test 主要文件及测试流程

    目录文件说明

    README

    如何运行测试文件,包含全部测试及分模块测试

    调用

    $ python test/alltests.py
    

    运行全部测试

    调用

    $ python test/db.py
    

    运行db模块测试

    alltest.py

    运行全部测试入口,调用 webtest 模块完成测试

    # alltest.py
    
    if __name__ == "__main__":
        webtest.main()
    

    webtest.py

    我们发现 webtest.py 中并没有 main 函数,而是从 web.test 中导入,

    # webtest.py
    from web.test import *
    

    也就是说,如果 web.test中有main函数的话,webtest.main() 其实是调用 web.test 中的main函数。

    感觉~ 好神奇

    web.test

    看web目录下的test.py文件,果然发现了main函数,终于找到入口啦~

    def main(suite=None):
        if not suite:
            main_module = __import__('__main__')
            # allow command line switches
            args = [a for a in sys.argv[1:] if not a.startswith('-')]
            suite = module_suite(main_module, args or None)
    
        result = runTests(suite)
        sys.exit(not result.wasSuccessful())
    

    把这个main函数改掉,再运行一下:

    $ python test/alltests.py
    

    果然是运行修改后的函数,所以这里确定是入口。

    在进入下一步之前,我们需要学习一下Python自动单元测试框架,即unittest模块。关于 unittest ,可以参考这篇文章: Python自动单元测试框架

    Read More ...
  • web.py 源代码分析之 web.test.application

    分模块测试

    application.py

    对 application.py 的测试,调用命令:

    python test/application.py
    
    Read More ...
  • web.py 源代码分析之 web.test.application.testRedirect

    分模块测试

    application.py

    对 application.py 的测试,调用命令:

    python test/application.py
    
    Read More ...
  • web.py 源代码分析之 web.test.application.test_UppercaseMethods

    分模块测试

    application.py

    对 application.py 的测试,调用命令:

    python test/application.py
    
    Read More ...
  • web.py 源代码分析之 web.test.application.test_reloader

    分模块测试

    application.py

    对 application.py 的测试,调用命令:

    python test/application.py
    

    test_reloader(self)

    def test_reloader(self):
        write('foo.py', data % dict(classname='a', output='a'))
        import foo
        app = foo.app
    
        self.assertEquals(app.request('/').data, 'a')
    
        # test class change
        time.sleep(1)
        write('foo.py', data % dict(classname='a', output='b'))
        self.assertEquals(app.request('/').data, 'b')
    
        # test urls change
        time.sleep(1)
        write('foo.py', data % dict(classname='c', output='c'))
        self.assertEquals(app.request('/').data, 'c')
    

    总的来说,这个过程会生成一个 foo.py 文件

    import web
    
    urls = ("/", "a")
    app = web.application(urls, globals(), autoreload=True)
    
    class a:
        def GET(self):
            return "a"
    

    这是一个典型的 web 服务端应用程序,表示对 / 发起 GET 请求时,会调用 class a 中的 GET 函数,测试就是看看 web.application 是否可以正常完成任务,即是否可以正确返回"a"

    下面详细看代码。

    首先使用 write 生成了一个 foo.py 程序:

            write('foo.py', data % dict(classname='a', output='a'))
    

    write 源代码:

    def write(filename, data):
        f = open(filename, 'w')
        f.write(data)
        f.close()
    

    data 定义: ```python data = """ import web

    urls = ("/", "%(classname)s") app = web.application(urls, globals(), autoreload=True)

    class %(classname)s: def GET(self): return "%(output)s"

    """ ```

    data 相当于一个小型 web 程序的模板,类名和返回值由 一个 dict 指定,生成一个字符串,由 write 生成文件。

    下面是类别和返回值为 a 时的 foo.py

    import web
    
    urls = ("/", "a")
    app = web.application(urls, globals(), autoreload=True)
    
    class a:
        def GET(self):
            return "a"
    

    测试的方式采用 TestCase 中的 assertEquals 函数,比较 实际值与预测值。

    import foo
    app = foo.app
    self.assertEquals(app.request('/').data, 'a')
    

    app.request('/') 会得到一个Storage类型的值:

    <Storage {'status': '200 OK', 'headers': {}, 'header_items': [], 'data': 'a'}>
    

    其中的 data 就是 foo.pyGET 返回的值。

    我对这个 app.request('/') 是比较困惑的。以 foo.py 为例, 之前写程序时,一般是有一个这样的程序:

    import web
    
    urls = ("/", "a")
    app = web.application(urls, globals(), autoreload=True)
    
    class a:
        def GET(self):
            return "a"
    
    if __name__ == "__main__":
        app.run()
    

    然后在浏览器中请求 0.0.0.0:8080/ 。 而在 request 之前,也没看到 run 啊,怎么就能 request 回 数据呢,而且通过上述代码运行后,程序会一直运行直到手动关闭, 而 request 的方式则是测试完后,整个程序也结束了。

    所以,下一部,想比较一下 application.runapplication.request 的不同。

    我们只看关键部分,即返回的值是如何被设值的。

    web.application.request 中:

    def request(self, localpart='/', method='GET', data=None,
                host="0.0.0.0:8080", headers=None, https=False, **kw):
    
        ...
    
    response = web.storage()
    def start_response(status, headers):
        response.status = status
        response.headers = dict(headers)
        response.header_items = headers
    response.data = "".join(self.wsgifunc()(env, start_response))
    return response
    

    上述代码中 self.wsgifunc()(env, start_response) 比较另人困惑, 我还以为是调用函数的新方式呢,然后看了一下 wsgifunc 的代码, 它会返回一个函数wsgiwsgi(env, start_response) 为参数。 在 wsgi 内部,又会调用 handle_with_processors, handle_with_processors 会再调用 handle

        def handle(self):
            fn, args = self._match(self.mapping, web.ctx.path)
            return self._delegate(fn, self.fvars, args)
    

    测试了一下,self._match() 会得到类名, self._delegate 会 得到返回的字符串,即 GET的返回值。

    进入 self._delegate, 会最终调用一个关键函数 handle_class:

    def handle_class(cls):
        meth = web.ctx.method
        if meth == 'HEAD' and not hasattr(cls, meth):
            meth = 'GET'
        if not hasattr(cls, meth):
            raise web.nomethod(cls)
        tocall = getattr(cls(), meth)
        return tocall(\*args)
    

    参数cls值为foo.a, meth 会得到方法名 GET, 然后 tocall 会得到函数 a.GET, 至此,我们终于得以调用, GET函数,得到了返回的字符串。

    从整个过程可以看出,没有启动服务器的代码,只是不断地调用 函数,最终来到 GET 函数。

    Read More ...
  • web.py 项目架构分析之 skeleton

    web.py 项目之 skeleton

    skeleton 是 web.py 官网上给出的一个最简单的项目结构示例。

    目录树

    .
    └── src  
        ├── code.py  
        ├── config.py  
        ├── db.py  
        ├── sql
        │   └── tables.sql
        ├── templates
        │   ├── base.html
        │   ├── item.html
        │   └── listing.html
        └── view.py
    
    3 directories, 9 files
    

    结构分析

    控制模块

    #code.py
    
    import web
    import view, config
    from view import render
    
    urls = (
        '/', 'index'
    )
    
    class index:
        def GET(self):
            return render.base(view.listing())
    
    if __name__ == "__main__":
        app = web.application(urls, globals())
        app.internalerror = web.debugerror
        app.run()
    

    code 模块作为入口:

    • app 的创建与启动
    • url 与 处理类的映射与处理入口

    但是,具体的处理并不在这里实现。而是放在了 view 模块中。

    这一模块是MVC中的C吗?
    

    显示模块

    #view.py
    
    import web
    import db
    import config
    
    t_globals = dict(
      datestr=web.datestr,
    )
    render = web.template.render('templates/', cache=config.cache, 
        globals=t_globals)
    render._keywords['globals']['render'] = render
    
    def listing(\**k):
        l = db.listing(\**k)
        return render.listing(l)
    

    从这里可以看出 view 模块

    • 与模板关联
    • 从数据库中取数据,然后发给模板

    我们再来看看模板:

    <!--
        templates/base.html
    -->
    
    $def with (page, title=None)
    <html><head>
    <title>my site\
    $if title: : $title\
    </title>
    </head><body>
    <h1><a href="/">my site</a></h1>
    $:page   
    </body></html>
    

    base.html 是所有模块的公共部分,每个模块只需要提供 它的 page ,即内容就可以了。

    <!--
        templates/listing.html
    -->
    
    $def with (items)
    
    $for item in items:
        $:render.item(item)
    
    这一模块是MVC中的V吗
    

    数据操作

    数据库操作分三部分

    • sql/tables.sql 数据库表定义
    • config.py 数据库连接
    • db.py 数据库操作
    /* tables.sql */
    CREATE TABLE items (
        id serial primary key,
        author_id int references users,
        body text,
        created timestamp default current_timestamp 
    );
    
    #config.py
    
    import web
    DB = web.database(dbn='mysql', db='skeleton', user='root', pw='xx')
    cache = False
    
    # db.py
    import config
    
    def listing(\**k):
        return config.DB.select('items', **k)
    
    这是MVC中的M吗
    

    这是 web.py 中最基本的一个项目结构。应该是使用的MVC设计模式。但是 因为程序本身不大,还体会不到 MVC 的好处。

    Read More ...
  • web.py 项目架构分析之 todolist

    web.py 项目之 todolist

    目录树

    .
    └── src
        ├── model.py
        ├── schema.sql
        ├── templates
        │   ├── base.html
        │   └── index.html
        ├── todo.py
    
    2 directories, 8 files
    

    项目说明

    项目来自 webpy.org, 主要实现一个基于web的 todolist,就是可以添加删除一些任务。 非常简单

    结构分析

    控制模块

    控制模块只有todo.py

    """ Basic todo list using webpy 0.3 """
    import web
    import model
    
    ### Url mappings
    
    urls = (
        '/', 'Index',
        '/del/(\d+)', 'Delete'
    )
    
    Read More ...
  • web.py 项目架构分析之 blog

    web.py 项目之 blog

    目录树

    src/
    ├── blog.py
    ├── model.py
    ├── schema.sql
    └── templates
        ├── base.html
        ├── edit.html
        ├── index.html
        ├── new.html
        └── view.html
    
    1 directory, 8 files
    

    项目说明

    项目来自 webpy.org, 主要实现一个基于web的博客系统,实现了基本的增删查改。

    结构分析

    控制模块

    控制模块包括 blog.py

    """ Basic blog using webpy 0.3 """
    import web
    import model
    
    ### Url mappings
    
    urls = (
        '/', 'Index',
        '/view/(\d+)', 'View',
        '/new', 'New',
        '/delete/(\d+)', 'Delete',
        '/edit/(\d+)', 'Edit',
    )
    
    Read More ...
  • web.py 项目架构分析之 googlemodules

    web.py 项目之 googlemodules

    项目说明

    项目来自 webpy.org, 这是一个真实的在线上运行的 项目: Google Modules, 可以上传,下载, 一些模块,还有一些评分,打标签等等功能。(不过这网站挺奇怪的。。。)

    目录树

    src/
        ├── application.py
        ├── forum.py
        ├── config_example.py
        ├── INSTALL
        ├── LICENCE
        ├── app
        │   ├── controllers
        │   ├── helpers
        │   ├── models
        │   └── views
        ├── app_forum
        │   ├── controllers
        │   ├── models
        │   └── views
        ├── data
        ├── public
        │   ├── css
        │   ├── img
        │   │   └── star
        │   ├── js
        │   └── rss
        ├── scripts
        └── sql
    
    18 directories
    

    终于遇到个稍微大一点的项目了,要好好看看。

    从目录上看,整个项目分成两个部分,app 和 app_forum,每个部分都使用了 典型的MVC结构,将app分成 controllers, models, views 三大部分。

    另外,网站使用的 css, js 文件,图片,也都统一放在了public目录下。

    INSTALL 文件描述了如何安装部署项目, 包括在哪里下载项目,哪里下载web.py,如何 配置 lighttpd, 如何配置项目。

    config_example.py 文件给了一个配置文件模板,按自己的需要修改其中内容,最后 把文件名改为 config.py 就可以了,其中包括对数据的配置,调试,缓存的开启等等。

    LICENCE 文件描述了项目使用的开源协议: GPLv3。

    项目使用的脚本放在scripts目录下,创建数据库使用的文件放在了sql目录下。

    代码统计

    先看看代码统计

    googlemodules_code_stat.jpg

    application 模块

    application.py

    #!/usr/bin/env python
    # Author: Alex Ksikes 
    
    # TODO: 
    # - setup SPF for sendmail and 
    # - emailerrors should be sent from same domain
    # - clean up schema.sql
    # - because of a bug in webpy unicode search fails (see models/sql_search.py)
    
    import web
    import config
    import app.controllers
    
    from app.helpers import custom_error
    
    import forum
    
    urls = (        
        # front page
        '/',                                    'app.controllers.base.index',
        '/page/([0-9]+)/',                      'app.controllers.base.list',
    
        # view, add a comment, vote
        '/module/([0-9]+)/',                    'app.controllers.module.show',
        '/module/([0-9]+)/comment/',            'app.controllers.module.comment',
        '/module/([0-9]+)/vote/',               'app.controllers.module.vote',
    
        # submit a module
        '/submit/',                             'app.controllers.submit.submit',
    
        # view author page
        '/author/(.*?)/',                       'app.controllers.author.show',      
    
        # search browse by tag name
        '/search/',                             'app.controllers.search.search',    
        '/tag/(.*?)/',                          'app.controllers.search.list_by_tag',
    
        # view tag clouds
        '/tags/',                               'app.controllers.cloud.tag_cloud',
        '/authors/',                            'app.controllers.cloud.author_cloud',
    
        # table modules
        '/modules/(?:by-(.*?)/)?([0-9]+)?/?',   'app.controllers.all_modules.list_by',
    
        # static pages
        '/feedback/',                           'app.controllers.feedback.send',
        '/about/',                              'app.controllers.base.about',
        '/help/',                               'app.controllers.base.help',
    
        # let lighttpd handle in production
        '/(?:css|img|js|rss)/.+',               'app.controllers.public.public',
    
        # canonicalize /urls to /urls/
        '/(.*[^/])',                            'app.controllers.public.redirect',
    
        # mini forum app
        '/forum',                               forum.app,    
    
        '/hello/(.*)',                            'hello',
    
        # site admin app
    #    '/admin',                              admin.app,    
    )
    
    app = web.application(urls, globals())
    custom_error.add(app)
    
    if __name__ == "__main__":
        app.run()
    

    可以看出,这是 application 部分的入口,这个模块仅仅是定义了各个请求的处理方式, 并完成程序的启动,所有的实现均不在这里出现,而是通过 import 导入,特别需要 注意 urls 最后定义的 /forum/admin 使用了子程序,而不是通过之前的字符串 实现映射。还需要注意对静态文件,即css,js,img,rss文件的单独处理。

    所有这些都与之前分析过的那些小项目不同,回想起我之前写的 BlogSystem, 所有的处理实现都放在 同一个文件中,导致最后一个文件居然 700多行,真是让人潸然泪下。。。 而且之前也不知道使用子程序,所有处理都堆在一起。看来读完这份源代码,真应该重构一 下了。

    app 模块

    app/
        ├── models # 数据模块,MVC中的 M
        ├── views # 显示模块,MVC中的 V
        ├── controllers # 控制模块,MVC中的 C
        └── helpers # 辅助模块,实现辅助功能
    
    4 directories
    

    controllers 模块

    controllers/
        ├── base.py         # 对基本页面,如网站主页,关于网页,帮助等的处理
        ├── all_modules.py  # 显示全部模块
        ├── module.py       # 对模块页面的处理
        ├── search.py       # 对搜索模块功能处理
        ├── submit.py       # 对提交模块的处理
        ├── author.py       # 查看模块作者信息
        ├── cloud.py        # 对标签云页面进行处理
        ├── feedback.py     # 处理反馈信息
        └── public.py       # 对静态文件的处理
    

    这个模块主要是对请求处理的实现,在 urls 里定义的那些映射关系, 很多被映射到这里。

    实现过程中,调用 models 模块对数据操作,再送入 views 模块通过模板引擎显示数据内容。

    models 模块

    models/
        ├── comments.py     # 对评论数据的处理
        ├── modules.py      # 对模块数据的处理
        ├── rss.py          # 对 rss 订阅的处理
        ├── sql_search.py   # 对搜索的数据处理
        ├── submission.py   # 对用户提交内容的处理
        ├── tags.py         # 对标签内容的数据处理
        └── votes.py        # 对用户投票的数据处理
    

    这个模块直接调用 web.py 的db模块对数据库进行操作,对数据库的连接在 config.py 中 已经完成。这里完成数据的获取,处理,返回。可以看出,对不同种类的数据又分成了 很多小的模块。

    Read More ...