Skip to content

Commit

Permalink
Merge branch 'release/1.5.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
kopertop committed Jan 6, 2015
2 parents e0f184c + 38773f1 commit 73113f8
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 31 deletions.
2 changes: 1 addition & 1 deletion botoweb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
__version__ = '1.5.2'
__version__ = '1.5.3'
env = None
import logging
log = logging.getLogger('botoweb')
Expand Down
80 changes: 73 additions & 7 deletions botoweb/db/converter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright (c) 2013 Chris Moyer http://coredumped.org/
# Copyright (c) 2014 Saikat DebRoy
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
Expand All @@ -14,15 +15,15 @@
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import boto.sdb
from botoweb.db.key import Key
from botoweb.db.coremodel import Model
from botoweb.db.blob import Blob
from botoweb.db.property import ListProperty, MapProperty
from botoweb.db.property import ListProperty, MapProperty, SetProperty, JSON, JSONProperty
from datetime import datetime, date, time
from botoweb.exceptions import TimeDecodeError
from botoweb import ISO8601
Expand Down Expand Up @@ -54,6 +55,7 @@ def __init__(self, manager):
time : (self.encode_time, self.decode_time),
Blob: (self.encode_blob, self.decode_blob),
str: (self.encode_string, self.decode_string),
JSON: (self.encode_json_item, self.decode_json_item),
}

def encode(self, item_type, value):
Expand All @@ -74,9 +76,9 @@ def decode(self, item_type, value):
return value

def encode_list(self, prop, value):
if value in (None, []):
if value in (None, [], set()):
return []
if not isinstance(value, list):
if not isinstance(value, (list, set)):
# This is a little trick to avoid encoding when it's just a single value,
# since that most likely means it's from a query
item_type = getattr(prop, 'item_type')
Expand Down Expand Up @@ -106,11 +108,25 @@ def encode_map(self, prop, value):
new_value.append('%s:%s' % (urllib.quote(key), encoded_value))
return new_value

def encode_json(self, prop, value):
if value is None:
return None
if isinstance(value, JSON):
value = value.value
if isinstance(value, dict):
return self.encode_map(prop, value)
elif isinstance(value, list):
return self.encode_list(prop, value)
else:
return self.encode_json_item(value)

def encode_prop(self, prop, value):
if isinstance(prop, ListProperty):
if isinstance(prop, (ListProperty, SetProperty)):
return self.encode_list(prop, value)
elif isinstance(prop, MapProperty):
return self.encode_map(prop, value)
elif isinstance(prop, JSONProperty):
return self.encode_json(prop, value)
else:
return self.encode(prop.data_type, value)

Expand All @@ -129,6 +145,8 @@ def decode_list(self, prop, value):
k = v
dec_val[k] = v
value = dec_val.values()
if issubclass(prop.data_type, set):
value = prop.data_type(value)
return value

def decode_map(self, prop, value):
Expand All @@ -154,14 +172,63 @@ def decode_map_element(self, item_type, value):
value = self.decode(item_type, value)
return (key, value)

def decode_json(self, prop, value):
if isinstance(value, (set, list)):
if not value:
return value
value = self.decode_map(prop, value)
if all(isinstance(k, (int, long)) for k in value):
if min(value.iterkeys()) == 0 and max(value.iterkeys()) == len(value) - 1:
# The keys are all distinct, the minimum is 0 and max is len(value) - 1
# So, keys == range(0, len(value)) and so, value must be a list
value = [value[k] for k in xrange(len(value))]
else:
value = self.decode_json_item(value)
return value

def decode_prop(self, prop, value):
if isinstance(prop, ListProperty):
if isinstance(prop, (ListProperty, SetProperty)):
return self.decode_list(prop, value)
elif isinstance(prop, MapProperty):
return self.decode_map(prop, value)
elif isinstance(prop, JSONProperty):
return self.decode_json(prop, value)
else:
return self.decode(prop.data_type, value)

def encode_json_item(self, value):
if value is None:
return None
if isinstance(value, JSON):
value = value.value
if isinstance(value, int):
return self.encode_int(value)
elif isinstance(value, long):
return self.encode_long(value)
elif isinstance(value, basestring):
return self.encode_string(value)
else:
import json

return json.dumps(value)

def decode_json_item(self, value):
if isinstance(value, basestring):
import json

try:
parsed_value = json.loads(value)
except ValueError:
value = self.decode_string(value)
else:
if isinstance(parsed_value, int):
value = self.decode_int(value)
elif isinstance(parsed_value, long):
value = self.decode_long(value)
else:
value = parsed_value
return value

def encode_int(self, value):
value = int(value)
value += 2147483648
Expand Down Expand Up @@ -362,4 +429,3 @@ def encode_date(self, value):
if isinstance(value, str) or isinstance(value, unicode):
return value
return value.isoformat()

9 changes: 3 additions & 6 deletions botoweb/db/coremodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# IN THE SOFTWARE.

from botoweb.db.manager import get_manager
from botoweb.db.property import Property
from botoweb.db.property import Property, JSON
from botoweb.db.key import Key
from botoweb.db.query import Query
from decimal import Decimal
Expand Down Expand Up @@ -344,6 +344,8 @@ def to_dict(self, recursive=False):
else:
rv[k] = str(val[k])
val = rv
elif isinstance(val, JSON):
val = val.value
else:
# Fall back to encoding as a string
try:
Expand Down Expand Up @@ -408,11 +410,6 @@ def _decode(cls, t, val, prop):
if not isinstance(val, list) and not isinstance(val, set):
val = [val]
val = [cls._decode(prop.item_type, v, prop) for v in val]
elif isinstance(t, tuple):
# Support for JSON multi-types
import json
if(val):
val = json.loads(val)
elif t not in (str, unicode, int):
val = t(val)
return val
Expand Down
82 changes: 71 additions & 11 deletions botoweb/db/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,24 +748,84 @@ def default_value(self):
return {}


class JSONProperty(Property):

data_type = (dict, list, str, unicode, int, long, float, type(None))
type_name = 'JSON'

def __init__(self, verbose_name=None, name=None, default=None, **kwds):
Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
class JSON(object):
def __init__(self, value):
if isinstance(value, JSON):
self.value = value.value
else:
self.set(value)

def validate(self, value):
def set(self, value):
import json
try:
json.dumps(value)
except Exception as e:
raise ValueError('%s in %s JSONProperty' % (str(e), self.name))
self.value = value

def __eq__(self, other):
return other is not None and self.value == other.value

def __ne__(self, other):
return other is None or self.value != other.value

def __str__(self):
return str(self.value)

def __unicode__(self):
return unicode(self.value)

def __len__(self):
# Note: this fails if self.value is a number
return len(self.value)


class JSONProperty(Property):

data_type = JSON
item_type = JSON
type_name = 'JSON'

def __init__(self, verbose_name=None, name=None, default=None, required=False,
validator=None, choices=None, unique=False):
default = self.make_value_from_datastore(default)
super(JSONProperty, self).__init__(verbose_name=verbose_name, name=name,
default=default, required=required, validator=validator,
choices=choices, unique=unique)

def make_value_from_datastore(self, value):
if value is not None:
value = self.data_type(value)
return value

def get_value_for_datastore(self, model_instance):
value = super(JSONProperty, self).get_value_for_datastore(model_instance)
if isinstance(value, self.data_type):
value = value.value
return value

def __set__(self, obj, value):
if not (value is None or isinstance(value, self.data_type)):
value = self.data_type(value)
super(JSONProperty, self).__set__(obj, value)

def __get__(self, obj, objtype):
return super(JSONProperty, self).__get__(obj, objtype)

def default_validator(self, value):
if value is None or value == self.default_value():
return
if not isinstance(value, self.data_type):
raise TypeError('Validation Error, %s.%s expecting %s, got %s' % (self.model_class.__name__, self.name, self.data_type, type(value)))

def empty(self, value):
return value is None
return not value

def default_value(self):
return None
def get_choices(self):
if callable(self.choices):
choices = self.choices()
else:
choices = self.choices
if choices:
choices = [self.make_value_from_datastore(c) for c in choices]
return choices
12 changes: 6 additions & 6 deletions botoweb/xmlize.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def __init__(self, file=None):

self.htmlparser = HTMLParser.HTMLParser()

def encode(self, prop_name, prop_value):
def encode(self, prop_name, prop_value, **params):
"""Encode this value to XML"""
if prop_value == None:
return None
Expand Down Expand Up @@ -104,12 +104,12 @@ def encode_str(self, prop_name, prop_value, **params):
def encode_int(self, prop_name, prop_value, **params):
return self.encode_default(prop_name, str(prop_value), "integer", **params)

def encode_list(self, prop_name, prop_value):
def encode_list(self, prop_name, prop_value, **params):
"""Encode a list by encoding each property individually"""
for val in prop_value:
self.encode(prop_name, val)

def encode_dict(self, prop_name, prop_value):
def encode_dict(self, prop_name, prop_value, **params):
"""Encode a dict by encoding each element individually with a name="" param"""
#TODO: make this support more then just strings
self.file.write("""<%s type="complexType">""" % prop_name)
Expand Down Expand Up @@ -154,16 +154,16 @@ def encode_object(self, prop_name, prop_value, **params):
params["id"] = prop_value
return self.encode_default(prop_name, "", "reference", **params)

def encode_query(self, prop_name, prop_value=None):
def encode_query(self, prop_name, prop_value=None, **params):
"""Encode a query, this is sent as a reference"""
#TODO: Fix this by somehow getting the ID into the href
self.file.write("""<%s type="reference" href="%s"/>""" % (prop_name, prop_name))

def encode_blob(self, prop_name, prop_value=None):
def encode_blob(self, prop_name, prop_value=None, **params):
"""Encode a blob, this is sent as a reference"""
self.file.write("""<%s type="blob" href="%s"/>""" % (prop_name, prop_name))

def encode_key(self, prop_name, prop_value=None):
def encode_key(self, prop_name, prop_value=None, **params):
"""Encode an S3Key, this is sent as a reference"""
self.file.write("""<%s type="s3key" href="%s"/>""" % (prop_name, prop_name))

Expand Down

0 comments on commit 73113f8

Please sign in to comment.