freebase-api 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/.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
|
+
]
|