1
# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
3
# Permission is hereby granted, free of charge, to any person obtaining a
4
# copy of this software and associated documentation files (the
5
# "Software"), to deal in the Software without restriction, including
6
# without limitation the rights to use, copy, modify, merge, publish, dis-
7
# tribute, sublicense, and/or sell copies of the Software, and to permit
8
# persons to whom the Software is furnished to do so, subject to the fol-
11
# The above copyright notice and this permission notice shall be included
12
# in all copies or substantial portions of the Software.
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
from boto.utils import find_class, Password
23
from boto.sdb.db.key import Key
24
from boto.sdb.db.model import Model
25
from datetime import datetime
26
from xml.dom.minidom import getDOMImplementation, parse, parseString, Node
28
ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
32
Responsible for converting base Python types to format compatible with underlying
33
database. For SimpleDB, that means everything needs to be converted to a string
34
when stored in SimpleDB and from a string when retrieved.
36
To convert a value, pass it to the encode or decode method. The encode method
37
will take a Python native value and convert to DB format. The decode method will
38
take a DB format value and convert it to Python native format. To find the appropriate
39
method to call, the generic encode/decode methods will look for the type-specific
40
method by searching for a method called "encode_<type name>" or "decode_<type name>".
42
def __init__(self, manager):
43
self.manager = manager
44
self.type_map = { bool : (self.encode_bool, self.decode_bool),
45
int : (self.encode_int, self.decode_int),
46
long : (self.encode_long, self.decode_long),
47
Model : (self.encode_reference, self.decode_reference),
48
Key : (self.encode_reference, self.decode_reference),
49
Password : (self.encode_password, self.decode_password),
50
datetime : (self.encode_datetime, self.decode_datetime)}
52
def get_text_value(self, parent_node):
54
for node in parent_node.childNodes:
55
if node.nodeType == node.TEXT_NODE:
59
def encode(self, item_type, value):
60
if item_type in self.type_map:
61
encode = self.type_map[item_type][0]
65
def decode(self, item_type, value):
66
if item_type in self.type_map:
67
decode = self.type_map[item_type][1]
70
value = self.get_text_value(value)
73
def encode_prop(self, prop, value):
74
if isinstance(value, list):
75
if hasattr(prop, 'item_type'):
78
item_type = getattr(prop, "item_type")
79
if Model in item_type.mro():
81
new_value.append(self.encode(item_type, v))
86
return self.encode(prop.data_type, value)
88
def decode_prop(self, prop, value):
89
if prop.data_type == list:
90
if hasattr(prop, 'item_type'):
91
item_type = getattr(prop, "item_type")
92
if Model in item_type.mro():
95
for item_node in value.getElementsByTagName('item'):
96
value = self.decode(item_type, item_node)
100
return self.get_text_value(value)
102
return self.decode(prop.data_type, value)
104
def encode_int(self, value):
108
def decode_int(self, value):
109
value = self.get_text_value(value)
116
def encode_long(self, value):
120
def decode_long(self, value):
121
value = self.get_text_value(value)
124
def encode_bool(self, value):
130
def decode_bool(self, value):
131
value = self.get_text_value(value)
132
if value.lower() == 'true':
137
def encode_datetime(self, value):
138
return value.strftime(ISO8601)
140
def decode_datetime(self, value):
141
value = self.get_text_value(value)
143
return datetime.strptime(value, ISO8601)
147
def encode_reference(self, value):
148
if isinstance(value, str) or isinstance(value, unicode):
153
val_node = self.manager.doc.createElement("object")
154
val_node.setAttribute('id', value.id)
155
val_node.setAttribute('class', '%s.%s' % (value.__class__.__module__, value.__class__.__name__))
158
def decode_reference(self, value):
162
value = value.childNodes[0]
163
class_name = value.getAttribute("class")
164
id = value.getAttribute("id")
165
cls = find_class(class_name)
166
return cls.get_by_ids(id)
170
def encode_password(self, value):
171
if value and len(value) > 0:
176
def decode_password(self, value):
177
value = self.get_text_value(value)
178
return Password(value)
181
class XMLManager(object):
183
def __init__(self, cls, db_name, db_user, db_passwd,
184
db_host, db_port, db_table, ddl_dir, enable_ssl):
187
db_name = cls.__name__.lower()
188
self.db_name = db_name
189
self.db_user = db_user
190
self.db_passwd = db_passwd
191
self.db_host = db_host
192
self.db_port = db_port
193
self.db_table = db_table
194
self.ddl_dir = ddl_dir
196
self.converter = XMLConverter(self)
197
self.impl = getDOMImplementation()
198
self.doc = self.impl.createDocument(None, 'objects', None)
200
self.connection = None
201
self.enable_ssl = enable_ssl
202
self.auth_header = None
205
base64string = base64.encodestring('%s:%s' % (self.db_user, self.db_passwd))[:-1]
206
authheader = "Basic %s" % base64string
207
self.auth_header = authheader
212
from httplib import HTTPSConnection as Connection
214
from httplib import HTTPConnection as Connection
216
self.connection = Connection(self.db_host, self.db_port)
218
def _make_request(self, method, url, post_data=None, body=None):
220
Make a request on this connection
222
if not self.connection:
225
self.connection.close()
228
self.connection.connect()
231
headers["Authorization"] = self.auth_header
232
self.connection.request(method, url, body, headers)
233
resp = self.connection.getresponse()
237
return self.impl.createDocument(None, 'objects', None)
239
def _object_lister(self, cls, doc):
240
for obj_node in doc.getElementsByTagName('object'):
242
class_name = obj_node.getAttribute('class')
243
cls = find_class(class_name)
244
id = obj_node.getAttribute('id')
246
for prop_node in obj_node.getElementsByTagName('property'):
247
prop_name = prop_node.getAttribute('name')
248
prop = obj.find_property(prop_name)
250
if hasattr(prop, 'item_type'):
251
value = self.get_list(prop_node, prop.item_type)
253
value = self.decode_value(prop, prop_node)
254
value = prop.make_value_from_datastore(value)
255
setattr(obj, prop.name, value)
264
def encode_value(self, prop, value):
265
return self.converter.encode_prop(prop, value)
267
def decode_value(self, prop, value):
268
return self.converter.decode_prop(prop, value)
270
def get_s3_connection(self):
272
self.s3 = boto.connect_s3(self.aws_access_key_id, self.aws_secret_access_key)
275
def get_list(self, prop_node, item_type):
278
items_node = prop_node.getElementsByTagName('items')[0]
281
for item_node in items_node.getElementsByTagName('item'):
282
value = self.converter.decode(item_type, item_node)
286
def get_object_from_doc(self, cls, id, doc):
287
obj_node = doc.getElementsByTagName('object')[0]
289
class_name = obj_node.getAttribute('class')
290
cls = find_class(class_name)
292
id = obj_node.getAttribute('id')
294
for prop_node in obj_node.getElementsByTagName('property'):
295
prop_name = prop_node.getAttribute('name')
296
prop = obj.find_property(prop_name)
297
value = self.decode_value(prop, prop_node)
298
value = prop.make_value_from_datastore(value)
301
setattr(obj, prop.name, value)
306
def get_props_from_doc(self, cls, id, doc):
308
Pull out the properties from this document
309
Returns the class, the properties in a hash, and the id if provided as a tuple
310
:return: (cls, props, id)
312
obj_node = doc.getElementsByTagName('object')[0]
314
class_name = obj_node.getAttribute('class')
315
cls = find_class(class_name)
317
id = obj_node.getAttribute('id')
319
for prop_node in obj_node.getElementsByTagName('property'):
320
prop_name = prop_node.getAttribute('name')
321
prop = cls.find_property(prop_name)
322
value = self.decode_value(prop, prop_node)
323
value = prop.make_value_from_datastore(value)
325
props[prop.name] = value
326
return (cls, props, id)
329
def get_object(self, cls, id):
330
if not self.connection:
333
if not self.connection:
334
raise NotImplementedError("Can't query without a database connection")
335
url = "/%s/%s" % (self.db_name, id)
336
resp = self._make_request('GET', url)
337
if resp.status == 200:
340
raise Exception("Error: %s" % resp.status)
341
return self.get_object_from_doc(cls, id, doc)
343
def query(self, cls, filters, limit=None, order_by=None):
344
if not self.connection:
347
if not self.connection:
348
raise NotImplementedError("Can't query without a database connection")
350
from urllib import urlencode
352
query = str(self._build_query(cls, filters, limit, order_by))
354
url = "/%s?%s" % (self.db_name, urlencode({"query": query}))
356
url = "/%s" % self.db_name
357
resp = self._make_request('GET', url)
358
if resp.status == 200:
361
raise Exception("Error: %s" % resp.status)
362
return self._object_lister(cls, doc)
364
def _build_query(self, cls, filters, limit, order_by):
367
raise Exception('Too many filters, max is 4')
369
properties = cls.properties(hidden=False)
370
for filter, value in filters:
371
name, op = filter.strip().split()
373
for property in properties:
374
if property.name == name:
376
if types.TypeType(value) == types.ListType:
379
val = self.encode_value(property, val)
380
filter_parts.append("'%s' %s '%s'" % (name, op, val))
381
parts.append("[%s]" % " OR ".join(filter_parts))
383
value = self.encode_value(property, value)
384
parts.append("['%s' %s '%s']" % (name, op, value))
386
raise Exception('%s is not a valid field' % name)
388
if order_by.startswith("-"):
394
parts.append("['%s' starts-with ''] sort '%s' %s" % (key, key, type))
395
return ' intersection '.join(parts)
397
def query_gql(self, query_string, *args, **kwds):
398
raise NotImplementedError, "GQL queries not supported in XML"
400
def save_list(self, doc, items, prop_node):
401
items_node = doc.createElement('items')
402
prop_node.appendChild(items_node)
404
item_node = doc.createElement('item')
405
items_node.appendChild(item_node)
406
if isinstance(item, Node):
407
item_node.appendChild(item)
409
text_node = doc.createTextNode(item)
410
item_node.appendChild(text_node)
412
def save_object(self, obj):
414
Marshal the object and do a PUT
416
doc = self.marshal_object(obj)
418
url = "/%s/%s" % (self.db_name, obj.id)
420
url = "/%s" % (self.db_name)
421
resp = self._make_request("PUT", url, body=doc.toxml())
422
new_obj = self.get_object_from_doc(obj.__class__, None, parse(resp))
424
for prop in obj.properties():
427
except AttributeError:
430
value = getattr(new_obj, prop.name)
432
setattr(obj, prop.name, value)
436
def marshal_object(self, obj, doc=None):
441
obj_node = doc.createElement('object')
444
obj_node.setAttribute('id', obj.id)
446
obj_node.setAttribute('class', '%s.%s' % (obj.__class__.__module__,
447
obj.__class__.__name__))
448
root = doc.documentElement
449
root.appendChild(obj_node)
450
for property in obj.properties(hidden=False):
451
prop_node = doc.createElement('property')
452
prop_node.setAttribute('name', property.name)
453
prop_node.setAttribute('type', property.type_name)
454
value = property.get_value_for_datastore(obj)
455
if value is not None:
456
value = self.encode_value(property, value)
457
if isinstance(value, list):
458
self.save_list(doc, value, prop_node)
459
elif isinstance(value, Node):
460
prop_node.appendChild(value)
462
text_node = doc.createTextNode(str(value))
463
prop_node.appendChild(text_node)
464
obj_node.appendChild(prop_node)
468
def unmarshal_object(self, fp, cls=None, id=None):
469
if isinstance(fp, str) or isinstance(fp, unicode):
470
doc = parseString(fp)
473
return self.get_object_from_doc(cls, id, doc)
475
def unmarshal_props(self, fp, cls=None, id=None):
477
Same as unmarshalling an object, except it returns
478
from "get_props_from_doc"
480
if isinstance(fp, str) or isinstance(fp, unicode):
481
doc = parseString(fp)
484
return self.get_props_from_doc(cls, id, doc)
486
def delete_object(self, obj):
487
url = "/%s/%s" % (self.db_name, obj.id)
488
return self._make_request("DELETE", url)
490
def set_key_value(self, obj, name, value):
491
self.domain.put_attributes(obj.id, {name : value}, replace=True)
493
def delete_key_value(self, obj, name):
494
self.domain.delete_attributes(obj.id, name)
496
def get_key_value(self, obj, name):
497
a = self.domain.get_attributes(obj.id, name)
503
def get_raw_item(self, obj):
504
return self.domain.get_item(obj.id)
506
def set_property(self, prop, obj, name, value):
509
def get_property(self, prop, obj, name):
512
def load_object(self, obj):
514
obj = obj.get_by_id(obj.id)