SAE Python Developer's Guide 1.1testing documentation

Version: 1.1testing
[首页]

SAE Python 开发者手册

目前SAE Python还处于测试阶段,欢迎使用。 点此申请内测

本文档,示例代码以及本地测试server下载:

git clone http://github.com/SAEPython/saepythondevguide.git

Contents:

Quick Start 快速指引

Hello, world!

创建新应用

登录SAE,进入 我的首页 ,点击 创建新应用 ,创建一个新的应用helloworld。

检出svn代码

执行下面的命令创建应用目录并检出svn代码。

jaime@westeros:~$ svn co https://svn.sinaapp.com/helloworld

创建index.wsgi

创建一个目录1作为默认版本,在此下面新建文件 index.wsgi

jaime@westeros:~$ cd helloworld
jaime@westeros:~/helloworld$ mkdir 1
jaime@westeros:~/helloworld$ cd 1
jaime@westeros:~/helloworld/1$ touch index.wsgi

编辑index.wsgi,内容如下:

import sae

def app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello, world!']

application = sae.create_wsgi_app(app)

提交代码

jaime@westeros:~/helloworld$ svn add 1/
jaime@westeros:~/helloworld$ svn ci -m "initialize project"

访问应用

在浏览器中输入 http://helloworld.sinaapp.com ,就可以访问刚提交的应用了。

Note

svn的仓库地址为:http://svn.sinaapp.com/<your-application-name>, 用户名和密码为sae的安全邮箱和安全密码。

使用web开发框架

Django

目前SAE Python使用的版本是 Django-1.2.7 , 请确保你安装的是这个版本。

  1. 建立一个新的Python应用,检出svn代码到本地目录,建立默认版本目录1并切换到此目录。

  2. 新建文件index.wsgi,内容如下

    import os
    import django.core.handlers.wsgi
    
    import sae
    
    os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
    
    application = sae.create_wsgi_app(django.core.handlers.wsgi.WSGIHandler())
    
  3. 初始化django应用:

    django-admin.py startproject mysite
    

    最终目录结构如下:

    jaime@westeros:~/pythondemo/1$ ls
    index.wsgi  media  mysite  README
    jaime@westeros:~/pythondemo/1$ ls media/
    css  img  js
    jaime@westeros:~/pythondemo/1$ ls mysite/
    demo  __init__.py  manage.py  settings.py  urls.py  views.py
    
  4. 提交代码

    访问 http://<your-application-name>.sinaapp.com ,就可看到Django的欢迎页面了。

  5. Hello, Django!

    在mysite/目录下新建一个views.py,内容如下

    from django.http import HttpResponse
    
    def hello(request):
        return HttpResponse("Hello, world! - Django")
    

    修改urls.py,新增一条规则解析hello。

    # Uncomment the next two lines to enable the admin:
    # from django.contrib import admin
    # admin.autodiscover()
    
    urlpatterns = patterns('',
        ...
        (r'^$', 'mysite.views.hello),
        #(r'^admin/', include(admin.site.urls)),
    )
    

    提交代码,访问 http://<your-application-name>.sinaapp.com/ ,ok,熟悉的Hello,World!出现了。

因为django的WSGI Handler不会处理静态文件请求(静态文件是由manage.py来处理的),如果你需要使用django的admin模块, 你需要从django安装目录复制admin 的media目录到应用目录下的/media目录中。

cp -rf django/contrib/admin/media/ <your-application-home>/media

如果你定义了自己的templates目录,admin应用的模板可能无法使用,需要将admin的系统模块添加到settings.py中:

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
+   '/usr/local/sae/python/lib/python2.6/site-packages/django/contrib/admin/templates/admin',
    os.path.join(PROJ_DIR, 'templates'),
)

FIXME: admin模块和自定义模块关系

Flask

index.wsgi

import sae

from myapp import app

application = sae.create_wsgi_app(app)

myapp.py

import MySQLdb
from flask import Flask, g, request

app = Flask(__name__)
app.debug = True

from sae.const import (MYSQL_HOST, MYSQL_HOST_S,
    MYSQL_PORT, MYSQL_USER, MYSQL_PASS, MYSQL_DB
)

@app.before_request
def before_request():
    appinfo = sae.core.Application()
    g.db = MySQLdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS,
                           MYSQL_DB, port=int(MYSQL_PORT))

@app.teardown_request
def teardown_request(exception):
    if hasattr(g, 'db'): g.db.close()

@app.route('/')
def hello():
    return "Hello, world! - Flask"

@app.route('/demo', methods=['GET', 'POST'])
def greeting():
    html = ''

    if request.method == 'POST':
        c = g.db.cursor()
        c.execute("insert into demo(text) values(%s)", (request.form['text']))

    html += """
    <form action="" method="post">
        <div><textarea cols="40" name="text"></textarea></div>
        <div><input type="submit" /></div>
    </form>
    """
    c = g.db.cursor()
    c.execute('select * from demo')
    msgs = list(c.fetchall())
    msgs.reverse()
    for row in msgs:
        html +=  '<p>' + row[-1] + '</p>'

    return html

Bottle

index.wsgi

from bottle import Bottle, run

import sae

app = Bottle()

@app.route('/')
def hello():
    return "Hello, world! - Bottle"

application = sae.create_wsgi_app(app)

Tornado

Warning

Tornado目前只支持WSGI模式,异步等功能无法使用。

index.wsgi

import tornado.wsgi

import sae

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world! - Tornado")

app = tornado.wsgi.WSGIApplication([
    (r"/", MainHandler),
])

application = sae.create_wsgi_app(app)

Uliweb

Thanks to limodou At gmail.com

uliweb的安装

为搭建本地开发环境,你需要安装uliweb 0.0.1a7以上版本或svn中的版本, 简单的安装可以是:

easy_install Uliweb

安装后在Python环境下就可以使用uliweb命令行工具了。

目前Uliweb支持Python 2.6和2.7版本。3.X还不支持。

Hello, Uliweb

让我们从最简单的Hello, Uliweb的开发开始。首先假设你已经有了sae的帐号.

  1. 创建一个新的应用,并且选择Python环境。

  2. 从svn环境中checkout一个本地目录

  3. 进入命令行,切換到svn目录下

  4. 创建Uliweb项目:

    uliweb makeproject project
    

    会在当前目录下创建一个 project 的目录。这个目录可以是其它名字,不过它是和后面要使用的 index.wsgi 对应的,所以建议不要修改。

  5. 创建 index.wsgi 文件,Uliweb提供了一个命令来做这事:

    uliweb support sae
    

    这样会在当前目录下创建一个 index.wsgi 的文件和 lib 目录。注意执行时是在svn的目录,即project的父目录中。

    index.wsgi 的内容是:

    import sae
    import sys, os
    
    path = os.path.dirname(os.path.abspath(__file__))
    project_path = os.path.join(path, 'project')
    sys.path.insert(0, project_path)
    sys.path.insert(0, os.path.join(path, 'lib'))
    
    from uliweb.manage import make_application
    app = make_application(project_dir=project_path)
    
    application = sae.create_wsgi_app(app)
    

    其中 projectlib 都已经加入到 sys.path 中了。所以建议使用上面 的路径,不然就要手工修改这个文件了。

  6. 然后就可以按正常的开发app的流程来创建app并写代码了,如:

    cd project
    uliweb makeapp simple_todo
    
    这时一个最简单的Hello, Uliweb已经开发完毕了。
    
  7. 如果有静态文件,则需要放在版本目录下,Uliweb提供了命令可以提取安装的app的静态文件:

    cd project
    uliweb exportstatic ../static
    
  8. 如果有第三方源码包同时要上传到sae中怎么办,Uliweb提供了export命令可以导出已经 安装的app或指定的模块的源码到指定目录下:

    cd project
    uliweb export -d ../lib #这样是导出全部安装的app
    uliweb export -d ../lib module1 module2 #这样是导出指定的模块
    

    为什么还需要导出安装的app,因为有些app不是放在uliweb.contrib中的,比如第三方 的,所以需要导出后上传。但是因为export有可能导出已经内置于uliweb中的app,所以 通常你可能还需要在 lib 目录下手工删除一些不需要的模块。

  9. 提交代码

    访问 http://<你的应用名称>.sinaapp.com ,就可看到项目的页面了。

数据库配置

Uliweb中内置了一个对sae支持的app,还在不断完善中,目前可以方便使用sae提供的MySql 数据库。

然后修改 project/apps/settings.iniGLOBAL/INSTALLED_APPS 最后添加:

[GLOBAL]
INSTALLED_APPS = [
...
'uliweb.contrib.sae'
]

然后为了支持每个请求建立数据库连接的方式,还需要添加一个Middleware在settings.ini中:

[MIDDLEWARES]
transaction = 'uliweb.orm.middle_transaction.TransactionMiddle'
db_connection = 'uliweb.contrib.sae.middle_sae_orm.DBConnectionMiddle'

其中第一行是事务支持的Middleware你也可以选择使用。

这样就配置好了。而相关的数据库表的创建维护因为sae不能使用命令行,所以要按sae的 文档说明通过phpMyAdmin来导入。以后Uliweb会増加相应的维护页面来做这事。

web.py

index.wsgi

import os

import sae
import web
        
urls = (
    '/', 'Hello'
)

app_root = os.path.dirname(__file__)
templates_root = os.path.join(app_root, 'templates')
render = web.template.render(templates_root)

class Hello:        
    def GET(self):
        return render.hello()

app = web.application(urls, globals()).wsgifunc()

application = sae.create_wsgi_app(app)

Tip

以上所有的示例代码的完整版本可以在我们的github repo中获得。

https://github.com/SAEPython/saepythondevguide/tree/master/examples/

SAE Python环境

环境信息

SAE Python应用运行于沙箱环境之中,SAE会根据负载在后端的多个节点中选择一个来处理HTTP请求。 SAE Python支持标准WSGI应用。

命名规范:

  • 应用目录

    SAE上的每个应用可以同时运行多个版本,版本以数字为标示,默认版本为1。 每个版本对应于应用svn根目录下以其版本号命名的一个目录,称为版本目录, 应用的代码(index.wsgi等)必须放到版本目录里。

    以应用longtalk为例,这个应用有6个版本:

    jaime@westeros:~/longtalk$ ls
    1  2  3  4  5  6
    jaime@westeros:~/longtalk/1$ ls
    index.wsgi myapp.py
    

    应用默认版本代码所在的目录,称为应用目录。 该目录会添加到Python runtime的 sys.path 中。该目录也为应用运行时的当前目录。

    访问指定版本的应用: http://<version>.<application-name>.sinaapp.com

    不推荐使用os.getcwd()来获取路径信息,建议使用__file__属性。

预装模块列表

名称 支持的版本 默认版本
django 1.2.7, 1.4 1.2.7
flask 0.7.2 0.7.2
flask-sqlalchemy 0.15 0.15
werkzeug 0.7.1 0.7.1
jinjia2 2.6 2.6
tornado 2.1.1 2.1.1
bottle 0.9.6 0.9.6
ulibweb 0.0.1a7 0.0.1a7
sqlalchemy 0.7.3 0.7.3
webpy 0.36 0.36
Flask-WTF 0.5.2 0.5.2
WTForms 0.6.3 0.6.3
PIL 1.1.7 1.1.7
MySQLdb 1.2.3 1.2.3
sinatpy 2.x-(2011-6-8) 2.x-(2011-6-8)

Note

需要使用非默认版本可以在config.yaml中指定。

请求处理

静态目录

  • /media
  • /static
  • /favicon.ico

其他所有请求,都被路由到/index.wsgi:application,即应用根目录index.wsgi文件, 名为application的callable,暂不可修改。

application 使用下列方式创建

sae.create_wsgi_app(app)

将标准wsgi应用封装为适宜在SAE上运行的应用

import sae

def app(environ, start_response):
    # Your app
    ...

application = sae.create_wsgi_app(app)

Python环境

Python runtime使用的是Python 2.6.7。

  • 仅支持运行纯Python的应用,不能动态加载C扩展,即.so,.dll等格式的模块不能使用
  • 进程,线程操作受限
  • 除临时文件,应用自身所在目录外,不可访问本地文件系统。本地文件系统不可写入。

本地文件系统可以读取本应用目录,Python标准库下的内容,不支持写入。 如需读写临时文件建议使用StringIO或者cStringIO来替代。

Python默认的模块搜索路径为:当前目录 > 系统目录。添加模块搜索目录的方法为:

import sys
sys.path.insert(0, your_custom_module_path)

注意:Python当前目录下的子目录只有包含__init__.py才会被Python认为是一个package, 才可以直接import。

SAE设置了一些自定义的环境变量,这些环境变量可以通过os.environ这个dict获取。

  • APP_NAME:应用名。
  • APP_VERSION: 当前应用使用的版本号。
  • SERVER_SOFTWARE: 当前server的版本(目前为sae/1.0.testing)。 可以使用这个环境变量来区分本地开发环境还是在线环境,本地开发环境未设置这个值。

日志系统

打印到stdout和stderr的内容会记录到应用的日志中心中, 所以直接使用print语句或者logging模块来记录应用的日志就可以了。

日志内容在 应用»日志中心» HTTP 中查看,类别为debug。

应用缓存

SAE Python会对应用导入的模块(包括index.wsgi)进行缓存,从而缩短请求响应时间, 对于缓存了的应用,请求处理只是取出index.wsgi中application这个callable并调用。

应用程序配置

应用程序的配置文件为应用目录下的config.yaml文件。

  • 使用第三方库

    libraries:
    - name: django
      version: "1.4"
    
    - name: numpy
      version: "1.5.0"
    

    name为第三方模块的名称,version为需要使用的版本,这两个字段为必填字段。

  • 静态文件处理

    静态文件夹

    handlers:
    - url: /static
      static_dir: static
    

    url为URL的前缀,static_dir为静态文件所在的目录(相对于应用目录)。

Note

  1. 部分第三方库已经包含在默认搜索路径中,可以不在config.yaml中指定直接使用。
  2. 如果config.yaml中没有设置静态文件相关的handlers,系统会默认将/static,/media 为前缀的URL转发到应用目录下的static和media目录。
  3. 以上两条规则仅为兼容性考虑保留,不推荐使用,请在config.yaml明确配置。

可用服务列表

注意:MySQL, TaskQueue, Memcache, KVDB 服务需开启才能使用,请在前端管理界面 服务管理 中开启并初始化。

访问互联网

直接使用urllib, urllib2或者httplib模块访问网络资源即可。

MySQL

连接信息

获取mysql的连接信息。

import sae.const

sae.const.MYSQL_DB      # 数据库名
sae.const.MYSQL_USER    # 用户名
sae.const.MYSQL_PASS    # 密码
sae.const.MYSQL_HOST    # 主库域名(可读写)
sae.const.MYSQL_PORT    # 端口,类型为<type 'str'>,请根据框架要求自行转换为int
sae.const.MYSQL_HOST_S  # 从库域名(只读)

下面就可以跟平常一样使用MySQL服务了,SAE Python内置了MySQLdb模块,对于MySQLdb的使用,可以参考其 官方文档

注意: MySQL 连接超时时间为30s 。

数据库导入

创建数据表:

django-admin.py sqlall all-installed-app-names > some-file

开发可使用本地mysql数据库,在发布时将其导入到SAE线上数据库:

  1. 使用mysqldump或者phpMyAdmin等工具从本地数据库导出数据库。
  2. 进入SAE后台应用管理>服务管理>MySQL页面,初始化MySQL。
  3. 进入管理MySQL页面,选择导入,导入刚才导出的sql文件即可。

导入时如果出现下面的错误:

Error
SQL query:

--
-- Dumping data for table `***`
--
LOCK TABLES `***` WRITE ;


MySQL said:

#1044 - Access denied for user '****'@'10.67.15.%' to database 'app_***'

请把要导入的sql文件中,所有LOCK, UNLOCK语句全部删除并重试。

字符集问题

在管理界面创建数据表,默认字符集为utf8,也可设为其他编码。

如果你是在本地开发环境建立的数据表,请确保使用utf8。在管理界面导入本地数据库时, 也可完成字符集的转换。

TaskQueue, Cron

什么是任务

出于安全性考虑,SAE不支持执行一段任意的代码程序。SAE的cron,和unix的cron意义不同,没有相关联的程序。

SAE的任务,实际上对应于一个URL地址。SAE worker节点每请求一次该URL,就算执行一次任务。 真正的任务处理代码,是app中处理该URL的handler。

任务执行有两种方式: Taskqueue 动态执行任务, Cron 定时执行任务

任务的执行情况可以在日志中心>TaskQueue栏中查询。

Taskqueue

sae.taskqueue.add_task(queue_name, url, payload=None)

快速添加任务

queue_name: 任务队列的名称

url: 任务的url,如: /tasks/task_name

payload: 可选,如果payload存在且不为None,则该任务为一POST任务,payload会作为请求 的POST的数据。

class sae.taskqueue.Task(url, payload=None, **kwargs)

Task类

url: 任务的url,如: /tasks/task_name

payload: 可选, 如果payload存在且不为None,则该任务为一POST任务,payload会作为请求 的POST的数据。

delay: 可选,设置任务延迟执行的时间,单位为秒,最大可以为600秒。

prior: 可选,如果设置为True,则任务会被添加到任务队列的头部。

class sae.taskqueue.TaskQueue(name, auth_token=None)

TaskQueue类

name: 任务队列的名称。

auth_token: 可选, 一个包含两个元素的元组 (access_key, secretkey_key)。

add(task)

添加一个任务

task: 添加的任务,可以为单个Task任务,也可以是一个Task列表。

size()

获取当前队列中还有多少未执行的任务。

Example:

  1. 添加一个任务。

    from sae.taskqueue import Task, TaskQueue
    
    queue = TaskQueue('queue_name')
    queue.add(Task("/tasks/foo"))
    
  2. 添加一个POST任务。

    queue.add(Task("/tasks/bar", "data"))
    
  3. 批量添加任务。

    tasks = [Task("/tasks/update", user) for user in users]
    queue.add(tasks)
    
  4. 快速添加任务。

    from sae.taskqueue import add_task
    add_task('queue_name', '/tasks/push', 'msg')
    

Note

任务的url现在已经改为相对的url,目前兼容绝对url,但是不推荐使用。 任务默认使用GET方式请求,如果Task带有payload参数且不为None则使用POST方式请求。

Cron

Cron的配置文件为 config.yaml ,Cron的执行状态可在应用的管理界面 服务管理>Cron 中查看。

  • 添加Cron:

    编辑config.yaml文件中,增加cron段,例如:

    name: crontest
    version: 1
    cron:
      - description: cron_test
        url: /cron/make
        schedule: */5 * * * *
    

    上面的示例添加了一个cron任务, 该任务每5分钟执行`http://crontest.sinaapp.com/cron/make`一次。

  • 删除cron:

    删除config.yaml中对应的cron描述段即可就行。

  • 语法字段含义

    • url

      cron任务的url。例如 /relative/url/to/cron

    • schedule

      任务描述,也就是何时执行这个cron,支持unix crontab语法。例如:

      # 每天00:05分执行
      5 0 * * *
      # 每月1号的14:15分执行
      15 14 1 * *
      # 每个工作日的晚上10点执行
      0 22 * * 1-5
      # 每分钟执行一次
      */1 * * * *
      

      具体的语法规则可以参考man手册,man 5 crontab

    • description

      可选。任务的说明,默认为空。

    • timezone

      可选。默认为Beijing,目前支持:Beijing, NewYork, London, Sydney, Moscow, Berlin

    • login

      可选。http basic auth设置,格式: 用户名@密码

    • times

      可选。设置cron最大执行的次数,默认没有次数限制。

Warning

Cron使用POST方式请求URL。

什么是POST和GET?请见 http://en.wikipedia.org/wiki/HTTP#Request_methods

登录和CRSF

SAE任务处理节点只是简单的请求任务URL,对于除http basic auth之外的登录信息,一无所知,故务必确认你的URL 可以不用登录直接访问。

http basic auth虽然支持,但是不推荐使用。 要保护任务URL不被外界访问,请使用IP白名单。

如果你在任务URL的POST处理程序中开启了CRSF,则会导致403认证失败错误。请在任务处理程序中关闭CRSF功能,涉及框架: Django, Flask等。

什么是CRSF? http://en.wikipedia.org/wiki/Cross-site_request_forgery

如何保护任务URL

为保护cron,taskqueue对应的url,可在config.yaml配置允许访问的IP地址。

建议将所有taskqueue,cron的url都挂载到/backend/下面:

/backend/
/backend/taskqueue/
/backend/cron

SAE内部节点IP范围: 10.0.0.0/8,如下配置只允许SAE内部节点访问:

- hostaccess: if(path ~ "/backend/") allow "10.0.0.0/8"

请确保SAE内部节点在白名单内,否则将无法正常执行。

Cron和Taskqueue中使用weibo api

因为现在weibo api需要提供调用者的ip(合法的公网ip),sae默认提供的是http请求的client的ip, 但是对于cron和taskqueue,由于是sae的内部请求,无法获取公网ip。所以需要用户手工设置一个。 设置方法如下:

import os
os.environ['REMOTE_ADDR'] = 调用者公网ip

请务必将这段代码放在请求处理代码执行的必经路径上。比如在Flask中::

@app.before_request
def before_request():
    import os
    os.environ['REMOTE_ADDR'] = 调用者公网ip

Cron 完整示例

每五分钟请求一次 /backend/cron/update URL

Flask URL 处理程序:

import pylibmc
import datetime

from appstack import app

mc = pylibmc.Client(['localhost'])

@app.route('/backend/cron/update', methods=['GET', 'POST'])
def update():
    update_time = mc.get('update_time')
    mc.set("update_time", str(datetime.datetime.now()))

    return update_time

config.yaml:

name: appstack
version: 4

cron:
- url: /backend/cron/update
  schedule: */5 * * * *

handle:
- hostaccess: if(path ~ "/backend/") allow "10.0.0.0/8"

Mail

class sae.mail.EmailMessage(**kwargs)

EmailMessage类

参数同下面的initialize

initialize(**kwargs)

初始化邮件的内容。

to: 收件人列表,多个收件人之间用逗号隔开。

subject: 邮件的标题。

body/html: 邮件正文。如果内容为纯文本,使用body,如果是html则使用html。

smtp: smtp服务器的信息。是一个包含5个元素的tuple。 (smtp主机,smtp端口, 用户名,密码,是否启用TLS)。

attachments: 可选。邮件的附件,必须为一个list,list里每个元素为一个 tuple,tuple的第一个元素为文件名,第二个元素为文件的内容。

send()

提交邮件发送请求至后端服务器。

__setattr__(attr, value)

attr: 属性名。 value: 属性的值。

sae.mail.send_mail(to, subject, body, smtp, **kwargs)

快速发送邮件。

字段的意义同EmailMessage.initialize()。

Examle:

  1. 快速发送一份邮件

    from sae.mail import send_mail
    
    send_mail("katherine@vampire.com", "invite", "to tonight's party"
              ("smtp.vampire.com", 25, "damon@vampire.com", "password", False))
    
  2. 发送一封html格式的邮件

    from sae.mail import EmailMessage
    
    m = EmailMessage()
    m.to = 'damon@vampire.com'
    m.subject = 'Re: inivte'
    m.html = '<b>my pleause!</b>'
    m.smtp = ('smtp.vampire.com', 25, 'katherine@vampire.com', 'password', False)
    m.send()
    
  3. 使用Gmail SMTP

    import sae.mail
    
    sae.mail.send_mail(to, subject, body,
            ('smtp.gmail.com', 587, from, passwd, True))
    

Memcache

请在前端管理界面启用Memcache服务。

SAE Python使用 http://sendapatch.se/projects/pylibmc/ 作为mc客户端。 不同之处在于,创建Client时不用指定servers。

示例代码:

import pylibmc

mc = pylibmc.Client()

mc.set("foo", "bar")
value = mc.get("foo")

if not mc.get('key'):
    mc.set("key", "1")
mc.incr("key")

文档参考:

http://sendapatch.se/projects/pylibmc/

详细用法和 python-memcached 基本一样,可参考下面安装包中的 memcache.html 文件

http://ftp.tummy.com/pub/python-memcached/old-releases/python-memcached-1.48.tar.gz

Storage

Storage是SAE为开发者提供的分布式文件存储服务,用来存放用户的持久化存储的文件。

用户需要先在在线管理平台创建Domain,每一个domain下面包含了你上传的数据。

class sae.storage.Object(data, **kwargs)

Object类

data: Object的内容。

expires: 设置Object在浏览器客户端的过期时间,格式同Apache的Expires格式: http://httpd.apache.org/docs/2.0/mod/mod_expires.html

content_type: 设置Object的Conent-Type Header。

content_encoding: 设置Object的Cotent-Encoding Header。

class sae.storage.Client(accesskey=ACCESS_KEY, secretkey=SECRET_KEY, prefix=APP_NAME)

Client类

put(domain, key_name, object)

将object存到某个domain中。返回object的public url。

get(domain, key_name)

返回domain中名为key_name的对象。

stat(domain, key_name)

返回domain中名为key_name的对象属性,返回值为一个dict。

delete(domain, key_name)

删除domain中名为key_name的对象。

list(domain)

返回domain中所有对象的列表。

list_domain():

返回所有domain的列表。

url(domain, key_name)

返回domain中key_name的对象的public url。

Example

import sae.storage

# 初始化一个Storage客户端。
s = sae.storage.Client()

# LIST所有的domain
s.list_domain()

# PUT object至某个domain下面,put操作返回object的public url。
ob = sae.storage.Object('pieces of data')
s.put('domain-name', 'object-name', ob)

# 设置object的属性
ob = sae.storage.Object('pieces of data',   \
  expires='A3600', content_type='text/html', content_encoding='gzip')
s.put('domain-name', 'object-name', ob)

# GET某个domain下的object
ob = s.get('domain-name', 'object-name')
data = ob.data

# 获取object的属性信息
ob = s.stat('domain-name', 'object-name')

# 获取object的public url
url = s.url('domain-name', 'object-name')

# DELETE一个object
s.delete('domain-name', 'object-name')

# LIST一个domain下所有的object
s.list('domain-name')

KVDB(TBD)

开启和关闭

http://sae.sina.com.cn/?m=kv

kvdb服务禁用后会清除所有数据,请谨慎操作。

sae.kvdb

class sae.kvdb.Error

通用错误

class sae.kvdb.RouterError

路由meta信息错误

class sae.kvdb.StatusError

kvdb状态不为OK

class sae.kvdb.KVClient(**kw)

KVDB客户端封装,基于python-memcached-1.48 memcache.Client,大多数method使用方法相同。 如果不能成功创建KVClient,则抛出 sae.kvdb.Error 异常。

kw: 传递给memcache.Client的keyword参数

set(key, val, time=0, min_compress_len=0)

设置key的值为val,成功则返回True

time 该key的超时时间,请参阅memcached协议Storage commands: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

min_compress_len 启用zlib.compress压缩val的最小长度,如果val的长度大于此值 则启用压缩,0表示不压缩。

add(key, val, time=0, min_compress_len=0)

同set,但只在key不存在时起作用

replace(key, val, time=0, min_compress_len=0)

同set,但只在key存在时起作用

delete(key, time=0)

删除key,成功返回1,失败返回0。

time 为后续多少秒内set/update操作会失败。

get(key)

获取key的值,失败则返回None

get_info()

获取本应用kvdb统计数据,返回一个字典:

{
    'outbytes': 126,
    'total_size': 3,
    'inbytes': 180,
    'set_count': 60,
    'delete_count': 21,
    'total_count': 1,
    'get_count': 42
}
disconnect_all()

关闭kvdb连接

示例代码

import sae.kvdb

kv = sae.kvdb.KVClient()

k = 'foo'
kv.set(k, 2)
kv.delete(k)

kv.add(k, 3)
kv.get(k)

kv.replace(k, 4)
kv.get(k)

print kv.get_info()

参考 http://sae.sina.com.cn/?m=devcenter&catId=199

第三方认证接入

新浪微博

  • 使用weibopy

    http://code.google.com/p/sinatpy/

    已内置。对binder.py做了修改,使用urllib2替代httplib,代码在 https://gist.github.com/1357670

    from flask import Flask, request, redirect, session
    from weibopy import OAuthHandler, oauth, API
    
    app = Flask(__name__)
    app.debug = True
    app.secret_key = 'test'
    
    consumer_key = '199***'
    consumer_secret = 'a1f8****'
    
    def get_referer():
        return request.headers.get('HTTP_REFERER', '/')
    
    def get_weibo_user():
        auth = OAuthHandler(consumer_key, consumer_secret)
        # Get currrent user access token from session
        access_token = session['oauth_access_token']
        auth.setToken(access_token.key, access_token.secret)
        api = API(auth)
        # Get info from weibo
        return api.me()
    
    def login_ok(f):
        def login_wrapper(*args, **kw):
            if 'oauth_access_token' not in session:
                return redirect('/login')
            return f(*args, **kw)
        return login_wrapper
    
    @app.route('/')
    @login_ok
    def hello():
        user = get_weibo_user()
        return "Hello, %s <img src=%s>" % (user.screen_name, user.profile_image_url)
    
    @app.route('/login')
    def login():
        session['login_ok_url'] = get_referer()
        callback = 'http://appstack.sinaapp.com/login_callback'
    
        auth = OAuthHandler(consumer_key, consumer_secret, callback)
        # Get request token and login url from the provider
        url = auth.get_authorization_url()
        session['oauth_request_token'] = auth.request_token
        # Redirect user to login
        return redirect(url)
    
    @app.route('/login_callback')
    def login_callback():
        # This is called by the provider when user has granted permission to your app
        verifier = request.args.get('oauth_verifier', None)
        auth = OAuthHandler(consumer_key, consumer_secret)
        request_token = session['oauth_request_token']
        del session['oauth_request_token']
        
        # Show the provider it's us really
        auth.set_request_token(request_token.key, request_token.secret)
        # Ask for a temporary access token
        session['oauth_access_token'] = auth.get_access_token(verifier)
        return redirect(session.get('login_ok_url', '/'))
    
    @app.route('/logout')
    def logout():
        del session['oauth_access_token']
        return redirect(get_referer())
    

    请参阅 examples/weibo 。

  • 使用sinaweibopy(推荐)

    新浪微博API OAuth 2 Python客户端

    http://open.weibo.com/wiki/SDK#Python_SDK

    http://code.google.com/p/sinaweibopy/

中文分词

分词服务请求

SAE分词服务请求采用以下形式的HTTP网址:

http://segment.sae.sina.com.cn/urlclient.php?parameters

parameters为请求参数,多个参数之间使用&分割,以下列出了这些参数和其可能的值。

  • word_tag: 是否返回词性数据。0表示不返回,1表示返回,默认为0不返回。
  • encoding: 请求分词的文本的编码,可以为: GB18030、UTF-8、UCS-2,默认为UTF-8。

请求分词的文本以post的形式提交。

  • context: 请求分词的文本。目前限制文本大小最大为10KB。

分词服务响应

分词服务的响应数据为json格式,格式如下:

[
    {"word":"采莲","word_tag":"171","index":"1"},
    {"word":"赋","word_tag":"170","index":"2"}
]

响应数据为一个list,list中每个元素为一个dict,每个dict中包含以下数据:

  • index: 序列号,按在请求文本中的位置依次递增。
  • word: 单词
  • word_tag: 单词的词性,仅当输入parameters里word_tag为1时包含该项。

词性代码:

0   POSTAG_ID_UNKNOW 未知
10  POSTAG_ID_A      形容词
20  POSTAG_ID_B      区别词
30  POSTAG_ID_C      连词
31  POSTAG_ID_C_N    体词连接
32  POSTAG_ID_C_Z    分句连接
40  POSTAG_ID_D      副词
41  POSTAG_ID_D_B    副词("不")
42  POSTAG_ID_D_M    副词("没")
50  POSTAG_ID_E      叹词
60  POSTAG_ID_F      方位词
61  POSTAG_ID_F_S    方位短语(处所词+方位词)
62  POSTAG_ID_F_N    方位短语(名词+方位词“地上”)
63  POSTAG_ID_F_V    方位短语(动词+方位词“取前”)
64  POSTAG_ID_F_Z    方位短语(动词+方位词“取前”)
70  POSTAG_ID_H      前接成分
71  POSTAG_ID_H_M    数词前缀(“数”---数十)
72  POSTAG_ID_H_T    时间词前缀(“公元”“明永乐”)
73  POSTAG_ID_H_NR   姓氏
74  POSTAG_ID_H_N    姓氏
80  POSTAG_ID_K      后接成分
81  POSTAG_ID_K_M    数词后缀(“来”--,十来个)
82  POSTAG_ID_K_T    时间词后缀(“初”“末”“时”)
83  POSTAG_ID_K_N    名词后缀(“们”)
84  POSTAG_ID_K_S    处所词后缀(“苑”“里”)
85  POSTAG_ID_K_Z    状态词后缀(“然”)
86  POSTAG_ID_K_NT   状态词后缀(“然”)
87  POSTAG_ID_K_NS   状态词后缀(“然”)
90  POSTAG_ID_M      数词
95  POSTAG_ID_N      名词
96  POSTAG_ID_N_RZ   人名(“毛泽东”)
97  POSTAG_ID_N_T    机构团体(“团”的声母为t,名词代码n和t并在一起。“公司”)
98  POSTAG_ID_N_TA   ....
99  POSTAG_ID_N_TZ   机构团体名("北大")
100 POSTAG_ID_N_Z    其他专名(“专”的声母的第1个字母为z,名词代码n和z并在一起。)
101 POSTAG_ID_NS     名处词
102 POSTAG_ID_NS_Z   地名(名处词专指:“中国”)
103 POSTAG_ID_N_M    n-m,数词开头的名词(三个学生)
104 POSTAG_ID_N_RB   n-rb,以区别词/代词开头的名词(该学校,该生)
107 POSTAG_ID_O      拟声词
108 POSTAG_ID_P      介词
110 POSTAG_ID_Q      量词
111 POSTAG_ID_Q_V    动量词(“趟”“遍”)
112 POSTAG_ID_Q_T    时间量词(“年”“月”“期”)
113 POSTAG_ID_Q_H    货币量词(“元”“美元”“英镑”)
120 POSTAG_ID_R      代词
121 POSTAG_ID_R_D    副词性代词(“怎么”)
122 POSTAG_ID_R_M    数词性代词(“多少”)
123 POSTAG_ID_R_N    名词性代词(“什么”“谁”)
124 POSTAG_ID_R_S    处所词性代词(“哪儿”)
125 POSTAG_ID_R_T    时间词性代词(“何时”)
126 POSTAG_ID_R_Z    谓词性代词(“怎么样”)
127 POSTAG_ID_R_B    区别词性代词(“某”“每”)
130 POSTAG_ID_S      处所词(取英语space的第1个字母。“东部”)
131 POSTAG_ID_S_Z    处所词(取英语space的第1个字母。“东部”)
132 POSTAG_ID_T      时间词(取英语time的第1个字母)
133 POSTAG_ID_T_Z    时间专指(“唐代”“西周”)
140 POSTAG_ID_U      助词
141 POSTAG_ID_U_N    定语助词(“的”)
142 POSTAG_ID_U_D    状语助词(“地”)
143 POSTAG_ID_U_C    补语助词(“得”)
144 POSTAG_ID_U_Z    谓词后助词(“了、着、过”)
145 POSTAG_ID_U_S    体词后助词(“等、等等”)
146 POSTAG_ID_U_SO   助词(“所”)
150 POSTAG_ID_W      标点符号
151 POSTAG_ID_W_D    顿号(“、”)
152 POSTAG_ID_W_SP   句号(“。”)
153 POSTAG_ID_W_S    分句尾标点(“,”“;”)
154 POSTAG_ID_W_L    搭配型标点左部
155 POSTAG_ID_W_R    搭配型标点右部(“》”“]”“)”)
156 POSTAG_ID_W_H    中缀型符号
160 POSTAG_ID_Y      语气词(取汉字“语”的声母。“吗”“吧”“啦”)
170 POSTAG_ID_V      及物动词(取英语动词verb的第一个字母。)
171 POSTAG_ID_V_O    不及物谓词(谓宾结构“剃头”)
172 POSTAG_ID_V_E    动补结构动词(“取出”“放到”)
173 POSTAG_ID_V_SH   动词“是”
174 POSTAG_ID_V_YO   动词“有”
175 POSTAG_ID_V_Q    趋向动词(“来”“去”“进来”)
176 POSTAG_ID_V_A    助动词(“应该”“能够”)
180 POSTAG_ID_Z      状态词(不及物动词,v-o、sp之外的不及物动词)
190 POSTAG_ID_X      语素字
191 POSTAG_ID_X_N    名词语素(“琥”)
192 POSTAG_ID_X_V    动词语素(“酹”)
193 POSTAG_ID_X_S    处所词语素(“中”“日”“美”)
194 POSTAG_ID_X_T    时间词语素(“唐”“宋”“元”)
195 POSTAG_ID_X_Z    状态词语素(“伟”“芳”)
196 POSTAG_ID_X_B    状态词语素(“伟”“芳”)
200 POSTAG_ID_SP     不及物谓词(主谓结构“腰酸”“头疼”)
201 POSTAG_ID_MQ     数量短语(“叁个”)
202 POSTAG_ID_RQ     代量短语(“这个”)
210 POSTAG_ID_AD     副形词(直接作状语的形容词)
211 POSTAG_ID_AN     名形词(具有名词功能的形容词)
212 POSTAG_ID_VD     副动词(直接作状语的动词)
213 POSTAG_ID_VN     名动词(指具有名词功能的动词)
230 POSTAG_ID_SPACE  空格

例:

chinese_text = """
这里填上需要分词的文本
"""

_SEGMENT_BASE_URL = 'http://segment.sae.sina.com.cn/urlclient.php'

payload = urllib.urlencode([('context', chinese_text),])
args = urllib.urlencode([('word_tag', 1), ('encoding', 'UTF-8'),])
url = _SEGMENT_BASE_URL + '?' + args
result = urllib2.urlopen(url, payload).read()

相关工具

使用dev_server进行调试

目前支持的服务包括:mysql, taskqueue, memcache, storage, mail。 大部分的服务直接运行dev_server.py进行调试就可以了,部分服务需要做一些配置。

注意: 本工具仅为应用开发便利之用,对sae python环境的模拟并不完整。

Install

$ git clone http://github.com/SAEPython/saepythondevguide.git
$ sudo python setup.py install

基本使用

使用svn检出app代码之后,建立以数字为标识的发布目录,切换到发布目录:

$ pwd
/home/jaime/source/blackfire/1

编辑index.wsgi和config.yaml:

$ vi index.wsgi
import sae

def app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello, world! reloading test3']

application = sae.create_wsgi_app(app)

$ vi config.yaml
---
name: blackfire
version: 1
...

运行dev_server.py。

$ dev_server.py
MySQL config not found: app.py
Start development server on http://localhost:8080/

访问 http://localhost:8080 端口就可以看到Hello, world!了。

使用MySQL服务

配置好MySQL本地开发server,使用 –mysql 参数运行dev_server.py。

$ dev_server.py --mysql=user:password@host:port

现在你可以在应用代码中像在SAE线上环境一样使用MySQL服务了。 dev_server.py默认使用名为 app_应用名 的数据库。

使用storage服务

使用 –storage-path 参数运行dev_server.py。

$ dev_server.py --storage-path=/path/to/local/storage/data

本地的storage服务使用以下的目录结构来模拟线上的storage。

storage-path/
      domain1/
            key1
            key2
      domain2/
      domain3/

–storage-path配置的路径下每个子文件夹会映射为storage中的一个domain, 而每个子文件夹下的文件映射为domain下的一个key,其内容为对应key的数据。

使用pylibmc

dev_server自带了一个dummy pylibmc,所以无须安装pylibmc就可以直接使用memcache服务了。 该模块将所有的数据存贮在内存中,dev_server.py进程结束时,所有的数据都会丢失。

使用virtualenv管理依赖关系

当你的应用依赖很多第三方包时,可以使用virtualenv来管理并导出这些依赖包, 流程如下:

首先,创建一个全新的Python虚拟环境目录ENV,启动虚拟环境。

$ virtualenv --no-site-packages ENV
$ source ENV/bin/activate
(ENV)$

可以看到命令行提示符的前面多了一个(ENV)的前缀,现在我们已经在一个全新的虚拟环境中了。

使用pip安装应用所依赖的包并导出依赖关系到requirements.txt。

(ENV)$ pip install Flask Flask-Cache Flask-SQLAlchemy
(ENV)$ pip freeze > requirements.txt

编辑requirements.txt文件,删除一些sae内置的模块,eg. flask, jinja2, wtforms。

使用dev_server/bundle_local.py工具, 将所有requirements.txt中列出的包导出到本地目录virtualenv.bundle目录中。 如果文件比较多的话,推荐压缩后再上传。

(ENV)$ bundle_local.py -r requirements.txt
(ENV)$ cd virtualenv.bundle/
(ENV)$ zip -r ../virtualenv.bundle.zip。

将virutalenv.bundle目录或者virtualenv.bundle.zip拷贝到应用的目录下。

修改index.wsgi文件,在导入其它模块之前,将virtualenv.bundle目录或者 virtualenv.bundle.zip添加到module的搜索路径中,示例代码如下:

import os
import sys

app_root = os.path.dirname(__file__)

# 两者取其一
sys.path.insert(0, os.path.join(app_root, 'virtualenv.bundle'))
sys.path.insert(0, os.path.join(app_root, 'virtualenv.bundle.zip'))

到此,所有的依赖包已经导出并加入到应用的目录里了。

更多virtualenv的使用可以参考其官方文档。 http://pypi.python.org/pypi/virtualenv

Note

  1. 请删除requirements.txt中的wsgiref==0.1.2这个依赖关系,否则可能导致 bundle_local.py导出依赖包失败。
  2. 有些包是not-zip-safe的,可能不工作,有待验证。 含有c扩展的package 不能工作。

使用saecloud部署应用

saecloud是一个简单的命令行部署工具。它分离了代码部署和代码托管,使你可以选择习惯使用的vcs工具,同时还能够快速部署本地app目录到SAE服务器上。

使用svn的代码目录结构:

jaime@westeros:~/source/app/memorystone$ ls
1  2
jaime@westeros:~/source/app/memorystone$ ls 1
index.wsgi
jaime@westeros:~/source/app/memorystone$ ls 2
index.wsgi
jaime@westeros:~/source/app/memorystone$ ls -a

该app根目录下面有两个子目录,分别对应于两个app版本,颇为麻烦。

使用saecloud deploy:

jaime@westeros:~/source/app/memorystone$ ls
index.wsgi
jaime@westeros:~/source/app/memorystone$

不再需要数字格式的版本目录了。

安装

jaime@westeros:~/saepythondevguide/dev_server$ sudo python setup.py install
[sudo] password for jaime:
running install
....
jaime@westeros:~/saepythondevguide/dev_server$ saecloud version
SAE command line v0.0.1
jaime@westeros:~/saepythondevguide/dev_server$

导出已有应用代码

帮助信息:

jaime@westeros:~/source/app$ saecloud
usage: saecloud [-h] {version,export,deploy} ...

positional arguments:
  {version,export,deploy}
                        sub commands
    export              export source code to local directory
    deploy              deploy source directory to SAE
    version             show version info

optional arguments:
  -h, --help            show this help message and exit
jaime@westeros:~/source/app$

导出memorystone应用版本2到本地目录:

jaime@westeros:~/source/app$ saecloud export memorystone 2 --username fooxxx@gmail.com --password barxxx
Exporting to memorystone
jaime@westeros:~/source/app$ cd memorystone
jaime@westeros:~/source/app/memorystone$ ls
index.wsgi
jaime@westeros:~/source/app/memorystone$

第一个参数为应用名字,第二个参数为版本,可选,默认为版本1。

第一次使用时,请指定你的代码访问帐号信息:username 安全邮箱, password。之后的命令不用在输入此信息。

部署新代码

新建config.yaml:

jaime@westeros:~/source/app/memorystone$ vi config.yaml
jaime@westeros:~/source/app/memorystone$ cat config.yaml
name: memorystone
version: 2
jaime@westeros:~/source/app/memorystone$ ls
config.yaml  index.wsgi

saecloud从config.yaml文件获得信息,判断将要把代码部署到哪个应用的哪个版本。

修改一下index.wsgi,然后运行 saecloud deploy:

jaime@westeros:~/source/app/memorystone$ saecloud deploy
Deploying http://2.memorystone.sinaapp.com
Updating cache
Finding changes
Pushing to server...  done
jaime@westeros:~/source/app/memorystone$

That’s it.

saecloud deploy命令接受一个可选参数: app代码所在路径,默认为当前目录’.’。 –username, –password同export命令。

修改一下config.yaml,部署到一个新版本3:

jaime@westeros:~/source/app/memorystone$ vi config.yaml
jaime@westeros:~/source/app/memorystone$ saecloud deploy
Deploying http://3.memorystone.sinaapp.com
Updating cache
Finding changes
Pushing to server...  done
jaime@westeros:~/source/app/memorystone$ cat config.yaml
name: memorystone
version: 3
jaime@westeros:~/source/app/memorystone$

注意:

  • 删除应用版本目前仍然只能在前端管理界面中操作。

Warning

cron中的配置 schedule: */5 * * * * 目前无法识别,会报语法错误

saecloud和git workflow

jaime@westeros:~/source/app$ rm -rf memorystone
jaime@westeros:~/source/app$ saecloud export memorystone 2
Exporting to memorystone
jaime@westeros:~/source/app$ cd memorystone
jaime@westeros:~/source/app/memorystone$ ls
config.yaml  index.wsgi
jaime@westeros:~/source/app/memorystone$ git init
Initialized empty Git repository in /home/jaime/source/app/memorystone/.git/
jaime@westeros:~/source/app/memorystone$ git add .
jaime@westeros:~/source/app/memorystone$ git ci -am "Testing saecloud"
[master (root-commit) fe7131e] Testing saecloud
 2 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 config.yaml
 create mode 100644 index.wsgi
jaime@westeros:~/source/app/memorystone$ git branch
* master


jaime@westeros:~/source/app/memorystone$ git co -b v3
Switched to a new branch 'v3'
jaime@westeros:~/source/app/memorystone$ git branch
  master
* v3
jaime@westeros:~/source/app/memorystone$ git st
# On branch v3
nothing to commit (working directory clean)
jaime@westeros:~/source/app/memorystone$ vi config.yaml
jaime@westeros:~/source/app/memorystone$ vi index.wsgi
jaime@westeros:~/source/app/memorystone$ git df
diff --git a/config.yaml b/config.yaml
index 658ce65..c645699 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,2 +1,2 @@
 name: memorystone
-version: 2
+version: 3
diff --git a/index.wsgi b/index.wsgi
index d2df150..7157797 100644
--- a/index.wsgi
+++ b/index.wsgi
@@ -4,6 +4,6 @@ def app(environ, start_response):
     status = '200 OK'
     response_headers = [('Content-type', 'text/plain')]
     start_response(status, response_headers)
-    return ['Hello, world! saecloud deploy']
+    return ['Hello, world! -v3']

 application = sae.create_wsgi_app(app)
jaime@westeros:~/source/app/memorystone$ git ci -am "Fix on v3"
[v3 a6e6c65] Fix on v3
 2 files changed, 2 insertions(+), 2 deletions(-)
jaime@westeros:~/source/app/memorystone$ saecloud deploy
Deploying http://3.memorystone.sinaapp.com
Updating cache
Finding changes
Pushing to server...  done


jaime@westeros:~/source/app/memorystone$ git branch
  master
* v3
jaime@westeros:~/source/app/memorystone$ git co master
Switched to branch 'master'
jaime@westeros:~/source/app/memorystone$ vi index.wsgi
jaime@westeros:~/source/app/memorystone$ git df
diff --git a/index.wsgi b/index.wsgi
index d2df150..5704e33 100644
--- a/index.wsgi
+++ b/index.wsgi
@@ -4,6 +4,6 @@ def app(environ, start_response):
     status = '200 OK'
     response_headers = [('Content-type', 'text/plain')]
     start_response(status, response_headers)
-    return ['Hello, world! saecloud deploy']
+    return ['Hello, world! -v2']

 application = sae.create_wsgi_app(app)
jaime@westeros:~/source/app/memorystone$ git ci -am "Fix on v2"
[master c6a90a4] Fix on v2
 1 files changed, 1 insertions(+), 1 deletions(-)
jaime@westeros:~/source/app/memorystone$ saecloud deploy
Deploying http://2.memorystone.sinaapp.com
Updating cache
Finding changes
Pushing to server...  done
jaime@westeros:~/source/app/memorystone$ git branch
* master
  v3
jaime@westeros:~/source/app/memorystone$ saecloud deploy
Deploying http://2.memorystone.sinaapp.com
Updating cache
Finding changes
No changes found
jaime@westeros:~/source/app/memorystone$

注意:

  • 如果代码量较大,则上传时间较慢,请耐心等待

  • 不推荐混合使用saecloud deploy和svn

    虽然saecloud deploy部署之前会自动更新代码,但是如果有代码冲突则会导致本地状态不一致。

    解决办法为删除本地cache目录:

    rm -rf ~/.saecloud
    
  • saecloud deploy 分离了部署和代码管理,导致用户不能像原来的svn方式那样,在不同机器之间共享代码版本历史。 请使用你的vcs工具在不同机器之间同步代码。

可用插件

SAE Python Shell

SAE Python Shell是一个wsgi中间件,提供了一个在线的interactive shell,便于在线调 试app,查看系统信息等。(由 shellpy 修改而来)。

class sae.ext.shell.ShellMiddleware(app, secret_code)

app: 你的应用callable

secret_code: 登录shell时需要输入的口令,用于保护shell不被非法访问。如本例的口令为 hugoxxxx,你可以设置你自己的口令,长度应不小于8个字节

使用步骤:

  • 该插件需要使用 sae.kvdb 服务,请事先开启。
  • 修改index.wsgi,启用shell插件,示例如下:

    import sae
    from sae.ext.shell import ShellMiddleware
    
    def app(environ, start_response):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers)
        return ["Hello, world!"]
    
    application = sae.create_wsgi_app(ShellMiddleware(app, 'hugoxxxx'))
    
  • 访问地址 https://$yourappname.sinaapp.com/_web/shell ,根据提示输入你设置的口令

Warning

请使用https方式访问shell地址 /_web/shell,这样可以加密传输口令。测试期间请谨慎使用,建议不使用时从源码中注释掉此shell。

FAQ

怎么寻求帮助

关于SAE Python相关服务的问题可以在以下地方反馈:

关于Python编程的其它问题,推荐到 CPyUG邮件列表Python编程豆瓣小组 寻求帮助。

如何调试

复杂程序建议您本地调试成功后,再上传运行。

SAE Python 版本为 2.6.7。如果你使用内置的第三方库版本,请注意使用同样的版本调试, 如支持的Django为1.2.7。

如何捕获wsgi应用的异常,请参阅 http://www.python.org/dev/peps/pep-0333/

501 页面对应的常见处理办法,请检查:

  • 使用dev_server查看是否有语法错误
  • 模块是否正确安装
  • 是否遵循WSGI规范,返回iterator
  • 数据库设置是否正确,是否已在SAE管理界面启用MYSQL,是否已创建数据表,初始化
  • 是否已经打开framework的debug功能

有的framework默认关闭了debug功能,如果程序有问题则只返回500 internal error,没有异常堆栈信息, 这样调试起来很困难。在开发过程中,请确认框架的debug功能处于开启状态。

对于无法加载index.wsgi,index.wsgi中没有application callable等等严重错误,SAE Python会直接在浏览器中打印出异常, 其余应用没有捕获的异常会打印到应用的日志中,如果需要SAE Python将所有应用未捕获的异常打印到浏览器,请按如下创建application。

Note

在header已经发出的情况下,异常在浏览器中可能显示不出来,请查看日志。

Python新手?入门教程

没有我要使用的包,怎么办?

Don’t panic. See howto-use-sae-python-with-virtualenv

关于svn的问题

Warning

不要使用svn cp,mv,目前还不支持这两个操作。

http://sae.sina.com.cn/?m=devcenter&catId=211

大文件,文件数多上传 http://www.douban.com/group/topic/23353500/

bug 静态目录不支持多级? http://www.douban.com/group/topic/23692928/

建议遇到奇怪svn错误,可以:

  1. 重新在本地新建目录,检出干净的svn
  2. 或者先保存代码,然后删除该版本,重新导入

你也许需要新建一个版本,默认版本无法删除。

WTF! MySQL gone away

MySQL连接超时时间为30s,所以你需要在代码中检查是否超时,是否需要重连。

【bug?】我用tornado db连接 出现了mysql gone away... http://www.douban.com/group/topic/23673391/

mysql中创建表的问题 http://www.douban.com/group/topic/23689631/

flask-sqlalchemy 如何在每次请求时重新连接数据库 http://www.douban.com/group/topic/24103570/

如何区分本地开发环境和线上环境?

一个可靠的方法:

if 'SERVER_SOFTWARE' in os.environ:
    # SAE
else:
    # Local

反馈

SAE Python 小组 http://www.douban.com/group/373262/