Skip to main content

使用 specs.frictionlessdata.io 上定义的数据包的实用程序

项目描述

数据包-py

特拉维斯 连体工作服 派皮 Github 吉特

用于处理数据包的库。

【重要提示】我们发布了Frictionless Framework。该框架提供了改进datapackage的功能,扩展为完整的数据解决方案。现有软件不中断的更改,因此不需要任何操作。请阅读从无摩擦框架迁移指南。datapackage

  • datapackage@1.x我们继续在这个存储库中修复错误,并且它在PyPi上可用,就像以前一样
  • 请注意frictionless@3.x,我们目前正在开发的版本 API 不稳定
  • 我们将frictionless@4.x在 2020 年底发布,成为第一个 SemVer/stable 版本

特征

  • Package处理数据包的类
  • Resource处理数据资源的类
  • Profile使用配置文件的类
  • validate验证数据包描述符的函数
  • infer推断数据包描述符的函数

内容

入门

安装

该包使用语义版本控制。这意味着主要版本可能包括重大更改。强烈建议datapackage在文件中指定版本范围,setup/requirements例如datapackage>=1.0,<2.0.

$ pip install datapackage

OSX 10.14+

如果cchardet在 Mac OSX 10.14 (Mojave) 或更高版本上安装 datapackage 时收到有关包的错误,请按照以下步骤操作:

  1. 通过在终端中运行以下命令,确保您拥有最新的 x 代码:xcode-select --install
  2. 然后转到https://developer.apple.com/download/more/并下载command line tools. 请注意,这需要 Apple ID。
  3. 然后,在终端中,运行open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg 您可以在这篇文章中阅读有关这些步骤的更多信息。

文档

介绍

让我们从一个简单的例子开始:

from datapackage import Package

package = Package('datapackage.json')
package.get_resource('resource').read()

使用包

用于处理数据包的类。它提供了各种功能,例如加载本地或远程数据包、推断数据包描述符、保存数据包描述符等等。

考虑我们在一个data目录中有一些本地 csv 文件。让我们使用一个类基于这些数据创建一个数据包Package

数据/cities.csv

city,location
london,"51.50,-0.11"
paris,"48.85,2.30"
rome,"41.89,12.51"

数据/人口.csv

city,year,population
london,2017,8780000
paris,2017,2240000
rome,2017,2860000

首先我们创建一个空白数据包:

package = Package()

现在我们准备好根据我们拥有的数据文件来推断数据包描述符。因为我们有两个 csv 文件,所以我们使用 glob 模式**/*.csv

package.infer('**/*.csv')
package.descriptor
#{ profile: 'tabular-data-package',
#  resources:
#   [ { path: 'data/cities.csv',
#       profile: 'tabular-data-resource',
#       encoding: 'utf-8',
#       name: 'cities',
#       format: 'csv',
#       mediatype: 'text/csv',
#       schema: [Object] },
#     { path: 'data/population.csv',
#       profile: 'tabular-data-resource',
#       encoding: 'utf-8',
#       name: 'population',
#       format: 'csv',
#       mediatype: 'text/csv',
#       schema: [Object] } ] }

一个infer方法找到了我们所有的文件并检查它以提取有用的元数据,如配置文件、编码、格式、表模式等。让我们稍微调整一下:

package.descriptor['resources'][1]['schema']['fields'][1]['type'] = 'year'
package.commit()
package.valid # true

因为我们的资源是表格的,所以我们可以将其读取为表格数据:

package.get_resource('population').read(keyed=True)
#[ { city: 'london', year: 2017, population: 8780000 },
#  { city: 'paris', year: 2017, population: 2240000 },
#  { city: 'rome', year: 2017, population: 2860000 } ]

让我们将描述符作为 zip 文件保存在磁盘上:

package.save('datapackage.zip')

要继续使用数据包,我们只需再次加载它,但这次使用 local datapackage.zip

package = Package('datapackage.zip')
# Continue the work

这只是对课程的基本介绍Package。要了解更多信息,让我们看一下Package类 API 参考。

使用资源

用于处理数据资源的类。您可以使用方法读取或迭代表格资源,iter/read并使用方法将所有资源作为字节读取或迭代row_iter/row_read

考虑我们有一些本地 csv 文件。它可以是内联数据或远程链接 -Resource类都支持(当然,浏览器内使用的本地文件除外)。但是data.csv现在说:

city,location
london,"51.50,-0.11"
paris,"48.85,2.30"
rome,N/A

让我们创建并读取一个资源。因为资源是表格的,我们可以使用resource.read带有keyed选项的方法来获取键控行数组:

resource = Resource({path: 'data.csv'})
resource.tabular # true
resource.read(keyed=True)
# [
#   {city: 'london', location: '51.50,-0.11'},
#   {city: 'paris', location: '48.85,2.30'},
#   {city: 'rome', location: 'N/A'},
# ]
resource.headers
# ['city', 'location']
# (reading has to be started first)

正如我们所见,我们的位置只是一个字符串。但它应该是地理点。罗马的位置也不可用,但它也只是一个N/A字符串而不是 Python None。首先我们必须推断资源元数据:

resource.infer()
resource.descriptor
#{ path: 'data.csv',
#  profile: 'tabular-data-resource',
#  encoding: 'utf-8',
#  name: 'data',
#  format: 'csv',
#  mediatype: 'text/csv',
# schema: { fields: [ [Object], [Object] ], missingValues: [ '' ] } }
resource.read(keyed=True)
# Fails with a data validation error

让我们修复不可用的位置。表模式规范中有一个missingValues属性。作为第一次尝试,我们设置missingValuesN/Ain resource.descriptor.schema。资源描述符可以就地更改,但所有更改应由以下人员提交resource.commit()

resource.descriptor['schema']['missingValues'] = 'N/A'
resource.commit()
resource.valid # False
resource.errors
# [<ValidationError: "'N/A' is not of type 'array'">]

作为一个优秀的公民,我们决定检查资源描述符的有效性。而且是无效的!我们应该使用数组作为missingValues属性。也不要忘记将空字符串作为缺失值:

resource.descriptor['schema']['missingValues'] = ['', 'N/A']
resource.commit()
resource.valid # true

都好。看起来我们已经准备好再次读取我们的数据了:

resource.read(keyed=True)
# [
#   {city: 'london', location: [51.50,-0.11]},
#   {city: 'paris', location: [48.85,2.30]},
#   {city: 'rome', location: null},
# ]

现在我们看到:

  • 位置是具有数字纬度和经度的数组
  • 罗马的位置是原生 JavaScriptnull

而且由于数据读取没有错误,我们可以确定我们的数据对我们的模式是有效的。让我们保存我们的资源描述符:

resource.save('dataresource.json')

让我们检查一下新创建的dataresource.json. 它包含我们的数据文件的路径、推断的元数据和我们的missingValues调整:

{
    "path": "data.csv",
    "profile": "tabular-data-resource",
    "encoding": "utf-8",
    "name": "data",
    "format": "csv",
    "mediatype": "text/csv",
    "schema": {
        "fields": [
            {
                "name": "city",
                "type": "string",
                "format": "default"
            },
            {
                "name": "location",
                "type": "geopoint",
                "format": "default"
            }
        ],
        "missingValues": [
            "",
            "N/A"
        ]
    }
}

如果我们决定进一步改进它,我们可以更新dataresource.json文件,然后使用本地文件名再次打开它:

resource = Resource('dataresource.json')
# Continue the work

这只是对课程的基本介绍Resource。要了解更多信息,让我们看一下Resource类 API 参考。

与小组合作

代表一组表格资源的类。组可用于将多个资源作为一个读取或将它们导出,例如,作为一个表导出到数据库。要定义组,请将group: <name>字段添加到相应的资源。组的元数据将从“领先”资源的元数据(具有组名称的第一个资源)创建。

假设我们有一个数据包,其中包含两个按年份分区的表和一个单独存储的共享模式:

汽车-2017.csv

name,value
bmw,2017
tesla,2017
nissan,2017

汽车-2018.csv

name,value
bmw,2018
tesla,2018
nissan,2018

汽车.schema.json

{
    "fields": [
        {
            "name": "name",
            "type": "string"
        },
        {
            "name": "value",
            "type": "integer"
        }
    ]
}

数据包.json

{
    "name": "datapackage",
    "resources": [
        {
            "group": "cars",
            "name": "cars-2017",
            "path": "cars-2017.csv",
            "profile": "tabular-data-resource",
            "schema": "cars.schema.json"
        },
        {
            "group": "cars",
            "name": "cars-2018",
            "path": "cars-2018.csv",
            "profile": "tabular-data-resource",
            "schema": "cars.schema.json"
        }
    ]
}

让我们分别阅读资源:

package = Package('datapackage.json')
package.get_resource('cars-2017').read(keyed=True) == [
    {'name': 'bmw', 'value': 2017},
    {'name': 'tesla', 'value': 2017},
    {'name': 'nissan', 'value': 2017},
]
package.get_resource('cars-2018').read(keyed=True) == [
    {'name': 'bmw', 'value': 2018},
    {'name': 'tesla', 'value': 2018},
    {'name': 'nissan', 'value': 2018},
]

另一方面,这些资源用一个group: cars字段定义。这意味着我们可以将它们视为一个群体:

package = Package('datapackage.json')
package.get_group('cars').read(keyed=True) == [
    {'name': 'bmw', 'value': 2017},
    {'name': 'tesla', 'value': 2017},
    {'name': 'nissan', 'value': 2017},
    {'name': 'bmw', 'value': 2018},
    {'name': 'tesla', 'value': 2018},
    {'name': 'nissan', 'value': 2018},
]

当我们需要将数据包保存到存储中时,我们可以使用这种方法,例如保存到 SQL 数据库中。有merge_groups启用分组行为的标志:

package = Package('datapackage.json')
package.save(storage='sql', engine=engine)
# SQL tables:
# - cars-2017
# - cars-2018
package.save(storage='sql', engine=engine, merge_groups=True)
# SQL tables:
# - cars

使用配置文件

一个表示来自Profiles Registry的 JSON Schema 配置文件的组件:

profile = Profile('data-package')

profile.name # data-package
profile.jsonschema # JSON Schema contents

try:
   valid = profile.validate(descriptor)
except exceptions.ValidationError as exception:
   for error in exception.errors:
       # handle individual error

使用外键

该库支持表模式规范中描述的外键。这意味着如果您的数据包描述符resources[].schema.foreignKeys对某些资源使用属性,则将在读取操作时检查数据完整性。

考虑我们有一个数据包:

DESCRIPTOR = {
  'resources': [
    {
      'name': 'teams',
      'data': [
        ['id', 'name', 'city'],
        ['1', 'Arsenal', 'London'],
        ['2', 'Real', 'Madrid'],
        ['3', 'Bayern', 'Munich'],
      ],
      'schema': {
        'fields': [
          {'name': 'id', 'type': 'integer'},
          {'name': 'name', 'type': 'string'},
          {'name': 'city', 'type': 'string'},
        ],
        'foreignKeys': [
          {
            'fields': 'city',
            'reference': {'resource': 'cities', 'fields': 'name'},
          },
        ],
      },
    }, {
      'name': 'cities',
      'data': [
        ['name', 'country'],
        ['London', 'England'],
        ['Madrid', 'Spain'],
      ],
    },
  ],
}

让我们检查teams资源的关系:

from datapackage import Package

package = Package(DESCRIPTOR)
teams = package.get_resource('teams')
teams.check_relations()
# tableschema.exceptions.RelationError: Foreign key "['city']" violation in row "4"

正如我们所见,存在外键违规。那是因为我们的查找表cities没有城市,Munich但我们有一个团队。我们需要在cities资源中修复它:

package.descriptor['resources'][1]['data'].append(['Munich', 'Germany'])
package.commit()
teams = package.get_resource('teams')
teams.check_relations()
# True

固定的!但不仅可以进行检查操作。我们可以使用方法的relations参数resource.iter/read来取消引用资源关系:

teams.read(keyed=True, relations=True)
#[{'id': 1, 'name': 'Arsenal', 'city': {'name': 'London', 'country': 'England}},
# {'id': 2, 'name': 'Real', 'city': {'name': 'Madrid', 'country': 'Spain}},
# {'id': 3, 'name': 'Bayern', 'city': {'name': 'Munich', 'country': 'Germany}}]

我们有一个包含城市数据的字典,而不是简单的城市名称。如果存在完整性问题,这些resource.iter/read方法将失败并显示与错误相同的错误。resource.check_relationsrelations=True前提是通过标志。

使用验证/推断

验证数据包描述符的独立函数:

from datapackage import validate, exceptions

try:
    valid = validate(descriptor)
except exceptions.ValidationError as exception:
   for error in exception.errors:
       # handle individual error

用于推断数据包描述符的独立函数。

descriptor = infer('**/*.csv')
#{ profile: 'tabular-data-resource',
#  resources:
#   [ { path: 'data/cities.csv',
#       profile: 'tabular-data-resource',
#       encoding: 'utf-8',
#       name: 'cities',
#       format: 'csv',
#       mediatype: 'text/csv',
#       schema: [Object] },
#     { path: 'data/population.csv',
#       profile: 'tabular-data-resource',
#       encoding: 'utf-8',
#       name: 'population',
#       format: 'csv',
#       mediatype: 'text/csv',
#       schema: [Object] } ] }

经常问的问题

访问代理服务器后面的数据?

package = Package("https://xxx.json")调用之前设置这些环境变量:

import os

os.environ["HTTP_PROXY"] = 'xxx'
os.environ["HTTPS_PROXY"] = 'xxx'

API 参考

cli

cli()

命令行界面

Usage: datapackage [OPTIONS] COMMAND [ARGS]...

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  infer
  validate

Package

Package(self,
        descriptor=None,
        base_path=None,
        strict=False,
        unsafe=False,
        storage=None,
        schema=None,
        default_base_path=None,
        **options)

包装表示

论据

  • 描述符(str/dict):数据包描述符为本地路径、url或对象
  • base_path (str) : 所有相对路径的基本路径
  • strict (bool) : 更改验证行为的严格标志。将其设置为True会导致对具有无效描述符的任何操作抛出错误
  • unsafe (bool) : 如果True允许不安全的路径。有关更多信息 https://specs.frictionlessdata.io/data-resource/#data-location。默认为False
  • storage (str/tableschema.Storage) : 存储名称sql或存储实例
  • options (dict) : 用于创建存储的存储选项

加注

  • DataPackageException: 如果出现问题,会引发错误

package.base_path

包的基本路径

退货

str/None:返回数据包基本路径

package.descriptor

包的描述符

退货

dict: 描述符

package.errors

验证错误

在严格模式下始终为空。

退货

Exception[]: 验证错误

package.profile

包的配置文件

退货

ProfileProfile:类的一个实例

package.resource_names

包的资源名称

退货

str[]:返回资源名称数组

package.resources

包的资源

退货

Resource[]Resource: 返回一个实例数组

package.valid

验证状态

在严格模式下始终为真。

退货

bool: 验证状态

package.get_resource

package.get_resource(name)

按名称获取数据包资源。

论据

  • name (str) : 数据资源名称

退货

Resource/None: 如果没有找到,则返回Resource实例或 null

package.add_resource

package.add_resource(descriptor)

将新资源添加到数据包。

数据包描述符将使用新添加的资源描述符进行验证。

论据

  • 描述符(dict):数据资源描述符

加注

  • DataPackageException: 如果出现问题,会引发错误

退货

Resource/None: 返回添加Resource的实例,如果没有添加则返回 null

package.remove_resource

package.remove_resource(name)

按名称删除数据包资源。

数据包描述符将在资源描述符删除后进行验证。

论据

  • name (str) : 数据资源名称

加注

  • DataPackageException: 如果出现问题,会引发错误

退货

Resource/None:如果没有找到,则返回已删除的Resource实例或 null

package.get_group

package.get_group(name)

按名称返回一组表格资源。

有关组的更多信息,请参阅

论据

  • name (str) : 一组资源的名称

加注

  • DataPackageException: 如果出现问题,会引发错误

退货

Group/None: 返回一个Group实例,如果没有找到则返回 null

package.infer

package.infer(pattern=False)

推断数据包元数据。

参数pattern仅适用于本地文件

如果pattern未提供,则仅推断现有资源(添加元数据,如编码、配置文件等)。如果pattern提供了带有文件名的新资源,则将添加和推断模式。它提交对数据包实例的更改。

论据

  • 模式(str):新资源的全局模式

退货

dict: 返回数据包描述符

package.commit

package.commit(strict=None)

如果描述符中存在就地更改,则更新数据包实例。

例子

package = Package({
    'name': 'package',
    'resources': [{'name': 'resource', 'data': ['data']}]
})

package.name # package
package.descriptor['name'] = 'renamed-package'
package.name # package
package.commit()
package.name # renamed-package

论据

  • strict (bool) : 改变strict模式以进行进一步的工作

加注

  • DataPackageException: 如果出现问题,会引发错误

退货

bool: 成功返回true,不修改返回false

package.save

package.save(target=None,
             storage=None,
             merge_groups=False,
             to_base_path=False,
             **options)

保存此数据包

如果传递参数,则将其保存到存储中;如果参数以结尾,storage则将此数据包的描述符保存到 json 文件,否则,将此数据包保存到 zip 文件。target.json

例子

file_or_path它使用此数据包的内容及其资源创建一个 zip 文件。内容存在于本地文件系统中的每个资源都将被复制到 zip 文件中。考虑以下数据包描述符:

{
    "name": "gdp",
    "resources": [
        {"name": "local", "format": "CSV", "path": "data.csv"},
        {"name": "inline", "data": [4, 8, 15, 16, 23, 42]},
        {"name": "remote", "url": "http://someplace.com/data.csv"}
    ]
}

zip 文件的最终结构将是:

./datapackage.json
./data/local.csv

与返回的内容datapackage.json相同datapackage.descriptor。资源的文件名是根据它们的nameformat字段生成的(如果它们存在的话)。如果资源没有name,它将被使用resource-X,其中Xresources列表中资源的索引(从零开始)。如果资源有format,它将被小写并附加到name,变成“ name.format”。

论据

  • target (string/filelike) : 将保存此数据包内容的文件路径或类文件对象。
  • storage (str/tableschema.Storage) : 存储名称sql或存储实例
  • merge_groups (bool):如果提供了存储,则将所有组的表格资源保存到一个桶中(例如,保存到一个 SQL 表中)。阅读有关集团的更多信息。
  • to_base_path (bool) : 使用 "<base_path>/<target>" 路由将包保存到包的基本路径
  • options (dict) : 用于创建存储的存储选项

加注

  • DataPackageException: 如果在编写包时出现错误,则引发

退货

bool/Storage: 成功返回 true 或Storage实例

Resource

资源self 
         descriptor = {},
         base_path = None 
         strict = False 
         unsafe =