kasabi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +3 -0
- data/README.md +55 -0
- data/Rakefile +76 -0
- data/lib/kasabi.rb +12 -0
- data/lib/kasabi/api/augment.rb +40 -0
- data/lib/kasabi/api/base_client.rb +29 -0
- data/lib/kasabi/api/facet.rb +91 -0
- data/lib/kasabi/api/lookup.rb +25 -0
- data/lib/kasabi/api/reconcile.rb +124 -0
- data/lib/kasabi/api/search.rb +80 -0
- data/lib/kasabi/api/sparql.rb +462 -0
- data/lib/kasabi/dataset/dataset.rb +21 -0
- data/tests/api/tc_augment.rb +28 -0
- data/tests/api/tc_facet.rb +89 -0
- data/tests/api/tc_lookup.rb +21 -0
- data/tests/api/tc_reconcile.rb +178 -0
- data/tests/api/tc_search.rb +30 -0
- data/tests/api/tc_sparql.rb +177 -0
- data/tests/api/tc_sparqlhelper.rb +262 -0
- data/tests/ts_kasabi.rb +9 -0
- metadata +167 -0
data/CHANGES
ADDED
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
KASABI.RB
|
2
|
+
---------
|
3
|
+
|
4
|
+
Kasabi.rb provides a lightweight Ruby client library for interacting with the
|
5
|
+
Kasabi API
|
6
|
+
|
7
|
+
[http://kasabi.com][0]
|
8
|
+
|
9
|
+
AUTHOR
|
10
|
+
------
|
11
|
+
|
12
|
+
Leigh Dodds (leigh@kasabi.com)
|
13
|
+
|
14
|
+
INSTALLATION
|
15
|
+
------------
|
16
|
+
|
17
|
+
Kasabi.rb is packaged as a Ruby Gem and can be installed as follows:
|
18
|
+
|
19
|
+
sudo gem install kasabi
|
20
|
+
|
21
|
+
The source for the project is maintained in github at:
|
22
|
+
|
23
|
+
http://github.com/kasabi/kasabi.rb
|
24
|
+
|
25
|
+
USAGE
|
26
|
+
-----
|
27
|
+
|
28
|
+
require 'rubygems'
|
29
|
+
require 'kasabi'
|
30
|
+
|
31
|
+
client = Kasabi::Sparql::Client.new("http://api.kasabi.com/api/example-api", ENV["KASABI_API_KEY"])
|
32
|
+
results = client.query(...)
|
33
|
+
|
34
|
+
See the examples directory for example scripts.
|
35
|
+
|
36
|
+
LICENSE
|
37
|
+
-------
|
38
|
+
|
39
|
+
Copyright 2011 Talis Systems Ltd
|
40
|
+
|
41
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
42
|
+
you may not use this file except in compliance with the License.
|
43
|
+
|
44
|
+
You may obtain a copy of the License at
|
45
|
+
|
46
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
47
|
+
|
48
|
+
Unless required by applicable law or agreed to in writing,
|
49
|
+
software distributed under the License is distributed on an "AS IS" BASIS,
|
50
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
51
|
+
|
52
|
+
See the License for the specific language governing permissions and limitations
|
53
|
+
under the License.
|
54
|
+
|
55
|
+
[0]: [http://kasabi.com]
|
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/clean'
|
6
|
+
|
7
|
+
NAME = "kasabi"
|
8
|
+
VER = "0.0.1"
|
9
|
+
|
10
|
+
RDOC_OPTS = ['--quiet', '--title', 'Kasabi Ruby Client Documentation']
|
11
|
+
|
12
|
+
PKG_FILES = %w( README.md Rakefile CHANGES ) +
|
13
|
+
Dir.glob("{bin,tests,etc,lib}/**/*")
|
14
|
+
|
15
|
+
CLEAN.include ['*.gem', 'pkg']
|
16
|
+
SPEC =
|
17
|
+
Gem::Specification.new do |s|
|
18
|
+
s.name = NAME
|
19
|
+
s.version = VER
|
20
|
+
s.platform = Gem::Platform::RUBY
|
21
|
+
s.required_ruby_version = ">= 1.8.5"
|
22
|
+
s.has_rdoc = true
|
23
|
+
s.extra_rdoc_files = ["CHANGES"]
|
24
|
+
s.rdoc_options = RDOC_OPTS
|
25
|
+
s.summary = "Ruby Client for Kasabi"
|
26
|
+
s.description = s.summary
|
27
|
+
s.author = "Leigh Dodds"
|
28
|
+
s.email = 'leigh@kasabi.com'
|
29
|
+
s.homepage = 'http://github.com/kasabi/kasabi.rb'
|
30
|
+
s.rubyforge_project = 'kasabi'
|
31
|
+
s.files = PKG_FILES
|
32
|
+
s.require_path = "lib"
|
33
|
+
s.bindir = "bin"
|
34
|
+
#s.executables = ["..."]
|
35
|
+
s.test_file = "tests/ts_kasabi.rb"
|
36
|
+
s.add_dependency("httpclient", ">= 2.1.3.1")
|
37
|
+
s.add_dependency("json", ">= 1.1.3")
|
38
|
+
s.add_dependency("mocha", ">= 0.9.5")
|
39
|
+
s.add_dependency("mime-types", ">= 1.16")
|
40
|
+
#FIXME versions
|
41
|
+
s.add_dependency("linkeddata")
|
42
|
+
end
|
43
|
+
|
44
|
+
Rake::GemPackageTask.new(SPEC) do |pkg|
|
45
|
+
pkg.need_tar = true
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
50
|
+
rdoc.options += RDOC_OPTS
|
51
|
+
rdoc.rdoc_files.include("CHANGES", "lib/**/*.rb")
|
52
|
+
#rdoc.main = "README"
|
53
|
+
end
|
54
|
+
|
55
|
+
#desc "Publish rdoc output to rubyforge"
|
56
|
+
#task "publish-docs" => ["rdoc"] do
|
57
|
+
# rubyforge_path = "/var/www/gforge-projects/#{NAME}/"
|
58
|
+
# sh "scp -r doc/* " +
|
59
|
+
# "#{ENV["RUBYFORGE_USER"]}@rubyforge.org:#{rubyforge_path}",
|
60
|
+
# :verbose => true
|
61
|
+
#end
|
62
|
+
|
63
|
+
Rake::TestTask.new do |test|
|
64
|
+
test.test_files = FileList['tests/*/tc_*.rb']
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Install from a locally built copy of the gem"
|
68
|
+
task :install do
|
69
|
+
sh %{rake package}
|
70
|
+
sh %{sudo gem install pkg/#{NAME}-#{VER}}
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "Uninstall the gem"
|
74
|
+
task :uninstall => [:clean] do
|
75
|
+
sh %{sudo gem uninstall #{NAME}}
|
76
|
+
end
|
data/lib/kasabi.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'httpclient'
|
3
|
+
require 'json'
|
4
|
+
require 'linkeddata'
|
5
|
+
|
6
|
+
require 'kasabi/api/base_client'
|
7
|
+
require 'kasabi/api/lookup'
|
8
|
+
require 'kasabi/api/sparql'
|
9
|
+
require 'kasabi/api/search'
|
10
|
+
require 'kasabi/api/facet'
|
11
|
+
require 'kasabi/api/augment'
|
12
|
+
require 'kasabi/api/reconcile'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Kasabi
|
2
|
+
|
3
|
+
module Augment
|
4
|
+
|
5
|
+
#Client for working with Kasabi Augmentation APIs
|
6
|
+
class Client < BaseClient
|
7
|
+
|
8
|
+
#Initialize the client to work with a specific endpoint
|
9
|
+
#
|
10
|
+
# The _options_ hash can contain the following values:
|
11
|
+
# * *:apikey*: required. apikey authorized to use the API
|
12
|
+
# * *:client*: HTTPClient object instance
|
13
|
+
def initialize(endpoint, options={})
|
14
|
+
super(endpoint, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Augment an RSS feed that can be retrieved from the specified URL, against data in this store
|
18
|
+
#
|
19
|
+
# uri:: the URL for the RSS 1.0 feed
|
20
|
+
def augment_uri(uri)
|
21
|
+
response = @client.get(@endpoint, {:apikey=>@apikey, "data-uri" => uri})
|
22
|
+
validate_response(response)
|
23
|
+
|
24
|
+
return response.content
|
25
|
+
end
|
26
|
+
|
27
|
+
# Augment data using POSTing it to the API
|
28
|
+
#
|
29
|
+
# Currently this is limited to RSS 1.0 feeds
|
30
|
+
#
|
31
|
+
# data:: a String containing the data to augment
|
32
|
+
def augment(data, content_type="application/rss+xml")
|
33
|
+
response = @client.post("#{@endpoint}?apikey=#{@apikey}", data, {"Content-Type" => "application/rss+xml"})
|
34
|
+
validate_response(response)
|
35
|
+
return response.content
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Kasabi
|
2
|
+
|
3
|
+
#Base class for API clients
|
4
|
+
class BaseClient
|
5
|
+
|
6
|
+
attr_reader :endpoint
|
7
|
+
attr_reader :client
|
8
|
+
attr_reader :apikey
|
9
|
+
|
10
|
+
#Initialize the client to work with a specific endpoint
|
11
|
+
#
|
12
|
+
# The _options_ hash can contain the following values:
|
13
|
+
# * *:apikey*: required. apikey authorized to use the API
|
14
|
+
# * *:client*: HTTPClient object instance
|
15
|
+
def initialize(endpoint, options={})
|
16
|
+
@endpoint = endpoint
|
17
|
+
@client = options[:client] || HTTPClient.new()
|
18
|
+
@apikey = options[:apikey] || nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_response(response)
|
22
|
+
if response.status != 200
|
23
|
+
raise "Unable to perform request. Status: #{response.status}. Message: #{response.content}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Kasabi
|
2
|
+
|
3
|
+
#This module organizes the classes related to the Facet service
|
4
|
+
module Search
|
5
|
+
|
6
|
+
module Facet
|
7
|
+
|
8
|
+
#A term returned in a facet search result
|
9
|
+
#
|
10
|
+
#A term has a number of hits, a reference to a search uri that can
|
11
|
+
#be used to find all of those hits, and a value
|
12
|
+
class Term
|
13
|
+
attr_reader :hits
|
14
|
+
attr_reader :search_uri
|
15
|
+
attr_reader :value
|
16
|
+
|
17
|
+
def initialize(hits, search_uri, value)
|
18
|
+
@hits = hits
|
19
|
+
@search_uri = search_uri
|
20
|
+
@value = value
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
#The results of a facetted search
|
26
|
+
class Results
|
27
|
+
|
28
|
+
#The query used to generate the facet results, as echoed in the response
|
29
|
+
attr_reader :query
|
30
|
+
|
31
|
+
#The fields used to generate the results
|
32
|
+
attr_reader :fields
|
33
|
+
|
34
|
+
#An array of Term objects
|
35
|
+
attr_reader :facets
|
36
|
+
|
37
|
+
def initialize(query, fields, facets=Hash.new)
|
38
|
+
@query = query
|
39
|
+
@fields = fields
|
40
|
+
@facets = facets
|
41
|
+
end
|
42
|
+
|
43
|
+
#Parses the XML format from a successful API response to generate
|
44
|
+
#a Results object instance
|
45
|
+
def Results.parse(data)
|
46
|
+
doc = REXML::Document.new(data)
|
47
|
+
root = doc.root
|
48
|
+
head = root.elements[1]
|
49
|
+
|
50
|
+
query = ""
|
51
|
+
fields = ""
|
52
|
+
queryEl = head.get_elements("query")[0]
|
53
|
+
if queryEl != nil
|
54
|
+
query = queryEl.text
|
55
|
+
end
|
56
|
+
fieldsEl = head.get_elements("fields")[0]
|
57
|
+
if fieldsEl != nil
|
58
|
+
fields = fieldsEl.text
|
59
|
+
end
|
60
|
+
|
61
|
+
results = Results.new(query, fields)
|
62
|
+
|
63
|
+
fields = root.get_elements("fields")[0]
|
64
|
+
if fields == nil
|
65
|
+
raise "No fields in document!"
|
66
|
+
end
|
67
|
+
|
68
|
+
fields.get_elements("field").each do |field|
|
69
|
+
field_name = field.attribute("name").value
|
70
|
+
results.facets[field_name] = Array.new
|
71
|
+
|
72
|
+
field.get_elements("term").each do |term|
|
73
|
+
term = Term.new(term.attribute("number").value.to_i,
|
74
|
+
term.attribute("search-uri").value,
|
75
|
+
term.text() )
|
76
|
+
|
77
|
+
results.facets[field_name] << term
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
return results
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Kasabi
|
2
|
+
|
3
|
+
module Lookup
|
4
|
+
|
5
|
+
class Client < BaseClient
|
6
|
+
|
7
|
+
#Initialize the client to work with a specific endpoint
|
8
|
+
#
|
9
|
+
# The _options_ hash can contain the following values:
|
10
|
+
# * *:apikey*: required. apikey authorized to use the API
|
11
|
+
# * *:client*: HTTPClient object instance
|
12
|
+
def initialize(endpoint, options={})
|
13
|
+
super(endpoint, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup(uri)
|
17
|
+
response = @client.get(@endpoint, {:about => uri, :apikey=>@apikey, :output=>"json"} )
|
18
|
+
validate_response(response)
|
19
|
+
return JSON.parse( response.content )
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Kasabi
|
2
|
+
|
3
|
+
module Reconcile
|
4
|
+
|
5
|
+
class Client < BaseClient
|
6
|
+
|
7
|
+
#Initialize the client to work with a specific endpoint
|
8
|
+
#
|
9
|
+
# The _options_ hash can contain the following values:
|
10
|
+
# * *:apikey*: required. apikey authorized to use the API
|
11
|
+
# * *:client*: HTTPClient object instance
|
12
|
+
def initialize(endpoint, options={})
|
13
|
+
super(endpoint, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
#Simple reconciliation request
|
17
|
+
def reconcile_label(label, &block)
|
18
|
+
return reconcile( Client.make_query(label), &block )
|
19
|
+
end
|
20
|
+
|
21
|
+
#Full reconciliation request, allows specifying of additional parameters
|
22
|
+
#
|
23
|
+
#Returns the array of results from the reconciliation request
|
24
|
+
#
|
25
|
+
#Accepts a block to support iteration through the results. Method will yield
|
26
|
+
#each result and its index.
|
27
|
+
def reconcile(query, &block)
|
28
|
+
response = @client.get( @endpoint, {"query" => query.to_json, :apikey=>@apikey } )
|
29
|
+
validate_response(response)
|
30
|
+
results = JSON.parse( response.content )
|
31
|
+
if results["result"] && block_given?
|
32
|
+
results["result"].each_with_index do |r, i|
|
33
|
+
yield r, i
|
34
|
+
end
|
35
|
+
end
|
36
|
+
return results["result"]
|
37
|
+
end
|
38
|
+
|
39
|
+
#Perform a number of reconciliation queries
|
40
|
+
#Submits a single request with a number of queries
|
41
|
+
#
|
42
|
+
#Accepts a block to support iteration through the results. Method will yield
|
43
|
+
#each result, its index, and the query
|
44
|
+
def reconcile_all(queries, &block)
|
45
|
+
#TODO add batching
|
46
|
+
#TODO make parallel?
|
47
|
+
json = {}
|
48
|
+
queries.each_with_index do |query, i|
|
49
|
+
json["q#{i}"] = query
|
50
|
+
end
|
51
|
+
response = @client.get( @endpoint, {"queries" => json.to_json, :apikey=>@apikey } )
|
52
|
+
validate_response(response)
|
53
|
+
results = JSON.parse( response.content )
|
54
|
+
if block_given?
|
55
|
+
queries.each_with_index do |query, i|
|
56
|
+
if results["q#{i}"]
|
57
|
+
yield results["q#{i}"]["result"], i, query
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
return results
|
62
|
+
end
|
63
|
+
|
64
|
+
#Make an array of reconciliation queries using a standard set of options for limiting,
|
65
|
+
#type matching and property filtering
|
66
|
+
#
|
67
|
+
# label:: text to reconcile on
|
68
|
+
# limit:: limit number of results, default is 3
|
69
|
+
# type_strict:: how to perform type matching, legal values are :any (default), :all, :should
|
70
|
+
# type:: string identifier of type, or array of string identifiers
|
71
|
+
# properties:: property filters, see make_property_filter
|
72
|
+
def Client.make_queries(labels, limit=3, type_strict=:any, type=nil, properties=nil)
|
73
|
+
queries = []
|
74
|
+
labels.each do |label|
|
75
|
+
queries << Client.make_query(label, limit, type_strict, type, properties)
|
76
|
+
end
|
77
|
+
return queries
|
78
|
+
end
|
79
|
+
|
80
|
+
#Make a reconciliation query
|
81
|
+
#
|
82
|
+
# label:: text to reconcile on
|
83
|
+
# limit:: limit number of results, default is 3
|
84
|
+
# type_strict:: how to perform type matching, legal values are :any (default), :all, :should
|
85
|
+
# type:: string identifier of type, or array of string identifiers
|
86
|
+
# properties:: property filters, see make_property_filter
|
87
|
+
def Client.make_query(label, limit=3, type_strict=:any, type=nil, properties=nil)
|
88
|
+
query = Hash.new
|
89
|
+
query[:query] = label
|
90
|
+
query[:limit] = limit
|
91
|
+
query[:type_strict] = type_strict
|
92
|
+
|
93
|
+
query[:type] = type if type != nil
|
94
|
+
query[:properties] = properties if properties != nil
|
95
|
+
|
96
|
+
return query
|
97
|
+
end
|
98
|
+
|
99
|
+
#Construct a property filter
|
100
|
+
#
|
101
|
+
#A property name or identifier must be specified. Both are legal but it is up to the
|
102
|
+
#service to decide which one it uses. Some services may have restrictions on whether
|
103
|
+
#they support names or identifiers.
|
104
|
+
#
|
105
|
+
# value:: a single value, or an array of string or number or object literal, e.g., "Japan"
|
106
|
+
# name:: string, property name, e.g., "country"
|
107
|
+
# id:: string, property ID, e.g., "/people/person/nationality" in the Freebase ID space
|
108
|
+
def Client.make_property_filter(value, name=nil, id=nil)
|
109
|
+
if name == nil and id == nil
|
110
|
+
raise "Must specify at least a property name or property identifier"
|
111
|
+
end
|
112
|
+
|
113
|
+
filter = Hash.new
|
114
|
+
filter[:v] = value
|
115
|
+
filter[:p] = name if name != nil
|
116
|
+
filter[:pid] = id if id != nil
|
117
|
+
|
118
|
+
return filter
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|