freebase-api 0.1

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