kasabi 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|