将关系数据导入CubicWeb实例¶
介绍¶
本教程介绍如何将数据从外部源(例如文件集合)导入CubicWeb多维数据集实例。
首先,一旦我们知道了我们想要导入的数据的格式,我们就设计了一个 数据模型 也就是说,一个反映数据结构方式的CubicWeb(Yams)模式。此架构在 schema.py
文件。在本教程中,我们将为特定的数据集描述这样一个模式,即diseasome数据(见下文)。
一旦定义了模式,我们就创建一个多维数据集和一个实例。多维数据集是应用程序的规范,而实例本身就是应用程序。
一旦定义了模式并创建了实例,就可以通过以下步骤执行导入:
为要导入的数据生成自定义分析器。因此,可以获得数据的python内存表示。
将分析的数据映射到中定义的数据模型
schema.py
.执行数据的实际导入。根据在2处定义的映射,这归结为使用在1处获得的内存表示来“填充”数据模型。
本教程说明了以RDF格式存储的关系数据上下文中的所有上述步骤。
更具体地说,我们描述了 Diseasome RDF/OWL数据。
建立数据模型¶
当使用CubicWeb从头创建应用程序时,首先要做的是设计一个 数据模型 也就是说,要建模的问题或要导入的数据结构的关系表示。
在这种模式中,我们定义一个实体类型 (EntityType
对象)。每种类型都有几个属性。如果属性是已知的cubicWeb(yams)类型,则为。数字、字符串或字符,然后它们被定义为属性,例如 attribute = Int()
对于名为 attribute
它是一个整数。
每种类型都有一组关系,它们的定义与属性类似,只是它们实际上表示正在讨论的类型的实体与在关系定义中指定的类型的对象之间的关系。
例如,对于疾病数据,我们有两种类型的实体,基因和疾病。因此,我们创建了两个继承自 EntityType
::
class Disease(EntityType):
# Corresponds to http://www.w3.org/2000/01/rdf-schema#label
label = String(maxsize=512, fulltextindexed=True)
...
#Corresponds to http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseasome/associatedGene
associated_genes = SubjectRelation('Gene', cardinality='**')
...
#Corresponds to 'http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseasome/chromosomalLocation'
chromosomal_location = SubjectRelation('ExternalUri', cardinality='?*', inlined=True)
class Gene(EntityType):
...
在这个模式中,有一些属性的值是数字或字符串。因此,它们是通过使用cubicWeb/yams原语类型来定义的,例如, label = String(maxsize=12)
.这些类型可以有多个约束或属性,例如 maxsize
.实体类型本身或与CubicWeb类型之间也存在关系, ExternalUri
.后者在CubicWeb中定义了一类URI对象。例如, chromosomal_location
属性是 Disease
实体和 ExternalUri
实体。关系由cubicWeb/yams标记。 SubjectRelation
方法。后者可以有几个可选的关键字参数,例如 cardinality
它指定与指定的关系类型相关的主题和对象的数量。例如, '?*'
中的基数 chromosomal_relation
关系类型表示零或更多 Disease
实体与零或一相关 ExternalUri
实体。换句话说,a Disease
实体最多与一个相关 ExternalUri
实体通过 chromosomal_location
关系类型,我们可以有零个或更多 Disease
数据库中的实体。对于实体类型本身之间的关系, associated_genes
协议双方: Disease
实体和 Gene
实体被定义为 Gene
实体可以与 Disease
可以有任何数量的 Disease
S如果A Gene
存在。
当然,在能够使用CubicWeb/Yams内置对象之前,我们需要导入它们:
from yams.buildobjs import EntityType, SubjectRelation, String, Int
from cubicweb.schemas.base import ExternalUri
构建自定义数据分析器¶
我们希望导入的数据是RDF格式的,作为包含一组行的文本文件。每行有三个字段。前两个字段是uri(“通用资源标识符”)。第三个字段是URI或字符串。每个字段都有特定的含义:
最左边的字段是保存要导入的实体的URI。请注意,数据模型中定义的实体(即
schema.py
)应与导入文件中指定其URI的实体相对应。中间字段是一个URI,它保存的关系的主题是由最左边字段定义的实体。注意,这也应该与数据模型中的定义相对应。
最右边的字段是URI或字符串。当这个字段是一个URI时,它给出中间字段定义的关系的对象。当最右边的字段是字符串时,中间的字段被解释为主题的属性(由最左边的字段引入),最右边的字段被解释为属性的值。
但是请注意,有些属性(即对象为字符串的关系)的对象定义为字符串,后面跟 ^^
另外一个URI;我们忽略了这一部分。
让我们举几个例子:
包含属性定义的行数:
<http://www4.wiwiss.fu-berlin.de/diseasome/resource/genes/CYP17A1> <http://www.w3.org/2000/01/rdf-schema#label> "CYP17A1" .
该行包含label
类型的实体的属性gene
.价值label
是'`'cyp17a1``'。包含关系定义的行数:
<http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseases/1> <http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseasome/associatedGene> <http://www4.wiwiss.fu-berlin.de/diseasome/resource/genes/HADH2> .
该行包含associatedGene
A之间的关系disease
主题实体标识人1
和Agene
对象实体定义者HADH2
.
因此,对于分析数据,我们可以(:注意:请参见 diseasome_parser
模块):
定义两个正则表达式来解析这两类行,
RE_ATTS
用于分析属性定义,以及RE_RELS
用于分析关系定义。定义一个函数,该函数遍历文件的行并检索 (
yield
s)每行一个(主题、关系、对象)元组。我们称之为_retrieve_structure
在diseasome_parser
模块。函数需要文件名和应检索信息的类型。
或者,可以使用中提供的RDF解析器,而不是手工生成解析器。 dataio
立方体。
一旦我们得到(主题、关系、对象)三元组,我们就需要将它们映射到数据模型中。
将数据映射到架构¶
在diseasome数据的情况下,我们可以定义两个字典,将解析器提取的关系名称映射到在 schema.py
数据模型。在 diseasome_parser
它们被调用的模块 MAPPING_ATTS
和 MAPPING_RELS
.考虑到原始数据中camelcase中给出了关系和属性名,因此,如果在数据模型中命名属性时遵循PEP08,则需要映射。例如,RDF关系 chromosomalLocation
映射到架构关系中 chromosomal_location
.
一旦定义了这些映射,我们只需迭代解析器提供的(主题、关系、对象)元组,然后提取实体及其属性和关系。因此,对于每个实体,我们都有一个带有两个键的字典, attributes
和 relations
.与 attributes
key是一个包含(attribute:value)对的字典,其中“value”是一个字符串,加上 cwuri
保存实体本身的URI的键/属性。与 relations
key是一个包含(relation:value)对的字典,其中“value”是一个URI。这是在 entities_from_rdf
模块接口功能 diseasome_parser
.此函数在包含 attributes
和 relations
所有实体的键。
然而,这是一个简单的例子。在现实生活中,事情可能变得更加复杂,映射也可能非常简单,尤其是当必须将多个数据源(可以遵循不同的格式,甚至是结构约定)映射到同一个数据模型时。
导入数据¶
数据导入代码应该放在Python模块中。让我们称之为 diseasome_import.py
.然后,应该通过 cubicweb-ctl
如下:
cubicweb-ctl shell diseasome_import.py -- <other arguments e.g. data file>
在导入模块中,我们应该使用 商店 做导入。存储是一个对象,它提供三种导入数据的方法:
用于导入实体及其属性值的方法。
用于导入实体之间关系的方法。
将导入提交到数据库的方法。
在CubicWeb,我们有四家商店:
ObjectStore
CubicWeb中存储的基类。它只为所有其他存储提供框架,并提供创建存储实体及其关系的内存结构(字典)的方法。RQLObjectStore
:使用rql语言执行数据库插入和更新的存储。它依赖于所有CubicWeb钩子机制,尤其是处理安全问题(数据库访问权限)。
NoHookRQLObjectStore
:使用rql语言执行数据库插入和更新的存储,但所有挂钩都被停用。这意味着没有对cubicWeb/yams模式(数据模型)执行某些检查。但是,从RQL查询中获得的所有SQL查询都是按顺序执行的,每个插入的实体一个查询。
SQLGenObjectStore
:直接使用SQL语言的存储。它可以按顺序插入实体,方法是对每个实体执行SQL查询,或者直接使用一个PostgresCOPY FROM
查询一组类似结构的实体。
对于真正大规模的进口(数百万或数十亿个实体),有一个立方体 dataio
它包含另一个商店,叫做 MassiveObjectStore
.这家商店和 SQLGenObjectStore
,但与CubicWeb相关的任何内容都将被忽略。也就是说,即使是CubicWebEID实体标识符也不会被处理。这个商店是最快的,但是它的API与上面提到的其他四个商店略有不同。而且,它有一个重要的限制,因为它不插入内联 1 数据库中的关系。
- 1
内联关系是使用关键字参数在架构中定义的关系。
inlined=True
.这样的关系作为其主题实体的属性插入到数据库中。
在下面的部分中,我们将看到如何使用cubicWeb中的存储导入数据。 dataimport
模块。
使用中的商店 dataimport
¶
ObjectStore
在现实生活中很少用于导入数据,因为它只是其他存储的基本存储,并且不执行数据的实际导入。然而,其他三个存储(导入数据)是基于 ObjectStore
并提供相同的API。
三家商店 RQLObjectStore
, NoHookRQLObjectStore
和 SQLGenObjectStore
为在SQL数据库中导入数据(即实体和关系)提供完全相同的API。
在使用商店之前,必须导入 dataimport
模块,然后用当前 session
作为参数:
import cubicweb.dataimport as cwdi
...
store = cwdi.RQLObjectStore(session)
每个这样的存储提供三种数据导入方法:
create_entity(Etype, **attributes)
,允许我们添加yams类型的实体Etype
到数据库。此实体的属性在attributes
字典。方法返回在数据库中创建的实体。例如,我们添加两个实体,一个人,Person
类型和位置Location
类型:person = store.create_entity('Person', name='Toto', age='18', height='190') location = store.create_entity('Location', town='Paris', arrondissement='13')
relate(subject_eid, r_type, object_eid)
,这允许我们添加yams类型的关系r_type
到数据库。关系的主题是EID为subject_eid
;其对象是另一个实体,其EID为object_eid
. 例如 2:store.relate(person.eid(), 'lives_in', location.eid(), **kwargs)
kwargs
仅用于SQLGenObjectStore
的relate
方法和允许我们在模式中将关系定义为内联时指定关系主题的类型。
- 2
- 这个
eid
通过定义实体的方法create_entity
收益率 创建实体时由CubicWeb分配的实体标识符。这仅适用于通过CubicWeb中的存储定义的实体
dataimport
模块。
关键字参数被理解为
SQLGenObjectStore
被称为subjtype
并持有主体实体的类型。在这里考虑的例子中,这就意味着 3:store.relate(person.eid(), 'lives_in', location.eid(), subjtype=person.cw_etype)
如果
subjtype
未指定,则存储区尝试推断主题的类型。但是,这并不总是有效的,例如当给定关系类型有几个可能的主题类型时。- 这个
- 3
- 这个
cw_etype
通过定义的实体的属性create_entity
持有 刚刚创建的实体的类型。这仅适用于通过CubicWeb中的存储定义的实体
dataimport
模块。在这里考虑的例子中,person.cw_etype
持有'Person'
.
所有其他商店
SQLGenObjectStore
忽略kwargs
参数。- 这个
flush()
,它允许我们执行数据库中的实际提交,以及一些清理操作。理想情况下,应该尽可能频繁地调用这个方法,即在数据库中每次插入之后,这样数据库会话就尽可能保持原子性。在实践中,我们通常两次调用这个方法:第一次,在所有实体都被创建之后,第二次,在所有关系都被创建之后。但是请注意,在每次提交之前,数据库插入必须与模式一致。因此,如果一个实体有一个通过关系(即一
SubjectRelation
)"1"
或"+"
对象基数,在将添加内容提交到数据库之前,我们必须创建讨论中的实体、讨论中的关系的对象实体以及关系本身。这个
flush
方法简单地称为:store.flush().
使用 MassiveObjectStore
在 dataio
立方体¶
这家商店,在 dataio
多维数据集,允许我们完全免除CubicWeb导入机制,从而通过SQL查询直接与数据库服务器交互。
此外,这些查询依赖于PostgreSQL COPY FROM
在单个查询中创建多个实体的说明。这对基于RQL的数据插入过程带来了巨大的性能改进。
但是,此存储的API与CubicWeb中存储的API略有不同。 dataimport
模块。
在使用商店之前,必须导入 dataio
立方体 dataimport
模块,然后通过提供 session
参数::
from cubicweb_dataio import dataimport as mcwdi
...
store = mcwdi.MassiveObjectStore(session)
这个 MassiveObjectStore
提供将数据插入数据库的六种方法:
init_rtype_table(SubjEtype, r_type, ObjEtype)
指定与数据库中的关系类型关联的表的创建。每个这样的表都有三列、主题实体的类型、关系的类型(即,通过关系定义的主题实体中属性的名称)和对象实体的类型。例如::store.init_rtype_table('Person', 'lives_in', 'Location')
请注意,这些表可以在实体之前创建,因为它们只指定它们的类型,而不指定它们的唯一标识符。
create_entity(Etype, **attributes)
,它允许我们添加新的实体,这些实体的属性在attributes
字典。但是,请注意,默认情况下,此方法 not 返回创建的实体。例如,调用方法,如:store.create_entity('Person', name='Toto', age='18', height='190', uri='http://link/to/person/toto_18_190') store.create_entity('Location', town='Paris', arrondissement='13', uri='http://link/to/location/paris_13')
为了能够在需要时通过关系将这些实体链接起来,我们必须为自己提供一种唯一标识实体的方法。一般来说,这是通过URI完成的,存储在如下属性中
uri
或cwuri
.只要属性的值对于每个实体都是唯一的,属性的名称就不相关。relate_by_iid(subject_iid, r_type, object_iid)
允许我们实际关联由subject_iid
和object_iid
通过类型关系r_type
. 例如::store.relate_by_iid('http://link/to/person/toto_18_190', 'lives_in', 'http://link/to/location/paris_13')
请注意这个方法 not 为内联关系工作!
convert_relations(SubjEtype, r_type, ObjEtype, subj_iid_attribute, obj_iid_attribute)
允许我们在数据库中实际插入关系。在这个方法的一个调用中,插入类型的所有关系rtype
在给定类型的实体之间。subj_iid_attribute
和object_iid_attribute
是存储由用户分配的实体唯一标识符的属性的名称。这些名称可以是相同的,只要它们的值是唯一的。例如,用于插入类型的所有关系lives_in
之间People
和Location
实体,我们写道:store.convert_relations('Person', 'lives_in', 'Location', 'uri', 'uri')
flush()
执行数据库中的实际提交。它只需要在create_entity
和relate_by_iid
电话。请注意relate_by_iid
做 not 在数据库中执行插入操作,因此调用flush()
因为它不会有任何效果。cleanup()
通过删除临时表来执行数据库清理。只应在导入结束时调用它。
对diseasome数据的应用¶
导入设置¶
我们定义了一个导入函数, diseasome_import
基本上有四件事:
通过以下行创建并初始化要使用的存储:
store = cwdi.SQLGenObjectStore(session)
在哪里?
cwdi
是进口的吗cubicweb.dataimport
或cubicweb_dataio.dataimport
.调用diseasome解析器,即
entities_from_rdf
功能在diseasome_parser
模块并在一行中迭代其结果,例如:for entity, relations in parser.entities_from_rdf(filename, ('gene', 'disease')):
在哪里?
parser
是进口的吗diseasome_parser
模块,以及filename
是包含数据的文件名(及其路径),例如../data/diseasome_dump.nt
.创建要插入到数据库中的实体;对于diseasome,有两种实体:
数据模型中定义的实体,即
Gene
和Disease
在我们的例子中。在CubicWeb/Yams中构建的实体,即
ExternalUri
定义URI。
当我们处理RDF数据时,每个实体都是通过一系列URI定义的。因此,每个“关系属性” 4 实体的定义是通过一个URI,也就是说,在cubicWeb术语中,通过一个
ExternalUri
实体。在上述循环中创建实体,如:ent = store.create_entity(etype, **entity)
在哪里?
etype
是适当的实体类型,或者Gene
或Disease
.
- 4
- 通过“关系属性”,我们表示一个(实体的)属性,它
通过关系定义,例如
chromosomal_location
属性Disease
实体,通过Disease
和一个ExternalUri
.
这个
ExternalUri
实体与数据文件中的URI相同。对于它们,我们定义了一个唯一的属性,uri
,其中包含正在讨论的URI::extu = store.create_entity('ExternalUri', uri="http://path/of/the/uri")
创建实体之间的关系。我们的关系是:
模式中定义的实体,例如
Disease
和Gene
实体,如associated_genes
为定义的关系Disease
实体。模式中定义的实体和
ExternalUri
实体,例如gene_id
.
关系添加到数据库的方式取决于存储:
用于CubicWeb中的商店
dataimport
模块,我们只使用store.relate
,在另一个循环中,关于关系(即前一个循环中的循环,在步骤2中提到)::for rtype, rels in relations.iteritems(): ... store.relate(ent.eid(), rtype, extu.eid(), **kwargs)
在哪里?
kwargs
当关系是内联的并且SQLGenObjectStore
使用。例如::... store.relate(ent.eid(), 'chromosomal_location', extu.eid(), subjtype='Disease')
对于
MassiveObjectStore
在dataio
立方体dataimport
模块中,关系的创建分为三个步骤:首先,为每个关系类型创建一个表,如:
... store.init_rtype_table(ent.cw_etype, rtype, extu.cw_etype)
这可以归结为:
store.init_rtype_table('Disease', 'associated_genes', 'Gene') store.init_rtype_table('Gene', 'gene_id', 'ExternalUri')
第二,在
relate_by_iid
方法,例如:disease_uri = 'http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseases/3' gene_uri = '<http://www4.wiwiss.fu-berlin.de/diseasome/resource/genes/HSD3B2' store.relate_by_iid(disease_uri, 'associated_genes', gene_uri)
第三,每个关系类型的关系将通过
convert_relations
方法,例如:store.convert_relations('Disease', 'associated_genes', 'Gene', 'cwuri', 'cwuri')
和:
store.convert_relations('Gene', 'hgnc_id', 'ExternalUri', 'cwuri', 'uri')
在哪里?
cwuri
和uri
是存储数据模型中定义的实体的URI的属性,以及ExternalUri
分别是实体。
刷新所有关系和实体::
store.flush()
它执行数据库中插入的实体和关系的实际提交。
如果 MassiveObjectStore
则应在导入结束时对临时SQL表执行清除::
store.cleanup()
时间基准¶
为了给导入脚本计时,我们只需用 timed
装饰师:
from logilab.common.decorators import timed
...
@timed
def diseasome_import(session, filename):
...
如“导入数据”部分所示运行导入功能后,我们获得两个时间测量值:
diseasome_import clock: ... / time: ...
这里,这些度量的含义是 5:
clock
是CubicWeb在服务器端所花费的时间(即,钩子和对SQL查询的数据预处理/后处理)。time
是介于clock
以及在PostgreSQL上花费的时间。
- 5
的含义
clock
和time
测量,当使用@timed
装饰工,从 a blog post on massive data import in CubicWeb .
导入函数被放入一个名为 diseasome_import
在这里。模块直接从CubicWeb外壳调用,如下所示:
cubicweb-ctl shell diseasome_instance diseasome_import.py \
-- -df diseasome_import_file.nt -st StoreName
模块接受两个参数:
数据文件,由
-df [--datafile]
和商店,由
-st [--store]
.
下表给出了用于导入4213的不同存储的计时(秒) Disease
实体和3919 Gene
刚刚描述的导入模块的实体:
商场 |
CubicWeb时间(时钟) |
PostgreSQL时间(时钟) |
总时间 |
---|---|---|---|
|
225.98 |
62.05 |
288.03 |
|
62.73 |
51.38 |
114.11 |
|
20.41 |
11.03 |
31.44 |
|
4.84 |
6.93 |
11.77 |
结论¶
在本教程中,我们了解了如何在CubicWeb应用程序实例中导入数据。我们首先了解了如何创建模式,然后了解了如何创建数据的解析器以及数据到模式的映射。最后,我们看到了将数据导入CubicWeb的四种方法。
其中三个集成到CubicWeb中,即 RQLObjectStore
, NoHookRQLObjectStore
和 SQLGenObjectStore
具有通用API的存储:
RQLObjectStore
是目前最慢的,尤其是在cubicWeb端花费的时间,因此它只能用于少量“敏感”数据(即,在安全问题上)。NoHookRQLObjectStore
在CubicWeb上花费的时间减少了近四倍,但速度也相当慢;在Postgres方面,速度和以前的商店一样慢。它应该用于不考虑安全性,但(与数据模型)一致性的数据。SQLGenObjectStore
在CubicWeb上花费的时间减少了三倍,在PostgreSQL上花费的时间减少了五倍。它应该用于相对大量的数据,在这些数据中,安全性和数据一致性不是一个问题。与前一个存储相比,它的缺点是,对于内联关系,我们必须指定其主题的类型。
对于非常庞大的数据,有第四个存储, MassiveObjectStore
,可从 dataio
立方体。它为所有其他的商店提供了一个辉煌的业绩:它几乎是25倍于 RQLObjectStore
快了将近三倍 SQLGenObjectStore
.但是,它有一些使用注意事项需要考虑:
它不能在架构中插入定义为内联的关系,
没有对数据执行安全性或一致性检查,
它的API与其他商店略有不同。
因此,当不考虑安全性和数据一致性,并且模式中没有内联关系时,应该使用此存储。