freebase-api 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +204 -0
- data/Rakefile +8 -0
- data/freebase-api.gemspec +19 -0
- data/lib/freebase-api.rb +32 -0
- data/lib/freebase_api/attribute.rb +29 -0
- data/lib/freebase_api/exceptions.rb +25 -0
- data/lib/freebase_api/ext/hash.rb +15 -0
- data/lib/freebase_api/image.rb +44 -0
- data/lib/freebase_api/session.rb +114 -0
- data/lib/freebase_api/topic.rb +186 -0
- data/lib/freebase_api/version.rb +3 -0
- data/spec/attribute_spec.rb +42 -0
- data/spec/fixtures/img.jpg +0 -0
- data/spec/fixtures/search.json +214 -0
- data/spec/fixtures/topic.json +477 -0
- data/spec/freebase_api_spec.rb +13 -0
- data/spec/image_spec.rb +57 -0
- data/spec/session_spec.rb +165 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/helpers.rb +12 -0
- data/spec/topic_spec.rb +166 -0
- data/travis.yml +8 -0
- metadata +95 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module FreebaseAPI
|
5
|
+
class Session
|
6
|
+
include HTTParty
|
7
|
+
disable_rails_query_string_format
|
8
|
+
|
9
|
+
attr_reader :env, :key, :query_options
|
10
|
+
|
11
|
+
# URIs of the Freebase API
|
12
|
+
API_URL = "https://www.googleapis.com/freebase/v1"
|
13
|
+
SANDBOX_API_URL = "https://www.googleapis.com/freebase/v1sandbox"
|
14
|
+
|
15
|
+
# Freebase default values
|
16
|
+
DEFAULT_LIMIT = 10
|
17
|
+
DEFAULT_LANGUAGE = :en
|
18
|
+
|
19
|
+
def initialize(options={})
|
20
|
+
options = { :env => :stable, :query_options => { :limit => DEFAULT_LIMIT, :lang => DEFAULT_LANGUAGE } }.deep_merge(options)
|
21
|
+
@env = options[:env]
|
22
|
+
@key = options[:key] || ENV['GOOGLE_API_KEY']
|
23
|
+
@query_options = options[:query_options]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Execute a MQL read query
|
27
|
+
# @see https://developers.google.com/freebase/v1/mql-overview
|
28
|
+
#
|
29
|
+
# @param [Hash] query the MQL query
|
30
|
+
# @param [Hash] options the MQL query options
|
31
|
+
# @return [Hash] the response
|
32
|
+
def mqlread(query, options={})
|
33
|
+
params = { :query => query.to_json, :lang => "/lang/#{@query_options[:lang]}", :limit => @query_options[:limit] }.merge(options)
|
34
|
+
response = get(surl('mqlread'), params, format: :json)
|
35
|
+
response['result']
|
36
|
+
end
|
37
|
+
|
38
|
+
# Execute a Search query
|
39
|
+
# @see https://developers.google.com/freebase/v1/search
|
40
|
+
#
|
41
|
+
# @param [String] query the query to search
|
42
|
+
# @param [Hash] options the options
|
43
|
+
# @return [Hash] the response
|
44
|
+
def search(query, options={})
|
45
|
+
params = { :query => query, :lang => @query_options[:lang], :limit => @query_options[:limit] }.merge(options)
|
46
|
+
response = get(surl('search'), params, format: :json)
|
47
|
+
response['result']
|
48
|
+
end
|
49
|
+
|
50
|
+
# Execute a Topic query
|
51
|
+
# @see https://developers.google.com/freebase/v1/topic
|
52
|
+
#
|
53
|
+
# @param [String] id the topic ID
|
54
|
+
# @param [Hash] options the options
|
55
|
+
# @return [Hash] the response
|
56
|
+
def topic(id, options={})
|
57
|
+
params = { :lang => @query_options[:lang], :limit => @query_options[:limit] }.merge(options)
|
58
|
+
get(surl('topic') + id, params, format: :json).parsed_response
|
59
|
+
end
|
60
|
+
|
61
|
+
# Execute a Image Service query
|
62
|
+
# @see https://developers.google.com/freebase/v1/topic-response#references-to-image-objects
|
63
|
+
#
|
64
|
+
# @param [String] id the topic ID
|
65
|
+
# @param [Hash] options the options
|
66
|
+
# @return [Hash] the response
|
67
|
+
def image(id, options={})
|
68
|
+
params = options
|
69
|
+
get(surl('image') + id, params).body
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Return the URL of a Freebase service
|
75
|
+
#
|
76
|
+
# @param [String] service the service
|
77
|
+
# @return [String] the url of the service
|
78
|
+
def surl(service)
|
79
|
+
service_url = @env == :stable ? API_URL : SANDBOX_API_URL
|
80
|
+
service_url = service_url + "/" + service
|
81
|
+
service_url.gsub!('www', 'usercontent') if service.to_s == 'image'
|
82
|
+
service_url
|
83
|
+
end
|
84
|
+
|
85
|
+
# Make a GET request
|
86
|
+
#
|
87
|
+
# @param [String] url the url to request
|
88
|
+
# @param [Hash] params the params of the request
|
89
|
+
# @return the request response
|
90
|
+
def get(url, params={}, options={})
|
91
|
+
FreebaseAPI.logger.debug("GET #{url}")
|
92
|
+
params[:key] = @key if @key
|
93
|
+
response = self.class.get(url, :format => options[:format], :query => params)
|
94
|
+
handle_response(response)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Handle the response
|
98
|
+
#
|
99
|
+
# If success, return the response body
|
100
|
+
# If failure, raise an error
|
101
|
+
def handle_response(response)
|
102
|
+
case response.code
|
103
|
+
when 200..299
|
104
|
+
response
|
105
|
+
else
|
106
|
+
if response.request.format == :json
|
107
|
+
raise FreebaseAPI::ServiceError.new(response['error'])
|
108
|
+
else
|
109
|
+
raise FreebaseAPI::NetError.new('code' => response.code, 'message' => response.response.message)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module FreebaseAPI
|
2
|
+
class Topic
|
3
|
+
|
4
|
+
# The maximum number of property values to return.
|
5
|
+
PROPERTIES_LIMIT = 100
|
6
|
+
|
7
|
+
attr_reader :properties
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Returns a new Topic filled with all the Freebase properties
|
11
|
+
#
|
12
|
+
# @param [String] id the Freebase ID
|
13
|
+
# @param [Hash] options the options
|
14
|
+
# @return [Topic] the topic
|
15
|
+
def get(id, options={})
|
16
|
+
topic = Topic.new(id, options)
|
17
|
+
topic.sync
|
18
|
+
topic
|
19
|
+
end
|
20
|
+
|
21
|
+
# Search using a query and returns the results as a hash of topics score based
|
22
|
+
#
|
23
|
+
# @param [String] query the string query
|
24
|
+
# @param [Hash] options the options
|
25
|
+
# @return [Hash] the topics
|
26
|
+
def search(query, options={})
|
27
|
+
hash = {}
|
28
|
+
FreebaseAPI.session.search(query, options).each do |topic|
|
29
|
+
hash[topic['score'].to_f] = Topic.new(topic['mid'], :data => construct_data(topic))
|
30
|
+
end
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# @private
|
37
|
+
def construct_data(topic)
|
38
|
+
lang = topic['lang']
|
39
|
+
data = {
|
40
|
+
"id" => topic['mid'],
|
41
|
+
"lang" => lang,
|
42
|
+
"property" => {
|
43
|
+
"/type/object/name" => {
|
44
|
+
"valuetype" => "string",
|
45
|
+
"values" => [
|
46
|
+
{
|
47
|
+
"text" => "#{topic['name']}",
|
48
|
+
"lang" => lang,
|
49
|
+
"value" => "#{topic['name']}"
|
50
|
+
}
|
51
|
+
]
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
data["property"]["/common/topic/notable_for"] = {
|
56
|
+
"valuetype" => "object",
|
57
|
+
"values" => [
|
58
|
+
{
|
59
|
+
"text" => topic['notable']['name'],
|
60
|
+
"lang" => lang,
|
61
|
+
"id" => topic['notable']['id']
|
62
|
+
}
|
63
|
+
],
|
64
|
+
} if topic.has_key?('notable')
|
65
|
+
data
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(id, options={})
|
70
|
+
@properties = {}
|
71
|
+
@excluded_properties = parse_exclusion(options.delete(:exclude))
|
72
|
+
@options = { :filter => 'commons' }.merge(options)
|
73
|
+
@data = { 'id' => id }.merge(options[:data] || {})
|
74
|
+
build
|
75
|
+
end
|
76
|
+
|
77
|
+
def id
|
78
|
+
@data['id']
|
79
|
+
end
|
80
|
+
|
81
|
+
def text
|
82
|
+
@data['text']
|
83
|
+
end
|
84
|
+
|
85
|
+
def lang
|
86
|
+
@data['lang']
|
87
|
+
end
|
88
|
+
|
89
|
+
def name
|
90
|
+
@name ||= extract_name
|
91
|
+
end
|
92
|
+
|
93
|
+
def types
|
94
|
+
@types ||= extract_types
|
95
|
+
end
|
96
|
+
|
97
|
+
def description
|
98
|
+
@description ||= extract_description
|
99
|
+
end
|
100
|
+
|
101
|
+
def property(name)
|
102
|
+
@properties[name]
|
103
|
+
end
|
104
|
+
|
105
|
+
def properties_domains
|
106
|
+
domains = {}
|
107
|
+
properties.keys.each do |prop|
|
108
|
+
d = prop.split('/')[1]
|
109
|
+
domains[d] ||= 0
|
110
|
+
domains[d] += properties[prop].size
|
111
|
+
end
|
112
|
+
domains
|
113
|
+
end
|
114
|
+
|
115
|
+
def sync
|
116
|
+
@data = FreebaseAPI.session.topic(self.id, @options)
|
117
|
+
build
|
118
|
+
end
|
119
|
+
|
120
|
+
def image(options={})
|
121
|
+
FreebaseAPI::Image.get(self.id, options)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def extract_name
|
127
|
+
if names = property('/type/object/name')
|
128
|
+
names.first.value
|
129
|
+
else
|
130
|
+
text
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def extract_types
|
135
|
+
if types = property('/type/object/type')
|
136
|
+
types.map(&:id)
|
137
|
+
else
|
138
|
+
[]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def extract_description
|
143
|
+
if articles = property('/common/topic/article')
|
144
|
+
articles.first.property('/common/document/text').first.value
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_exclusion(exclusion)
|
149
|
+
if !exclusion
|
150
|
+
[]
|
151
|
+
elsif exclusion.is_a?(Array)
|
152
|
+
exclusion
|
153
|
+
else
|
154
|
+
[exclusion]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def build
|
159
|
+
if @data['property']
|
160
|
+
FreebaseAPI.logger.debug("Building topic #{self.id} : #{@data['property'].size} properties")
|
161
|
+
@data['property'].each do |key, value|
|
162
|
+
build_property(key, value)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
invalidate_lazy_properties
|
166
|
+
end
|
167
|
+
|
168
|
+
def build_property(property, content)
|
169
|
+
unless @excluded_properties.select { |p| property.start_with?(p) }.any?
|
170
|
+
case content['valuetype'].to_sym
|
171
|
+
when :compound, :object
|
172
|
+
# Note : some referenced topics have empty ids, we need to exclude them
|
173
|
+
@properties[property] = content['values'].reject { |s| s['id'].empty? }.map { |at| Topic.new(at['id'], :data => at) }
|
174
|
+
else
|
175
|
+
@properties[property] = content['values'].map { |at| Attribute.new(at, :type => content['valuetype']) }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def invalidate_lazy_properties
|
181
|
+
@name = nil
|
182
|
+
@types = nil
|
183
|
+
@description = nil
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FreebaseAPI::Attribute do
|
4
|
+
|
5
|
+
let(:data) {
|
6
|
+
{
|
7
|
+
"text" => "Github",
|
8
|
+
"lang" => "en",
|
9
|
+
"value" => "Github ()",
|
10
|
+
"creator" => "/user/mwcl_wikipedia_en",
|
11
|
+
"timestamp" => "2008-08-18T10:47:44.000Z"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
let(:attribute) { FreebaseAPI::Attribute.new(data, :type => "string") }
|
16
|
+
|
17
|
+
describe "#value" do
|
18
|
+
it "should return the 'value' value" do
|
19
|
+
attribute.value.should == "Github ()"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#text" do
|
24
|
+
it "should return the 'text' value" do
|
25
|
+
attribute.text.should == "Github"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#lang" do
|
30
|
+
it "should return the 'lang' value" do
|
31
|
+
attribute.lang.should == "en"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#type" do
|
36
|
+
it "should return the attribute type" do
|
37
|
+
attribute.type.should == "string"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end
|
Binary file
|
@@ -0,0 +1,214 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"mid": "/m/01vrncs",
|
4
|
+
"id": "/en/bob_dylan",
|
5
|
+
"name": "Bob Dylan",
|
6
|
+
"notable": {
|
7
|
+
"name": "Singer-songwriter",
|
8
|
+
"id": "/m/016z4k"
|
9
|
+
},
|
10
|
+
"lang": "en",
|
11
|
+
"score": 72.587578
|
12
|
+
},
|
13
|
+
{
|
14
|
+
"mid": "/m/04prwq",
|
15
|
+
"id": "/en/dylan_and_cole_sprouse",
|
16
|
+
"name": "Dylan and Cole Sprouse",
|
17
|
+
"lang": "en",
|
18
|
+
"score": 38.116348
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"mid": "/m/02fkm",
|
22
|
+
"id": "/en/dylan_thomas",
|
23
|
+
"name": "Dylan Thomas",
|
24
|
+
"notable": {
|
25
|
+
"name": "Poet",
|
26
|
+
"id": "/m/05z96"
|
27
|
+
},
|
28
|
+
"lang": "en",
|
29
|
+
"score": 32.650681
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"mid": "/m/03061d",
|
33
|
+
"id": "/en/dylan_mcdermott",
|
34
|
+
"name": "Dylan McDermott",
|
35
|
+
"notable": {
|
36
|
+
"name": "Actor",
|
37
|
+
"id": "/m/02hrh1q"
|
38
|
+
},
|
39
|
+
"lang": "en",
|
40
|
+
"score": 29.216131
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"mid": "/m/02f70",
|
44
|
+
"id": "/en/dylan",
|
45
|
+
"name": "Dylan",
|
46
|
+
"notable": {
|
47
|
+
"name": "Programming Language",
|
48
|
+
"id": "/computer/programming_language"
|
49
|
+
},
|
50
|
+
"lang": "en",
|
51
|
+
"score": 28.707029
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"mid": "/m/035plv",
|
55
|
+
"id": "/en/jakob_dylan",
|
56
|
+
"name": "Jakob Dylan",
|
57
|
+
"notable": {
|
58
|
+
"name": "Singer",
|
59
|
+
"id": "/m/09l65"
|
60
|
+
},
|
61
|
+
"lang": "en",
|
62
|
+
"score": 27.369299
|
63
|
+
},
|
64
|
+
{
|
65
|
+
"mid": "/m/02p7m2",
|
66
|
+
"id": "/en/dylan_moran",
|
67
|
+
"name": "Dylan Moran",
|
68
|
+
"notable": {
|
69
|
+
"name": "Stand-up comedian",
|
70
|
+
"id": "/m/04tkfj6"
|
71
|
+
},
|
72
|
+
"lang": "en",
|
73
|
+
"score": 27.203932
|
74
|
+
},
|
75
|
+
{
|
76
|
+
"mid": "/m/0d0w6b",
|
77
|
+
"id": "/en/dylan_postl",
|
78
|
+
"name": "Hornswoggle",
|
79
|
+
"notable": {
|
80
|
+
"name": "Wrestler",
|
81
|
+
"id": "/m/02nx2gc"
|
82
|
+
},
|
83
|
+
"lang": "en",
|
84
|
+
"score": 26.060364
|
85
|
+
},
|
86
|
+
{
|
87
|
+
"mid": "/m/0219gl",
|
88
|
+
"id": "/en/eric_harris_and_dylan_klebold",
|
89
|
+
"name": "Eric Harris and Dylan Klebold",
|
90
|
+
"lang": "en",
|
91
|
+
"score": 25.414064
|
92
|
+
},
|
93
|
+
{
|
94
|
+
"mid": "/m/07g7jw",
|
95
|
+
"id": "/en/dylan_walsh",
|
96
|
+
"name": "Dylan Walsh",
|
97
|
+
"notable": {
|
98
|
+
"name": "Actor",
|
99
|
+
"id": "/m/02hrh1q"
|
100
|
+
},
|
101
|
+
"lang": "en",
|
102
|
+
"score": 24.362093
|
103
|
+
},
|
104
|
+
{
|
105
|
+
"mid": "/m/03dqyd",
|
106
|
+
"id": "/en/dylan_dog",
|
107
|
+
"name": "Dylan Dog",
|
108
|
+
"notable": {
|
109
|
+
"name": "Fictional Character",
|
110
|
+
"id": "/fictional_universe/fictional_character"
|
111
|
+
},
|
112
|
+
"lang": "en",
|
113
|
+
"score": 23.879669
|
114
|
+
},
|
115
|
+
{
|
116
|
+
"mid": "/m/07nx9j",
|
117
|
+
"id": "/en/dylan_baker",
|
118
|
+
"name": "Dylan Baker",
|
119
|
+
"notable": {
|
120
|
+
"name": "Actor",
|
121
|
+
"id": "/m/02hrh1q"
|
122
|
+
},
|
123
|
+
"lang": "en",
|
124
|
+
"score": 22.494648
|
125
|
+
},
|
126
|
+
{
|
127
|
+
"mid": "/m/0dtb_z3",
|
128
|
+
"id": "/authority/musicbrainz/dd48dca0-74b5-33e0-8a3b-7b5af62a2203",
|
129
|
+
"name": "Dylan",
|
130
|
+
"notable": {
|
131
|
+
"name": "Musical Album",
|
132
|
+
"id": "/music/album"
|
133
|
+
},
|
134
|
+
"lang": "en",
|
135
|
+
"score": 22.073868
|
136
|
+
},
|
137
|
+
{
|
138
|
+
"mid": "/m/05q546",
|
139
|
+
"id": "/en/dylan_ratigan",
|
140
|
+
"name": "Dylan Ratigan",
|
141
|
+
"notable": {
|
142
|
+
"name": "TV Personality",
|
143
|
+
"id": "/tv/tv_personality"
|
144
|
+
},
|
145
|
+
"lang": "en",
|
146
|
+
"score": 21.934017
|
147
|
+
},
|
148
|
+
{
|
149
|
+
"mid": "/m/0lqsy",
|
150
|
+
"id": "/en/full_house",
|
151
|
+
"name": "Full House",
|
152
|
+
"notable": {
|
153
|
+
"name": "TV Program",
|
154
|
+
"id": "/tv/tv_program"
|
155
|
+
},
|
156
|
+
"lang": "en",
|
157
|
+
"score": 21.582886
|
158
|
+
},
|
159
|
+
{
|
160
|
+
"mid": "/m/0gw_yr3",
|
161
|
+
"id": "/authority/imdb/name/nm3729721",
|
162
|
+
"name": "Dylan O'Brien",
|
163
|
+
"notable": {
|
164
|
+
"name": "Actor",
|
165
|
+
"id": "/m/02hrh1q"
|
166
|
+
},
|
167
|
+
"lang": "en",
|
168
|
+
"score": 21.544655
|
169
|
+
},
|
170
|
+
{
|
171
|
+
"mid": "/m/0b2p4m",
|
172
|
+
"id": "/en/dylan_bruno",
|
173
|
+
"name": "Dylan Bruno",
|
174
|
+
"notable": {
|
175
|
+
"name": "Actor",
|
176
|
+
"id": "/m/02hrh1q"
|
177
|
+
},
|
178
|
+
"lang": "en",
|
179
|
+
"score": 21.431660
|
180
|
+
},
|
181
|
+
{
|
182
|
+
"mid": "/m/08c8zn",
|
183
|
+
"id": "/en/sara_dylan",
|
184
|
+
"name": "Sara Dylan",
|
185
|
+
"notable": {
|
186
|
+
"name": "Model",
|
187
|
+
"id": "/m/0d1pc"
|
188
|
+
},
|
189
|
+
"lang": "en",
|
190
|
+
"score": 20.911421
|
191
|
+
},
|
192
|
+
{
|
193
|
+
"mid": "/m/027p9zq",
|
194
|
+
"id": "/en/brad_benton",
|
195
|
+
"name": "Dylan Vox",
|
196
|
+
"notable": {
|
197
|
+
"name": "Actor",
|
198
|
+
"id": "/m/02hrh1q"
|
199
|
+
},
|
200
|
+
"lang": "en",
|
201
|
+
"score": 20.779615
|
202
|
+
},
|
203
|
+
{
|
204
|
+
"mid": "/m/01wk7ld",
|
205
|
+
"id": "/en/dizzee_rascal",
|
206
|
+
"name": "Dizzee Rascal",
|
207
|
+
"notable": {
|
208
|
+
"name": "Rapper",
|
209
|
+
"id": "/m/0hpcdn2"
|
210
|
+
},
|
211
|
+
"lang": "en",
|
212
|
+
"score": 20.738529
|
213
|
+
}
|
214
|
+
]
|