About
RSS

Bit Focus


Flask / MongoDB 搭建简易图片服务器

前期准备

通过 pip 或 easy_install 安装了 pymongo 之后, 就能通过 Python 调教 mongodb 了.
接着安装个 flask 用来当 web 服务器.
当然 mongo 也是得安装的. 对于 Ubuntu 用户, 特别是使用 Server 12.04 的同学, 安装最新版要略费些周折, 具体说是

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-10gen

如果你跟我一样觉得让通过上传文件名的后缀判别用户上传的什么文件完全是捏着山药当小黄瓜一样欺骗自己, 那么最好还准备个 Pillow 库

pip install Pillow

或 (更适合 Windows 用户)

easy_install Pillow

正片

Flask 文件上传

    Flask 官网上那个例子居然分了两截让人无从吐槽. 这里先弄个最简单的, 无论什么文件都先弄上来
import flask

app = flask.Flask(__name__)
app.debug = True

@app.route('/upload', methods=['POST'])
def upload():
    f = flask.request.files['uploaded_file']
    print f.read()
    return flask.redirect('/')

@app.route('/')
def index():
    return '''
    <!doctype html>
    <html>
    <body>
    <form action='/upload' method='post' enctype='multipart/form-data'>
         <input type='file' name='uploaded_file'>
         <input type='submit' value='Upload'>
    </form>
    '''

if __name__ == '__main__':
    app.run(port=7777)
  • 注: 在 upload 函数中, 使用 flask.request.files[KEY] 获取上传文件对象, KEY 为页面 form 中 input 的 name 值
    因为是在后台输出内容, 所以测试最好拿纯文本文件来测.

保存到 mongodb

    如果不那么讲究的话, 最快速基本的存储方案里只需要

Permanent Link: /p/514 Load full text

Post tags:

 Python
 Web Server
 Flask
 Tutorial
 MongoDB

PLY 构造词法分析工具

    PLY 需要安装一份. 可以直接通过 pip 安装

# pip install ply

    这东西并非一个扩展的正则表达式工具, 而是一个完备的编译器构造工具, 不过这篇文章只打算讨论其词法分析器构造部分.

基本例子

    PLY 很魔法的一点是它使用到了模块内部反射. 也就是说在产生一个词法分析器时, 并不是把词法规则传递给 PLY 的接口, 而是依次将一些指定名字的变量或函数定义在 py 文件中.
    下面给出第一个例子, 从文本中抓出十进制数值.
import ply.lex

tokens = (
    'NUMBER',
)

t_NUMBER = r'\d*[.]?\d+'

def t_error(t):
    t.lexer.skip(1)

ply.lex.lex()
ply.lex.input('''
    The Chinese mathematician Zu Chongzhi, around 480 AD, calculated that
        pi ~= 355/113
    (a fraction that goes by the name Milv in Chinese), using Liu Hui's
    algorithm applied to a 12288-sided polygon. With a correct value for its
    seven first decimal digits, this value of 3.141592920... remained the most
    accurate approximation of pi available for the next 800 years.

    <From Wikipedia:Pi>
''')

for token in iter(ply.lex.token, None):
    print token.type, token.value
    输出

NUMBER 480
NUMBER 355
NUMBER 113
NUMBER 12288
NUMBER 3.141592920
NUMBER 800

    上面的代码有这么一些要点
  • 文件中定义了 tokens 表示可能的词元类型; 在官方例子中, 其中的取值通常以全大写的形式出现
  • 定义词元规则 t_NUMBER, 其名字是 token 变量中的成员 'NUMBER' 加上前缀 t_; 在构造词法分析器时, PLY 会将以 t_ 开头的所有定义收集起来
  • 词元规则 t_NUMBER 的取值是正则表达式, 用来匹配所有的数值
  • 定义 t_error 函数, 如果什么奇怪的东西混进来, 这个函数会被调用; 不过现在只是抓取数值, 无视其它符号, 所以实现只是跳过一个字符 (skip 的参数是字符数量)
  • 调用 ply.lex.lex 构造词法分析器
  • 调用 ply.lex.input 喂一些输入进去
  • ply.lex.token 获得词法分析的结果

利用分词输出

    从这个基本例子迈向一个用于简单表达式计算的词法分析器并不困难. 如果按照之前一篇文章里所演示的那个功能 (请将该文章中最后一段代码保存在 expr_eval.py 中以供这次使用), 需要至少以下这些词元类型
  • 二元算符 */^
  • 二元或一元算符 +-
  • 括号
  • 整数

Permanent Link: /p/513 Load full text

Post tags:

 Compiler Construction
 Python
 Tutorial
 PLY

NodeJS liteview Postgres Stekinscript 搭建留言板 [完]

上篇

前期准备

安装软件与库

    需要安装
  • Postgres (建议 9 或以上版本)
  • libpq-dev
    npm 安装 Postgres 库
npm install pg

配置数据库与表

    参考这篇文章前半部分配置 Postgres 数据库.
    如果已经为 PG 用户 psuser 授权使用 mydb 这个数据库, 现在进入数据库
psql -Upsuser -dmydb -W
Password for user psuser:
建立数据表用于存放留言
mydb=> CREATE TABLE message (
mydb(>   id SERIAL,
mydb(>   content TEXT,
mydb(>   post_time TIMESTAMP DEFAULT NOW(),
mydb(>   PRIMARY KEY(id)
mydb(> );
NOTICE:  CREATE TABLE will create implicit sequence "message_id_seq" for serial column "message.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "message_pkey" for table "message"
CREATE TABLE

显示留言信息

更新页面模板

    之前的页面模板实在显得太单薄, 下面修改一下准备放入留言数据
<html>
<head><title>Guestbook</title></head>
<body>
#{foreach(#messages)}
<div>
    <p style='font-size: 0.6em'>#{[post_time]}</p>
    <p style='margin-left: 20px'>#{[content]}</p>
</div>
#{end}
    liteview 文档如是说
  • #{foreach(#XXX)} 即在 XXX 对象上作循环
  • #{[YYY]} 即每次循环的循环变量的 YYY 属性
    因此上面的页面模板需要传入留言对象列表, 每个留言对象有属性 post_time, content. 这命名与数据库表是一致的.
    然后改一下 index.stkn, 放假数据进去先看看效果
view: require('liteview').create()

exports.get: (request, response):
    response.writeHead(200, { 'Content-Type':: 'text/html' })
    response.end(view.render('index.html', {messages: [
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Nostalgia',
        },
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Eternal Rite',
        },
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Shinto Shrine',
        },
    ]}))
    刷下页面, 这些东西就出现了.

从数据库加载内容

    巧妇难为无米之炊, 现在首先要做的是向数据库插入一些数据. 还是更刚才建表一样, 进入 psql, 使用下面的语句插入一些水帖

Permanent Link: /p/500 Load full text

Post tags:

 Postgres
 Stekin
 Tutorial
 Web Server
 NodeJS

NodeJS liteview Postgres Stekinscript 搭建留言板 [零]

前期准备

配置软件与库

    需要安装
  • nodejs 0.8 或以上版本 (之后编译 Postgres 模块需要高版本)
  • npm
  • 下载编译 Stekinscript (非必需) $ git clone git://github.com/neuront/stekinscript.git
    Ubuntu 用户安装 nodejs 0.8 版本请加入下面的源
add-apt-repository ppa:chris-lea/node.js
    创建一个工作目录, 通过 npm 安装要用到的模块
npm install liteview
npm install validator

Stekinscript 快速入门 (非必需)

    请参见 https://github.com/neuront/stekinscript/wiki/Language-Specification.
    对 Stekinscript 没有兴趣的同学可以无视这一步, 在每节之后生成的 Javascript 代码会奉上, 对于 Stekinscript 代码, 可以认为它是更易阅读的 JS.

开工

上手

    创建如下内容的 main.stkn 文件, 先启动一个简单的服务器
http: require('http')

port: 8888

http.createServer((request, response):
    response.writeHead(200, { 'Content-Type':: 'text/html' })
    response.end('<html><body>Hello, World!')
).listen(port, '127.0.0.1')

console.log('Server running at local host port=' + port)
    使用 stekin 编译它 (请用合适的路径替换下面的 stekin, 或者将编译生成的 stekin 可执行程序加入 PATH)
stekin -i require < main.stkn > main.js
    生成的 JS 代码类似 (下面这是经过去除额外括号与重新排版的, 实际上现在 Stekinscript 生成的代码真是惨不忍睹)
(function () {
    const s_http = require("http");
    s_http.createServer(function (s_request, s_response) {
        s_response.writeHead(200, {
            "Content-Type": "text/html"
        });
        s_response.end("<html><body>Hello, World!");
    }).listen(8888, "127.0.0.1");
    console.log("Server running at local host port=8888");
})();
    然后使用 node 来运行它, 就在本地搭建了一枚服务器
node main.js
Server running at local host port=8888
    访问 http://localhost:8888/ 就可以看到服务器响应了.

Makefile

Permanent Link: /p/499 Load full text

Post tags:

 NodeJS
 Stekin
 Web Server
 Tutorial

模板之工 - Mako 上手篇

简介

Mako 是一个 Python 模板程序. 用于给定一个字符串, 或给定一个文件, 使用 Python 对象值动态地替换其中的一部分内容.

可是使用 pip 或 easy_install 安装 Mako 库.
# pip install Mako
# easy_install Mako

使用

基本文本替换

安装完成后, 在交互环境中可以通过下面的代码来测试 Mako
>>> import mako.template
>>> print mako.template.Template('Hello, ${ what }!').render(what='Mako')
Hello, Mako!
上述代码中, 引入 Mako 模板模块, 并使用模板对字符串 Hello, ${ what }! 中的动态内容进行替换. 动态内容是形如 ${ what } 这样的 Mako 标记, 它表示将传递给 render 函数的字典中, what 对应的项的值替换这个标记. 因此上面的 render 结果便是 Hello, Mako!.

替换文件中的动态内容

更多的应用场景是将需要生成的内容写成一个文件模板 (如 HTML 页面), 读取这个页面并生成结果内容. 在当前目录下创建 hello-mako.html 文件, 内容如下
<html>
<body>
Dear ${ username }, welcome back!
并使用如下的 Python 代码
import mako.template
templ = mako.template.Template(filename='hello-mako.html')
print templ.render(username='Mako')
就能看到输出的结果 HTML 代码了.

使用中文及指定编码

上面的页面模板代码中如果包含中文或其它非 ASCII 字符, 如
<html>
<body>
你好, ${ username }, 欢迎回来!
那么这一段程序在构造 mako.template.Template 对象时多半会挂掉. 要为 Mako 模板指定输入编码方式才行
import mako.template
templ = mako.template.Template(filename='hello-mako.html', input_encoding='utf-8')
print templ.render(username=u'柑奈')

分支

接下来, 如果这个用户是个管理员用户, 那么希望为这个用户呈现一个后台管理的入口 (当然其它用户就没这个福利了), 那么可以用 Mako 提供的分支来实现
你好, ${ username }, 欢迎回来!

%if is_admin:
    <a href='/admin'>管理页面</a>
%endif
并使用如下语句分别测试
templ = mako.template.Template(filename='hello-mako.html', input_encoding='utf-8')
print templ.render(username=u'柑奈', is_admin=False)
print templ.render(username=u'柑奈', is_admin=True)
%if %endif 之间表示一段分支. 如果确实需要书写一个百分号, 而不是作为 Mako 的指令符号, 那么此处须连续两个百分号 %% 来转义, 通常页面上似乎也不会出现那么多百分号, 所以这个应该还是可以接受的吧.

关于分支, 更详细的例子是
%if condition_a:
    do_a
%elif condition_b:
    do_b
%else:
    do_c
%endif

对象式引用

Permanent Link: /p/491 Load full text

Post tags:

 Python
 Tutorial
 Template
 Mako

风之力 - Tornado 搭建基于 WebSocket 的聊天服务

    这年头 Python web 框架是有点泛滥了. 下面要介绍的是 facebook 的开源框架 tornado. 这东西比较简单, 而且自带 WebSocket 支持, 可以用它做个简单的聊天室.
    读者最好已具备 Javascript 与 WebSocket 的基础知识.

安装

    使用 easy_install 能很方便地爬到 tornado. 或者, 下载源代码, 解包后在源码目录执行
$ python setup.py build
# python setup.py install
即可.

开张

    首先还是来个 hello world.
import tornado.web
import tornado.ioloop

class Index(tornado.web.RequestHandler):
    def get(self):
        self.write('<html><body>Hello, world!')

if __name__ == '__main__':
    app = tornado.web.Application([
        ('/', Index),
    ])
    app.listen(8000)
    tornado.ioloop.IOLoop.instance().start()
    保存为 main.py, 然后执行
$ python main.py
    并访问 http://localhost:8000/ 即可看到页面中的 "Hello, world!".

    在分支中定义的 app 在构造时接受的一个列表参数
[
    ('/', Index),
]
用来配置 URL 映射, 比如这里访问根路径则映射至 Index 实例去处理, 在 Index 实例中, 定义的 get 方法将会处理请求.

处理 WebSocket 连接

添加请求处理类

    接下来就进入 WebSocket 环节. 先修改返回的页面, 让这个页面在加载后连接服务器.
class Index(tornado.web.RequestHandler):
    def get(self):
        self.write('''
<html>
<head>
<script>
var ws = new WebSocket('ws://localhost:8000/soc');
ws.onmessage = function(event) {
    document.getElementById('message').innerHTML = event.data;
};
</script>
</head>
<body>
<p id='message'></p>
        ''')
    修改这个类后, 然后在控制台中止服务器 (猛击 Ctrl-C), 并重新启动之.
    现在, 访问 http://localhost:8000/ 会遇到 404 错误, 因为 WebSocket 请求的 URL "ws://localhost:8000/soc" 还没有映射任何处理器, 因此这里需要再添加一个, 用于处理 WebSocket 请求的类.
import tornado.websocket

class SocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        self.write_message('Welcome to WebSocket')
    并为这个类加上 URL 映射

Permanent Link: /p/489 Load full text

Post tags:

 Python
 Web Server
 Tornado
 Tutorial

对象炼金术 - SQLAlchemy 多外键关联同一实体类

    继续上节 SQLAlchemy 外键的探索.
    假设现在需求改变了, 需要给每个 Album 加个字段表示作词者, 而作词者本身也应是 Artist 的实例 (比如很多演唱者会自己为歌曲作词编曲等等), 这时 Album 会有超过一个关联到 Artist 的外键.
    如果仅仅简单如下处理
class Album(Base):
    __tablename__ = 'album'
    album_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)
    artist_id = sqla.Column('artist', sqla.ForeignKey('artist.id'))
    artist = sqlorm.relationship(
          Artist,
          backref=sqlorm.backref('albums', cascade='all,delete-orphan'))
    lyricist_id = sqla.Column('lyricist', sqla.ForeignKey('artist.id'))
    lyricist = sqlorm.relationship(
          Artist,
          backref=sqlorm.backref('written_albums', cascade='all,delete-orphan'))
然后添加一点数据进去
def save():
    artist0 = Artist(name='aki misawa')
    artist1 = Artist(name='katou emiri')
    artist2 = Artist(name='yamada hirosi')
    album0 = Album(name='stella musica', artist=artist0, lyricist=artist0)
    album1 = Album(name='hoshikage no ama no hara', artist=artist0,
                   lyricist=artist0)
    album2 = Album(name='jump!', artist=artist1, lyricist=artist2)
    session = Session()
    try:
        session.add(album0)
        session.add(album1)
        session.add(album2)
        session.flush()
        session.commit()
    finally:
        session.close()

if __name__ == '__main__':
    save()
结果运行立即悲剧, SQLAlchemy 会报下面的错误
sqlalchemy.exc.ArgumentError: Could not determine join condition between parent/child tables on relationship Album.artist. Specify a 'primaryjoin' expression.  If 'secondary' is present, 'secondaryjoin' is needed as well.
也许这是 SQLAlchemy 的问题, 明明这么明了的外键关系咋就跪了呢.

    好吧, 简单的正确答案是, 手动配置一下外键的 primaryjoin 属性, 如下

Permanent Link: /p/488 Load full text

Post tags:

 ORM
 SQLAlchemy
 Python
 Tutorial

对象炼金术 - SQLAlchemy 中关系的级联

    在上一节中谈到了如何把有关联的对象一起塞进数据表中, 现在来试试从数据表里面取出一条数据然后删掉.
    在之前代码的基础上添加下面的函数
def remove_album(name):
    session = Session()
    try:
        for a in session.query(Album).filter(Album.name == name).all():
            session.delete(a)
        session.flush()
        session.commit()
    finally:
        session.close()
    在这个函数中, 查询 Album 类型, 然后调用查询对象的 filter 函数, 指定列 name 的值严格等于该 name 参数. 接下来, 将查询得到的对象逐个删除.
    来看看搞起来如何
if __name__ == '__main__':
    save()
    remove_album('stella musica')
    list_all()
    嗯, 看起来还不错的样子.

    那么, 接下来试试删 Artist
def remove_artist(name):
    session = Session()
    try:
        for a in session.query(Artist).filter(Artist.name == name).all():
            session.delete(a)
        session.flush()
        session.commit()
    finally:
        session.close()

if __name__ == '__main__':
    save()
    remove_artist('katou emiri')
    list_all()
于是乎就悲剧了.
    崩盘之前的几行输出似乎是这样子的
= Albums =
stella musica
+ Artist: aki misawa
hoshikage no ama no hara
+ Artist: aki misawa
jump!
+ Artist:
    也就是说, remove_artist 确实从数据库中删去了名字为 'katou emiri' 的那个值, 因此名为 'jump!' 的那个 Album 实例的 artist 域成了 None, 悲剧就发生了.

    要解决这个问题, 就得在 AlbumArtist 的关联关系上动动手脚, 加上级联信息.

Permanent Link: /p/487 Load full text

Post tags:

 SQLAlchemy
 ORM
 Python
 Tutorial

Page 0 1 2


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site