ken 0.1.2

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,91 @@
1
+ module Ken
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, Ken::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,151 @@
1
+ module Ken
2
+ class Resource
3
+ include Extlib::Assertions
4
+
5
+ attr_reader :data
6
+
7
+ # initializes a resource using a json result
8
+ def initialize(data)
9
+ assert_kind_of 'data', data, Hash
10
+ @schema_loaded, @attributes_loaded, @data = false, false, data
11
+ @data_fechted = data["/type/reflect/any_master"] != nil
12
+ end
13
+
14
+ # resource id
15
+ # @api public
16
+ def id
17
+ @data["id"] || ""
18
+ end
19
+
20
+ # resource guid
21
+ # @api public
22
+ def guid
23
+ @data['guid'] || ""
24
+ end
25
+
26
+ # resource name
27
+ # @api public
28
+ def name
29
+ @data["name"] || ""
30
+ end
31
+
32
+ # @api public
33
+ def to_s
34
+ name || id || ""
35
+ end
36
+
37
+ # @api public
38
+ def inspect
39
+ result = "#<Resource id=\"#{id}\" name=\"#{name || "nil"}\">"
40
+ end
41
+
42
+ # returns all assigned types
43
+ # @api public
44
+ def types
45
+ load_schema! unless schema_loaded?
46
+ @types
47
+ end
48
+
49
+ # returns all available views based on the assigned types
50
+ # @api public
51
+ def views
52
+ @views ||= Ken::Collection.new(types.map { |type| Ken::View.new(self, type) })
53
+ end
54
+
55
+ # returns individual view based on the requested type id
56
+ # @api public
57
+ def view(type)
58
+ views.each { |v| return v if v.type.id =~ /^#{Regexp.escape(type)}$/}
59
+ nil
60
+ end
61
+
62
+ # returns individual type based on the requested type id
63
+ # @api public
64
+ def type(type)
65
+ types.each { |t| return t if t.id =~ /^#{Regexp.escape(type)}$/}
66
+ nil
67
+ end
68
+
69
+ # returns all the properties from all assigned types
70
+ # @api public
71
+ def properties
72
+ @properties = Ken::Collection.new
73
+ types.each do |type|
74
+ @properties.concat(type.properties)
75
+ end
76
+ @properties
77
+ end
78
+
79
+ # returns all attributes for every type the resource is an instance of
80
+ # @api public
81
+ def attributes
82
+ load_attributes! unless attributes_loaded?
83
+ @attributes.values
84
+ end
85
+
86
+ # search for an attribute by name and return it
87
+ # @api public
88
+ def attribute(name)
89
+ attributes.each { |a| return a if a.property.id == name }
90
+ nil
91
+ end
92
+
93
+ # returns true if type information is already loaded
94
+ # @api public
95
+ def schema_loaded?
96
+ @schema_loaded
97
+ end
98
+
99
+ # returns true if attributes are already loaded
100
+ # @api public
101
+ def attributes_loaded?
102
+ @attributes_loaded
103
+ end
104
+ # returns true if json data is already loaded
105
+ # @api public
106
+ def data_fetched?
107
+ @data_fetched
108
+ end
109
+
110
+ private
111
+ # executes the fetch data query in order to load the full set of types, properties and attributes
112
+ # more info at http://lists.freebase.com/pipermail/developers/2007-December/001022.html
113
+ # @api private
114
+ def fetch_data
115
+ return @data if @data["/type/reflect/any_master"]
116
+ @data = Ken.session.mqlread(FETCH_DATA_QUERY.merge!(:id => id))
117
+ end
118
+
119
+ # loads the full set of attributes using reflection
120
+ # information is extracted from master, value and reverse attributes
121
+ # @api private
122
+ def load_attributes!
123
+ fetch_data unless data_fetched?
124
+ # master & value attributes
125
+ raw_attributes = Ken::Util.convert_hash(@data["/type/reflect/any_master"])
126
+ raw_attributes.merge!(Ken::Util.convert_hash(@data["/type/reflect/any_value"]))
127
+ @attributes = {}
128
+ raw_attributes.each_pair do |a, d|
129
+ properties.select { |p| p.id == a}.each do |p|
130
+ @attributes[p.id] = Ken::Attribute.create(d, p)
131
+ end
132
+ end
133
+ # reverse properties
134
+ raw_attributes = Ken::Util.convert_hash(@data["/type/reflect/any_reverse"])
135
+ raw_attributes.each_pair do |a, d|
136
+ properties.select { |p| p.master_property == a}.each do |p|
137
+ @attributes[p.id] = Ken::Attribute.create(d, p)
138
+ end
139
+ end
140
+ @attributes_loaded = true
141
+ end
142
+
143
+ # loads the resource's metainfo
144
+ # @api private
145
+ def load_schema!
146
+ fetch_data unless data_fetched?
147
+ @types = Ken::Collection.new(@data["ken:type"].map { |type| Ken::Type.new(type) })
148
+ @schema_loaded = true
149
+ end
150
+ end # class Resource
151
+ end # module Ken
@@ -0,0 +1,129 @@
1
+ module Ken
2
+ class << self
3
+ attr_accessor :session
4
+ end
5
+
6
+ # A class for returing errors from the freebase api.
7
+ # For more infomation see the freebase documentation:
8
+ class ReadError < ArgumentError
9
+ attr_accessor :code, :msg
10
+ def initialize(code,msg)
11
+ self.code = code
12
+ self.msg = msg
13
+ end
14
+ def message
15
+ "#{code}: #{msg}"
16
+ end
17
+ end
18
+
19
+ class AttributeNotFound < StandardError ; end
20
+ class PropertyNotFound < StandardError ; end
21
+ class ResourceNotFound < StandardError ; end
22
+ class ViewNotFound < StandardError ; end
23
+
24
+ # partially taken from chris eppstein's freebase api
25
+ # http://github.com/chriseppstein/freebase/tree
26
+ class Session
27
+ public
28
+ # Initialize a new Ken Session
29
+ # Ken::Session.new(host{String, IO}, username{String}, password{String})
30
+ #
31
+ # @param host<String> the API host
32
+ # @param username<String> freebase username
33
+ # @param password<String> user password
34
+ def initialize(host, username, password)
35
+ @host = host
36
+ @username = username
37
+ @password = password
38
+
39
+ Ken.session = self
40
+
41
+ # TODO: check connection
42
+ Ken.logger.info("connection established.")
43
+ end
44
+
45
+ SERVICES = {
46
+ :mqlread => '/api/service/mqlread',
47
+ :mqlwrite => '/api/service/mqlwrite',
48
+ :login => '/api/account/login',
49
+ :upload => '/api/service/upload'
50
+ }
51
+
52
+ # get the service url for the specified service.
53
+ def service_url(svc)
54
+ "#{@host}#{SERVICES[svc]}"
55
+ end
56
+
57
+ SERVICES.each_key do |k|
58
+ define_method("#{k}_service_url") do
59
+ service_url(k)
60
+ end
61
+ end
62
+
63
+ # raise an error if the inner response envelope is encoded as an error
64
+ def handle_read_error(inner)
65
+ unless inner['code'][0, '/api/status/ok'.length] == '/api/status/ok'
66
+ Ken.logger.error "Read Error #{inner.inspect}"
67
+ error = inner['messages'][0]
68
+ raise ReadError.new(error['code'], error['message'])
69
+ end
70
+ end # handle_read_error
71
+
72
+ # Perform a mqlread and return the results
73
+ # Specify :cursor => true to batch the results of a query, sending multiple requests if necessary.
74
+ # TODO: should support multiple queries
75
+ # you should be able to pass an array of queries
76
+ def mqlread(query, options = {})
77
+ Ken.logger.info ">>> Sending Query: #{query.to_json}"
78
+ cursor = options[:cursor]
79
+ if cursor
80
+ query_result = []
81
+ while cursor
82
+ response = get_query_response(query, cursor)
83
+ query_result += response['result']
84
+ cursor = response['cursor']
85
+ end
86
+ else
87
+ response = get_query_response(query, cursor)
88
+ cursor = response['cursor']
89
+ query_result = response['result']
90
+ end
91
+ query_result
92
+ end
93
+
94
+ protected
95
+ # returns parsed json response from freebase mqlread service
96
+ def get_query_response(query, cursor=nil)
97
+ envelope = { :qname => {:query => query }}
98
+ envelope[:qname][:cursor] = cursor if cursor
99
+
100
+ response = http_request mqlread_service_url, :queries => envelope.to_json
101
+
102
+ result = JSON.parse response
103
+
104
+ inner = result['qname']
105
+ handle_read_error(inner)
106
+ Ken.logger.info "<<< Received Response: #{inner['result'].inspect}"
107
+ inner
108
+ end
109
+
110
+ # encode parameters
111
+ def params_to_string(parameters)
112
+ parameters.keys.map {|k| "#{URI.encode(k.to_s)}=#{URI.encode(parameters[k])}" }.join('&')
113
+ end
114
+
115
+ # does the dirty work
116
+ def http_request(url, parameters = {})
117
+ params = params_to_string(parameters)
118
+ url << '?'+params unless params !~ /\S/
119
+
120
+ return Net::HTTP.get_response(::URI.parse(url)).body
121
+
122
+ fname = "#{MD5.md5(params)}.mql"
123
+ open(fname,"w") do |f|
124
+ f << response
125
+ end
126
+ Ken.logger.info("Wrote response to #{fname}")
127
+ end
128
+ end # class Session
129
+ end # module Ken
@@ -0,0 +1,53 @@
1
+ module Ken
2
+ class Type
3
+
4
+ include Extlib::Assertions
5
+
6
+ # initializes a resource using a json result
7
+ def initialize(data)
8
+ assert_kind_of 'data', data, Hash
9
+ @data = data
10
+ end
11
+
12
+ # access property info
13
+ # @api public
14
+ def properties
15
+ @properties ||= Ken::Collection.new(@data["properties"].map { |property| Ken::Property.new(property, self) })
16
+ end
17
+
18
+ # type id
19
+ # @api public
20
+ def id
21
+ @data["id"]
22
+ end
23
+
24
+ # type name
25
+ # @api public
26
+ def name
27
+ @data["name"]
28
+ end
29
+
30
+ # @api public
31
+ def to_s
32
+ name || id || ""
33
+ end
34
+
35
+ # @api public
36
+ def inspect
37
+ result = "#<Type id=\"#{id}\" name=\"#{name || "nil"}\">"
38
+ end
39
+
40
+ # delegate to property_get
41
+ def method_missing sym
42
+ property_get(sym.to_s)
43
+ end
44
+
45
+ private
46
+ # @api private
47
+ # search for a property by name and return it
48
+ def property_get(name)
49
+ properties.each { |p| return p if p.id =~ /\/#{name}$/ }
50
+ raise PropertyNotFound
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,18 @@
1
+ module Ken
2
+ module Util
3
+ # magic hash conversion
4
+ def convert_hash(source)
5
+ source.inject({}) do |result, item|
6
+ if result[item["link"]]
7
+ result[item["link"]] << { "id" => item["id"], "name" => item["name"], "value" => item["value"] }
8
+ else
9
+ result[item["link"]] = []
10
+ result[item["link"]] << { "id" => item["id"], "name" => item["name"], "value" => item["value"] }
11
+ end
12
+ result
13
+ end
14
+ end
15
+ module_function :convert_hash
16
+ end
17
+ end
18
+
@@ -0,0 +1,56 @@
1
+ # provides an interface to view a resource as a specific type
2
+ # provides an interface for working with attributes, properties
3
+ module Ken
4
+ class View
5
+
6
+ include Extlib::Assertions
7
+
8
+ # initializes a resource by json result
9
+ def initialize(resource, type)
10
+ assert_kind_of 'resource', resource, Ken::Resource
11
+ assert_kind_of 'type', type, Ken::Type
12
+ @resource, @type = resource, type
13
+ end
14
+
15
+ # @api public
16
+ def to_s
17
+ @type.to_s
18
+ end
19
+
20
+ # return correspondent type
21
+ # @api public
22
+ def type
23
+ @type
24
+ end
25
+
26
+ # @api public
27
+ def inspect
28
+ result = "#<View type=\"#{type.id || "nil"}\">"
29
+ end
30
+
31
+ # returns attributes which are member of the view's type
32
+ # @api public
33
+ def attributes
34
+ @resource.attributes.select { |a| a.property.type == @type}
35
+ end
36
+
37
+ # search for an attribute by name and return it
38
+ # @api public
39
+ def attribute(name)
40
+ attributes.each { |a| return a if a.property.id =~ /\/#{name}$/ }
41
+ nil
42
+ end
43
+
44
+ # returns properties which are member of the view's type
45
+ # @api public
46
+ def properties
47
+ @resource.properties.select { |p| p.type == @type}
48
+ end
49
+
50
+ # delegate to attribute
51
+ def method_missing sym
52
+ attribute(sym.to_s)
53
+ end
54
+
55
+ end
56
+ end