将关系数据导入CubicWeb实例

介绍

本教程介绍如何将数据从外部源(例如文件集合)导入CubicWeb多维数据集实例。

首先,一旦我们知道了我们想要导入的数据的格式,我们就设计了一个 数据模型 也就是说,一个反映数据结构方式的CubicWeb(Yams)模式。此架构在 schema.py 文件。在本教程中,我们将为特定的数据集描述这样一个模式,即diseasome数据(见下文)。

一旦定义了模式,我们就创建一个多维数据集和一个实例。多维数据集是应用程序的规范,而实例本身就是应用程序。

一旦定义了模式并创建了实例,就可以通过以下步骤执行导入:

  1. 为要导入的数据生成自定义分析器。因此,可以获得数据的python内存表示。

  2. 将分析的数据映射到中定义的数据模型 schema.py .

  3. 执行数据的实际导入。根据在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 和A gene 对象实体定义者 HADH2 .

因此,对于分析数据,我们可以(:注意:请参见 diseasome_parser 模块):

  1. 定义两个正则表达式来解析这两类行, RE_ATTS 用于分析属性定义,以及 RE_RELS 用于分析关系定义。

  2. 定义一个函数,该函数遍历文件的行并检索 (yield s)每行一个(主题、关系、对象)元组。我们称之为 _retrieve_structurediseasome_parser 模块。函数需要文件名和应检索信息的类型。

或者,可以使用中提供的RDF解析器,而不是手工生成解析器。 dataio 立方体。

一旦我们得到(主题、关系、对象)三元组,我们就需要将它们映射到数据模型中。

将数据映射到架构

在diseasome数据的情况下,我们可以定义两个字典,将解析器提取的关系名称映射到在 schema.py 数据模型。在 diseasome_parser 它们被调用的模块 MAPPING_ATTSMAPPING_RELS .考虑到原始数据中camelcase中给出了关系和属性名,因此,如果在数据模型中命名属性时遵循PEP08,则需要映射。例如,RDF关系 chromosomalLocation 映射到架构关系中 chromosomal_location .

一旦定义了这些映射,我们只需迭代解析器提供的(主题、关系、对象)元组,然后提取实体及其属性和关系。因此,对于每个实体,我们都有一个带有两个键的字典, attributesrelations .与 attributes key是一个包含(attribute:value)对的字典,其中“value”是一个字符串,加上 cwuri 保存实体本身的URI的键/属性。与 relations key是一个包含(relation:value)对的字典,其中“value”是一个URI。这是在 entities_from_rdf 模块接口功能 diseasome_parser .此函数在包含 attributesrelations 所有实体的键。

然而,这是一个简单的例子。在现实生活中,事情可能变得更加复杂,映射也可能非常简单,尤其是当必须将多个数据源(可以遵循不同的格式,甚至是结构约定)映射到同一个数据模型时。

导入数据

数据导入代码应该放在Python模块中。让我们称之为 diseasome_import.py .然后,应该通过 cubicweb-ctl 如下:

cubicweb-ctl shell diseasome_import.py -- <other arguments e.g. data file>

在导入模块中,我们应该使用 商店 做导入。存储是一个对象,它提供三种导入数据的方法:

  • 用于导入实体及其属性值的方法。

  • 用于导入实体之间关系的方法。

  • 将导入提交到数据库的方法。

在CubicWeb,我们有四家商店:

  1. ObjectStore CubicWeb中存储的基类。它只为所有其他存储提供框架,并提供创建存储实体及其关系的内存结构(字典)的方法。

  2. RQLObjectStore :使用rql语言执行数据库插入和更新的存储。它依赖于所有CubicWeb钩子机制,尤其是处理安全问题(数据库访问权限)。

  1. NoHookRQLObjectStore :使用rql语言执行数据库插入和更新的存储,但所有挂钩都被停用。这意味着没有对cubicWeb/yams模式(数据模型)执行某些检查。但是,从RQL查询中获得的所有SQL查询都是按顺序执行的,每个插入的实体一个查询。

  1. SQLGenObjectStore :直接使用SQL语言的存储。它可以按顺序插入实体,方法是对每个实体执行SQL查询,或者直接使用一个Postgres COPY FROM 查询一组类似结构的实体。

对于真正大规模的进口(数百万或数十亿个实体),有一个立方体 dataio 它包含另一个商店,叫做 MassiveObjectStore .这家商店和 SQLGenObjectStore ,但与CubicWeb相关的任何内容都将被忽略。也就是说,即使是CubicWebEID实体标识符也不会被处理。这个商店是最快的,但是它的API与上面提到的其他四个商店略有不同。而且,它有一个重要的限制,因为它不插入内联 1 数据库中的关系。

1

内联关系是使用关键字参数在架构中定义的关系。 inlined=True .这样的关系作为其主题实体的属性插入到数据库中。

在下面的部分中,我们将看到如何使用cubicWeb中的存储导入数据。 dataimport 模块。

使用中的商店 dataimport

ObjectStore 在现实生活中很少用于导入数据,因为它只是其他存储的基本存储,并且不执行数据的实际导入。然而,其他三个存储(导入数据)是基于 ObjectStore 并提供相同的API。

三家商店 RQLObjectStoreNoHookRQLObjectStoreSQLGenObjectStore 为在SQL数据库中导入数据(即实体和关系)提供完全相同的API。

在使用商店之前,必须导入 dataimport 模块,然后用当前 session 作为参数:

import cubicweb.dataimport as cwdi
...

store = cwdi.RQLObjectStore(session)

每个这样的存储提供三种数据导入方法:

  1. 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')
    
  2. 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 仅用于 SQLGenObjectStorerelate 方法和允许我们在模式中将关系定义为内联时指定关系主题的类型。

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 参数。

  1. flush() ,它允许我们执行数据库中的实际提交,以及一些清理操作。理想情况下,应该尽可能频繁地调用这个方法,即在数据库中每次插入之后,这样数据库会话就尽可能保持原子性。在实践中,我们通常两次调用这个方法:第一次,在所有实体都被创建之后,第二次,在所有关系都被创建之后。

    但是请注意,在每次提交之前,数据库插入必须与模式一致。因此,如果一个实体有一个通过关系(即一 SubjectRelation"1""+" 对象基数,在将添加内容提交到数据库之前,我们必须创建讨论中的实体、讨论中的关系的对象实体以及关系本身。

    这个 flush 方法简单地称为:

    store.flush().
    

使用 MassiveObjectStoredataio 立方体

这家商店,在 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 提供将数据插入数据库的六种方法:

  1. init_rtype_table(SubjEtype, r_type, ObjEtype) 指定与数据库中的关系类型关联的表的创建。每个这样的表都有三列、主题实体的类型、关系的类型(即,通过关系定义的主题实体中属性的名称)和对象实体的类型。例如::

    store.init_rtype_table('Person', 'lives_in', 'Location')
    

    请注意,这些表可以在实体之前创建,因为它们只指定它们的类型,而不指定它们的唯一标识符。

  2. 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完成的,存储在如下属性中 uricwuri .只要属性的值对于每个实体都是唯一的,属性的名称就不相关。

  3. relate_by_iid(subject_iid, r_type, object_iid) 允许我们实际关联由 subject_iidobject_iid 通过类型关系 r_type . 例如::

    store.relate_by_iid('http://link/to/person/toto_18_190',
                        'lives_in',
                        'http://link/to/location/paris_13')
    

    请注意这个方法 not 为内联关系工作!

  4. convert_relations(SubjEtype, r_type, ObjEtype, subj_iid_attribute, obj_iid_attribute) 允许我们在数据库中实际插入关系。在这个方法的一个调用中,插入类型的所有关系 rtype 在给定类型的实体之间。 subj_iid_attributeobject_iid_attribute 是存储由用户分配的实体唯一标识符的属性的名称。这些名称可以是相同的,只要它们的值是唯一的。例如,用于插入类型的所有关系 lives_in 之间 PeopleLocation 实体,我们写道:

    store.convert_relations('Person', 'lives_in', 'Location', 'uri', 'uri')
    
  5. flush() 执行数据库中的实际提交。它只需要在 create_entityrelate_by_iid 电话。请注意 relate_by_iidnot 在数据库中执行插入操作,因此调用 flush() 因为它不会有任何效果。

  6. cleanup() 通过删除临时表来执行数据库清理。只应在导入结束时调用它。

对diseasome数据的应用

导入设置

我们定义了一个导入函数, diseasome_import 基本上有四件事:

  1. 通过以下行创建并初始化要使用的存储:

    store = cwdi.SQLGenObjectStore(session)
    

    在哪里? cwdi 是进口的吗 cubicweb.dataimportcubicweb_dataio.dataimport .

  2. 调用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 .

  3. 创建要插入到数据库中的实体;对于diseasome,有两种实体:

    1. 数据模型中定义的实体,即 GeneDisease 在我们的例子中。

    2. 在CubicWeb/Yams中构建的实体,即 ExternalUri 定义URI。

    当我们处理RDF数据时,每个实体都是通过一系列URI定义的。因此,每个“关系属性” 4 实体的定义是通过一个URI,也就是说,在cubicWeb术语中,通过一个 ExternalUri 实体。在上述循环中创建实体,如:

    ent = store.create_entity(etype, **entity)
    

    在哪里? etype 是适当的实体类型,或者 GeneDisease .

4
通过“关系属性”,我们表示一个(实体的)属性,它

通过关系定义,例如 chromosomal_location 属性 Disease 实体,通过 Disease 和一个 ExternalUri .

这个 ExternalUri 实体与数据文件中的URI相同。对于它们,我们定义了一个唯一的属性, uri ,其中包含正在讨论的URI::

extu = store.create_entity('ExternalUri', uri="http://path/of/the/uri")
  1. 创建实体之间的关系。我们的关系是:

    1. 模式中定义的实体,例如 DiseaseGene 实体,如 associated_genes 为定义的关系 Disease 实体。

    2. 模式中定义的实体和 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')
      
    • 对于 MassiveObjectStoredataio 立方体 dataimport 模块中,关系的创建分为三个步骤:

      1. 首先,为每个关系类型创建一个表,如:

        ...
        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')
        
      2. 第二,在 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)
        
      3. 第三,每个关系类型的关系将通过 convert_relations 方法,例如:

        store.convert_relations('Disease', 'associated_genes', 'Gene', 'cwuri', 'cwuri')
        

        和:

        store.convert_relations('Gene', 'hgnc_id', 'ExternalUri', 'cwuri', 'uri')
        

        在哪里? cwuriuri 是存储数据模型中定义的实体的URI的属性,以及 ExternalUri 分别是实体。

  2. 刷新所有关系和实体::

    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

的含义 clocktime 测量,当使用 @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时间(时钟)

总时间

RQLObjectStore

225.98

62.05

288.03

NoHookRQLObjectStore

62.73

51.38

114.11

SQLGenObjectStore

20.41

11.03

31.44

MassiveObjectStore

4.84

6.93

11.77

结论

在本教程中,我们了解了如何在CubicWeb应用程序实例中导入数据。我们首先了解了如何创建模式,然后了解了如何创建数据的解析器以及数据到模式的映射。最后,我们看到了将数据导入CubicWeb的四种方法。

其中三个集成到CubicWeb中,即 RQLObjectStoreNoHookRQLObjectStoreSQLGenObjectStore 具有通用API的存储:

  • RQLObjectStore 是目前最慢的,尤其是在cubicWeb端花费的时间,因此它只能用于少量“敏感”数据(即,在安全问题上)。

  • NoHookRQLObjectStore 在CubicWeb上花费的时间减少了近四倍,但速度也相当慢;在Postgres方面,速度和以前的商店一样慢。它应该用于不考虑安全性,但(与数据模型)一致性的数据。

  • SQLGenObjectStore 在CubicWeb上花费的时间减少了三倍,在PostgreSQL上花费的时间减少了五倍。它应该用于相对大量的数据,在这些数据中,安全性和数据一致性不是一个问题。与前一个存储相比,它的缺点是,对于内联关系,我们必须指定其主题的类型。

对于非常庞大的数据,有第四个存储, MassiveObjectStore ,可从 dataio 立方体。它为所有其他的商店提供了一个辉煌的业绩:它几乎是25倍于 RQLObjectStore 快了将近三倍 SQLGenObjectStore .但是,它有一些使用注意事项需要考虑:

  1. 它不能在架构中插入定义为内联的关系,

  2. 没有对数据执行安全性或一致性检查,

  3. 它的API与其他商店略有不同。

因此,当不考虑安全性和数据一致性,并且模式中没有内联关系时,应该使用此存储。