Strom 上手指南

翻译自
https://storm.canonical.com/Tutorial
[url=#st1]1.开始吧(It runs)![/url]
[url=#st2]2.引入(Importing)[/url]
[url=#st3]3.基本定义(Basic definition)[/url]
[url=#st4]4.创建数据库和存储器(Creating a database and the store)[/url]
[url=#st5]5.创建一个对象(Creating an object)[/url]
[url=#st6]6.对象和存储器(The store of an object)[/url]
[url=#st7]7.获取对象(Finding an object)[/url]
[url=#st8]8.缓存机制(Caching behavior)[/url]
[url=#st9]9.刷入后端数据库(Flushing)[/url]
[url=#st10]10.通过存储器来改变对象属性(Changing objects with the Store)[/url]
[url=#st11]11.向后端数据库提交更改(Committing)[/url]
[url=#st12]12.回滚事务操作(Rolling back)[/url]
[url=#st13]13.构造函数(Constructors)[/url]
[url=#st14]14.对象关系维护与继承(References and subclassing)[/url]
[url=#st15]15.多对一的对象关联(Many-to-one reference sets)[/url]
[url=#st16]16 多对多关系与复合键(Many-to-many reference sets and composed keys)[/url]
[url=#st17]17.连接查询(Joins)[/url]
[url=#st18]18.子查询(Sub-selects)[/url]
[url=#st19]19.排序和限制查找结果数量(Ordering and limiting results)[/url]
[url=#st20]20.一个查询中获取不同类的数据(Multiple types with one query)[/url]
[url=#st21]21.Strom基类(The Storm base class)[/url]
[url=#st22]22.加载钩(Loading hook)[/url]
[url=#st23]23.执行表达式(Executing expressins)[/url]
[url=#st24]24.属性值自动重加载(Auto-reloading values)[/url]
[url=#st25]25.表达式赋值(Expression values)[/url]
[url=#st26]26.别名(Aliases)[/url]
[url=#st27]27.更多(Much more!)[/url]
1.开始吧(It runs)!
--------------------
这份Storm上手指南的英文版位于storm源码 /test/tutorial.txt。
2.引入 (Importing)
--------------------
让我们以引入变量到名称空间 (namespace)作为我们Storm之旅的开始。
>>> from storm.locals import *
>>>
3.基本定义 (Basic definition)
--------------------
现在我们定义一个新的类和一些这个类的属性来描述我们将要映射的信息。
>>> class Person(object):
...     __storm_table__ = "person"
...     id = Int(primary=True)
...     name = Unicode()
注意上面的代码中,没有任何的Storm相关基类或构造器.
4.创建数据库和存储器(Creating a database and the store)
--------------------
目前我们还没有连接到任何实体数据库上,所以,现在就让我们创建一个内存中的(in-memory)SQLite 数据库和一个储存器(sotre).
>>> database = create_database("sqlite:")
>>> store = Store(database)
>>>
Storm 目前支持三个后端数据库:SQLite,MySQL,PostgreSQL. 连接后端数据库时,参数URL以下面的格式传递:
database =create_database("scheme://username:password@hostname:port/database_name")
schema 可以是 "sqlite","postgres",或"mysql"中的一个.
在刚才连接的in- memory SQLte数据库中创建一张表来实际储存映射到Person类的数据。
>>> store.execute("CREATE TABLE person "
...              "(id INTEGER PRIMARY KEY, name VARCHAR)")
执行完sotre.execute后,sotrm会有输出执行结果。我们可以通过可选参数 noresult=Ture 来完全禁止输出执行结果。
5.创建一个对象(Creating an object)
--------------------
让我们来创建一个Person类的对象.
>>> joe = Person()
>>> joe.name = u"Joe Johnes"
>>> print "%r, %r" % (joe.id, joe.name)
None, u'Joe Johnes'
目前为止,joe这个对象还没有和后端数据库发生联系。让我们将它添加进刚刚创建的存储器 (store)中。
>>> store.add(joe)
>>> print "%r, %r" % (joe.id, joe.name)
None, u'Joe Johnes'
注意,在被加入进储存器后这个对象没有发生任何改变,这是因为这个对象的信息还没有被刷入 (flushed)后端数据库。
6.对象和存储器(The store of an object)
--------------------
一但对象被加入存储器,或从某个存储器中取得,此对象和这个存储器的绑定关系就被确定了,我们能轻松的判断某个对象是否和特定存储器绑定。
>>> Store.of(joe) is store
True
>>> Store.of(Person()) is None
True
7.获取对象(Finding an object)
--------------------
好了,我们怎么通过Storm获取一个name属性为 “Joe Johnes”的Person对象呢?
>>> person = store.find(Person, Person.name == u"JoeJohnes").one()
>>> print "%r, %r" % (person.id, person.name)
1, u'Joe Johnes'
>>>
看吧,这个Person对象就在这里。 嚯,这就是你期待的吧.  :-)
当然,我们也可以通过主键找到相应的对象.
>>> store.get(Person, 1).name
u'Joe Johnes'
8.缓存机制(Caching behavior)
--------------------
一个有趣的问题出现了:是否我们获取的 Person对象和最初的joe(变量名)是同一个对象呢?答案是肯定的。
>>> person is joe
True
产生这个现象的原应是每个储存器都带有一个对象缓存(objectcache)。当一个对象和一个储存器关联(linked)后,它将被这个储存器缓存,只要这个对象仍然有变量引用(reference)存在,或者这个对象在发生了没有刷入后端数据库的改变(hasunflushed changes).
Storm保证在数据操作过程中至少一定数量的对象被缓存在内存中,这使得最常使用的对象数据不会每次都从后端数据库中取得。
9.刷入后端数据库 (Flushing)
--------------------
当我们第一次尝试获取Joe时,我们注意到这个对象的id属性被神奇的赋值了.这是因为这个对象被隐性的刷入了后端数据库(flushedimplicitly),造成这次隐性刷入的是find操作。
当然,刷入后端数据库的操作也能显性的被执行.
>>> mary = Person()
>>> mary.name = u"Mary Margaret"
>>> store.add(mary)
>>> print "%r, %r" % (mary.id, mary.name)
None, u'Mary Margaret'
>>> store.flush()
>>> print "%r, %r" % (mary.id, mary.name)
2, u'Mary Margaret'
10.通过存储器来改变对象属性(Changing objects with the Store)
--------------------
当需要改变对象属性时,除了正常的通过改变对象属性后再flush的方法外,由于受益于对象和存储器的联系,使得我们可以通过存储器直接改变对象属性(译者注:以及数据库中相应的数据)。
>>> store.find(Person, Person.name == u"MaryMargaret").set(name=u"Mary Maggie")
>>> mary.name
u'Mary Maggie'
这个操作会改变每个匹配的对象,无论它有没有被刷入后端数据库。
        
11.向后端数据库提交更改 (Committing)
--------------------
如果后端数据库支持事务(transation),在两次提交操作之间我们的所有数据操作都在一个事务内.我们可以选择向数据库提交这些操作(无论操作是否刷入数据库)使其持久化,或者我们可以通过回滚撤销所有操作.
提交更改非常简单,只需要像下面这样.
>>> store.commit()
>>>
这个操作很直观的,任何属性都没发生改变,他们的值现在已经真实的写入了后端数据库.
(译者注: commit时,flush会自动被调用)
12.回滚事务操作(Rolling back)
--------------------
插销任何未提交(commit)的数据操作同样非常直观.
>>> joe.name = u"Tom Thomas"
>>>
我们可以看到,这个改变已经能被Storm和后端数据库感知到.
>>> person = store.find(Person, Person.name == u"TomThomas").one()
>>> person is joe
True
我们现在回滚事务,让这个操作撤销
>>> store.rollback()
>>>
哈, 没发生任何事情?
事实上,刚才为Joe重命名的操作已经被撤销.看Joe又回来了.
>>> print "%r, %r" % (joe.id, joe.name)
1, u'Joe Johnes'
13.构造函数 (Constructors)
--------------------
Person类中,没有使用构造函数.现在让我们引入一个新类到我们的模型体系中来.我们会建立一个新的命名为Company的类,在这个新类中,我们将使用构造函数.我打赌这是你见过的所有Company类中最简单的.
>>> class Company(object):
...     __storm_table__ = "company"
...     id = Int(primary=True)
...     name = Unicode()
...
...     def __init__(self, name):
...         self.name = name
我们的构造参数不是可选的,当然可以设置成可选参数,如果我们愿意的话。
现在可以往内存中的SQLite数据库添加相应的表了
>>> store.execute("CREATE TABLE company "
...              "(id INTEGER PRIMARY KEY, name VARCHAR)", noresult=True)
接下来,我们创建这个类的一个对象.
& lt;br>>>> circus = Company(u"Circus Inc.")
>>> print "%r, %r" % (circus.id, circus.name)
None, u'Circus Inc.'
id属性没有被定义,因为我们没有把这个操作刷入后端数据库,事实上,我们甚至没有把这个对象添加进存储器。
*译者注:设值是通过Storm属性类(Int,Unicode,RawStr.....)的__set__方法(重载 '='操作符”) 实现的。
所以,采用object.__dict__['parm'] = value 的方式设值,属性值变化不会被Storm察觉
14.对象关系维护与继承 (References and subclassing)
____________________
现在我们要往公司类中分配几个雇员,以其修改这个Preson类定义,我们保持这个类为基类,然后继承一个新的Employee子类,在这个子类中我们添加一个新的属性,其所属的Companyid.
>>> class Employee(Person):
...     __storm_table__ = "employee"
...     company_id = Int()
...     company = Reference(company_id, Company.id)
...
...     def __init__(self, name):
...         self.name = name
请留心类定义,我们注意到在Employee中我们引入了company_id和company两个属性,后者其实是个到Company类对象的Strom引用。Employee类里仍然有构造器,但构造器没有为company相关信息做任何定义。
像往常一样,我们需要在后端数据库中建立一张新的表,SQLite数据库不支持外键,所以我们没必要在建表SQL里定义外键。
>>> store.execute("CREATE TABLE employee "
...              "(id INTEGER PRIMARY KEY, name VARCHAR, company_id INTEGER)",
...              noresult=True)
现在我们让Ben作为我们Employee类的第一个对象出现。
>
>>> ben = store.add(Employee(u"Ben Bill"))
>>> print "%r, %r, %r" % (ben.id, ben.name, ben.company_id)
None, u'Ben Bill', None
我们让ben和circus (前一节定义的Company对象变量)关联起来。
>>> ben.company = circus
>>> print "%r, %r" % (ben.company_id, ben.company.name)
None, u'Circus Inc.'
由于我们没有把对ben和circus的创建刷入数据库,ben的company_id目前仍然是 None.但是Storm仍然能保持他们的关联关系。
如果这些操作被显性或隐性的刷入后端数据库。对象将会被赋予id(译者注:需要数据支持序列生成)。如果某个操作改变了对象之间的引用关系,在刷入后端数据库时,这些对象之间的关系也能得到更新。
>>> store.flush()
>>> print "%r, %r" % (ben.company_id, ben.company.name)
1, u'Circus Inc.'
我们可以看到这两个对象都已被刷入了后端数据库。如果留心我们之前的代码,你会发现circus这个Company类的对象没有被显性的添加到存储器。这里Strom自动的为我们把circus对象添加到了存储器内(因为ben对象引用了它)。
让我们创建另一个Company对象。这一次我们将在第一时间把它添加进存储器并刷入后端数据库。
>>> sweets = store.add(Company(u"Sweets Inc."))
>>> store.flush()
>>> sweets.id
2
我们已经得到这个新Company类对象的id了,现在如果我们仅仅改变ben对象的company_id属性,情况将会怎样呢?
>>> ben.company_id = 2
>>> ben.company.name
u'Sweets Inc.'
>>> ben.company is sweets
True
哈,这就是你期待的,对吧? ;-)
让我们提交这一切更改.
>>> store.commit()
>>>
15. 多对一的对象关联(Many-to-one reference sets)
____________________
在我们的模型定义里,一个雇员只能为一家公司工作。一家公司当然能同时雇佣多个员工。在Strom中我们用引用集合(referencesets)来表达这种关系。
我们不重新定义一次Company类,作为替代,我们为原有的Company类赋予一个新的属性。
>>> Company.employees = ReferenceSet(Company.id,Employee.company_id)
>>>
不需要做任何其他更改,我们现在就能查找那些雇员为特定公司工作。
>>> sweets.employees.count()
1
>>> for employee in sweets.employees:
...     print "%r, %r" % (employee.id,employee.name)
...     print employee is ben
...
1, u'Ben Bill'
True
我们将要创建一个新的雇员对象,这次我们不再为他指定一个公司对象;相反,我们将把它添加到公司对象的雇员集合属性中。
>>> mike = store.add(Employee(u"Mike Mayer"))
>>> sweets.employees.add(mike)
>>>
很明显,这意味着mike将为sweets公司工作,这需要在mike身上的到体现。
>>> mike.company_id
2
>>> mike.company is sweets
True
(译者注:维护多对一关系时,Strom不需要指明其中哪端为主动端)
16 多对多关系与复合键(Many-to-many reference sets and composed keys)
--------------------
我们的模型世界里已经有了公司和雇员,我们还想把会计师也体现出来。一个公司会聘用多个会计师,一个会计师同时也会为多个公司服务。所以我们用多对多关系表现上述公司和会计师的关系。
现在让我们创建Accountant,会计师类和他们的关系类 CompanyAccountant。
>>> class Accountant(Person):
...     __storm_table__ = "accountant"
...     def __init__(self, name):
...         self.name = name
>>> class CompanyAccountant(object):
...     __storm_table__ = "company_accountant"
...     __storm_primary__ = "company_id","accountant_id"
...     company_id = Int()
...     accountant_id = Int()
哈,我们刚刚创建了一个有复合主键的CompanyAccountant 类。
现在,在这个类的帮助下可以往Company类中申明Company类与Accountant类的多对多关系了。这次我们仍然还是仅仅把新属性赋予已经存在的Company类。当然,如果在Company类被定义时就申明这个关系或许更好。
>>> Company.accountants = ReferenceSet(Company.id,
...                                   CompanyAccountant.company_id,
...                                   CompanyAccountant.accountant_id,
...                                   Accountant.id)
好了,注意ReferenceSet()中的参数顺序哈。
把缺的数据库表补上。
>>> store.execute("CREATE TABLE accountant "
...              "(id INTEGER PRIMARY KEY, name VARCHAR)", noresult=True)
...
>>> store.execute("CREATE TABLE company_accountant "
...              "(company_id INTEGER, accountant_id INTEGER,"
...              " PRIMARY KEY (company_id, accountant_id))", noresult=True)
我们现在可以实例化几个会计师对象了,然后我们会维护这些会计师和我们已有的两个公司的关系。
>>> karl = Accountant(u"Karl Kent")
>>> frank = Accountant(u"Frank Fourt")
>>> sweets.accountants.add(karl)
>>> sweets.accountants.add(frank)
>>> circus.accountants.add(frank)
>>>
好了,注意我们没有把这两个Accountant类的对象添加到存储器。这是自动隐性进行的,因为他们和其他已经在存储器中的类发生了关联。在维护关系的过程中,我们也不需要关心关系类.
现在我们可以查看刚才的操作结果。
>>> sweets.accountants.count()
2
>>> circus.accountants.count()
1
我们一般不直接使用 CompanyAccountant这个关系类,为了满足好奇心,我们也能获取这个类的实例化对象。
>>> store.get(CompanyAccountant, (sweets.id, frank.id))
因为CompanyAccountant使用的是复合主键,我们通过一个tuple 来获取对象。
如果我们想知道,特定会计师为那些公司服务,我们可以简单的定义一个Accountant类到 Company类的多对多关系。
>>> Accountant.companies = ReferenceSet(Accountant.id,
...                                    CompanyAccountant.accountant_id,
...                                    CompanyAccountant.company_id,
...                                    Company.id)
>>> [company.name for company in frank.companies]
[u'Circus Inc.', u'Sweets Inc.']
>>> [company.name for company in karl.companies]
[u'Sweets Inc.']
17.连接查询 (Joins)
--------------------
我们目前已经有些基础数据可以供稍微复杂些的查询演示了。让我们从找到至少聘有一名叫Ben的员工的公司开始。
我们有两种方法来进行这个查找。
首先我们用隐性连接(implicit join)查询
>>> result = store.find(Company,
...                    Employee.company_id == Company.id,
...                    Employee.name.like(u"Ben %"))
...
>>> [company.name for company in result]
[u'Sweets Inc.']
接着,我们用显性连接(explicit join)来查询同样的内容。这在用Storm查询取代复杂SQL连接查询时特别有用。
>>> origin = [Company, Join(Employee, Employee.company_id ==Company.id)]
>>> result = store.using(*origin).find(Company,Employee.name.like(u"Ben %"))
>>> [company.name for company in result]
[u'Sweets Inc.']
如果我们已经获得这个公司的对象,但是要找到名叫Ben的员工对象,那将会非常简单。
>>> result = sweets.employees.find(Employee.name.like(u"Ben%"))
>>> [employee.name for employee in result]
[u'Ben Bill']
18.子查询(Sub- selects)
--------------------
假如我们想找到所有没有和任何公司关联的会计师,我们可以利用一个子查询来得到我们需要的数据。
>>> laura = Accountant(u"Laura Montgomery")
>>> store.add(laura)
>>> subselect = Select(CompanyAccountant.accountant_id,distinct=True)
>>> result = store.find(Accountant,Not(Accountant.id.is_in(subselect)))
>>> result.one() is laura
True
>>>
19. 排序和限制查找结果数量(Ordering and limiting results)
--------------------
排序和限制查找结果数量在一个ORM工具中是最常用和简单的操作,所以Strom中这两个操作都被设置得特别直观和容易理解。
代码是最有说服力的,下面是一些简单的演示:
>>> garry = store.add(Employee(u"Garry Glare"))
>>> result = store.find(Employee)
>>> [employee.name for employee inresult.order_by(Employee.name)]
[u'Ben Bill', u'Garry Glare', u'Mike Mayer']
>>> [employee.name for employee inresult.order_by(Desc(Employee.name))]
[u'Mike Mayer', u'Garry Glare', u'Ben Bill']
>>> [employee.name for employee inresult.order_by(Employee.name)[:2]]
[u'Ben Bill', u'Garry Glare']
20.一个查询中获取不同类的数据(Multiple types with one query)
--------------------
有些时候,我们想在一个查询中获取多个不同类的对象,比如,我们在获取雇有ben的公司对象时,我们还想获取ben的对象。这可以通过下面的一个查询取得。
>>> result = store.find((Company, Employee),
...                    Employee.company_id == Company.id,
...                    Employee.name.like(u"Ben %"))
>>> [(company.name, employee.name) for company, employee inresult]
[(u'Sweets Inc.', u'Ben Bill')]
21.Strom基类(The Storm base class)
--------------------
到目前为止,我们都是直接使用各种类和其属性来直接定义的引用(references)和引用集合(reference sets).(例如:
Employee类中,定义company属性  “company = Reference(company_id,Company.id)” )这样做在带来一些方便(调试方便)的同时,也使得需要的类必须要在当前本地变量作用域中(localscope).这可能产生循环引入(circular import)的问题。
为了防止这种情况,Strom支持用这些类和其属性的字符串形式来定义这些引用。唯一的要求是,所有涉及的类都需要继承自Strom基类。
我们通过定义一个新的类来演示。为了体现字符串形式引用的作用,我们将刻意的引用一个还未被定义的类。
>>> class Country(Storm):
...     __storm_table__ = "country"
...     id = Int(primary=True)
...     name = Unicode()
...     currency_id = Int()
...     currency = Reference(currency_id,"Currency.id")   #留意 "Currency.id"
>>> class Currency(Storm):
...     __storm_table__ = "currency"
...     id = Int(primary=True)
...     symbol = Unicode()
>>> store.execute("CREATE TABLE country "
...              "(id INTEGER PRIMARY KEY, name VARCHAR, currency_id INTEGER)",
...              noresult=True)
>>> store.execute("CREATE TABLE currency "
...              "(id INTEGER PRIMARY KEY, symbol VARCHAR)", noresult=True)
现在让我们来看看这种方法有没有作用.
>>>> real=store.add(Currency())
>>> real.id = 1
>>> real.symbol = u"BRL"
>>> brazil = store.add(Country())
>>> brazil.name = u"Brazil"
>>> brazil.currency_id = 1
>>> brazil.currency.symbol
u'BRL'
22.加载钩(Loading hook)
--------------------
当特定事件发生时,Strom能通过不同的钩 (hooks)调用特定代码。其中较有趣的一个钩会在特定类对象被从数据库加载进内存时调用。
我们通过定义一个 Person的子类来演示这个 “__storm_loaded__” 钩。
>>> class PersonWithHook(Person):
...     def __init__(self, name):
...         print "Creating %s"% name
...         self.name = name
...
...     def __storm_loaded__(self):
...         print "Loaded %s" %self.name
>>> earl = store.add(PersonWithHook(u"Earl Easton"))
Creating Earl Easton
>>> earl = store.find(PersonWithHook, name=u"EarlEaston").one()
>>> store.invalidate(earl)
>>> del earl
>>> import gc
>>> collected = gc.collect()
>>> earl = store.find(PersonWithHook, name=u"EarlEaston").one()
Loaded Earl Easton
在第一次find操作时,我们的stromloaded钩没有得到调用,因为这个对象还在内存里被缓存着。然后我们通过store.invalidate操作从Strom内部缓存中清除这个对象,然后又手工执行PythonGC操作来保证这个对象被彻底从内存中清除。这时候,我们再执行find操作,使得这个对象被从数据库中加载进内存,我们可以看到这个钩得到了调用。
23.执行表达式(Executing expressins)
--------------------
当需要时,Strom还提供了一种数据库无关的表达式查询方法。
例如:
>>> result = store.execute(Select(Person.name, Person.id == 1))
>>> result.get_one()
(u'Joe Johnes',)
storm自身用这种机制来实现更高级别的功能。
24.属性值自动重加载(Auto-reloading values)
--------------------
Strom提供一些特殊值当被赋于Strom对象属性时,会有特殊的作用,其中之一是AutoReload.如果它被赋于某个Strom对象作为其属性时,每次这个属性被访问,返回结果均直接来自即时数据库查询。就算是主键属性也能被赋予该值,下面是一些列子.
>>> from storm.locals import AutoReload
>>> ruy = store.add(Person())
>>> ruy.name = u"Ruy"
>>> print ruy.id
None
>>> ruy.id = AutoReload
>>> print ruy.id
4
一但把AutoReload设为Storm对象的属性值,会使得属性所在对象在需要时把修改自动刷入数据库(automaticallyflushed)。
25.表达式赋值(Expression values)
--------------------
除了自动重加载,我们也能把懒表达式(lazyexpression)赋于属性。当这个属性被访问,或当这个对象被刷入数据库时,这个表达式执行结果也将被刷入数据库。
from storm.locals import SQL
>>> ruy.name = SQL("(SELECT name || ' Ritcher' FROM personWHERE id=4)")
>>> ruy.name
u'Ruy Ritcher'
注意,上面的例子仅仅展示了Strom支持这种操作,你完全可以用Strom提供的基于类的查询语句来替代上面的原始SQL。或者你可以完全不使用这种懒表达式赋值方法。
26.别名 (Aliases)
--------------------
现在假如我们要知道在同一个公司工作的每一双人。(我想不通为什么有人想查这样的数据,但这确实是个展示别名操作的好例子)
首先我们需要把 ClassAlias引入我们当前的命名空间(当前版本的Strom中ClassAlias已经在storm.locals下了)。
>>> from storm.info import ClassAlias
>>> AnotherEmployee = ClassAlias(Employee)
好的,接下来我们就可以直观的查询我们想得到的结果:
>>> result = store.find((Employee, AnotherEmployee),
...                    Employee.company_id == AnotherEmployee.company_id,
...                    Employee.id > AnotherEmployee.id)
>>> for employee1, employee2 in result:
...     print (employee1.name, employee2.name)
(u'Mike Mayer', u'Ben Bill')
呵呵,我们发现Mike和Ben在为同一家公司工作。
27.更多(Much more!)
--------------------
Strom还有很多值得发掘的东西,这个上手指南仅仅让我们熟悉一些简单概念和用法。如果你关于Strom的问题在其他地方找不到答案,欢迎到Strom邮件列表发帖。