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.
@@ -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,3 @@
1
+ module FreebaseAPI
2
+ VERSION = "0.1"
3
+ 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
+ ]