存档

作者存档

QT 中控件关闭的几个 issue

2015年5月25日 没有评论

最近在写一个小应用,需要一个持久化的窗口,即用户在关闭窗口的时候需要捕获这个事件,让窗口暂时隐藏. 总结了 QT 中的控件关闭时的一些小 issue:

lastWindowClosed() and setQuitOnLastWindowClosed()

如果一个控件是一个可见的窗口类型,并且没有父容器,那么关闭该窗口导致的 结果是整个应用程序会退出. 连控件的析构函数都来不及调用。不过,QT 会在这 种情况发生的时候发送一个 QGuiApplication::lastWindowClosed() 的信号, 连接这个信号就可以获取这个事件,用来做一些清理的操作,例如:

connect(QApplication::instance(), SIGNAL(lastWindowClosed()), this, SLOT((cleanup())));

如果想忽略这种行为,在 QGuiApplication 这个类里,提供了一个静态方法, setQuitOnLastWindowClosed(bool) 来控制这种行为, 比如:

QApplication::setQuitOnLastWindowClosed(false);

这样, 当最后一个可视窗口关闭,整个程序就不会退出。

closeEvent()

QWidget 基类提供了一个 protect closeEvent() 方法,在用户点击关闭窗口 的时候,会调用改方法,使得 QWidget 子类的实现可以控制改关闭事件,比如下面的代码, 调用 QCloseEvent::ignore() 来忽略该事件,只是隐藏窗口显示.

void Window::closeEvent(QCloseEvent *event)
{
    event->ignore();
    this->hide();
}

分类: QT 标签:

比 grep 强悍太多的文本检索工具 ACK

2015年4月24日 1 条评论

对程序员来说, grep, cat, 真心是伟大的前辈们赐予我们这些晚辈最好的礼物, 不会用 grep, cat 之类程序员真心没有出息.

但是不知是否有人经常使用以下的语法, 而且感到要是 grep 要是在智能一点就好了

$ grep terminate_instance $(find . -name '*.py' | grep -v tests)

不错, awk 就是为了满足我们希望 grep 更好用这样的前提下被开发出来的, 首先从他的 官网 beyondgrep.com 就可以看出来 🙂 官网给出比 grep 好的理由是:

  • 速度非常快 因为它只搜索有意义的东西.
  • 更好的搜索方案 默认忽略那些不是你想搜索的源码的东西, .e.g. git, svn 目录
  • 为源代码搜索而设计 用更少的击键完成任务
  • 移植性非常好 该程序用 Perl 完成的, 可以在几乎所有主流平台上使用, windows, mac, linux 等
  • 免费开源

Table of Contents

安装

不求那些炫酷的安装方式, 在 一般的 linux 平台, 用包管理器, 比如在 centos/fedora:

# yum install -y ack

在 OSX, 使用 brew

$ brew install ack

使用

回到文首的那个用法, 现在可以很简单的用 ack 来完成, 而且速度暴快:

$ ack --python --ignore-dir=tests terminate_instance

该程序的一般使用很简单:

$ ack --type PATTERN

其中 type 指定需要搜索的源代码类型, 比如 –python, 其它大部分选项和 grep 一样, 文档是说 99%.

高级使用

请参考 官网

分类: Tools 标签:

Build Emacs As A Python IDE

2014年12月30日 没有评论

众所周知在很久很久以前, 初学者要把 Emacs 搭建成比较容易上手的环境, 需要 一点精力和耐心, 但这恰恰是 Emacs 的魅力所在(超高度的可定制化), 但对初学者来说, 由于 el-get包管理器 的出现, 情况变得越来越好了. 比如要马上开始用 Emacs 来作为开发 Python 的工具, 现在已经很简单了.

安装 el-get

将下列配置写入到 .emacs 中

(add-to-list 'load-path "~/.emacs.d/el-get/el-get")
(unless (require 'el-get nil 'noerror)
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.github.com/dimitri/el-get/master/el-get-install.el")
    (let (el-get-master-branch)
      (goto-char (point-max))
      (eval-print-last-sexp))))
 
(el-get 'sync)

just it! 启动 emacs 的时候会自动初始化 el-get 需要的配置信息

jedi

jedi 主要是一个自动补全的插件, Emacs 已经有一个名为 auto-complete 的强大的 补全插件了, 通吃所有语言. 没错, jedi 的自动补全就是利用该插件作为后端了, 不仅如此, 他还可以在编写代码的时候实时查看函数的成员信息, 函数的参数信息和文档信息等, 总之很强大.

使用 el-get-install 回车, 输出 jedi 安装, 借助 el-get 的强大, 所有的依赖都会 自动安装(.e.g. auto-complete, epc)

M-x el-get-install

在 .emacs 里加入 jedi 的配置

add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:complete-on-dot t)

ropemacs

ropemacs 是借助 rope, pymacs 来做 python 的工程管理的东西, 在它面前, 神马 代码重构, 代码跳转, 自动模块导入, 类成员补全神马都是浮云. (注意, 所有代码 补全我都使用 jedi 来做, 不用到这里的功能)

安装也很方便, 直接 el-get-install 然后回车输入 ropemacs 就可以了

安装完成在 .emacs 里加入

(pymacs-load "ropemacs" "rope-")
(setq ropemacs-enable-autoimport t)
 
(autoload 'pymacs-apply "pymacs")
(autoload 'pymacs-call "pymacs")
(autoload 'pymacs-eval "pymacs" nil t)
(autoload 'pymacs-exec "pymacs" nil t)
(autoload 'pymacs-load "pymacs" nil t)

flycheck

该插件是一个实时代码检查的工具, 也就是在编代码的过程中, 会根据改语言的编码 规范, 实时检查和提示源代码的错误, 然后给出警告, 比如语法错误, 编码规范 等. 该插件也是通吃所有语言, 这里我们只关注 python 相关的

直接 el-get-install 然后回车输入 flycheck 安装.

在 .emacs 里面加入

(add-hook 'after-init-hook #'global-flycheck-mode)

注意, flycheck 需要一个检查语法的后端程序, 如果是 python 的话, 推荐 pylink,

# pip install pylink

后记

不考虑其他的配置, 什么强大的 ido, ibuffer, 按键绑定配置, 窗口配置等等, 一个 标准的 python IDE 就配置好了, 就这么简单.

附上所有的 .emacs 配置

(add-to-list 'load-path "~/.emacs.d/el-get/el-get")
(unless (require 'el-get nil 'noerror)
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.github.com/dimitri/el-get/master/el-get-install.el")
    (let (el-get-master-branch)
      (goto-char (point-max))
      (eval-print-last-sexp))))
 
(el-get 'sync)
(add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:complete-on-dot t)
 
(add-hook 'after-init-hook #'global-flycheck-mode)
 
(pymacs-load "ropemacs" "rope-")
(setq ropemacs-enable-autoimport t)
 
(autoload 'pymacs-apply "pymacs")
(autoload 'pymacs-call "pymacs")
(autoload 'pymacs-eval "pymacs" nil t)
(autoload 'pymacs-exec "pymacs" nil t)
(autoload 'pymacs-load "pymacs" nil t)

分类: Emacs, python 标签: ,

给 Mac 一个 ssh-copy-id

2014年12月25日 没有评论

Mac 下默认是没有这个非常好用的工具的, 最开始我每次手动把 ~/.ssh/id_rsa.pub 的 内容追加到服务器的 ~/.ssh/authorized_keys 文件中.

后来我自己写了一个脚本, 放到可执行目录中, 爽了很多:

#!/bin/bash
 
# file: ssh-copy-id
 
if [ $# -lt 1 ]; then
    echo 'Usage: ssh-copy-id [user1@]hostname1 [user2@]hostname2]'
fi
 
KEY_FIEL="$HOME/.ssh/id_rsa.pub"
if [ -f $KEY_FIEL ]; then
    key=`cat $KEY_FIEL`
    for server in $@; do
        ssh $server "echo $key >> ~/.ssh/authorized_keys"
    done
else
    echo 'ssh key(id_rsa.pub) does not exist, run ssh-keygen to generate it'
    exit 1
fi

在后来, 我偶然发现 brew 中直接有一个包就叫 ssh-copy-id, 我靠, 一直被 linux 系统给误导了, 在 linux 系统中, ssh-copy-id 是包含在名为 openssh-clients 的包中的. 废话不说, 用这个吧, 毕竟人家写的 300 多行, 考虑了各种兼容性等等

$ brew install ssh-copy-id

分类: linux 标签:

CentOS 7 修改时区

2014年12月25日 没有评论

Linux 系统(我特指发行版, 没说内核) 下大部分软件的风格就是不会仔细去考虑向后 的兼容性, 比如你上个版本能用这种程序配置, 没准到了下一个版本, 该程序已经不见了. 比如 sysvinit 这种东西.

设置时区同样, 在 CentOS 7 中, 引入了一个叫 timedatectl 的设置设置程序.

用法很简单:

# timedatectl # 查看系统时间方面的各种状态
      Local time: 四 2014-12-25 10:52:10 CST
  Universal time: 四 2014-12-25 02:52:10 UTC
        RTC time: 四 2014-12-25 02:52:10
        Timezone: Asia/Shanghai (CST, +0800)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: n/a
# timedatectl list-timezones # 列出所有时区
# timedatectl set-local-rtc 1 # 将硬件时钟调整为与本地时钟一致, 0 为设置为 UTC 时间
# timedatectl set-timezone Asia/Shanghai # 设置系统时区为上海

其实不考虑各个发行版的差异化, 从更底层出发的话, 修改时间时区比想象中要简单:

# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

分类: linux 标签:

Glance 源码分析(3) – WSGI 框架

2014年10月27日 没有评论

Table of Contents

Server

OpenStack 大多数模块封装了 eventlet 以及 eventlet.wsgi.server 组成一个 class Server 来作为一个 HTTP RestFul API Server. 咋一看他的封装好像很复杂, 但是如果了解 eventlet 的基本概念, 这部分代码其实非常简单.

eventlet 在它内部定义的 greenthread(绿色线程) 里, 它利用协程来实现并发. 协程的介绍可以看我以前写的文章: Coroutine(协程) 介绍

为了彻底实现高性能的 I/O 并发, eventlet 甚至把底层的一些 API 都做了修改, .e.g socket, listen. 更多的 eventlet 的内容不在这讨论, 可以去 官网 查看相关的文档

下面是一个简单的小例子利用 eventlet 构建一个 HTTP Server, 基本上就是 glance 的 class Server 的核心骨架:

class APP(object):
    @webob.dec.wsgify
    def __call__(self, req):
        return 'hello world'
 
# 创建一个绑定在本机 8000 端口 的 socket.
sock = eventlet.listen(('0.0.0.0', 8000))
# 启动 wsgi server
eventlet.wsgi.server(sock, APP())

that’s it, so easy. 把他保存到文件并运行, 就是一个简单的 Http server, 并且 我可以付责任的告诉你, openstack 中的 WSGI 服务就是这么运行的.

所以我们回过头看 glance.common.wsgi.py:Server 这个类, 它只不过包装了一下 eventlet 的参数, 并且 WSGI 实例是在子进程里面运行的, 支持多进程运行该服务(多核考虑), 对外提供了 Server:start()Server:wait() 两个 API, 其他的, 就真没什么了.

paste 的工厂函数

请先复习一下 paste 的相关内容. 然后看下面的图:

API 框架

图中每一个 filter 都对应着一个工厂函数(可调用的实例), 比如:

[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory

对应的实例都是 Middleware 其实是的子类, 前面我们讲过, paste配置里面的工厂函数 所创造的是一个能调用的实例(包含__call__方法), 当收到用户请求的时候就会自动调用该 实例, 也就是调用 __call__ 方法. 这里 openstack 所有模块抽象除了一个类 Middleware, 封装了 process_request 方法. 每个 filter 子类只需要继承 该类, 如果需要做处理, 就覆盖 process_request 方法, 然后 Middleware 里面 的 __call__ 方法会根据 process_request 的返回值来判断是否交给下一个 filter 处理.

# file: glance/api/middleware/version_negotiation.py
class VersionNegotiationFilter(wsgi.Middleware):
class Middleware(object):
    def __init__(self, application):
        self.application = application
 
    @classmethod
    def factory(cls, global_conf, **local_conf):
        def filter(app):
            return cls(app)
        return filter
 
    def process_request(self, req):
        return None
 
    def process_response(self, response):
        return response
 
    @webob.dec.wsgify
    def __call__(self, req):
        # 首先调用子类的 process_request 方法, 如果子类没有实现这个方法或者
        # 返回值不为空, 那么直接将子类的返回回复给用户, 否则进行下一个 filter
        # 的处理. 这其实是一个递归的过程, 最后返回从下游(filter或app)的到的返回
        # 给用户
        response = self.process_request(req)
        if response:
            return response
        response = req.get_response(self.application)
        response.request = req
        try:
            return self.process_response(response)
        except webob.exc.HTTPException as e:
            return e

分类: OpenStack 标签:

Glance 源码分析(2) – 配置文件

2014年10月25日 没有评论

这里我们会分析 glance-api 读取以下两个配置文件

  • glance-api.conf: glance-api 的用户配置文件
  • glance-api-paste.ini: glance-api 的 WSGI 配置文件

glance-api.conf

该配置文件的读取是利用 oslo 模块来实现的, oslo 提供了 .ini 格式的配置 文件的解析, 被所有 OpenStack 模块用来解析配置文件.

oslo 的用法很简单, 下面举个简单的例子:

from oslo.config import cfg
 
default_opts = [
    cfg.StrOpt('bind_host',
               default='0.0.0.0',
               help='IP address to listen on'),
    cfg.IntOpt('bind_port',
               default=9292,
               help='Port number to listen on')
]
 
app_opt = cfg.StrOpt('name',
                     default='blog',
                     help='name of this app')
 
# cfg.CONF 是在 oslo.cfg 模块中的一个全局变量, 首先我们需要得到一个它的引用
# 然后调用 register_opt() 注册我们需要解析的配置项, 或者使用 register_opts()
# 同时注册多个配置项
# 如果配置文件中可以找到配置项, 那么使用配置项中的值, 不然使用注册该配置项时指定
# 的默认值
CONF = cfg.CONF
CONF.register_opt(app_opt, group='app')
CONF.register_opts(default_opts)
CONF(default_config_files=['app.conf'])
 
# 使用的时候可以用 CONF.cfgname 来使用, 如果该 cfgname 不在 [DEFAULT] 段下,
# 那么使用 CONF.section.cfgname 来引用
print CONF.items()
print CONF.app.name
print CONF.bind_host
print CONF.bind_port
# file: app.conf
[DEFAULT]
bind_port = 8080
[app]
name = test
# python test.py 
[('bind_port', 8080), ('config_dir', None), ('config_file', ['app.conf']), ('bind_host', '0.0.0.0'), ('app', <oslo.config.cfg.GroupAttr object at 0x7fa4a3d75c50>)]
0.0.0.0
8080
test

那么问题来了… 前面讲过, glance 中的配置文件通过 config.parse_args() 来调用的, 也就是所有的配置都是在 glance.common.config:parse_args() 中完成的, 通过上面的分析, 这个函数其实很简单, 这里就不深入探讨了. 总之, 以后, glance 的各个模块在想访问用户提供的配置的时候, 只需要使用类似以下的代码就可以了:

from oslo.config import cfg
CONF = cfg.CONF
print CONF.enable_v2_api

glance-api-paste.ini

该内容和 glance 无关, 只要搞懂了 python paste 模块的用法, 就很简单了. 关于 paste 模块的使用说明, 请参考 python paste.deploy 探索

下面把之前在框架那里的内容再贴一份:

API 框架

[pipeline:glance-api-keystone]
pipeline = versionnegotiation authtoken context rootapp
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app

分类: OpenStack 标签:

Glance 源码分析(1) – 框架

2014年10月24日 没有评论

以下主要分析 V2 版本的代码, V1 和 V2的最大区别就是:

  • V1: 包含两个服务 glance-api 和 glance-registry. glance-api 接受客户端的 所有命令, 分发并响应, 涉及到数据库的操作由内部转发到 glance-registry 完成
  • V2 简化了流程, 所有的处理都在内部实现, 不需要额外的服务. 因此只需要 glance-api 一个服务

启动流程

启动流程

# glance/cmd/api.py
def main():
    # 载入配置, 下面的函数在 glance/common/config.py 中定义, 调用此函数会初始化
    # oslo 模块中的模块变量 CONF, 把 glance-api.conf 中的值设置在 CONF 的属性中,
    # 使用的时候按照 "CONF.enable_v1_api" 此方式调用, 具体的细节不多讲
    config.parse_args()
 
    # 初始化后端存储,
    # 1. 将所有后端存储的类名注册到 glance/store/__init__.py:REGISTERED_STORES
    # 2. 将所有后端存储的名字和实例注册到 glance/store/location.py:SCHEME_TO_CLS_MAP
    #    以后 glance-api 可以根据用户的请求和配置文件找到具体的后端存储的实例, 调用相应的
    #    实例的函数(add/delete)来操作
    glance.store.create_stores()
 
    # 启动 WSGI 程序
    # load_paste_app 会在默认位置(/etc/glance/)找到 glance-api-paste.ini,
    # 然后根据用户的配置(是否启用 keystone等)调用 paste.loadapp 载入相应的 app,
    # 最后传递给 server.start 启动 WSGI 应用, 具体的细节之后的系列会讲到
    server = wsgi.Server()
    server.start(config.load_paste_app('glance-api'), default_port=9292)
    server.wait()

API 框架

API 框架

# file: /etc/glance/glance-api-paste.ini
[pipeline:glance-api-keystone]
pipeline = versionnegotiation authtoken context rootapp
[filter:versionnegotiation]
paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
delay_auth_decision = true
[filter:context]
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app

关于 paste 模块的使用说明, 请参考 python paste.deploy 探索

流程如下:

  • 首先 WSGI 接收到用户请求, 将用户的请求信息交给 paste 模块处理
  • paste 模块根据配置文件的规则依次经过 versionnegotiation authtoken context 这几个过滤器
  • 最后交由 rootapp, 这是个类型为 app 的处理, rootapp 内部再根据用户提供的版本 信息(v1/v2) 交由 apiv1app 或者 apiv2app 处理, 最后把返回 HTTP Response
  • 真正的业务逻辑在 apiv1app/apiv2app 内部实现, 见下面的处理流程

处理流程

apiv2app 定义的工厂方法在 glance/api/v2/router:API.factory 中, API 类继承自 wsgi.Router, Router 类利用了 python-route 模块做 url 的选择处理, 具体的流程 见下(详细的分析请参考后面的系列): 处理流程 关于 route 模块的使用说明, 请参考 python Route 简单使用笔记

分类: OpenStack 标签:

Glance 源码分析(0) – 介绍

2014年10月23日 没有评论

这周我把 glance 的源代码通读了一遍(业务流程主要是 V2 版本), 感觉这个模块虽然是 OpenStack 里面相对较小的模块(vs Nova), 但是他的设计模式, 架构一点都不含糊, 我看完 glance 之后再去看其它项目, 发现 API 的等框架大部分地方都是一模一样. 也就是 说看完这个模块, 再去分析其它模块就省事很多了, 以下将我的分析拆分做一个记录:

分类: OpenStack 标签:

python Route 简单使用笔记

2014年10月15日 没有评论

Routes is a Python re-implementation of the Rails routes system for mapping URLs to application actions, and conversely to generate URLs. Routes makes it easy to create pretty and concise URLs that are RESTful with little effort.

Routes 其实就是 Python 版本的 Rails 的 routes. 它用来将用户的不同 URLs 自动匹配 到不同的应用上, 对于开发 RESTful 的 API 和其他 web 的应用非常方便.

例如下面根据官网上的例子修改的, 简单明了:

>>> from routes import Mapper
>>> map = Mapper()
>>> map.connect(None, "/error/{action}/{id}", controller="error")
>>> map.connect("home", "/", controller="main", action="index")
>>> print map
Route name Methods Path                
                   /error/{action}/{id}
home               /                   
>>> print map.match('/error/myapp/4')
{'action': u'myapp', 'controller': u'error', 'id': u'4'}
>>> print map.match('/')
{'action': u'index', 'controller': u'main'}

简单来讲, routes 的使用有以下简单的几步:

  1. 创建一个用来响应用户请求的控制器 C
  2. 创建一个 Mapper m
  3. 用 m.connect(或者 resource) 连接需要解析的 URLs 到 Controller 上
  4. 使用 routes.middleware.RoutesMiddleware 自动化 URLs 到 Controller 的处理

下面是一个例子

import webob.dec
import eventlet
from eventlet import wsgi, listen
from routes import Mapper, middleware
 
 
# 控制器
# 只有两个动作: index 和 add
class controller(object):
    def index(self):
        return "do index()"
 
    def add(self):
        return "do show()"
 
 
# WSGI App, 详情请参考我前面的文章 python paste.deploy 探索
class App(object):
    def __init__(self):
        # 上面提到的 1, 2, 3, 4
        self.controller = controller()
        m = Mapper()
        m.connect('blog', '/blog/{action}/{id}', controller=controller,
                  conditions={'method': ['GET']})
        self.router = middleware.RoutesMiddleware(self.dispatch, m)
 
    @webob.dec.wsgify
    def dispatch(self, req):
        # RoutesMiddleware 会根据接收到的 url, 自动调用 map.match(), 做路由匹配,
        # 然后调用第一个参数, 这里即是 self.dispatch()
        match = req.environ['wsgiorg.routing_args'][1]
        if not match:
            return 'error url: %s' % req.environ['PATH_INFO']
 
        # 根据用户的请求动作, 调用相应的 action 处理函数(index/add)
        action = match['action']
        if hasattr(self.controller, action):
            func = getattr(self.controller, action)
            ret = func()
            return ret
        else:
            return "has no action:%s" % action
 
    @webob.dec.wsgify
    def __call__(self, req):
        return self.router
 
if __name__ == '__main__':
    socket = listen(('0.0.0.0', 8000))
 
    server = eventlet.spawn(wsgi.server, socket, App())
    server.wait()

Table of Contents

其它

关于使用 Mapper.resource() 来批量创建 RESTful API, 这里不介绍了, 详情请到 官网 查看

分类: programming, python 标签: ,