appengine-apis 0.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.
- data/History.txt +3 -0
- data/Manifest.txt +21 -0
- data/PostInstall.txt +12 -0
- data/README.rdoc +27 -0
- data/Rakefile +28 -0
- data/lib/appengine-apis.rb +23 -0
- data/lib/appengine-apis/apiproxy.rb +47 -0
- data/lib/appengine-apis/datastore.rb +515 -0
- data/lib/appengine-apis/datastore_types.rb +340 -0
- data/lib/appengine-apis/logger.rb +69 -0
- data/lib/appengine-apis/merb-logger.rb +55 -0
- data/lib/appengine-apis/testing.rb +123 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/datastore_spec.rb +150 -0
- data/spec/datastore_types_spec.rb +198 -0
- data/spec/logger_spec.rb +68 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +11 -0
- data/tasks/rspec.rake +21 -0
- metadata +97 -0
@@ -0,0 +1,340 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# Ruby wrappers for the Java semantic data types for the datastore. These types
|
20
|
+
# are expected to be set as attributes of Entities.
|
21
|
+
|
22
|
+
require 'java'
|
23
|
+
|
24
|
+
class Time
|
25
|
+
def to_java
|
26
|
+
java.util.Date.new(to_i * 1000)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.new_from_java(date)
|
30
|
+
at(date.time / 1000)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module AppEngine
|
35
|
+
module Datastore
|
36
|
+
JavaDatastore = Java.ComGoogleAppengineApiDatastore
|
37
|
+
|
38
|
+
class Error < StandardError; end
|
39
|
+
class NeedIndex < Error; end
|
40
|
+
class Timeout < Error; end
|
41
|
+
class InternalError < Error; end
|
42
|
+
class Rollback < Error; end
|
43
|
+
class TransactionFailed < Error; end
|
44
|
+
class EntityNotFound < Error; end
|
45
|
+
class TooManyResults < Error; end
|
46
|
+
|
47
|
+
#A long string type.
|
48
|
+
#
|
49
|
+
# Strings of any length can be stored in the datastore using this
|
50
|
+
# type.
|
51
|
+
#
|
52
|
+
class Text < String
|
53
|
+
def to_java
|
54
|
+
JavaDatastore::Text.new(self.to_java_string)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.new_from_java(text)
|
58
|
+
self.new(text.getValue)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# A blob type, appropriate for storing binary data of any length.
|
63
|
+
class Blob < String
|
64
|
+
def to_java
|
65
|
+
JavaDatastore::Blob.new(self.to_java_bytes)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.new_from_java(blob)
|
69
|
+
self.new(self.from_java_bytes(blob.getBytes))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# A byte-string type, appropriate for storing short amounts of indexed data.
|
74
|
+
#
|
75
|
+
# This behaves identically to Blob, except it's used only for short, indexed
|
76
|
+
# byte strings.
|
77
|
+
#
|
78
|
+
class ByteString < Blob
|
79
|
+
def to_java
|
80
|
+
JavaDatastore::ShortBlob.new(self.to_java_bytes)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# A fully qualified URL. Usually http: scheme, but may also be file:, ftp:,
|
85
|
+
# news:, among others.
|
86
|
+
#
|
87
|
+
class Link < String
|
88
|
+
def to_java
|
89
|
+
JavaDatastore::Link.new(self.to_java_string)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.new_from_java(link)
|
93
|
+
self.new(link.getValue)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Key = JavaDatastore::Key
|
98
|
+
|
99
|
+
# The primary key for a datastore entity.
|
100
|
+
#
|
101
|
+
# A datastore GUID. A Key instance uniquely identifies an entity
|
102
|
+
# across all apps, and includes all information necessary to fetch
|
103
|
+
# the entity from the datastore with #Datastore.get(Key).
|
104
|
+
#
|
105
|
+
# See http://code.google.com/appengine/docs/java/javadoc/com/google/appengine/api/datastore/Key.html
|
106
|
+
#
|
107
|
+
class Key
|
108
|
+
|
109
|
+
# Converts a Key into a websafe string. For example, this string
|
110
|
+
# can safely be used as an URL parameter embedded in a HTML document.
|
111
|
+
#
|
112
|
+
def to_s
|
113
|
+
JavaDatastore::KeyFactory.keyToString(self)
|
114
|
+
end
|
115
|
+
|
116
|
+
alias :inspect :to_string
|
117
|
+
alias :id :get_id
|
118
|
+
alias :== :equals?
|
119
|
+
|
120
|
+
def id_or_name
|
121
|
+
name || id
|
122
|
+
end
|
123
|
+
|
124
|
+
class << self
|
125
|
+
|
126
|
+
# Creates a new Key from an encoded String.
|
127
|
+
def new(encoded)
|
128
|
+
JavaDatastore::KeyFactory.stringToKey(encoded)
|
129
|
+
end
|
130
|
+
|
131
|
+
# call-seq:
|
132
|
+
# Key.from_path(parent=nil, kind, id, [kind, id]...) -> Key
|
133
|
+
# Constructs a Key out of a path.
|
134
|
+
#
|
135
|
+
# This is useful when an application wants to use just the 'id'
|
136
|
+
# portion of a key in e.g. a URL, where the rest of the URL
|
137
|
+
# provides enough context to fill in the rest, i.e. the app id
|
138
|
+
# (always implicit), the entity kind, and possibly an ancestor
|
139
|
+
# key. Since the 'id' is a relatively small int, it is more
|
140
|
+
# attractive for use in end-user-visible URLs than the full
|
141
|
+
# string representation of a key.
|
142
|
+
#
|
143
|
+
# Args:
|
144
|
+
# - parent: Optional parent key
|
145
|
+
# - kind: the entity kind (a string)
|
146
|
+
# - id: the id (an integer)
|
147
|
+
# - Additional, optional 'kind' and 'id' arguments are allowed in
|
148
|
+
# an alternating order (kind1, 1, kind2, 2, ...)
|
149
|
+
# - options: a Hash. If specified, options[:parent] is used as
|
150
|
+
# the parent Key.
|
151
|
+
#
|
152
|
+
# Returns:
|
153
|
+
# - A new Key instance whose #kind and #id methods return the *last*
|
154
|
+
# kind and id arugments passed
|
155
|
+
#
|
156
|
+
def from_path(parent_or_kind, kind_or_id, *args)
|
157
|
+
# Extract parent
|
158
|
+
parent = nil
|
159
|
+
if parent_or_kind.is_a? Key
|
160
|
+
parent = parent_or_kind
|
161
|
+
args[0,0] = [kind_or_id]
|
162
|
+
else
|
163
|
+
args[0,0] = [parent_or_kind, kind_or_id]
|
164
|
+
end
|
165
|
+
|
166
|
+
if args.size % 2 != 0
|
167
|
+
raise ArgumentError, 'Expected an even number of arguments ' \
|
168
|
+
'(kind1, id1, kind2, id2, ...); received ' \
|
169
|
+
"#{args.inspect}"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Type-check parent
|
173
|
+
if parent
|
174
|
+
unless parent.is_a? Key
|
175
|
+
raise ArgumentError, 'Expected nil or a Key as a parent; ' \
|
176
|
+
"received #{parent} (a #{parent.class})."
|
177
|
+
end
|
178
|
+
unless parent.is_complete?
|
179
|
+
raise KeyError, 'The parent key has not yet been Put.'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
current = parent
|
184
|
+
(0...args.size).step(2) do |i|
|
185
|
+
kind, id = args[i,2]
|
186
|
+
kind = kind.to_s if kind.is_a? Symbol
|
187
|
+
if current
|
188
|
+
current = current.getChild(kind, id)
|
189
|
+
else
|
190
|
+
current = JavaDatastore::KeyFactory.createKey(kind, id)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
return current
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
Entity = JavaDatastore::Entity
|
200
|
+
|
201
|
+
# Entity is the fundamental unit of data storage. It has an
|
202
|
+
# immutable identifier (contained in the Key) object, a
|
203
|
+
# reference to an optional parent Entity, a kind (represented
|
204
|
+
# as an arbitrary string), and a set of zero or more typed
|
205
|
+
# properties.
|
206
|
+
#
|
207
|
+
# See http://code.google.com/appengine/docs/java/javadoc/com/google/appengine/api/datastore/Entity.html
|
208
|
+
#
|
209
|
+
class Entity
|
210
|
+
include Enumerable
|
211
|
+
|
212
|
+
SPECIAL_JAVA_TYPES = {
|
213
|
+
JavaDatastore::Text => Text,
|
214
|
+
JavaDatastore::Blob => Blob,
|
215
|
+
JavaDatastore::ShortBlob => ByteString,
|
216
|
+
JavaDatastore::Link => Link,
|
217
|
+
java.util.Date => Time,
|
218
|
+
}.freeze
|
219
|
+
|
220
|
+
alias :inspect :to_string
|
221
|
+
alias :== :equals?
|
222
|
+
|
223
|
+
# Returns the property with the specified name.
|
224
|
+
def get_property(name)
|
225
|
+
name = name.to_s if name.kind_of? Symbol
|
226
|
+
prop = Datastore.convert_exceptions { getProperty(name) }
|
227
|
+
ruby_type = SPECIAL_JAVA_TYPES[prop.class]
|
228
|
+
if ruby_type
|
229
|
+
ruby_type.new_from_java(prop)
|
230
|
+
else
|
231
|
+
prop
|
232
|
+
end
|
233
|
+
end
|
234
|
+
alias :[] :get_property
|
235
|
+
|
236
|
+
# Sets the property named, +name+, to +value+.
|
237
|
+
#
|
238
|
+
# As the value is stored in the datastore, it is converted to the
|
239
|
+
# datastore's native type.
|
240
|
+
#
|
241
|
+
# All Enumerables are prone to losing their sort order and their
|
242
|
+
# original types as they are stored in the datastore. For example, a
|
243
|
+
# Set may be returned as an Array from #getProperty, with an
|
244
|
+
# arbitrary re-ordering of elements.
|
245
|
+
#
|
246
|
+
# +value+ may be one of the supported datatypes, or a heterogenous
|
247
|
+
# Enumerable of one of the supported datatypes.
|
248
|
+
#
|
249
|
+
# Throws ArgumentError if the value is not of a type that
|
250
|
+
# the data store supports.
|
251
|
+
#
|
252
|
+
def set_property(name, value)
|
253
|
+
name = name.to_s if name.kind_of? Symbol
|
254
|
+
value = Datastore.ruby_to_java(value)
|
255
|
+
Datastore.convert_exceptions do
|
256
|
+
setProperty(name, value)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
alias :[]= :set_property
|
260
|
+
|
261
|
+
# Removes any property with the specified name. If there is no
|
262
|
+
# property with this name set, simply does nothing.
|
263
|
+
#
|
264
|
+
def delete(name)
|
265
|
+
name = name.to_s if name.kind_of? Symbol
|
266
|
+
Datastore.convert_exceptions do
|
267
|
+
removeProperty(name)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Returns true if a property has been set. This function can
|
272
|
+
# be used to test if a property has been specifically set
|
273
|
+
# to nil.
|
274
|
+
#
|
275
|
+
def has_property?(name)
|
276
|
+
name = name.to_s if name.kind_of? Symbol
|
277
|
+
Datastore.convert_exceptions do
|
278
|
+
hasProperty(name)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
alias :has_property :has_property?
|
282
|
+
|
283
|
+
# Add the properties from +other+ to this Entity.
|
284
|
+
# Other may be an Entity or Hash
|
285
|
+
def update(other)
|
286
|
+
hash.each do |name, value|
|
287
|
+
self[name] = value
|
288
|
+
end
|
289
|
+
self
|
290
|
+
end
|
291
|
+
alias merge! update
|
292
|
+
|
293
|
+
# Iterates over all the properties in this Entity.
|
294
|
+
def each(&proc) # :yields: name, value
|
295
|
+
getProperties.each(&proc)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
SPECIAL_RUBY_TYPES = [Time, Text, Blob, ByteString, Link].freeze
|
300
|
+
|
301
|
+
def Datastore.ruby_to_java(value) # :nodoc:
|
302
|
+
if SPECIAL_RUBY_TYPES.include? value.class
|
303
|
+
value.to_java
|
304
|
+
else
|
305
|
+
case value
|
306
|
+
when Fixnum
|
307
|
+
java.lang.Long.new(value)
|
308
|
+
when Float
|
309
|
+
java.lang.Double.new(value)
|
310
|
+
when String
|
311
|
+
value.to_java_string
|
312
|
+
else
|
313
|
+
value
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def Datastore.convert_exceptions # :nodoc:
|
319
|
+
begin
|
320
|
+
yield
|
321
|
+
rescue java.lang.IllegalArgumentException => ex
|
322
|
+
raise ArgumentError, ex.message
|
323
|
+
rescue java.util.ConcurrentModificationException => ex
|
324
|
+
raise TransactionFailed, ex.message
|
325
|
+
rescue java.util.NoSuchElementException => ex
|
326
|
+
raise IndexError, ex.message
|
327
|
+
rescue JavaDatastore::DatastoreNeedIndexException => ex
|
328
|
+
raise NeedIndex, ex.message
|
329
|
+
rescue JavaDatastore::DatastoreTimeoutException => ex
|
330
|
+
raise Timeout, ex.message
|
331
|
+
rescue JavaDatastore::DatastoreFailureException => ex
|
332
|
+
raise InternalError, ex.message
|
333
|
+
rescue JavaDatastore::EntityNotFoundException => ex
|
334
|
+
raise EnityNotFound, ex.message
|
335
|
+
rescue JavaDatastore::PreparedQuery::TooManyResultsException => ex
|
336
|
+
raise TooManyResults, ex.message
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# Replacement for the standard logger.rb which uses the Google App Engine
|
20
|
+
# logging API.
|
21
|
+
|
22
|
+
require 'appengine-apis/apiproxy'
|
23
|
+
require 'logger'
|
24
|
+
|
25
|
+
module AppEngine
|
26
|
+
class Logger < ::Logger
|
27
|
+
SEVERITIES = {
|
28
|
+
DEBUG => ApiProxy::LogRecord::Level::debug,
|
29
|
+
INFO => ApiProxy::LogRecord::Level::info,
|
30
|
+
WARN => ApiProxy::LogRecord::Level::warn,
|
31
|
+
ERROR => ApiProxy::LogRecord::Level::error,
|
32
|
+
FATAL => ApiProxy::LogRecord::Level::fatal,
|
33
|
+
}
|
34
|
+
SEVERITIES.default = ApiProxy::LogRecord::Level::info
|
35
|
+
|
36
|
+
def initialize(*args)
|
37
|
+
super(STDERR)
|
38
|
+
end
|
39
|
+
|
40
|
+
def <<(msg)
|
41
|
+
write_log(INFO, msg.to_s, "")
|
42
|
+
end
|
43
|
+
|
44
|
+
def add(severity, msg=nil, progname=nil, &block)
|
45
|
+
severity ||= UNKNOWN
|
46
|
+
return if severity < @level
|
47
|
+
progname ||= @progname
|
48
|
+
if msg.nil?
|
49
|
+
if block_given?
|
50
|
+
msg = yield
|
51
|
+
else
|
52
|
+
msg = progname
|
53
|
+
progname = @progname
|
54
|
+
end
|
55
|
+
end
|
56
|
+
write_log(severity, msg, progname)
|
57
|
+
end
|
58
|
+
alias log add
|
59
|
+
|
60
|
+
private
|
61
|
+
def write_log(severity, msg, progname)
|
62
|
+
level = SEVERITIES[severity]
|
63
|
+
if progname && !progname.empty? && progname != msg
|
64
|
+
msg = "#{progname}: #{msg}"
|
65
|
+
end
|
66
|
+
ApiProxy.log(level, msg)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# Switches the Merb Logger class to use the Google App Engine logging API.
|
20
|
+
|
21
|
+
require 'merb-core/logger'
|
22
|
+
|
23
|
+
module Merb
|
24
|
+
class Logger
|
25
|
+
def <<(string = nil)
|
26
|
+
AppEngine::ApiProxy.log(
|
27
|
+
AppEngine::ApiProxy::LogRecord::Level::info, string)
|
28
|
+
end
|
29
|
+
alias :push :<<
|
30
|
+
|
31
|
+
# Re-generate the logging methods for Merb.logger for each log level.
|
32
|
+
Levels.each_pair do |name, number|
|
33
|
+
class_eval <<-LEVELMETHODS, __FILE__, __LINE__
|
34
|
+
|
35
|
+
# Appends a message to the log if the log level is at least as high as
|
36
|
+
# the log level of the logger.
|
37
|
+
#
|
38
|
+
# ==== Parameters
|
39
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
40
|
+
#
|
41
|
+
# ==== Returns
|
42
|
+
# self:: The logger object for chaining.
|
43
|
+
def #{name}(message = nil)
|
44
|
+
if #{number} >= level
|
45
|
+
message = block_given? ? yield : message
|
46
|
+
AppEngine::ApiProxy.log(
|
47
|
+
AppEngine::ApiProxy::LogRecord::Level::#{name}, message)
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
alias :#{name}! :#{name}
|
52
|
+
LEVELMETHODS
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|