用于创建数据收集插件和从CLI、REST或Python调用它们链的轻量级框架
项目描述
sofine解决了什么问题?
您需要从多个来源获取与一组键相关的数据:网络爬虫、Web API、平面文件、数据存储。如果您可以使用一个命令行、REST或Python调用构建一个综合数据集,那不是很好吗?如果您写的每个数据检索脚本都是一个可重用的插件,您可以将它与其他任何插件组合起来,那不是更好吗?
您需要一个“粘合API”。
这是sofine解决的问题。这是一个足够小的问题,您可以自己解决它。但sofine的部署和编写插件都是最小的,并且已经以最灵活的方式决定了您自己编写时必须做出的相同设计决策。
功能
(几乎)不需要比编写一次性数据收集脚本更多的努力
在您喜欢的任何目录结构中管理您的数据检索插件
从命令行、作为REST资源或从Python调用插件
将尽可能多的插件调用串联在一起,并获取一个包含从所有串联调用收集的所有数据的JSON数据集
如果从命令行调用,sofine如果存在,则从stdin读取数据,并始终输出到stdout。因此,sofine管道调用本身也可以在更大的管道表达式中组成。
为了娱乐,这里提供了一个功能4和5的示例,将一个sofine管道与神奇的JSON查询工具jq结合,以进行进一步过滤。
echo '{"AAPL":[]}' | python $PYTHONPATH/sofine/runner.py '--SF-s ystockquotelib --SF-g example | --SF-s google_search_results --SF-g example' | jq 'map(recurse(.results) | {titleNoFormatting}'
概述
要开始,请
pip安装sofine
确保您的$PYTHONPATH指向pip安装sofine的包目录
创建一个插件目录并将其路径赋值给环境变量SOFINE_PLUGIN_PATH
编写并调用一些数据检索插件(或者直接开始使用附带的插件)
在简单情况下,插件需要两个属性和一个方法,在最为复杂的情况下需要三个方法。您可以可选地定义两个额外的属性供客户端使用以了解您的插件。
sofine附带了一些有用的插件以帮助您入门并给您启发;您可以将这些插件与您的自定义插件结合使用,无需额外的配置或代码。附带的插件有
sofine.plugins.standard.file_source - 从JSON文件中检索键以添加到正在构建的数据集中。有关详细信息,请参阅此处
example.archive_dot_org_search_results - 接收一个搜索查询并返回来自www.archive.org的结果
example.google_search_results - 接收一个搜索查询并返回来自Google搜索API的结果
example.fidelity - 接收一个userId、pin、accountId和email,登录Fidelity,抓取账户投资组合,并以键的形式返回找到的股票代码和每个股票代码的四个数据属性
example.ystockquotelib - 接收一个股票代码列表,并返回每个股票代码从Yahoo! Finance可获得的数据
以下是使用示例检索数据:键“AAPL”,结合从Yahoo! Finance和Google搜索API检索的所有属性。
从命令行
$ echo '{"AAPL":[]}' | python $PYTHONPATH/sofine/runner.py '--SF-s ystockquotelib --SF-g example | --SF-s google_search_results --SF-g example'
REST-fully
$ python $PYTHONPATH/sofine/rest_runner.py $ curl -X POST -d '{"AAPL":[]}' --header "Content-Type:application/json" http://localhost:10000/SF-s/ystockquotelib/SF-g/example/SF-s/google_search_results/SF-g/example
从Python
import sofine.runner as runner data = {"AAPL": []} data_sources = ['ystockquotelib', 'google_search_results'] data_source_groups = ['example', 'example'] data_source_args = [[], []] data = runner.get_data_batch(data, data_sources, data_source_groups, data_source_args)
这三种调用方式返回相同的数据集。sofine数据集将字符串键映射到属性数组,这些属性是Python字典。默认情况下,这些数据作为JSON返回到标准输出。此外,sofine还附带了对CSV的支持,并且您可以编写自己的数据格式插件(更多内容见下文)。
以下是一个使用附带的数据检索插件检索的示例:键“AAPL”,结合从Yahoo! Finance和Google搜索API检索的所有属性。
{ "AAPL": [ { "results": [ { "GsearchResultClass": "GwebSearch", "cacheUrl": "http://www.google.com/search?q=cache:XhbIlCyrcXMJ:finance.yahoo.com", "content": "View the basic <b>AAPL</b> stock chart on Yahoo! Finance. Change the date range, chart type and compare Apple Inc. against other companies.", "title": "<b>AAPL</b>: Summary for Apple Inc.- Yahoo! Finance", "titleNoFormatting": "AAPL: Summary for Apple Inc.- Yahoo! Finance", "unescapedUrl": "http://finance.yahoo.com/q?s=AAPL", "url": "http://finance.yahoo.com/q%3Fs%3DAAPL", "visibleUrl": "finance.yahoo.com" }, ... ... ] }, {"avg_daily_volume": "59390100"}, {"book_value": "20.193"}, {"change": "+1.349"}, {"dividend_per_share": "1.7771"}, {"dividend_yield": "1.82"}, {"earnings_per_share": "6.20"}, {"ebitda": "59.128B"}, {"fifty_day_moving_avg": "93.8151"}, {"fifty_two_week_high": "99.24"}, {"fifty_two_week_low": "63.8886"}, {"market_cap": "592.9B"}, {"price": "99.02"}, {"price_book_ratio": "4.84"}, {"price_earnings_growth_ratio": "1.26"}, {"price_earnings_ratio": "15.75"}, {"price_sales_ratio": "3.28"}, {"short_ratio": "1.70"}, {"stock_exchange": "\"NasdaqNM\""}, {"two_hundred_day_moving_avg": "82.8458"}, {"volume": "55317688"} ] }
安装sofine
pip install sofine
然后,确保您的$PYTHONPATH变量已设置并指向pip安装sofine的Python的site-packages目录。
export PYTHONPATH=<MY PYTHON SITE-PACKAGES DIRECTORY>
然后,创建一个插件目录并将其路径赋值给环境变量SOFINE_PLUGIN_PATH。您可能希望将其添加到您的shell配置文件中。
export SOFINE_PLUGIN_PATH=<MY PATH>
sofine在10000端口上运行其REST服务器。如果您想使用不同的端口,请设置环境变量SOFINE_REST_PORT。您可能希望将其添加到您的shell配置文件中。
export SOFINE_REST_PORT=<MY PORT>
如果您将要创建数据格式插件,创建一个数据格式插件目录并将其路径赋值给环境变量SOFINE_DATA_FORMAT_PLUGIN_PATH。
export SOFINE_DATA_FORMAT_PLUGIN_PATH=<MY PATH>
如果您想使用包含在plugins.examples插件组中的fidelity和ystockquotelib插件,请安装以下内容
easy_install mechanize easy_install beautifulsoup4 pip install ystockquote
两种类型的插件:数据检索和数据格式
sofine 使用两种类型的插件。 数据检索插件 是您在单行或链式表达式中调用以返回数据集的方式。当文档中提到“插件”时,它指的是数据检索插件。但 sofine 也支持数据集数据格式的插件。默认情况下,sofine 期望在 stdin 中以 JSON 格式输入,并将 JSON 写入 stdout。但也有 CSV 插件。
Python 数据检索插件的工作原理及如何编写它们
模板
所有插件都继承自超级类,sofine.plugins.plugin_base.PluginBase。您的插件 __init__ 方法必须调用超级类 __init__。
class ArchiveDotOrgSearchResults(plugin_base.PluginBase): def __init__(self): super(ArchiveDotOrgSearchResults, self).__init__()
您的插件最后一行应将模块作用域变量 plugin 赋值给您的插件类名称。例如
plugin = ArchiveDotOrgResults
插件属性
基类定义了四个属性
self.name - string。插件的名称
self.group - string。插件的插件组。这是插件目录中插件部署的子目录。
self.schema - list of string。调用 get_data 时可以与传递给 get_data 的键关联的属性键集。
self.adds_keys - boolean。表示插件是否向正在构建的数据集添加键,或者只是向现有键添加属性。
您必须始终定义 name 和 group。
名称
name 必须与插件模块的模块名称匹配,即您在 import 语句中使用的名称。
组
group 必须与插件部署的插件目录子目录的名称匹配。 sofine 使用 name 和 group 来加载和运行您的插件,因此它们必须存在并且必须正确。
模式
schema 是可选的。它允许您的插件用户对其进行内省。
schema 是一个字符串列表,它告诉您的插件客户端,对于它收到的每个键,您的插件返回的可能属性键集。
self.schema = ['quote']
添加键
adds_keys 允许用户询问在 sofine 调用它时,您的插件是否向正在构建的数据集添加键,或者它只是为接收到的键添加属性。
例如,sofine.plugins.example 组中的 ystockquotelib 插件接收一组股票代号作为键,并从雅虎财经检索每个键的可用数据。此插件具有属性声明 self.adds_keys = False。另一方面,sofine.plugins.fidelity 插件是一个爬虫,可以登录富达,转到登录用户的投资组合页面,爬取该投资组合中所有证券的代号,并将这些键以及它找到的任何数据添加到正在构建的数据集中。此插件 adds_keys 的值为 True。
插件方法
插件还有四个方法。
获取数据
get_data 在基类中没有实现,必须在您的插件中实现。
该方法接收一个键列表和一个参数列表。它必须返回一个字典,其中键是其接收到的键的超集(返回的键集可以比传递给 get_data 的键多,如果插件添加了键)。此字典必须具有字符串键,并为每个键提供一个字典值。字典值是针对每个键检索到的数据。该字典中的键必须是一个字符串集合,它是 self.schema 中的字符串集合的真子集。
以下是来自 sofine 插件 sofine.plugins.example.ystockquotelib 的 get_data 的示例。
def get_data(self, keys, args): """ * `keys` - `list`. The list of keys to process. * `args` - `'list`. Empty for this plugin. Calls the Yahoo API to get all available fields for each ticker provided as a key in `keys`.""" return {ticker : ystockquote.get_all(ticker) for ticker in keys}
get_namespaced_data
这是由 sofine 提供的围绕 get_data 的包装器,它返回具有插件组命名空间和名称包裹的属性键的相同数据。因此,我们上面提到的示例 quote 属性在返回的数据集中看起来是这样的
{"trading::get_quotes::quote" : 47.65}
parse_args
您通常还需要实现另一个方法 parse_args。如果您的 get_data 不需要参数,则不需要实现 parse_args。但是,如果您的 get_data 调用需要参数,则必须实现 parse_args。该方法接受一个 argv-样式的交替参数名称和值的列表,并负责验证参数名称和值的正确性,并返回一个包含两个成员的元组。第一个成员是一个布尔值 is_valid。第二个是解析后的参数值列表(不包含参数名称)。
以下是来自 sofine 插件 sofine.plugins.standard.file_source 的示例。
def parse_args(self, argv): """`[-p|--path]` - Path to the file listing the keys to load into this data source.""" usage = "[-p|--path] - Path to the file listing the keys to load into this data source." parser = OptionParser(usage=usage) parser.add_option("-p", "--path", action="store", dest="path", help="Path to the file listing the keys to load into this data source. Required.") (opts, args) = parser.parse_args(argv) is_valid = True if not opts.path: print "Invalid argument error." print "Your args: path {0}".format(opts.path) print usage is_valid = False return is_valid, [opts.path]
get_schema
第三个方法是 get_schema。您很少需要实现此方法。任何知道可以返回给键的属性集合的插件都不需要实现 get_schema,并且可以依赖于默认值,该默认值返回您定义的属性键集合。
get_namespaced_schema
get_namespaced_schema 返回您在 self.schema 中定义的属性键集合,该集合由插件组和名称限定。例如,如果上面提到的股票报价插件命名为 get_quotes,并且它位于 trading 组中,则 get_schema 的返回值将是 ["trading::get_quotes::quote"]。无论您是否实现了 get_schema,您都不需要实现此方法,因为 sofine 通过包装 get_schema 来提供它。
完整的插件示例
与编写一次性脚本相比,这只是一点点开销,但投资回报率是能够知道您的插件在哪里,用标准语法调用它们,并以任何有用的组合将它们组合起来。
有多小?这里有一个与 sofine 一起分发的 Google 搜索 API 插件。
它从一个辅助函数开始,您必须编写任何一次性脚本来调用 API。
import urllib import urllib2 import json def query_google_search(k): url = 'https://ajax.googleapis.ac.cn/ajax/services/search/web?v=1.0&q={0}'.format(urllib.quote(k)) ret = urllib2.urlopen(url) ret = ret.read() ret = json.loads(ret) if ret: 2 ret = {'results' : ret['responseData']['results']} else: ret = {'results' : []} return ret
现在,这里有使您的插件在 sofine 中运行的 11 行附加代码。
from sofine.plugins import plugin_base as plugin_base class GoogleSearchResults(plugin_base.PluginBase): def __init__(self): super(GoogleSearchResults, self).__init__() self.name = 'google_search_results' self.group = 'example' self.schema = ['results'] self.adds_keys = False def get_data(self, keys, args): return {k : query_google_search(k) for k in keys} plugin = GoogleSearchResults
仅为了好玩,这里是一个第二个示例。它显示了将现有的 Python API 包装器作为 sofine 插件包装是多么容易。
from sofine.plugins import plugin_base as plugin_base import ystockquote class YStockQuoteLib(plugin_base.PluginBase): def __init__(self): super(YStockQuoteLib, self).__init__() self.name = 'ystockquotelib' self.group = 'example' self.schema = ['fifty_two_week_low', 'market_cap', 'price', 'short_ratio', 'volume','dividend_yield', 'avg_daily_volume', 'ebitda', 'change', 'dividend_per_share', 'stock_exchange', 'two_hundred_day_moving_avg', 'fifty_two_week_high', 'price_sales_ratio', 'price_earnings_growth_ratio', 'fifty_day_moving_avg', 'price_book_ratio', 'earnings_per_share', 'price_earnings_ratio', 'book_value'] self.adds_keys = False def get_data(self, keys, args): return {ticker : ystockquote.get_all(ticker) for ticker in keys} plugin = YStockQuoteLib
HTTP 数据检索插件如何工作以及如何编写它们
您还可以将插件实现为HTTP服务器。在这种情况下,您可以使用任何您喜欢的语言来实现您的插件。您可以通过CLI API或REST API以与调用Python插件相同的方式调用HTTP服务器插件。sofine将动态构造以下元素来调用您的HTTP插件:
您在环境变量SOFINE_HTTP_PLUGIN_URL中设置的值
您为plugin_name传递的值
您为plugin_group传递的值
例如,假设您已将SOFINE_HTTP_PLUGIN_URL设置为http://localhostthis,sofine`调用
python $PYTHONPATH/sofine/runner.py '--SF-s google_search_results --SF-g example_http --SF-a get_data
将调用此URL上的插件
http://localhost/google_search_results/example_http/get_data
HTTP插件路由
HTTP插件有四个路由,映射到Python插件中的方法。
/get_data
/get_data接受一个键列表和一个参数列表。参数作为路由调用中的查询字符串参数以keys和args的形式传递,因此必须在您路由的实现中检索。
您的路由处理程序必须返回一个字典,其键是它接收到的键的适当超集(如果插件添加了键,则返回键集的键可以多于传递给get_data的键)。此字典必须具有字符串键,每个键对应一个字典值。字典值是针对每个键检索到的数据。该字典中的键必须是一个字符串集合,它是self.schema中字符串集合的适当子集。
sofine附带了一个用ruby编写的示例HTTP插件。它重新实现了Python示例插件,该插件调用Google搜索结果API。以下是HTTP插件get_data路由和处理程序:
get '/' + PLUGIN_NAME + '/' + PLUGIN_GROUP + '/get_data' do keys = params['keys'].split(',') ret = Hash[keys.map {|key| [key, query_google_search(key)]}] JSON.dump(ret) end
您的HTTP插件必须实现此路由。
对sofine的get_data调用的底层调用如下。
127.0.0.1 - - [06/Oct/2014 23:55:16] "GET /google_search_results/example_http/get_data?keys=AAPL,MSFT&args= HTTP/1.1" 200 4648 0.1324
/get_namespaced_data
这是对get_data的包装,它返回具有插件组名称和名称的命名空间的属性键包装的相同数据。此路由是可选的。
/parse_args
如果您的get_data不需要参数,则不需要实现parse_args。但是,如果您的get_data调用需要参数,则必须实现parse_args。该方法接受一个交替的arg名称和值列表的argv样式列表作为查询字符串参数args,并负责验证参数名称和值的正确性,并返回一个包含两个成员的元组。第一个成员是一个布尔值is_valid。第二个是解析后的参数值列表(不包括参数名称)。
以下是从同一示例HTTP插件google_search_results.rb的一个(有些简单)示例。
get '/' + PLUGIN_NAME + '/' + PLUGIN_GROUP + '/parse_args' do JSON.dump({"parsed_args" => params['args'], "is_valid" => true}) end
/get_schema
如果您的插件在调用时不知道它返回哪些属性,则您需要实现此功能。例如,standard/file_source.py插件是sofine的一部分,它从一个平面文件加载任意集的键,因此无法知道它可能返回什么数据。
它返回一个包含单个键schema的JSON对象。此键的值是路由返回的JSON的结构。
来自同一Google插件的示例
get '/' + PLUGIN_NAME + '/' + PLUGIN_GROUP + '/get_schema' do '{"schema" : ["results"]}' end
get_namespaced_schema
get_namespaced_schema and names返回一个具有插件组名称和名称限定符的属性键集合。
示例
get '/' + PLUGIN_NAME + '/' + PLUGIN_GROUP + '/get_schema' do '{"schema" : ["example_http::google_search_results::results"]}' end
添加键
adds_keys 允许用户询问在 sofine 调用它时,您的插件是否向正在构建的数据集添加键,或者它只是为接收到的键添加属性。
它返回一个包含单个键的JSON对象,adds_keys接受一个布尔值,指示插件在调用时是否添加键。
以下是从Google插件的示例
get '/' + PLUGIN_NAME + '/' + PLUGIN_GROUP + '/adds_keys' do '{"adds_keys" : false}' end
数据格式插件的工作原理以及如何编写它们
sofine 默认期望输入和输出以 JSON 格式。该库还包含一个 CSV 数据格式插件。如果这些不能满足您的需求,您可以编写自己的插件,将它们部署到您的 SOFINE_DATA_FORMAT_PLUGIN_PATH 插件目录中,并通过在调用中传递额外的数据格式参数来使用它们。
deserialize(data) - 将数据格式的数据转换为 Python 数据结构
serialize(data) - 将 Python 数据结构转换为数据格式
get_content_type() - 返回数据格式的正确 HTTP Content-Type 头值
包含的 format_json 插件提供了一个简单的示例
import json def deserialize(data): return json.loads(data) def serialize(data): return json.dumps(data) def get_content_type(): return 'application/json'
没有与 Python 字典和列表(对应于 JSON 对象和数组)同构映射的格式需要一些实现。具体来说,您的插件需要了解用于数据检索数据集的 sofine 数据结构,以便在 deserialize 中将其转换为该 Python 数据结构,并在 serialize 中将其转换为您的数据格式(以符合逻辑并在插件中记录的方式)。
记住,sofine 数据集看起来是这样的
{ "AAPL": [ { "results": [ { "GsearchResultClass": "GwebSearch", ... }, ... ] }, {"avg_daily_volume": "59390100"}, {"book_value": "20.193"}, ... ] }
例如,以下是包含的 format_csv 插件中的两个方法
def deserialize(data): ret = {} schema = [] reader = csv.reader(data.split(lineterminator), delimiter=delimiter, i lineterminator='', quoting=quoting, quotechar=quotechar) for row in reader: if not len(row): continue # 0th elem in CSV row is data row key key = row[0] key.encode('utf-8') attr_row = row[1:] ret[key] = [{attr_row[j].encode('utf-8') : attr_row[j + 1].encode('utf-8')} for j in range(0, len(attr_row) - 1, 2)] return ret def serialize(data): out_strm = BytesIO() writer = csv.writer(out_strm, delimiter=delimiter, lineterminator='|', quoting=quoting, quotechar=quotechar) # Flatten each key -> [attrs] 'row' in data into a CSV row with # key in the 0th position, and the attr values in an array in fields 1 .. N for key, attrs in data.iteritems(): row = [] row.append(key) for attr in attrs: row.append(attr.keys()[0]) row.append(attr.values()[0]) writer.writerow(row) ret = out_strm.getvalue() out_strm.close() return ret
包含数据格式插件的格式
format_json
format_json 插件与内部 sofine 数据格式同构。输入数据是映射到对象数组的 JSON,每个对象有一个字符串键和一个字符串值。键是 sofine 数据集键;对象数组是与该键关联的键/值属性数组。
因此,JSON 输入和输出格式如下
{ "AAPL": [ {"avg_daily_volume": "59390100"}, {"book_value": "20.193"}, ... ] }
format_csv
CSV 数据不是分层结构的,因此 sofine 必须就如何将数据格式表示为 CSV 做出一些设计决策。库期望输入和输出在 CSV 中结构化,以便每个记录的键在行的第一个字段中,属性键和值映射到该键,在相同的行中交替出现。本质上,每个 sofine 记录都简化为一个 CSV 行。
使用相同的示例
AAPL, avg_daily_volume, 59390100, book_value, 20.193
format_xml
XML 格式试图将 sofine 的 JSON 分层数据格式映射到合理的 XML 表示。相同的示例的 XML 输入和输出如下
<data> <row> <key>AAPL</key> <attributes> <attribute> <attribute_key>avg_daily_volume</attribute_key> <attribute_value>59390100</attribute_value> </attribute> <attribute> <attribute_key>book_value</attribute_key> <attribute_value>20.193</attribute_value> </attribute> ... ... </attributes> </row> ... ... </data>
如何调用数据检索插件
如上所述,在介绍部分中,有三种调用插件的方式,从命令行调用、作为 REST 资源调用或在 Python 中调用。当调用插件以检索数据时,您需要传递三个或四个参数,data、插件名称、插件组和插件操作。
有六个操作,分别对应于五个方法 get_data、get_namespaced_data、parse_args、get_schema 和 get_namespaced_schema,而 adds_keys 返回插件 self.adds_keys 的值。
get_data get_namespaced_data parse_args get_schema get_namespaced_schema adds_keys
从命令行调用
当调用数据检索插件时,您可以可选地传递此参数来控制 sofine 期望的任何输入格式和返回数据集的数据格式。此参数在所有 sofine 数据检索调用之前传递一次,并将该格式应用于所有数据检索调用。
[--SF-d|--SF-data-format] - 用于数据检索调用和返回数据集的数据格式。可选。默认为‘json’。
然后为每次数据检索调用传递这些参数
[--SF-s|--SF-data-source] - 被调用的数据源名称。这是被调用的插件模块的名称。必需。
[--SF-g|--SF-data-source-group] - 插件所在的插件组。这是插件模块部署的子目录。必需。
[--SF-a|--SF-action] - 被调用的插件动作。如果动作是 get_data,则为可选。
调用 get_data 所需的任何其他参数应紧随 --SF-s 和 --SF-g 参数之后传递。
通过 REST 调用
sofine 配备了一个服务器,您可以在 python sofine/rest_runner.py 中启动它,通过 HTTP 调用插件。服务器默认在 localhost 的 10000 端口上运行。您可以通过设置环境变量 SOFINE_REST_PORT 来更改其运行的端口号。REST 调用使用与 CLI 调用相同的参数,但不带前导连字符。参数及其值交替形成资源路径。请参阅下一节中的示例。
get_data 示例
以下是调用 get_data 的示例
python $PYTHONPATH/sofine/runner.py '--SF-s fidelity --SF-g example -c <CUSTOMER_ID> -p <PIN> -a <ACCOUNT_ID> -e <EMAIL> | --SF-s ystockquotelib --SF-g example'
请注意,省略了 --SF-a,这意味着这是一个使用默认动作 get_data 的链式调用,首先是来自 fidelity 插件(因为它首先被调用,因为它添加了返回的键集合),然后是来自 ystockquotelib 插件(它向从 fidelity 收到的键添加属性)。
如果您想通过 REST 调用此操作,它的外观几乎相同。链式调用语法通过将参数名称和值的序列转换为 REST 资源路径来表示。
curl -X POST -d '{}' --header "Content-Type:application/json" http://localhost:10000/SF-s/fidelity/SF-g/example/c/<CUSTOMER_ID>/p/<PIN>/a/<ACCOUNT_ID>/e/<EMAIL>/SF-s/ystockquotelib/SF-g/example
以下是相同的 Python 示例
import sofine.runner as runner data = {} data_sources = ['fidelity', 'ystockquotelib'] data_source_groups = ['example', 'example'] data_source_args = [[customer_id, pin, account_id, email], []] data = runner.get_data_batch(data, data_sources, data_source_groups, data_source_args)
此调用返回上述形式的数据集。以下是 JSON 输出
{ "key_1": [{"attribute_1": value_1}, {"attribute_2": value_2}, ...], "key_2": ... }
使用数据格式插件进行 get_data 示例
这是相同的调用,但使用 CSV 而不是默认的 JSON 作为数据格式
python $PYTHONPATH/sofine/runner.py '--SF-d format_csv --SF-s fidelity --SF-g example -c <CUSTOMER_ID> -p <PIN> -a <ACCOUNT_ID> -e <EMAIL> | --SF-s ystockquotelib --SF-g example'
其他动作
最后,让我们讨论 get_data 之外的其他动作。请注意,这些动作中没有一个可以链式调用。
get_namespaced_data
与 get_data 的工作方式相同,但在 CLI 调用中必须包含 --SF-a 参数或在 REST 调用中包含 SF-a 参数。
python $PYTHONPATH/sofine/runner.py '--SF-s fidelity --SF-g example --SF-a get_namespaced_data -c <CUSTOMER_ID> -p <PIN> -a <ACCOUNT_ID> -e <EMAIL> | --SF-s ystockquotelib --SF-g example --SF-a get_namespaced_data' curl -X POST -d '{}' --header "Content-Type:application/json" http://localhost:10000/SF-s/fidelity/SF-g/example/SF-a/get_namespaced_data/c/<CUSTOMER_ID>/p/<PIN>/a/<ACCOUNT_ID>/e/<EMAIL>/SF-s/ystockquotelib/SF-g/example/SF-a/get_namespaced_data
此调用返回上述形式的数据集。以下是 JSON 输出。
{ "key_1": [{"plugin_group::plugin_name::attribute_1": value_1}, i {"plugin_group::plugin_name::attribute_2": value_2}, ...], "key_2": ... }
get_data_batch
这是一个仅在 Python 中可用的辅助动作,用于支持将插件调用组合成一个批处理调用,该调用返回一个数据集,相当于在一个调用中链式调用命令行或 REST 插件。
import sofine.runner as runner data = {} data_sources = ['fidelity', 'ystockquotelib'] data_source_groups = ['example', 'example'] data_source_args = [[customer_id, pin, account_id, email], []] data = runner.get_data_batch(data, data_sources, data_source_groups, data_source_args)
请注意,该函数接受一个插件名称列表、一个插件组列表和一个列表列表的参数。每个列表必须将相应的插件、组和参数按顺序放置。
parse_args
您很少需要直接调用插件 parse_args。一种用法是测试您计划传递给 get_data 的参数是否有效 - 例如,在执行长时间运行的 get_data 调用之前,您可能想这样做。
从 CLI
python $PYTHONPATH/sofine/runner.py '--SF-s file_source --SF-g standard --SF-a parse_args -p "./sofine/tests/fixtures/file_source_test_data.txt"'
从 REST
curl -X POST -d '{}' --header "Content-Type:application/json" http://localhost:10000/SF-s/file_source/SF-g/standard/SF-a/parse_args/p/.%2Fsofine%2Ftests%2Ffixtures%2Ffile_source_test_data.txt
从Python
def test_parse_args_file_source(self): data_source = 'file_source' data_source_group = 'standard' path = './sofine/tests/fixtures/file_source_test_data.txt' args = ['-p', path] actual = runner.parse_args(data_source, data_source_group, args) self.assertTrue(actual['is_valid'] and actual['parsed_args'] == [path])
该调用返回以下JSON,并且该调用只支持JSON输出。
{"is_valid": true|false, "parsed_args": [arg_1, arg_2, ...]}
get_schema
调用 get_schema 有几种用例,尤其是在 Python 中。例如,您可能希望从一起调用的一个或多个插件中检索属性键,以过滤或查询所有属性键的子集返回的数据。
命令行界面
python $PYTHONPATH/sofine/runner.py '--SF-s ystockquotelib --SF-g example --SF-a get_schema'
REST
curl -X POST -d '{}' --header "Content-Type:application/json" http://localhost:10000/SF-s/ystockquotelib/SF-g/example/SF-a/get_schema
Python
data_source = 'ystockquotelib' data_source_group = 'example' schema = runner.get_schema(data_source, data_source_group)
该调用返回以下JSON,并且该调用只支持JSON输出。
{"schema": [attribute_key_name_1, attribute_key_name_2, ...]}
get_namespaced_schema
与 get_schema 完全相同,但以命名空间形式返回模式字段。
命令行界面
python $PYTHONPATH/sofine/runner.py '--SF-s ystockquotelib --SF-g example --SF-a get_namespaced_schema'
REST
curl -X POST -d '{}' --header "Content-Type:application/json" http://localhost:10000/SF-s/ystockquotelib/SF-g/example/SF-a/get_namespaced_schema
Python
data_source = 'ystockquotelib' data_source_group = 'example' schema = runner.get_namespaced_schema(data_source, data_source_group)
该调用返回以下JSON,并且该调用只支持JSON输出。
{ "schema": [plugin_group::plugin_name::attribute_key_name_1, plugin_group::plugin_name::attribute_key_name_2, ...] }
添加键
adds_keys 动作允许您以编程方式询问插件是否向 sofine 构建的数据集添加键。假设您想了解在调用 sofine 插件的序列中哪些步骤添加键以及它们添加了哪些键。
for name, group in plugin_map: prev_keys = set(data.keys()) data = runner.get_data(data, name, group, args_map[name]) if runner.adds_keys(name, group): new_keys = set(data.keys()) - prev_keys logger.log(new_keys)
以下是调用 adds_keys 的示例
命令行界面
python $PYTHONPATH/sofine/runner.py '--SF-s ystockquotelib --SF-g example --SF-a adds_keys'
REST
curl -X POST -d '{}' --header "Content-Type:application/json" http://localhost:10000/SF-s/ystockquotelib/SF-g/example/SF-a/adds_keys
Python
data_source = 'ystockquotelib' data_source_group = 'example' adds_keys = runner.adds_keys(data_source, data_source_group)
该调用返回以下JSON,并且该调用只支持JSON输出。
{"adds_keys": true|false}
附加便捷方法
从 Python 调用的插件还公开了两个便捷方法,允许您获取插件模块或插件类的引用。
get_plugin
get_plugin 动作允许您在 Python 中获取插件对象的实例。这可以让您直接访问类范围方法或实例属性。
Python
data_source = 'google_search_results' data_source_group = 'example' plugin = runner.get_plugin(data_source, data_source_group) schema = plugin.schema
get_plugin_module
get_plugin_module 动作允许您在 Python 中获取插件模块的实例。这可以让您直接访问模块范围方法或变量。例如,Google 搜索结果模块实现了一个名为 get_child_schema 的附加辅助程序,该辅助程序返回它为传递给它的每个键返回的 results JSON 对象中的属性列表。由于这是嵌套数据,更有趣的属性位于返回数据的下一级,辅助程序会告诉我们这些信息。
data_source = 'google_search_results' data_source_group = 'example' mod = runner.get_plugin_module(data_source, data_source_group) # The google plugin implements an additional helper method in the module that returns # the list of attributes in each 'results' object it returns mapped to each key child_shema = mod.get_child_schema()
管理 Python 数据检索插件
管理数据检索插件非常简单。选择一个目录,从该目录调用您的插件。定义环境变量 SOFINE_PLUGIN_PATH 并将其分配给您的插件目录路径。
插件本身只是满足“插件如何工作以及如何编写它们”部分中详细说明的要求的 Python 模块(或以 HTTP 插件的形式公开所需 HTTP 端点的代码文件)。
插件不能部署在插件目录的根目录中。相反,您必须创建一个或多个子目录并将插件放置在其中。任何插件都可以位于任何子目录中。如果您愿意,甚至可以将插件放置在多个插件目录中。插件模块名称必须与插件的自定义属性 self.name 匹配,而插件目录名称必须与插件的自定义属性 self.group 匹配。
这种方法意味着您可以无需对 sofine 有任何依赖来管理您的插件目录。您可以像管理自己的代码库一样管理您的插件目录,并在插件目录中包含单元测试或配置文件等。
管理 HTTP 数据检索插件
sofine 只需要定义一个配置依赖项,即定义 SOFINE_HTTP_PLUGIN_URL。当然,在调用您的插件时,它需要在该 URL 上运行。除此之外,您可以完全独立于 sofine 来管理 HTTP 插件的开源代码和部署。
管理数据格式插件
选择一个目录,从该目录调用您的插件。定义环境变量 SOFINE_DATA_FORMAT_PLUGIN_PATH 并将其分配给您的插件目录路径。
与数据检索插件不同,数据格式插件应直接部署在您的插件目录中,而不是子目录中。
数据格式插件仅仅是模块。按照惯例,它们应该被命名为 format_<FORMAT_NAME>.py,例如,format_json.py。这虽然是可选的,但提供了一个标准化的方式来避免与以数据格式命名的内置或第三方模块发生名称冲突,例如Python标准库中的 json 和 csv 模块。
附录:数据检索算法
返回的数据集(让我们称其为“数据”)始终是一个JSON对象,其键为字符串,值为一个零或多个对象值的数组,其中每个对象是一个属性键和属性值对的单一属性。
在 sofine 链的每次调用中,将返回给数据的新键添加到数据中,并将返回给该键的所有键属性数据添加到数据中。
映射到键的所有属性都是JSON对象,它们本身由字符串键映射到合法的JSON值。
因此,对 sofine 管道的调用结果是所有插件调用检索到的所有键的并集,每个键映射到所有插件调用返回的所有属性的并集。
使用sofine代码库进行开发
上述所有文档涵盖了使用sofine作为库来管理和调用您自己的插件的情况,这是一种非常常见的用例。
然而,您可能希望更直接地使用sofine进行开发。也许您想将库的某些部分用于其他目的,或者分叉库以添加功能,甚至贡献代码!
在这种情况下,您需要开发者文档:http://marksweiss.github.io/sofine/
项目详情
下载文件
下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。