PageRenderTime 86ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/boto/sdb/db/manager/xmlmanager.py

https://github.com/d1on/boto
Python | 517 lines | 486 code | 2 blank | 29 comment | 0 complexity | 2c55a7880c87d418d88cee0cb01b6f97 MD5 | raw file
  1. # Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
  2. #
  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-
  9. # lowing conditions:
  10. #
  11. # The above copyright notice and this permission notice shall be included
  12. # in all copies or substantial portions of the Software.
  13. #
  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
  20. # IN THE SOFTWARE.
  21. import boto
  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
  27. ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
  28. class XMLConverter:
  29. """
  30. Responsible for converting base Python types to format compatible with underlying
  31. database. For SimpleDB, that means everything needs to be converted to a string
  32. when stored in SimpleDB and from a string when retrieved.
  33. To convert a value, pass it to the encode or decode method. The encode method
  34. will take a Python native value and convert to DB format. The decode method will
  35. take a DB format value and convert it to Python native format. To find the appropriate
  36. method to call, the generic encode/decode methods will look for the type-specific
  37. method by searching for a method called "encode_<type name>" or "decode_<type name>".
  38. """
  39. def __init__(self, manager):
  40. self.manager = manager
  41. self.type_map = { bool : (self.encode_bool, self.decode_bool),
  42. int : (self.encode_int, self.decode_int),
  43. long : (self.encode_long, self.decode_long),
  44. Model : (self.encode_reference, self.decode_reference),
  45. Key : (self.encode_reference, self.decode_reference),
  46. Password : (self.encode_password, self.decode_password),
  47. datetime : (self.encode_datetime, self.decode_datetime)}
  48. def get_text_value(self, parent_node):
  49. value = ''
  50. for node in parent_node.childNodes:
  51. if node.nodeType == node.TEXT_NODE:
  52. value += node.data
  53. return value
  54. def encode(self, item_type, value):
  55. if item_type in self.type_map:
  56. encode = self.type_map[item_type][0]
  57. return encode(value)
  58. return value
  59. def decode(self, item_type, value):
  60. if item_type in self.type_map:
  61. decode = self.type_map[item_type][1]
  62. return decode(value)
  63. else:
  64. value = self.get_text_value(value)
  65. return value
  66. def encode_prop(self, prop, value):
  67. if isinstance(value, list):
  68. if hasattr(prop, 'item_type'):
  69. new_value = []
  70. for v in value:
  71. item_type = getattr(prop, "item_type")
  72. if Model in item_type.mro():
  73. item_type = Model
  74. new_value.append(self.encode(item_type, v))
  75. return new_value
  76. else:
  77. return value
  78. else:
  79. return self.encode(prop.data_type, value)
  80. def decode_prop(self, prop, value):
  81. if prop.data_type == list:
  82. if hasattr(prop, 'item_type'):
  83. item_type = getattr(prop, "item_type")
  84. if Model in item_type.mro():
  85. item_type = Model
  86. values = []
  87. for item_node in value.getElementsByTagName('item'):
  88. value = self.decode(item_type, item_node)
  89. values.append(value)
  90. return values
  91. else:
  92. return self.get_text_value(value)
  93. else:
  94. return self.decode(prop.data_type, value)
  95. def encode_int(self, value):
  96. value = int(value)
  97. return '%d' % value
  98. def decode_int(self, value):
  99. value = self.get_text_value(value)
  100. if value:
  101. value = int(value)
  102. else:
  103. value = None
  104. return value
  105. def encode_long(self, value):
  106. value = long(value)
  107. return '%d' % value
  108. def decode_long(self, value):
  109. value = self.get_text_value(value)
  110. return long(value)
  111. def encode_bool(self, value):
  112. if value == True:
  113. return 'true'
  114. else:
  115. return 'false'
  116. def decode_bool(self, value):
  117. value = self.get_text_value(value)
  118. if value.lower() == 'true':
  119. return True
  120. else:
  121. return False
  122. def encode_datetime(self, value):
  123. return value.strftime(ISO8601)
  124. def decode_datetime(self, value):
  125. value = self.get_text_value(value)
  126. try:
  127. return datetime.strptime(value, ISO8601)
  128. except:
  129. return None
  130. def encode_reference(self, value):
  131. if isinstance(value, str) or isinstance(value, unicode):
  132. return value
  133. if value == None:
  134. return ''
  135. else:
  136. val_node = self.manager.doc.createElement("object")
  137. val_node.setAttribute('id', value.id)
  138. val_node.setAttribute('class', '%s.%s' % (value.__class__.__module__, value.__class__.__name__))
  139. return val_node
  140. def decode_reference(self, value):
  141. if not value:
  142. return None
  143. try:
  144. value = value.childNodes[0]
  145. class_name = value.getAttribute("class")
  146. id = value.getAttribute("id")
  147. cls = find_class(class_name)
  148. return cls.get_by_ids(id)
  149. except:
  150. return None
  151. def encode_password(self, value):
  152. if value and len(value) > 0:
  153. return str(value)
  154. else:
  155. return None
  156. def decode_password(self, value):
  157. value = self.get_text_value(value)
  158. return Password(value)
  159. class XMLManager(object):
  160. def __init__(self, cls, db_name, db_user, db_passwd,
  161. db_host, db_port, db_table, ddl_dir, enable_ssl):
  162. self.cls = cls
  163. if not db_name:
  164. db_name = cls.__name__.lower()
  165. self.db_name = db_name
  166. self.db_user = db_user
  167. self.db_passwd = db_passwd
  168. self.db_host = db_host
  169. self.db_port = db_port
  170. self.db_table = db_table
  171. self.ddl_dir = ddl_dir
  172. self.s3 = None
  173. self.converter = XMLConverter(self)
  174. self.impl = getDOMImplementation()
  175. self.doc = self.impl.createDocument(None, 'objects', None)
  176. self.connection = None
  177. self.enable_ssl = enable_ssl
  178. self.auth_header = None
  179. if self.db_user:
  180. import base64
  181. base64string = base64.encodestring('%s:%s' % (self.db_user, self.db_passwd))[:-1]
  182. authheader = "Basic %s" % base64string
  183. self.auth_header = authheader
  184. def _connect(self):
  185. if self.db_host:
  186. if self.enable_ssl:
  187. from httplib import HTTPSConnection as Connection
  188. else:
  189. from httplib import HTTPConnection as Connection
  190. self.connection = Connection(self.db_host, self.db_port)
  191. def _make_request(self, method, url, post_data=None, body=None):
  192. """
  193. Make a request on this connection
  194. """
  195. if not self.connection:
  196. self._connect()
  197. try:
  198. self.connection.close()
  199. except:
  200. pass
  201. self.connection.connect()
  202. headers = {}
  203. if self.auth_header:
  204. headers["Authorization"] = self.auth_header
  205. self.connection.request(method, url, body, headers)
  206. resp = self.connection.getresponse()
  207. return resp
  208. def new_doc(self):
  209. return self.impl.createDocument(None, 'objects', None)
  210. def _object_lister(self, cls, doc):
  211. for obj_node in doc.getElementsByTagName('object'):
  212. if not cls:
  213. class_name = obj_node.getAttribute('class')
  214. cls = find_class(class_name)
  215. id = obj_node.getAttribute('id')
  216. obj = cls(id)
  217. for prop_node in obj_node.getElementsByTagName('property'):
  218. prop_name = prop_node.getAttribute('name')
  219. prop = obj.find_property(prop_name)
  220. if prop:
  221. if hasattr(prop, 'item_type'):
  222. value = self.get_list(prop_node, prop.item_type)
  223. else:
  224. value = self.decode_value(prop, prop_node)
  225. value = prop.make_value_from_datastore(value)
  226. setattr(obj, prop.name, value)
  227. yield obj
  228. def reset(self):
  229. self._connect()
  230. def get_doc(self):
  231. return self.doc
  232. def encode_value(self, prop, value):
  233. return self.converter.encode_prop(prop, value)
  234. def decode_value(self, prop, value):
  235. return self.converter.decode_prop(prop, value)
  236. def get_s3_connection(self):
  237. if not self.s3:
  238. self.s3 = boto.connect_s3(self.aws_access_key_id, self.aws_secret_access_key)
  239. return self.s3
  240. def get_list(self, prop_node, item_type):
  241. values = []
  242. try:
  243. items_node = prop_node.getElementsByTagName('items')[0]
  244. except:
  245. return []
  246. for item_node in items_node.getElementsByTagName('item'):
  247. value = self.converter.decode(item_type, item_node)
  248. values.append(value)
  249. return values
  250. def get_object_from_doc(self, cls, id, doc):
  251. obj_node = doc.getElementsByTagName('object')[0]
  252. if not cls:
  253. class_name = obj_node.getAttribute('class')
  254. cls = find_class(class_name)
  255. if not id:
  256. id = obj_node.getAttribute('id')
  257. obj = cls(id)
  258. for prop_node in obj_node.getElementsByTagName('property'):
  259. prop_name = prop_node.getAttribute('name')
  260. prop = obj.find_property(prop_name)
  261. value = self.decode_value(prop, prop_node)
  262. value = prop.make_value_from_datastore(value)
  263. if value != None:
  264. try:
  265. setattr(obj, prop.name, value)
  266. except:
  267. pass
  268. return obj
  269. def get_props_from_doc(self, cls, id, doc):
  270. """
  271. Pull out the properties from this document
  272. Returns the class, the properties in a hash, and the id if provided as a tuple
  273. :return: (cls, props, id)
  274. """
  275. obj_node = doc.getElementsByTagName('object')[0]
  276. if not cls:
  277. class_name = obj_node.getAttribute('class')
  278. cls = find_class(class_name)
  279. if not id:
  280. id = obj_node.getAttribute('id')
  281. props = {}
  282. for prop_node in obj_node.getElementsByTagName('property'):
  283. prop_name = prop_node.getAttribute('name')
  284. prop = cls.find_property(prop_name)
  285. value = self.decode_value(prop, prop_node)
  286. value = prop.make_value_from_datastore(value)
  287. if value != None:
  288. props[prop.name] = value
  289. return (cls, props, id)
  290. def get_object(self, cls, id):
  291. if not self.connection:
  292. self._connect()
  293. if not self.connection:
  294. raise NotImplementedError("Can't query without a database connection")
  295. url = "/%s/%s" % (self.db_name, id)
  296. resp = self._make_request('GET', url)
  297. if resp.status == 200:
  298. doc = parse(resp)
  299. else:
  300. raise Exception("Error: %s" % resp.status)
  301. return self.get_object_from_doc(cls, id, doc)
  302. def query(self, cls, filters, limit=None, order_by=None):
  303. if not self.connection:
  304. self._connect()
  305. if not self.connection:
  306. raise NotImplementedError("Can't query without a database connection")
  307. from urllib import urlencode
  308. query = str(self._build_query(cls, filters, limit, order_by))
  309. if query:
  310. url = "/%s?%s" % (self.db_name, urlencode({"query": query}))
  311. else:
  312. url = "/%s" % self.db_name
  313. resp = self._make_request('GET', url)
  314. if resp.status == 200:
  315. doc = parse(resp)
  316. else:
  317. raise Exception("Error: %s" % resp.status)
  318. return self._object_lister(cls, doc)
  319. def _build_query(self, cls, filters, limit, order_by):
  320. import types
  321. if len(filters) > 4:
  322. raise Exception('Too many filters, max is 4')
  323. parts = []
  324. properties = cls.properties(hidden=False)
  325. for filter, value in filters:
  326. name, op = filter.strip().split()
  327. found = False
  328. for property in properties:
  329. if property.name == name:
  330. found = True
  331. if types.TypeType(value) == types.ListType:
  332. filter_parts = []
  333. for val in value:
  334. val = self.encode_value(property, val)
  335. filter_parts.append("'%s' %s '%s'" % (name, op, val))
  336. parts.append("[%s]" % " OR ".join(filter_parts))
  337. else:
  338. value = self.encode_value(property, value)
  339. parts.append("['%s' %s '%s']" % (name, op, value))
  340. if not found:
  341. raise Exception('%s is not a valid field' % name)
  342. if order_by:
  343. if order_by.startswith("-"):
  344. key = order_by[1:]
  345. type = "desc"
  346. else:
  347. key = order_by
  348. type = "asc"
  349. parts.append("['%s' starts-with ''] sort '%s' %s" % (key, key, type))
  350. return ' intersection '.join(parts)
  351. def query_gql(self, query_string, *args, **kwds):
  352. raise NotImplementedError, "GQL queries not supported in XML"
  353. def save_list(self, doc, items, prop_node):
  354. items_node = doc.createElement('items')
  355. prop_node.appendChild(items_node)
  356. for item in items:
  357. item_node = doc.createElement('item')
  358. items_node.appendChild(item_node)
  359. if isinstance(item, Node):
  360. item_node.appendChild(item)
  361. else:
  362. text_node = doc.createTextNode(item)
  363. item_node.appendChild(text_node)
  364. def save_object(self, obj, expected_value=None):
  365. """
  366. Marshal the object and do a PUT
  367. """
  368. doc = self.marshal_object(obj)
  369. if obj.id:
  370. url = "/%s/%s" % (self.db_name, obj.id)
  371. else:
  372. url = "/%s" % (self.db_name)
  373. resp = self._make_request("PUT", url, body=doc.toxml())
  374. new_obj = self.get_object_from_doc(obj.__class__, None, parse(resp))
  375. obj.id = new_obj.id
  376. for prop in obj.properties():
  377. try:
  378. propname = prop.name
  379. except AttributeError:
  380. propname = None
  381. if propname:
  382. value = getattr(new_obj, prop.name)
  383. if value:
  384. setattr(obj, prop.name, value)
  385. return obj
  386. def marshal_object(self, obj, doc=None):
  387. if not doc:
  388. doc = self.new_doc()
  389. if not doc:
  390. doc = self.doc
  391. obj_node = doc.createElement('object')
  392. if obj.id:
  393. obj_node.setAttribute('id', obj.id)
  394. obj_node.setAttribute('class', '%s.%s' % (obj.__class__.__module__,
  395. obj.__class__.__name__))
  396. root = doc.documentElement
  397. root.appendChild(obj_node)
  398. for property in obj.properties(hidden=False):
  399. prop_node = doc.createElement('property')
  400. prop_node.setAttribute('name', property.name)
  401. prop_node.setAttribute('type', property.type_name)
  402. value = property.get_value_for_datastore(obj)
  403. if value is not None:
  404. value = self.encode_value(property, value)
  405. if isinstance(value, list):
  406. self.save_list(doc, value, prop_node)
  407. elif isinstance(value, Node):
  408. prop_node.appendChild(value)
  409. else:
  410. text_node = doc.createTextNode(unicode(value).encode("ascii", "ignore"))
  411. prop_node.appendChild(text_node)
  412. obj_node.appendChild(prop_node)
  413. return doc
  414. def unmarshal_object(self, fp, cls=None, id=None):
  415. if isinstance(fp, str) or isinstance(fp, unicode):
  416. doc = parseString(fp)
  417. else:
  418. doc = parse(fp)
  419. return self.get_object_from_doc(cls, id, doc)
  420. def unmarshal_props(self, fp, cls=None, id=None):
  421. """
  422. Same as unmarshalling an object, except it returns
  423. from "get_props_from_doc"
  424. """
  425. if isinstance(fp, str) or isinstance(fp, unicode):
  426. doc = parseString(fp)
  427. else:
  428. doc = parse(fp)
  429. return self.get_props_from_doc(cls, id, doc)
  430. def delete_object(self, obj):
  431. url = "/%s/%s" % (self.db_name, obj.id)
  432. return self._make_request("DELETE", url)
  433. def set_key_value(self, obj, name, value):
  434. self.domain.put_attributes(obj.id, {name : value}, replace=True)
  435. def delete_key_value(self, obj, name):
  436. self.domain.delete_attributes(obj.id, name)
  437. def get_key_value(self, obj, name):
  438. a = self.domain.get_attributes(obj.id, name)
  439. if a.has_key(name):
  440. return a[name]
  441. else:
  442. return None
  443. def get_raw_item(self, obj):
  444. return self.domain.get_item(obj.id)
  445. def set_property(self, prop, obj, name, value):
  446. pass
  447. def get_property(self, prop, obj, name):
  448. pass
  449. def load_object(self, obj):
  450. if not obj._loaded:
  451. obj = obj.get_by_id(obj.id)
  452. obj._loaded = True
  453. return obj