basuco 0.0.3

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,30 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ Dir[File.dirname(__FILE__) + '/basuco/*.rb'].each {|file| require file }
5
+
6
+ module Basuco
7
+ include Request
8
+
9
+ #hash of all statuses
10
+ def self.check_statuses
11
+ response = http_request status_service_url
12
+ result = JSON.parse response
13
+ result
14
+ end
15
+
16
+ end # module Basuco
17
+
18
+
19
+ #todo
20
+
21
+ #search = search,mql's, session = login/logout, status, trans = images, blurbs
22
+ #status all-in-one
23
+
24
+ #re-structure
25
+ #check all calls, add and modify
26
+ #writing ?
27
+ #rspec's
28
+
29
+ #write wd
30
+ #post wd
@@ -0,0 +1,26 @@
1
+ module Basuco
2
+ class << self
3
+ attr_accessor :api
4
+ end
5
+
6
+ class Api
7
+ include Request
8
+ def initialize(options = {:host => 'http://www.freebase.com', :username => 'un', :password => 'pw'})
9
+ @host = options[:host]
10
+ @username = options[:username]
11
+ @password = options[:password]
12
+ Basuco.api = self
13
+ end
14
+
15
+ def api(options = {})
16
+ options.merge!({:query => query})
17
+
18
+ response = http_request search_service_url, options
19
+ result = JSON.parse response
20
+
21
+ handle_read_error(result)
22
+
23
+ result['result']
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,67 @@
1
+ module Basuco
2
+ class Attribute
3
+ #include Extlib::Assertions
4
+ attr_reader :property
5
+
6
+ # initializes a resource by json result
7
+ def initialize(data, property)
8
+ #assert_kind_of 'data', data, Array
9
+ #assert_kind_of 'property', property, Basuco::Property
10
+ @data, @property = data, property
11
+ end
12
+
13
+ # factory method for creating an attribute instance
14
+ # @api semipublic
15
+ def self.create(data, property)
16
+ Basuco::Attribute.new(data, property)
17
+ end
18
+
19
+ # @api public
20
+ def to_s
21
+ subject.to_s
22
+ end
23
+
24
+ # @api public
25
+ def inspect
26
+ result = "#<Attribute property=\"#{property.id || "nil"}\">"
27
+ end
28
+
29
+ # returns a collection of values
30
+ # in case of a unique property the array holds just one value
31
+ # @api public
32
+ def values
33
+ subject
34
+ end
35
+
36
+ # unique properties can have at least one value
37
+ # @api public
38
+ def unique?
39
+ @property.unique?
40
+ end
41
+
42
+ # object type properties always link to resources
43
+ # @api public
44
+ def object_type?
45
+ @property.object_type?
46
+ end
47
+
48
+ # returns true if the property is a value type
49
+ # value type properties refer to simple values like /type/text
50
+ # @api public
51
+ def value_type?
52
+ @property.value_type?
53
+ end
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
+
61
+ private
62
+ # initializes the subject if used for the first time
63
+ def subject
64
+ @subject ||= Basuco::Collection.new(@data.map { |r| object_type? ? Basuco::Resource.new(r) : r["value"] })
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ module Basuco
2
+ class Collection < Array
3
+ def to_s
4
+ self.inject("") { |m,i| "#{m}#{i.to_s}\n"}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,91 @@
1
+ module Basuco
2
+ class Property
3
+
4
+ #include Extlib::Assertions
5
+
6
+ VALUE_TYPES = %w{
7
+ /type/id
8
+ /type/int
9
+ /type/float
10
+ /type/boolean
11
+ /type/text
12
+ /type/rawstring
13
+ /type/uri
14
+ /type/datetime
15
+ /type/key
16
+ }
17
+
18
+ # initializes a resource by json result
19
+ def initialize(data, type)
20
+ # assert_kind_of 'data', data, Hash
21
+ # assert_kind_of 'type', type, Basuco::Type
22
+ @data, @type = data, type
23
+ end
24
+
25
+ # property id
26
+ # @api public
27
+ def id
28
+ @data["id"]
29
+ end
30
+
31
+ # property name
32
+ # @api public
33
+ def name
34
+ @data["name"]
35
+ end
36
+
37
+ # @api public
38
+ def to_s
39
+ name || id || ""
40
+ end
41
+
42
+ # @api public
43
+ def inspect
44
+ result = "#<Property id=\"#{id}\" expected_type=\"#{expected_type || "nil"}\" unique=\"#{unique?}\" object_type=\"#{object_type?}\">"
45
+ end
46
+
47
+ # returns the type of which the property is a part of
48
+ # every property always has exactly one type.
49
+ # that's why /type/property/schema is a unique property
50
+ # @api public
51
+ def type
52
+ @type
53
+ end
54
+
55
+ # reverse property, which represent incoming links
56
+ # @api public
57
+ def reverse_property
58
+ @data["reverse_property"]
59
+ end
60
+
61
+ # master property, which represent an outgoing link (or primitive value)
62
+ # @api public
63
+ def master_property
64
+ @data["master_property"]
65
+ end
66
+
67
+ # returns true if the property is unique
68
+ # @api public
69
+ def unique?
70
+ return @data["unique"]==true
71
+ end
72
+
73
+ # returns true if the property is an object type
74
+ # @api public
75
+ def object_type?
76
+ !value_type?
77
+ end
78
+
79
+ # returns true if the property is a value type
80
+ # @api public
81
+ def value_type?
82
+ VALUE_TYPES.include?(expected_type)
83
+ end
84
+
85
+ # type, which attribute values of that property are expected to have
86
+ # @api public
87
+ def expected_type
88
+ @data["expected_type"]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,86 @@
1
+ module Request
2
+
3
+ SERVICES = {
4
+ :mqlread => '/api/service/mqlread',
5
+ :mqlwrite => '/api/service/mqlwrite',
6
+ :blurb => '/api/trans/blurb/guid/',
7
+ :raw => '/api/trans/raw/guid/',
8
+ :login => '/api/account/login', #not done
9
+ :logout => '/api/account/logout', #not done
10
+ :upload => '/api/service/upload',
11
+ :topic => '/experimental/topic',
12
+ :search => '/api/service/search',
13
+ :status => '/api/status', #not done
14
+ :thumb => 'api/trans/image_thumb'
15
+
16
+ }
17
+
18
+ def service_url(svc)
19
+ "#{@host}#{SERVICES[svc]}"
20
+ end
21
+
22
+ SERVICES.each_key do |k|
23
+ define_method("#{k}_service_url") do
24
+ service_url(k)
25
+ end
26
+ end
27
+
28
+
29
+ # A class for returing errors from the freebase api.
30
+ # For more infomation see the freebase documentation:
31
+ class ReadError < ArgumentError
32
+ attr_accessor :code, :msg
33
+ def initialize(code,msg)
34
+ self.code = code
35
+ self.msg = msg
36
+ end
37
+ def message
38
+ "#{code}: #{msg}"
39
+ end
40
+ end
41
+
42
+ class AttributeNotFound < StandardError ; end
43
+ class PropertyNotFound < StandardError ; end
44
+ class ResourceNotFound < StandardError ; end
45
+ class TopicNotFound < StandardError ; end
46
+ class ViewNotFound < StandardError ; end
47
+
48
+ # raise an error if the inner response envelope is encoded as an error
49
+ def handle_read_error(inner)
50
+ unless inner['code'][0, '/api/status/ok'.length] == '/api/status/ok'
51
+ error = inner['messages'][0]
52
+ raise ReadError.new(error['code'], error['message'])
53
+ end
54
+ end # handle_read_error
55
+
56
+ # returns parsed json response from freebase mqlread service
57
+ def get_query_response(query, cursor=nil)
58
+ envelope = { :qname => {:query => query, :escape => false }}
59
+ envelope[:qname][:cursor] = cursor if cursor
60
+
61
+ response = http_request mqlread_service_url, :queries => envelope.to_json
62
+ result = JSON.parse response
63
+ inner = result['qname']
64
+ handle_read_error(inner)
65
+ inner
66
+ end
67
+
68
+ # encode parameters
69
+ def params_to_string(parameters)
70
+ parameters.keys.map {|k| "#{URI.encode(k.to_s)}=#{URI.encode(parameters[k].to_s)}" }.join('&')
71
+ end
72
+
73
+ # does the dirty work
74
+ def http_request(url, parameters = {})
75
+ params = params_to_string(parameters)
76
+ url << '?'+params unless params !~ /\S/
77
+
78
+ return Net::HTTP.get_response(::URI.parse(url)).body
79
+
80
+ fname = "#{MD5.md5(params)}.mql"
81
+ open(fname,"w") do |f|
82
+ f << response
83
+ end
84
+ end
85
+
86
+ end #Request
@@ -0,0 +1,215 @@
1
+ module Basuco
2
+ class Resource
3
+ attr_reader :data
4
+
5
+ # initializes a resource using a json result
6
+ def initialize(data)
7
+ #assert_kind_of 'data', data, Hash
8
+ @schema_loaded, @attributes_loaded, @data = false, false, data
9
+ @data_fetched = data["/type/reflect/any_master"] != nil
10
+ end
11
+
12
+ # Executes an Mql Query against the Freebase API and returns the result wrapped
13
+ # in a <tt>Resource</tt> Object.
14
+ #
15
+ # == Examples
16
+ #
17
+ # Basuco::Resource.get('/en/the_police') => #<Resource id="/en/the_police" name="The Police">
18
+ # @api public
19
+ def self.get(id)
20
+ # assert_kind_of 'id', id, String
21
+ arr = Resource.define_query
22
+ result = Basuco.search.mqlread(arr.merge!(:id => id))
23
+ raise ResourceNotFound unless result
24
+ Basuco::Resource.new(result)
25
+ end
26
+
27
+ # resource id
28
+ # @api public
29
+ def id
30
+ @data["id"] || ""
31
+ end
32
+
33
+ # resource guid
34
+ # @api public
35
+ def guid
36
+ @data['guid'] || ""
37
+ end
38
+
39
+ # resource name
40
+ # @api public
41
+ def name
42
+ @data["name"] || ""
43
+ end
44
+
45
+ # @api public
46
+ def to_s
47
+ name || id || ""
48
+ end
49
+
50
+ # @api public
51
+ def inspect
52
+ result = "#<Resource id=\"#{id}\" name=\"#{name || "nil"}\">"
53
+ end
54
+
55
+ # returns all assigned types
56
+ # @api public
57
+ def types
58
+ load_schema! unless schema_loaded?
59
+ @types
60
+ end
61
+
62
+ # returns all available views based on the assigned types
63
+ # @api public
64
+ def views
65
+ @views ||= Basuco::Collection.new(types.map { |type| Basuco::View.new(self, type) })
66
+ end
67
+
68
+ # returns individual view based on the requested type id
69
+ # @api public
70
+ def view(type)
71
+ views.each { |v| return v if v.type.id =~ /^#{Regexp.escape(type)}$/}
72
+ nil
73
+ end
74
+
75
+ # returns individual type based on the requested type id
76
+ # @api public
77
+ def type(type)
78
+ types.each { |t| return t if t.id =~ /^#{Regexp.escape(type)}$/}
79
+ nil
80
+ end
81
+
82
+ # returns all the properties from all assigned types
83
+ # @api public
84
+ def properties
85
+ @properties = Basuco::Collection.new
86
+ types.each do |type|
87
+ @properties.concat(type.properties)
88
+ end
89
+ @properties
90
+ end
91
+
92
+ # returns all attributes for every type the resource is an instance of
93
+ # @api public
94
+ def attributes
95
+ load_attributes! unless attributes_loaded?
96
+ @attributes.values
97
+ end
98
+
99
+ # search for an attribute by name and return it
100
+ # @api public
101
+ def attribute(name)
102
+ attributes.each { |a| return a if a.property.id == name }
103
+ nil
104
+ end
105
+
106
+ # returns true if type information is already loaded
107
+ # @api public
108
+ def schema_loaded?
109
+ @schema_loaded
110
+ end
111
+
112
+ # returns true if attributes are already loaded
113
+ # @api public
114
+ def attributes_loaded?
115
+ @attributes_loaded
116
+ end
117
+ # returns true if json data is already loaded
118
+ # @api public
119
+ def data_fetched?
120
+ @data_fetched
121
+ end
122
+
123
+
124
+
125
+ private
126
+ # executes the fetch data query in order to load the full set of types, properties and attributes
127
+ # more info at http://lists.freebase.com/pipermail/developers/2007-December/001022.html
128
+ # @api private
129
+ def fetch_data
130
+ return @data if @data["/type/reflect/any_master"]
131
+ @data = Basuco.search.mqlread(define_query.merge!(:id => id))
132
+ end
133
+
134
+ # loads the full set of attributes using reflection
135
+ # information is extracted from master, value and reverse attributes
136
+ # @api private
137
+ def load_attributes!
138
+ fetch_data unless data_fetched?
139
+ # master & value attributes
140
+ raw_attributes = Basuco::Util.convert_hash(@data["/type/reflect/any_master"])
141
+ raw_attributes.merge!(Basuco::Util.convert_hash(@data["/type/reflect/any_value"]))
142
+ @attributes = {}
143
+ raw_attributes.each_pair do |a, d|
144
+ properties.select { |p| p.id == a}.each do |p|
145
+ @attributes[p.id] = Basuco::Attribute.create(d, p)
146
+ end
147
+ end
148
+ # reverse properties
149
+ raw_attributes = Basuco::Util.convert_hash(@data["/type/reflect/any_reverse"])
150
+ raw_attributes.each_pair do |a, d|
151
+ properties.select { |p| p.master_property == a}.each do |p|
152
+ @attributes[p.id] = Basuco::Attribute.create(d, p)
153
+ end
154
+ end
155
+ @attributes_loaded = true
156
+ end
157
+
158
+ # loads the resource's metainfo
159
+ # @api private
160
+ def load_schema!
161
+ fetch_data unless data_fetched?
162
+ @types = Basuco::Collection.new(@data["Basuco:type"].map { |type| Basuco::Type.new(type) })
163
+ @schema_loaded = true
164
+ end
165
+
166
+ def self.define_query
167
+ {
168
+ # :id => id, # needs to be merge!d in instance method
169
+ :guid => nil,
170
+ :name => nil,
171
+ :"Basuco:type" => [{
172
+ :id => nil,
173
+ :name => nil,
174
+ :properties => [{
175
+ :id => nil,
176
+ :name => nil,
177
+ :expected_type => nil,
178
+ :unique => nil,
179
+ :reverse_property => nil,
180
+ :master_property => nil,
181
+ }]
182
+ }],
183
+ :"/type/reflect/any_master" => [
184
+ {
185
+ :id => nil,
186
+ :link => nil,
187
+ :name => nil,
188
+ :optional => true,
189
+ :limit => 999999
190
+ }
191
+ ],
192
+ :"/type/reflect/any_reverse" => [
193
+ {
194
+ :id => nil,
195
+ :link => nil,
196
+ :name => nil,
197
+ :optional => true,
198
+ :limit => 999999
199
+ }
200
+ ],
201
+ :"/type/reflect/any_value" => [
202
+ {
203
+ :link => nil,
204
+ :value => nil,
205
+ :optional => true,
206
+ :limit => 999999
207
+ # TODO: support multiple language
208
+ # :lang => "/lang/en",
209
+ # :type => "/type/text"
210
+ }
211
+ ]
212
+ }
213
+ end
214
+ end # class Resource
215
+ end # module Basuco