lightweight SQLAlchemy based ORM
Python
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
thing
.gitignore
MANIFEST.in
README.md
setup.py

README.md

     /\  \         /\__\          ___        /\__\         /\  \    
     \:\  \       /:/  /         /\  \      /::|  |       /::\  \   
      \:\  \     /:/__/          \:\  \    /:|:|  |      /:/\:\  \  
      /::\  \   /::\  \ ___      /::\__\  /:/|:|  |__   /:/  \:\  \ 
     /:/\:\__\ /:/\:\  /\__\  __/:/\/__/ /:/ |:| /\__\ /:/__/_\:\__\
    /:/  \/__/ \/__\:\/:/  / /\/:/  /    \/__|:|/:/  / \:\  /\ \/__/
   /:/  /           \::/  /  \::/__/         |:/:/  /   \:\ \:\__\  
   \/__/            /:/  /    \:\__\         |::/  /     \:\/:/  /  
                   /:/  /      \/__/         /:/  /       \::/  /   
                   \/__/                     \/__/         \/__/    

Thing 是什么?

Thing是一个基于SQLAlchemy的配置简单、使用简单且灵活的ORM。

使用方法

举个简单的例子,假如有3个表:comment, post, user, 3个表的字段分别是:

comment表:

+---------+------------------+------+-----+---------+----------------+
| Field   | Type             | Null | Key | Default | Extra          |
+---------+------------------+------+-----+---------+----------------+
| id      | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| user_id | int(11)          | YES  | MUL | NULL    |                |
| post_id | int(11)          | YES  | MUL | NULL    |                |
| content | text             | YES  |     | NULL    |                |
+---------+------------------+------+-----+---------+----------------+

post表:

+---------+------------------+------+-----+---------+----------------+
| Field   | Type             | Null | Key | Default | Extra          |
+---------+------------------+------+-----+---------+----------------+
| id      | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| user_id | int(11)          | YES  | MUL | NULL    |                |
| created | int(11)          | YES  |     | NULL    |                |
| content | text             | YES  |     | NULL    |                |
| title   | varchar(255)     | YES  |     | NULL    |                |
+---------+------------------+------+-----+---------+----------------+

user表:

+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| name  | varchar(30)      | YES  |     | NULL    |                |
+-------+------------------+------+-----+---------+----------------+

定义Model

先来看看目录结构

├── __init.py__
├── conn.py # 用于数据库连接
├── models
│   ├── __init__.py
│   ├── comment.py
│   ├── post.py
│   ├── user.py
└── test.py

test.py就是进行测试的地方,先来看看各个model的内容:

comment.py

from thing import thing

class Comment(thing.Thing):
    _belongs_to = {
            'post': {
                'model': 'models.post.Post',
                'foreign_key': 'post_id',
                },
            'author': {
                'model': 'models.user.User',
                'foreign_key': 'user_id',
                },
            }

post.py

from thing import thing

class Post(thing.Thing):
    _belongs_to = {
            'author': {
                'model': 'models.user.User',
                'foreign_key': 'user_id',
                }
            }
    _has_many = {
            'comments': {
                'model': 'models.comment.Comment',
                'foreign_key': 'user_id',
                }
            }

user.py

from thing import thing

class User(thing.Thing):
    _has_many = {
            'posts': {
                'model': 'models.post.Post',
                'foreign_key': 'user_id'
                },
            'comments': {
                'model':  'models.comment.Comment',
                'foreign_key': 'user_id'
                }
            }

再来看看conn.py

conn.py

from thing import thing

config = {
        'db': {
            'master': {
                'url': 'mysql://root:123456@127.0.0.1:3306/test?charset=utf8',
                'echo': False,
                },
            'slave': {
                'url': 'mysql://root:123456@127.0.0.1:3306/test?charset=utf8',
                'echo': False,
                },
            },
        'redis': {
            'host': 'localhost',
            'port': 6379,
            'db': 1,
            },
        'thing': {
            'debug': True,
            }
        }

thing.Thing.config(config)

OK,万事具备,开工!

import conn
from models.comment import Comment
from models.user import User
from models.post import Post

# -------- 插入数据 --------
user = User()
user.name = 'foo'
user.save()
# 或者 user = User(name='foo').save()

# -------- 获取数据 --------
user = User().find(1)
print user.name

# -------- 获取关联数据 -------
posts = User().find(1).posts.findall()
# 如果要设置offset / limit, 在findall里加入参数即可
# posts = User().find(1).posts.findall(offset = 0, limit = 20)

# ------- 删除数据 -------
User().find(1).delete()

# ------- 更新数据 -------
user = User().find(1)
user.name = 'bar'
user.save()

动态查询

这个是受Rails影响,觉得很方便就拿来了。比如 Post().count_by_user_id(3),就可以找到user_id为3的用户发表的文章数量。要获取user_id为3的用户发表的文章,可以Post().findall_by_user_id(3, limit=20),比起Post().where('user_id', '=', 3).findall()更加简洁和明了。

关于性能和缓存

Thing内置了Redis作为缓存,你甚至都不需要知道Redis的存在,正常该怎么用还怎么用,Thing会自动处理缓存的生成、读取、过期、删除等操作。

假设表post里有5条数据,在获取每条post后,还想获取该post对应的用户信息,代码如下:

posts = Post().findall(limit=5)

for post in posts:
    print post.author

在开启Debug的情况下,可以在终端看到如下显示:

DEBUG - [cost:0.0032] - SELECT post.id, post.user_id, post.created, post.content, post.title
FROM post ORDER BY post.id DESC
LIMIT :param_1 OFFSET :param_2
DEBUG - Cache Read: thing.User:1
{u'id': 1, u'name': u'lzyy'}
DEBUG - Cache Read: thing.User:1
{u'id': 1, u'name': u'lzyy'}
DEBUG - Cache Read: thing.User:1
{u'id': 1, u'name': u'lzyy'}
DEBUG - Cache Read: thing.User:1
{u'id': 1, u'name': u'lzyy'}
DEBUG - Cache Read: thing.User:1
{u'id': 1, u'name': u'lzyy'}

可以看到用户的信息都是从缓存中读取的,所以不用担心n+1的问题。 假如用户的信息被更新,缓存也会自动更新。

其他

  • 配置信息里的masterslave为必选项,可以相同。Thing会根据不同的查询,自动找到对应的db。如find/findall会找slave,update/delete会找master。
  • 配置信息里的redis项为必选项。
  • 动态查询目前支持find_by, findall_by, findall_in, count_by
  • 内置了8个钩子,会在相应的事件发生时被调用,分别是:_before_insert,_after_insert,_before_update,_after_update,_before_delete,_after_delete,_before_find,_after_find,可以在子类里覆盖这些方法来实现自己的逻辑。
  • 复杂的SQL可以使用execute方法,返回的结果是SQLAlchemy的ResultProxy
  • 如果要一次更新多处的话,可以使用updateall方法,Post().where('user_id', '=', 1).updateall(user_id=2)
  • 表名如果和小写的类名不一样的话,可以在子类里重新设置_tablename
  • 每个表一定要有主键,默认为id,可以在子类里重新设置_primary_key
  • 支持has_many和belongs_to,可以在子类里定义_has_many_belongs_to
  • 没有join方法

ChangeLog

0.3.3

  • 修复无法从pip安装的bug
  • 修复安装时对redis-py的依赖
  • import thing变为from thing import thing

0.3.2

  • 修复了并发情况下会出现「Exception _mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now"」错误。
  • Redis缓存变为可配置项。如果不想要Redis的话,在config里取消Redis配置即可。

0.3.1

  • 取消了对Validation的支持
  • 取消了对Sharding和Partition的支持
  • 取消了事件分发机制