ken 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -107,6 +107,9 @@ Freebase itself doesn't.~
107
107
  #<Resource id="/guid/9202a8c04000641f8000000002fa20d3" name="Everything's Gone Green">, ... ]
108
108
  # e.g. => ["1980"]
109
109
  end
110
+
111
+ # alternatively you can access them directly
112
+ resource.attribute('/music/artist/album') # => #<Attribute property="/music/artist/album">
110
113
  </code>
111
114
  </pre>
112
115
 
@@ -223,7 +226,7 @@ h3. Low Level API
223
226
  Sometimes you may want to do specific queries instead of inspecting Resources as a whole.
224
227
  In such a case you would want to use Ken's low level API.
225
228
 
226
- _mqlread_ works like the regular _mqlread service_, except that you are able to pass ruby hashes instead of JSON.
229
+ _mqlread_ works like the regular _mqlread service_, except that you are able to pass Ruby hashes instead of JSON.
227
230
  And you don't have to deal with HTTP, parameter encoding and parsing JSON.
228
231
 
229
232
  <pre>
@@ -243,6 +246,62 @@ And you don't have to deal with HTTP, parameter encoding and parsing JSON.
243
246
  </code>
244
247
  </pre>
245
248
 
249
+
250
+ h3. Topic API
251
+
252
+ Please first have a look at the official "Topic HTTP API documentation":http://www.freebase.com/docs/topic_api .
253
+
254
+ The API provides general meta-data such as name, description, links and images for a given topic,
255
+ as well as all properties directly related to that topic in the graph.
256
+ The API wraps a series of MQL queries that are needed to get this data, which otherwise must be performed separately.
257
+ So for gaining common interest information about a specific topic the Topic API is a way faster alternative to mqlread.
258
+
259
+ The latest update of Ken provides an easy way to access Freebase Topics using Ruby.
260
+ As usual Ken wraps the JSON result of the web service to convenient Ruby Objects.
261
+
262
+ For now Ken only returns simple properties. Support for so called mediator properties (aka 'CVT') will be added later.
263
+ To be honest, I just don't know how to wrap them appropriately using the existing Ken object model. Any API ideas are welcome, btw! ;)
264
+ However, in the meanwhile you can access CVT's by using the plain JSON result returned by the low level Ken.session.topic method.
265
+
266
+
267
+ The API for Topics is quite the same as for Resources.
268
+
269
+ <pre>
270
+ <code>
271
+ t = Ken::Topic.get("/en/new_order")
272
+ # => <Topic id="/en/new_order" name="New Order">
273
+
274
+ t.types
275
+ # => [ #<Type id="/music/artist" name="Musical Artist">, #<Type id="/music/musical_group" name="Musical Group">, ... ]
276
+
277
+ t.views
278
+ # => [ #<View type="/music/artist">, #<View type="/music/musical_group">, ... ]
279
+
280
+ t.properties
281
+ # => [ #<Property id="/music/artist/similar_artist" expected_type="/music/artist">, ... ]
282
+
283
+ t.attributes
284
+ # => [ #<Attribute property="/music/artist/similar_artist">, #<Attribute property="/music/artist/album">, ... ]
285
+
286
+ </code>
287
+ </pre>
288
+
289
+ Additionally you can access some general meta-data, most importantly the topic's description which otherwise would need an additional request to the raw service.
290
+
291
+ <pre>
292
+ <code>
293
+
294
+ t.name # => "New Order"
295
+ t.description # => "New Order were an English musical group formed in 1980 by Bernard Sumner ... "
296
+ t.aliases # => [ "NewOrder", "Englandneworder" ]
297
+ t.webpages # => [ {"url"=>"http://en.wikipedia.org/wiki/index.html?curid=22146", "text"=>"Wikipedia"}, ... ]
298
+ t.url # => "http://www.freebase.com/view/en/new_order"
299
+ t.thumbnail => "http://api.freebase.com/api/trans/image_thumb/en/new_order"
300
+
301
+ </code>
302
+ </pre>
303
+
304
+
246
305
  h2. Project Status
247
306
 
248
307
  h3. Features
@@ -255,6 +314,7 @@ h3. Features
255
314
  * Low Level API (mqlread)
256
315
  * Rails and Merb support
257
316
  * Views on Resources to group Attributes based on the Resource's types
317
+ * Accessing Topics using the new Freebase Topic API
258
318
 
259
319
  h3. Roadmap
260
320
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.2.0
data/ken.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ken}
8
- s.version = "0.1.3"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["michael"]
12
- s.date = %q{2009-10-23}
12
+ s.date = %q{2009-11-02}
13
13
  s.email = %q{ma[at]zive[dot]at}
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE",
@@ -35,6 +35,7 @@ Gem::Specification.new do |s|
35
35
  "lib/ken/property.rb",
36
36
  "lib/ken/resource.rb",
37
37
  "lib/ken/session.rb",
38
+ "lib/ken/topic.rb",
38
39
  "lib/ken/type.rb",
39
40
  "lib/ken/util.rb",
40
41
  "lib/ken/view.rb",
@@ -43,12 +44,14 @@ Gem::Specification.new do |s|
43
44
  "tasks/spec.rb",
44
45
  "test/fixtures/music_artist.json",
45
46
  "test/fixtures/the_police.json",
47
+ "test/fixtures/the_police_topic.json",
46
48
  "test/integration/ken_test.rb",
47
49
  "test/test_helper.rb",
48
50
  "test/unit/attribute_test.rb",
49
51
  "test/unit/property_test.rb",
50
52
  "test/unit/resource_test.rb",
51
53
  "test/unit/session_test.rb",
54
+ "test/unit/topic_test.rb",
52
55
  "test/unit/type_test.rb",
53
56
  "test/unit/view_test.rb"
54
57
  ]
@@ -64,6 +67,7 @@ Gem::Specification.new do |s|
64
67
  "test/unit/property_test.rb",
65
68
  "test/unit/resource_test.rb",
66
69
  "test/unit/session_test.rb",
70
+ "test/unit/topic_test.rb",
67
71
  "test/unit/type_test.rb",
68
72
  "test/unit/view_test.rb",
69
73
  "examples/artist.rb",
@@ -89,3 +93,4 @@ Gem::Specification.new do |s|
89
93
  s.add_dependency(%q<addressable>, [">= 0"])
90
94
  end
91
95
  end
96
+
data/lib/ken/attribute.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module Ken
2
2
  class Attribute
3
-
4
3
  include Extlib::Assertions
5
4
  attr_reader :property
6
5
 
@@ -53,6 +52,12 @@ module Ken
53
52
  @property.value_type?
54
53
  end
55
54
 
55
+ # type, which attribute values of that property are expected to have
56
+ # @api public
57
+ def expected_type
58
+ @property.expected_type
59
+ end
60
+
56
61
  private
57
62
  # initializes the subject if used for the first time
58
63
  def subject
data/lib/ken/resource.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Ken
2
2
  class Resource
3
3
  include Extlib::Assertions
4
-
4
+ extend Extlib::Assertions
5
5
  attr_reader :data
6
6
 
7
7
  # initializes a resource using a json result
@@ -11,6 +11,20 @@ module Ken
11
11
  @data_fechted = data["/type/reflect/any_master"] != nil
12
12
  end
13
13
 
14
+ # Executes an Mql Query against the Freebase API and returns the result wrapped
15
+ # in a <tt>Resource</tt> Object.
16
+ #
17
+ # == Examples
18
+ #
19
+ # Ken::Resource.get('/en/the_police') => #<Resource id="/en/the_police" name="The Police">
20
+ # @api public
21
+ def self.get(id)
22
+ assert_kind_of 'id', id, String
23
+ result = Ken.session.mqlread(FETCH_DATA_QUERY.merge!(:id => id))
24
+ raise ResourceNotFound unless result
25
+ Ken::Resource.new(result)
26
+ end
27
+
14
28
  # resource id
15
29
  # @api public
16
30
  def id
@@ -123,7 +137,7 @@ module Ken
123
137
  fetch_data unless data_fetched?
124
138
  # master & value attributes
125
139
  raw_attributes = Ken::Util.convert_hash(@data["/type/reflect/any_master"])
126
- raw_attributes.merge!(Ken::Util.convert_hash(@data["/type/reflect/any_value"]))
140
+ raw_attributes.merge!(Ken::Util.convert_hash(@data["/type/reflect/any_value"]))
127
141
  @attributes = {}
128
142
  raw_attributes.each_pair do |a, d|
129
143
  properties.select { |p| p.id == a}.each do |p|
data/lib/ken/session.rb CHANGED
@@ -19,6 +19,7 @@ module Ken
19
19
  class AttributeNotFound < StandardError ; end
20
20
  class PropertyNotFound < StandardError ; end
21
21
  class ResourceNotFound < StandardError ; end
22
+ class TopicNotFound < StandardError ; end
22
23
  class ViewNotFound < StandardError ; end
23
24
 
24
25
  # partially taken from chris eppstein's freebase api
@@ -48,7 +49,8 @@ module Ken
48
49
  :blurb => '/api/trans/blurb',
49
50
  :raw => '/api/trans/raw',
50
51
  :login => '/api/account/login',
51
- :upload => '/api/service/upload'
52
+ :upload => '/api/service/upload',
53
+ :topic => '/experimental/topic',
52
54
  }
53
55
 
54
56
  # get the service url for the specified service.
@@ -94,29 +96,36 @@ module Ken
94
96
  end
95
97
 
96
98
  def raw_content(id, options = {})
97
- puts raw_service_url
98
99
  response = http_request raw_service_url+id, options
99
100
  Ken.logger.info "<<< Received Raw Content Response: #{response}"
100
101
  response
101
102
  end
102
103
 
103
104
  def blurb_content(id, options = {})
104
- puts blurb_service_url
105
105
  response = http_request blurb_service_url+id, options
106
106
  Ken.logger.info "<<< Received Blurb Content Response: #{response}"
107
107
  response
108
108
  end
109
+
110
+ def topic(id, options = {})
111
+ options.merge!({:id => id})
112
+
113
+ response = http_request topic_service_url+"/standard", options
114
+ result = JSON.parse response
115
+ inner = result[id]
116
+ handle_read_error(inner)
117
+ Ken.logger.info "<<< Received Topic Response: #{inner['result'].inspect}"
118
+ inner['result']
119
+ end
109
120
 
110
121
  protected
111
122
  # returns parsed json response from freebase mqlread service
112
123
  def get_query_response(query, cursor=nil)
113
- envelope = { :qname => {:query => query }}
124
+ envelope = { :qname => {:query => query, :escape => false }}
114
125
  envelope[:qname][:cursor] = cursor if cursor
115
126
 
116
127
  response = http_request mqlread_service_url, :queries => envelope.to_json
117
-
118
128
  result = JSON.parse response
119
-
120
129
  inner = result['qname']
121
130
  handle_read_error(inner)
122
131
  Ken.logger.info "<<< Received Response: #{inner['result'].inspect}"
@@ -132,7 +141,7 @@ module Ken
132
141
  def http_request(url, parameters = {})
133
142
  params = params_to_string(parameters)
134
143
  url << '?'+params unless params !~ /\S/
135
-
144
+
136
145
  return Net::HTTP.get_response(::URI.parse(url)).body
137
146
 
138
147
  fname = "#{MD5.md5(params)}.mql"
data/lib/ken/topic.rb ADDED
@@ -0,0 +1,194 @@
1
+ module Ken
2
+ class Topic
3
+ include Extlib::Assertions
4
+ extend Extlib::Assertions
5
+
6
+ attr_reader :data
7
+
8
+ # initializes a topic using a json result
9
+ def initialize(data)
10
+ assert_kind_of 'data', data, Hash
11
+ @data = data
12
+ @schema_loaded, @attributes_loaded = false, false
13
+ end
14
+
15
+ # Retrieves a topic using the Topic API by Freebase
16
+ # returns a <tt>Topic</tt> Object.
17
+ #
18
+ # == Examples
19
+ #
20
+ # Ken::Topic.get('/en/the_police') => #<Topic id="/en/the_police" name="The Police">
21
+ # @api public
22
+ def self.get(id)
23
+ assert_kind_of 'id', id, String
24
+ result = Ken.session.topic(id)
25
+ raise TopicNotFound unless result
26
+ Ken::Topic.new(result)
27
+ end
28
+
29
+ # topic id
30
+ # @api public
31
+ def id
32
+ @data["id"] || ""
33
+ end
34
+
35
+ # topic aliases
36
+ def aliases
37
+ @data["alias"]
38
+ end
39
+
40
+ # topic freebase url
41
+ def url
42
+ @data["url"]
43
+ end
44
+
45
+ # topic name
46
+ # @api public
47
+ def name
48
+ text
49
+ end
50
+
51
+ # topic description
52
+ # @api public
53
+ def description
54
+ @data["description"]
55
+ end
56
+
57
+ # topic text
58
+ # @api public
59
+ def text
60
+ @data["text"]
61
+ end
62
+
63
+ # topic thumbnail
64
+ def thumbnail
65
+ @data["thumbnail"]
66
+ end
67
+
68
+ # @api public
69
+ def to_s
70
+ name || id || ""
71
+ end
72
+
73
+ # @api public
74
+ def inspect
75
+ result = "#<Topic id=\"#{id}\" name=\"#{name || "nil"}\">"
76
+ end
77
+
78
+ # topic webpages
79
+ # currently returned as an array of hashes containing the keys "text" and "url"
80
+ # that hashes may be wrapped into a Webpage class later
81
+ # @api public
82
+ def webpages
83
+ @data["webpage"]
84
+ end
85
+
86
+ # returns all assigned types
87
+ # @api public
88
+ def types
89
+ load_schema! unless schema_loaded?
90
+ @types
91
+ end
92
+
93
+ # returns individual type based on the requested type id
94
+ # @api public
95
+ def type(type)
96
+ types.each { |t| return t if t.id =~ /^#{Regexp.escape(type)}$/}
97
+ nil
98
+ end
99
+
100
+ # returns all the properties from all assigned types
101
+ # @api public
102
+ def properties
103
+ @properties = Ken::Collection.new
104
+ types.each do |type|
105
+ @properties.concat(type.properties)
106
+ end
107
+ @properties
108
+ end
109
+
110
+ # returns all attributes for every type the topic is an instance of
111
+ # @api public
112
+ def attributes
113
+ load_attributes! unless attributes_loaded?
114
+ @attributes.values
115
+ end
116
+
117
+ # returns all available views based on the assigned types
118
+ # @api public
119
+ def views
120
+ @views ||= Ken::Collection.new(types.map { |type| Ken::View.new(self, type) })
121
+ end
122
+
123
+ # returns individual view based on the requested type id
124
+ # @api public
125
+ def view(type)
126
+ views.each { |v| return v if v.type.id =~ /^#{Regexp.escape(type)}$/}
127
+ nil
128
+ end
129
+
130
+
131
+ # search for an attribute by name and return it
132
+ # @api public
133
+ def attribute(name)
134
+ attributes.each { |a| return a if a.property.id == name }
135
+ nil
136
+ end
137
+
138
+ # returns true if type information is already loaded
139
+ # @api public
140
+ def schema_loaded?
141
+ @schema_loaded
142
+ end
143
+
144
+ # returns true if attributes are already loaded
145
+ # @api public
146
+ def attributes_loaded?
147
+ @attributes_loaded
148
+ end
149
+
150
+ private
151
+
152
+ # loads the full set of attributes using reflection
153
+ # information is extracted from master, value and reverse attributes
154
+ # @api private
155
+ def load_attributes!
156
+ load_schema! unless schema_loaded?
157
+
158
+ @attributes = {}
159
+ @data["properties"].each do |id, data|
160
+ # skip mediator properties for now
161
+ if !data["properties"]
162
+ values = []
163
+ data["values"].each do |value|
164
+ values << { "id" => value["id"], "name" => value["text"], "value" => value["text"] }
165
+ end
166
+ @attributes[id] = Ken::Attribute.create(values, properties.select { |p| p.id == id }.first)
167
+ end
168
+ end
169
+
170
+ @attributes_loaded = true
171
+ end
172
+
173
+ # loads the topic's metainfo
174
+ # @api private
175
+ def load_schema!
176
+ result = {}
177
+
178
+ @data["type"].each do |type|
179
+ result[type["id"]] = { "id" => type["id"], "name" => type["text"], "properties" => [] }
180
+ end
181
+ @data["properties"].each do |id, data|
182
+ result[id.gsub(/\/\w*$/, "")]["properties"] << {
183
+ "expected_type" => data["expected_type"]["id"],
184
+ "id" => id,
185
+ "name" => data["text"],
186
+ "unique" => true
187
+ }
188
+ end
189
+ @types = Ken::Collection.new(result.values.map { |type| Ken::Type.new(type) })
190
+ @schema_loaded = true
191
+ end
192
+
193
+ end # class Topic
194
+ end # module Ken
data/lib/ken/view.rb CHANGED
@@ -1,15 +1,14 @@
1
- # provides an interface to view a resource as a specific type
1
+ # provides an interface to view a subject (resource or topic) as a specific type
2
2
  # provides an interface for working with attributes, properties
3
3
  module Ken
4
4
  class View
5
5
 
6
6
  include Extlib::Assertions
7
7
 
8
- # initializes a resource by json result
9
- def initialize(resource, type)
10
- assert_kind_of 'resource', resource, Ken::Resource
8
+ # initializes a subject (resource or topic) by json result
9
+ def initialize(subject, type)
11
10
  assert_kind_of 'type', type, Ken::Type
12
- @resource, @type = resource, type
11
+ @subject, @type = subject, type
13
12
  end
14
13
 
15
14
  # @api public
@@ -31,7 +30,7 @@ module Ken
31
30
  # returns attributes which are member of the view's type
32
31
  # @api public
33
32
  def attributes
34
- @resource.attributes.select { |a| a.property.type == @type}
33
+ @subject.attributes.select { |a| a.property.type == @type}
35
34
  end
36
35
 
37
36
  # search for an attribute by name and return it
@@ -44,7 +43,7 @@ module Ken
44
43
  # returns properties which are member of the view's type
45
44
  # @api public
46
45
  def properties
47
- @resource.properties.select { |p| p.type == @type}
46
+ @subject.properties.select { |p| p.type == @type}
48
47
  end
49
48
 
50
49
  # delegate to attribute
data/lib/ken.rb CHANGED
@@ -8,13 +8,14 @@ require 'addressable/uri'
8
8
  dir = Pathname(__FILE__).dirname.expand_path + 'ken'
9
9
 
10
10
  require dir + 'util'
11
+ require dir + 'session'
11
12
  require dir + 'resource'
12
13
  require dir + 'type'
13
14
  require dir + 'view'
14
15
  require dir + 'property'
15
16
  require dir + 'attribute'
16
17
  require dir + 'collection'
17
- require dir + 'session'
18
+ require dir + 'topic'
18
19
  require dir + 'logger'
19
20
 
20
21
  # init logger as soon as the library is required
@@ -117,10 +118,7 @@ module Ken
117
118
  # Ken.get('/en/the_police') => #<Resource id="/en/the_police" name="The Police">
118
119
  # @api public
119
120
  def self.get(id)
120
- assert_kind_of 'id', id, String
121
- result = Ken.session.mqlread(FETCH_DATA_QUERY.merge!(:id => id))
122
- raise ResourceNotFound unless result
123
- Ken::Resource.new(result)
121
+ Ken::Resource.get(id)
124
122
  end
125
123
 
126
124
  end # module Ken