From 504c4fea29278b3a93e91ed9d5f39b01943aab60 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 19 Aug 2013 11:13:41 -0400 Subject: [PATCH 001/151] have DocumentSchema use jsonobject deletes a ton of code whose functionality has moved to jsonobject --- couchdbkit/schema/__init__.py | 2 +- couchdbkit/schema/base.py | 419 ++-------- couchdbkit/schema/properties.py | 1069 +------------------------ couchdbkit/schema/properties_proxy.py | 391 +-------- couchdbkit/utils.py | 10 + tests/test_schema.py | 5 +- 6 files changed, 95 insertions(+), 1801 deletions(-) diff --git a/couchdbkit/schema/__init__.py b/couchdbkit/schema/__init__.py index 341bf2a..8a353d5 100644 --- a/couchdbkit/schema/__init__.py +++ b/couchdbkit/schema/__init__.py @@ -163,7 +163,7 @@ class A(Dcoument): value_to_json, MAP_TYPES_PROPERTIES, value_to_python, dict_to_python, \ list_to_python, convert_property, value_to_property, LazyDict, LazyList, \ LazySet -from .base import ReservedWordError, ALLOWED_PROPERTY_TYPES, \ +from .base import ReservedWordError, \ DocumentSchema, SchemaProperties, DocumentBase, QueryMixin, \ AttachmentMixin, Document, StaticDocument, valid_id from .properties_proxy import SchemaProperty, SchemaListProperty, \ diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 61f1b8b..779d955 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -6,14 +6,12 @@ """ module that provides a Document object that allows you to map CouchDB document in Python statically, dynamically or both """ +import copy - -from . import properties as p -from .properties import value_to_python, \ -convert_property, MAP_TYPES_PROPERTIES, ALLOWED_PROPERTY_TYPES, \ -LazyDict, LazyList -from ..exceptions import DuplicatePropertyError, ResourceNotFound, \ -ReservedWordError +import jsonobject +from jsonobject.exceptions import DeleteNotAllowed +from couchdbkit.utils import ProxyDict +from ..exceptions import ResourceNotFound, ReservedWordError __all__ = ['ReservedWordError', 'ALLOWED_PROPERTY_TYPES', 'DocumentSchema', @@ -35,377 +33,66 @@ def valid_id(value): return value raise TypeError('id "%s" is invalid' % value) -class SchemaProperties(type): - - def __new__(cls, name, bases, attrs): - # init properties - properties = {} - defined = set() - for base in bases: - if hasattr(base, '_properties'): - property_keys = base._properties.keys() - duplicate_properties = defined.intersection(property_keys) - if duplicate_properties: - raise DuplicatePropertyError( - 'Duplicate properties in base class %s already defined: %s' % (base.__name__, list(duplicate_properties))) - defined.update(property_keys) - properties.update(base._properties) - - doc_type = attrs.get('doc_type', False) - if not doc_type: - doc_type = name - else: - del attrs['doc_type'] - - attrs['_doc_type'] = doc_type - - for attr_name, attr in attrs.items(): - # map properties - if isinstance(attr, p.Property): - check_reserved_words(attr_name) - if attr_name in defined: - raise DuplicatePropertyError('Duplicate property: %s' % attr_name) - properties[attr_name] = attr - attr.__property_config__(cls, attr_name) - # python types - elif type(attr) in MAP_TYPES_PROPERTIES and \ - not attr_name.startswith('_') and \ - attr_name not in _NODOC_WORDS: - check_reserved_words(attr_name) - if attr_name in defined: - raise DuplicatePropertyError('Duplicate property: %s' % attr_name) - prop = MAP_TYPES_PROPERTIES[type(attr)](default=attr) - properties[attr_name] = prop - prop.__property_config__(cls, attr_name) - attrs[attr_name] = prop - - attrs['_properties'] = properties - return type.__new__(cls, name, bases, attrs) - - -class DocumentSchema(object): - __metaclass__ = SchemaProperties - - _dynamic_properties = None - _allow_dynamic_properties = True - _doc = None - _db = None - - def __init__(self, _d=None, **properties): - self._dynamic_properties = {} - self._doc = {} - - if _d is not None: - if not isinstance(_d, dict): - raise TypeError('d should be a dict') - properties.update(_d) - - doc_type = getattr(self, '_doc_type', self.__class__.__name__) - self._doc['doc_type'] = doc_type - - for prop in self._properties.values(): - if prop.name in properties: - value = properties.pop(prop.name) - if value is None: - value = prop.default_value() - else: - value = prop.default_value() - prop.__property_init__(self, value) - self.__dict__[prop.name] = value - - _dynamic_properties = properties.copy() - for attr_name, value in _dynamic_properties.iteritems(): - if attr_name not in self._properties \ - and value is not None: - if isinstance(value, p.Property): - value.__property_config__(self, attr_name) - value.__property_init__(self, value.default_value()) - elif isinstance(value, DocumentSchema): - from couchdbkit.schema import SchemaProperty - value = SchemaProperty(value) - value.__property_config__(self, attr_name) - value.__property_init__(self, value.default_value()) - - - setattr(self, attr_name, value) - # remove the kwargs to speed stuff - del properties[attr_name] - - def dynamic_properties(self): - """ get dict of dynamic properties """ - if self._dynamic_properties is None: - return {} - return self._dynamic_properties.copy() - @classmethod - def properties(cls): - """ get dict of defined properties """ - return cls._properties.copy() - - def all_properties(self): - """ get all properties. - Generally we just need to use keys""" - all_properties = self._properties.copy() - all_properties.update(self.dynamic_properties()) - return all_properties - - def to_json(self): - if self._doc.get('doc_type') is None: - doc_type = getattr(self, '_doc_type', self.__class__.__name__) - self._doc['doc_type'] = doc_type - return self._doc - - #TODO: add a way to maintain custom dynamic properties - def __setattr__(self, key, value): - """ - override __setattr__ . If value is in dir, we just use setattr. - If value is not known (dynamic) we test if type and name of value - is supported (in ALLOWED_PROPERTY_TYPES, Property instance and not - start with '_') a,d add it to `_dynamic_properties` dict. If value is - a list or a dict we use LazyList and LazyDict to maintain in the value. - """ - - if key == "_id" and valid_id(value): - self._doc['_id'] = value - elif key == "_deleted": - self._doc["_deleted"] = value - elif key == "_attachments": - if key not in self._doc or not value: - self._doc[key] = {} - elif not isinstance(self._doc[key], dict): - self._doc[key] = {} - value = LazyDict(self._doc[key], init_vals=value) +class SchemaProperties(jsonobject.JsonObjectMeta): + def __new__(mcs, name, bases, dct): + if isinstance(dct.get('doc_type'), basestring): + doc_type = dct.pop('doc_type') else: - check_reserved_words(key) - if not hasattr( self, key ) and not self._allow_dynamic_properties: - raise AttributeError("%s is not defined in schema (not a valid property)" % key) - - elif not key.startswith('_') and \ - key not in self.properties() and \ - key not in dir(self): - if type(value) not in ALLOWED_PROPERTY_TYPES and \ - not isinstance(value, (p.Property,)): - raise TypeError("Document Schema cannot accept values of type '%s'." % - type(value).__name__) - - if self._dynamic_properties is None: - self._dynamic_properties = {} - - if isinstance(value, dict): - if key not in self._doc or not value: - self._doc[key] = {} - elif not isinstance(self._doc[key], dict): - self._doc[key] = {} - value = LazyDict(self._doc[key], init_vals=value) - elif isinstance(value, list): - if key not in self._doc or not value: - self._doc[key] = [] - elif not isinstance(self._doc[key], list): - self._doc[key] = [] - value = LazyList(self._doc[key], init_vals=value) - - self._dynamic_properties[key] = value - - if not isinstance(value, (p.Property,)) and \ - not isinstance(value, dict) and \ - not isinstance(value, list): - if callable(value): - value = value() - self._doc[key] = convert_property(value) - else: - object.__setattr__(self, key, value) + doc_type = name + cls = super(SchemaProperties, mcs).__new__(mcs, name, bases, dct) + cls._doc_type = doc_type + return cls - def __delattr__(self, key): - """ delete property - """ - if key in self._doc: - del self._doc[key] +class DocumentSchema(jsonobject.JsonObject): - if self._dynamic_properties and key in self._dynamic_properties: - del self._dynamic_properties[key] - else: - object.__delattr__(self, key) + __metaclass__ = SchemaProperties - def __getattr__(self, key): - """ get property value - """ - if self._dynamic_properties and key in self._dynamic_properties: - return self._dynamic_properties[key] - elif key in ('_id', '_rev', '_attachments', 'doc_type'): - return self._doc.get(key) - try: - return self.__dict__[key] - except KeyError, e: - raise AttributeError(e) + @jsonobject.StringProperty + def doc_type(self): + return self._doc_type - def __getitem__(self, key): - """ get property value - """ - try: - attr = getattr(self, key) - if callable(attr): - raise AttributeError("existing instance method") - return attr - except AttributeError: - if key in self._doc: - return self._doc[key] - raise - - def __setitem__(self, key, value): - """ add a property - """ - setattr(self, key, value) + @property + def _doc(self): + return ProxyDict(self, self._obj) + @property + def _dynamic_properties(self): + from jsonobject.base import get_dynamic_properties + return get_dynamic_properties(self) + + def clone(self, **kwargs): + cls = self.__class__ def __delitem__(self, key): - """ delete a property - """ try: - delattr(self, key) - except AttributeError, e: - raise KeyError, e + super(DocumentSchema, self).__delitem__(key) + except DeleteNotAllowed: + self[key] = None + def __delattr__(self, name): + try: + super(DocumentSchema, self).__delattr__(name) + except DeleteNotAllowed: + setattr(self, name, None) - def __contains__(self, key): - """ does object contain this propery ? - - @param key: name of property - - @return: True if key exist. - """ - if key in self.all_properties(): - return True - elif key in self._doc: - return True - return False - - def __iter__(self): - """ iter document instance properties - """ - for k in self.all_properties().keys(): - yield k, self[k] - raise StopIteration - - iteritems = __iter__ - - def items(self): - """ return list of items - """ - return [(k, self[k]) for k in self.all_properties().keys()] - - - def __len__(self): - """ get number of properties - """ - return len(self._doc or ()) - - def __getstate__(self): - """ let pickle play with us """ - obj_dict = self.__dict__.copy() - return obj_dict - - @classmethod - def wrap(cls, data): - """ wrap `data` dict in object properties """ - instance = cls() - instance._doc = data - for prop in instance._properties.values(): - if prop.name in data: - value = data[prop.name] - if value is not None: - value = prop.to_python(value) - else: - value = prop.default_value() - else: - value = prop.default_value() - prop.__property_init__(instance, value) - - if cls._allow_dynamic_properties: - for attr_name, value in data.iteritems(): - if attr_name in instance.properties(): - continue - if value is None: - continue - elif attr_name.startswith('_'): - continue - elif attr_name == 'doc_type': - continue - else: - value = value_to_python(value) - setattr(instance, attr_name, value) - return instance - from_json = wrap - - def validate(self, required=True): - """ validate a document """ - for attr_name, value in self._doc.items(): - if attr_name in self._properties: - self._properties[attr_name].validate( - getattr(self, attr_name), required=required) - return True - - def clone(self, **kwargs): - """ clone a document """ - kwargs.update(self._dynamic_properties) - obj = self.__class__(**kwargs) - obj._doc = self._doc - return obj - - @classmethod - def build(cls, **kwargs): - """ build a new instance from this document object. """ - properties = {} - for attr_name, attr in kwargs.items(): - if isinstance(attr, (p.Property,)): - properties[attr_name] = attr - attr.__property_config__(cls, attr_name) - elif type(attr) in MAP_TYPES_PROPERTIES and \ - not attr_name.startswith('_') and \ - attr_name not in _NODOC_WORDS: - check_reserved_words(attr_name) - - prop = MAP_TYPES_PROPERTIES[type(attr)](default=attr) - properties[attr_name] = prop - prop.__property_config__(cls, attr_name) - properties[attr_name] = prop - return type('AnonymousSchema', (cls,), properties) class DocumentBase(DocumentSchema): - """ Base Document object that map a CouchDB Document. - It allow you to statically map a document by - providing fields like you do with any ORM or - dynamically. Ie unknown fields are loaded as - object property that you can edit, datetime in - iso3339 format are automatically translated in - python types (date, time & datetime) and decimal too. - - Example of documentass - - .. code-block:: python - - from couchdbkit.schema import * - class MyDocument(Document): - mystring = StringProperty() - myotherstring = unicode() # just use python types + _id = jsonobject.StringProperty() + _rev = jsonobject.StringProperty() + _attachments = jsonobject.DictProperty() - Document fields can be accessed as property or - key of dict. These are similar : ``value = instance.key or value = instance['key'].`` - - To delete a property simply do ``del instance[key'] or delattr(instance, key)`` - """ _db = None - def __init__(self, _d=None, **kwargs): - _d = _d or {} - - docid = kwargs.pop('_id', _d.pop("_id", "")) - docrev = kwargs.pop('_rev', _d.pop("_rev", "")) - - DocumentSchema.__init__(self, _d, **kwargs) - - if docid: self._doc['_id'] = valid_id(docid) - if docrev: self._doc['_rev'] = docrev + def to_json(self): + doc = copy.copy(super(DocumentBase, self).to_json()) + for special in ('_id', '_rev', '_attachments'): + if not doc[special]: + del doc[special] + return doc + + # The rest of this class is mostly copied from couchdbkit 0.5.7 @classmethod def set_db(cls, db): @@ -425,15 +112,15 @@ def save(self, **params): @params db: couchdbkit.core.Database instance """ - self.validate() + # self.validate() db = self.get_db() doc = self.to_json() db.save_doc(doc, **params) if '_id' in doc and '_rev' in doc: - self._doc.update(doc) + self.update(doc) elif '_id' in doc: - self._doc.update({'_id': doc['_id']}) + self.update({'_id': doc['_id']}) store = save @@ -469,7 +156,7 @@ def get(cls, docid, rev=None, db=None, dynamic_properties=True): @classmethod def get_or_create(cls, docid=None, db=None, dynamic_properties=True, **params): """ get or create document with `docid` """ - + if db: cls.set_db(db) cls._allow_dynamic_properties = dynamic_properties @@ -498,9 +185,9 @@ def delete(self): """ if self.new_document: raise TypeError("the document is not saved") - + db = self.get_db() - + # delete doc db.delete_doc(self._id) diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index c3bfba1..cad833d 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -1,1047 +1,28 @@ # -*- coding: utf-8 - # -# This file is part of couchdbkit released under the MIT license. +# This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. - -""" properties used by Document object """ - -import decimal -import datetime -import re -import time - -try: - from collections import MutableSet, Iterable - - def is_iterable(c): - return isinstance(c, Iterable) -except ImportError: - from sets import Set as MutableSet - - def is_iterable(o): - return hasattr(c, '__iter__') - -from couchdbkit.exceptions import BadValueError - -__all__ = ['ALLOWED_PROPERTY_TYPES', 'Property', 'StringProperty', - 'IntegerProperty', 'DecimalProperty', 'BooleanProperty', - 'FloatProperty', 'DateTimeProperty', 'DateProperty', - 'TimeProperty', 'DictProperty', 'ListProperty', - 'StringListProperty', 'SetProperty', - 'dict_to_json', 'list_to_json', - 'value_to_json', 'MAP_TYPES_PROPERTIES', 'value_to_python', - 'dict_to_python', 'list_to_python', 'convert_property', - 'value_to_property', 'LazyDict', 'LazyList', 'LazySet'] - -ALLOWED_PROPERTY_TYPES = set([ - basestring, - str, - unicode, - bool, - int, - long, - float, - datetime.datetime, - datetime.date, - datetime.time, - decimal.Decimal, - dict, - list, - set, - type(None) -]) - -re_date = re.compile('^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$') -re_time = re.compile('^([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?$') -re_datetime = re.compile('^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])(\D?([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?([zZ]|([\+-])([01]\d|2[0-3])\D?([0-5]\d)?)?)?$') -re_decimal = re.compile('^(\d+)\.(\d+)$') - -class Property(object): - """ Property base which all other properties - inherit.""" - creation_counter = 0 - - def __init__(self, verbose_name=None, name=None, - default=None, required=False, validators=None, - choices=None): - """ Default constructor for a property. - - :param verbose_name: str, verbose name of field, could - be use for description - :param name: str, name of field - :param default: default value - :param required: True if field is required, default is False - :param validators: list of callable or callable, field validators - function that are executed when document is saved. - """ - self.verbose_name = verbose_name - self.name = name - self.default = default - self.required = required - self.validators = validators - self.choices = choices - self.creation_counter = Property.creation_counter - Property.creation_counter += 1 - - def __property_config__(self, document_class, property_name): - self.document_class = document_class - if self.name is None: - self.name = property_name - - def __property_init__(self, document_instance, value): - """ method used to set value of the property when - we create the document. Don't check required. """ - if value is not None: - value = self.to_json(self.validate(value, required=False)) - document_instance._doc[self.name] = value - - def __get__(self, document_instance, document_class): - if document_instance is None: - return self - - value = document_instance._doc.get(self.name) - if value is not None: - value = self._to_python(value) - - return value - - def __set__(self, document_instance, value): - value = self.validate(value, required=False) - document_instance._doc[self.name] = self._to_json(value) - - def __delete__(self, document_instance): - pass - - def default_value(self): - """ return default value """ - - default = self.default - if callable(default): - default = default() - return default - - def validate(self, value, required=True): - """ validate value """ - if required and self.empty(value): - if self.required: - raise BadValueError("Property %s is required." % self.name) - else: - if self.choices and value is not None: - if isinstance(self.choices, list): choice_list = self.choices - if isinstance(self.choices, dict): choice_list = self.choices.keys() - if isinstance(self.choices, tuple): choice_list = [key for (key, name) in self.choices] - - if value not in choice_list: - raise BadValueError('Property %s is %r; must be one of %r' % ( - self.name, value, choice_list)) - if self.validators: - if isinstance(self.validators, (list, tuple,)): - for validator in self.validators: - if callable(validator): - validator(value) - elif callable(self.validators): - self.validators(value) - return value - - def empty(self, value): - """ test if value is empty """ - return not value or value is None - - def _to_python(self, value): - if value == None: - return value - return self.to_python(value) - - def _to_json(self, value): - if value == None: - return value - return self.to_json(value) - - def to_python(self, value): - """ convert to python type """ - return unicode(value) - - def to_json(self, value): - """ convert to json, Converted value is saved in couchdb. """ - return self.to_python(value) - - data_type = None - -class StringProperty(Property): - """ string property str or unicode property - - *Value type*: unicode - """ - - to_python = unicode - - def validate(self, value, required=True): - value = super(StringProperty, self).validate(value, - required=required) - - if value is None: - return value - - if not isinstance(value, basestring): - raise BadValueError( - 'Property %s must be unicode or str instance, not a %s' % (self.name, type(value).__name__)) - return value - - data_type = unicode - -class IntegerProperty(Property): - """ Integer property. map to int - - *Value type*: int - """ - to_python = int - - def empty(self, value): - return value is None - - def validate(self, value, required=True): - value = super(IntegerProperty, self).validate(value, - required=required) - - if value is None: - return value - - if value is not None and not isinstance(value, (int, long,)): - raise BadValueError( - 'Property %s must be %s or long instance, not a %s' - % (self.name, type(self.data_type).__name__, - type(value).__name__)) - - return value - - data_type = int -LongProperty = IntegerProperty - -class FloatProperty(Property): - """ Float property, map to python float - - *Value type*: float - """ - to_python = float - data_type = float - - def validate(self, value, required=True): - value = super(FloatProperty, self).validate(value, - required=required) - - if value is None: - return value - - if not isinstance(value, float): - raise BadValueError( - 'Property %s must be float instance, not a %s' - % (self.name, type(value).__name__)) - - return value -Number = FloatProperty - -class BooleanProperty(Property): - """ Boolean property, map to python bool - - *ValueType*: bool - """ - to_python = bool - data_type = bool - - def validate(self, value, required=True): - value = super(BooleanProperty, self).validate(value, - required=required) - - if value is None: - return value - - if value is not None and not isinstance(value, bool): - raise BadValueError( - 'Property %s must be bool instance, not a %s' - % (self.name, type(value).__name__)) - - return value - - def empty(self, value): - """test if boolean is empty""" - return value is None - -class DecimalProperty(Property): - """ Decimal property, map to Decimal python object - - *ValueType*: decimal.Decimal - """ - data_type = decimal.Decimal - - def to_python(self, value): - return decimal.Decimal(value) - - def to_json(self, value): - return unicode(value) - -class DateTimeProperty(Property): - """DateTime property. It convert iso3339 string - to python and vice-versa. Map to datetime.datetime - object. - - *ValueType*: datetime.datetime - """ - - def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, - **kwds): - super(DateTimeProperty, self).__init__(verbose_name, **kwds) - self.auto_now = auto_now - self.auto_now_add = auto_now_add - - def validate(self, value, required=True): - value = super(DateTimeProperty, self).validate(value, required=required) - - if value is None: - return value - - if value and not isinstance(value, self.data_type): - raise BadValueError('Property %s must be a %s, current is %s' % - (self.name, self.data_type.__name__, type(value).__name__)) - return value - - def default_value(self): - if self.auto_now or self.auto_now_add: - return self.now() - return Property.default_value(self) - - def to_python(self, value): - if isinstance(value, basestring): - try: - value = value.split('.', 1)[0] # strip out microseconds - value = value[0:19] # remove timezone - value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S') - except ValueError, e: - raise ValueError('Invalid ISO date/time %r [%s]' % - (value, str(e))) - return value - - def to_json(self, value): - if self.auto_now: - value = self.now() - - if value is None: - return value - return value.replace(microsecond=0).isoformat() + 'Z' - - data_type = datetime.datetime - - @staticmethod - def now(): - return datetime.datetime.utcnow() - -class DateProperty(DateTimeProperty): - """ Date property, like DateTime property but only - for Date. Map to datetime.date object - - *ValueType*: datetime.date - """ - data_type = datetime.date - - @staticmethod - def now(): - return datetime.datetime.now().date() - - def to_python(self, value): - if isinstance(value, basestring): - try: - value = datetime.date(*time.strptime(value, '%Y-%m-%d')[:3]) - except ValueError, e: - raise ValueError('Invalid ISO date %r [%s]' % (value, - str(e))) - return value - - def to_json(self, value): - if value is None: - return value - return value.isoformat() - -class TimeProperty(DateTimeProperty): - """ Date property, like DateTime property but only - for time. Map to datetime.time object - - *ValueType*: datetime.time - """ - - data_type = datetime.time - - @staticmethod - def now(self): - return datetime.datetime.now().time() - - def to_python(self, value): - if isinstance(value, basestring): - try: - value = value.split('.', 1)[0] # strip out microseconds - value = datetime.time(*time.strptime(value, '%H:%M:%S')[3:6]) - except ValueError, e: - raise ValueError('Invalid ISO time %r [%s]' % (value, - str(e))) - return value - - def to_json(self, value): - if value is None: - return value - return value.replace(microsecond=0).isoformat() - - -class DictProperty(Property): - """ A property that stores a dict of things""" - - def __init__(self, verbose_name=None, default=None, - required=False, **kwds): - """ - :args verbose_name: Optional verbose name. - :args default: Optional default value; if omitted, an empty list is used. - :args**kwds: Optional additional keyword arguments, passed to base class. - - Note that the only permissible value for 'required' is True. - """ - - if default is None: - default = {} - - Property.__init__(self, verbose_name, default=default, - required=required, **kwds) - - data_type = dict - - def validate(self, value, required=True): - value = super(DictProperty, self).validate(value, required=required) - if value and value is not None: - if not isinstance(value, dict): - raise BadValueError('Property %s must be a dict' % self.name) - value = self.validate_dict_contents(value) - return value - - def validate_dict_contents(self, value): - try: - value = validate_dict_content(value) - except BadValueError: - raise BadValueError( - 'Items of %s dict must all be in %s' % - (self.name, ALLOWED_PROPERTY_TYPES)) - return value - - def default_value(self): - """Default value for list. - - Because the property supplied to 'default' is a static value, - that value must be shallow copied to prevent all fields with - default values from sharing the same instance. - - Returns: - Copy of the default value. - """ - value = super(DictProperty, self).default_value() - if value is None: - value = {} - return dict(value) - - def to_python(self, value): - return LazyDict(value) - - def to_json(self, value): - return value_to_json(value) - - - -class ListProperty(Property): - """A property that stores a list of things. - - """ - def __init__(self, verbose_name=None, default=None, - required=False, item_type=None, **kwds): - """Construct ListProperty. - - - :args verbose_name: Optional verbose name. - :args default: Optional default value; if omitted, an empty list is used. - :args**kwds: Optional additional keyword arguments, passed to base class. - - - """ - if default is None: - default = [] - - if item_type is not None and item_type not in ALLOWED_PROPERTY_TYPES: - raise ValueError('item_type %s not in %s' % (item_type, ALLOWED_PROPERTY_TYPES)) - self.item_type = item_type - - Property.__init__(self, verbose_name, default=default, - required=required, **kwds) - - data_type = list - - def validate(self, value, required=True): - value = super(ListProperty, self).validate(value, required=required) - if value and value is not None: - if not isinstance(value, list): - raise BadValueError('Property %s must be a list' % self.name) - value = self.validate_list_contents(value) - return value - - def validate_list_contents(self, value): - value = validate_list_content(value, item_type=self.item_type) - try: - value = validate_list_content(value, item_type=self.item_type) - except BadValueError: - raise BadValueError( - 'Items of %s list must all be in %s' % - (self.name, ALLOWED_PROPERTY_TYPES)) - return value - - def default_value(self): - """Default value for list. - - Because the property supplied to 'default' is a static value, - that value must be shallow copied to prevent all fields with - default values from sharing the same instance. - - Returns: - Copy of the default value. - """ - value = super(ListProperty, self).default_value() - if value is None: - value = [] - return list(value) - - def to_python(self, value): - return LazyList(value, item_type=self.item_type) - - def to_json(self, value): - return value_to_json(value, item_type=self.item_type) - - -class StringListProperty(ListProperty): - """ shorthand for list that should containe only unicode""" - - def __init__(self, verbose_name=None, default=None, - required=False, **kwds): - super(StringListProperty, self).__init__(verbose_name=verbose_name, - default=default, required=required, item_type=basestring, **kwds) - - -class SetProperty(Property): - """A property that stores a Python set as a list of unique - elements. - - Note that Python set operations like union that return a set - object do not alter list that will be stored with the next save, - while operations like update that change a set object in-place do - keep the list in sync. - """ - def __init__(self, verbose_name=None, default=None, required=None, - item_type=None, **kwds): - """Construct SetProperty. - - :args verbose_name: Optional verbose name. - - :args default: Optional default value; if omitted, an empty - set is used. - - :args required: True if field is required, default is False. - - :args item_type: Optional data type of items that set - contains. Used to assist with JSON - serialization/deserialization when data is - stored/retireved. - - :args **kwds: Optional additional keyword arguments, passed to - base class. - """ - if default is None: - default = set() - if item_type is not None and item_type not in ALLOWED_PROPERTY_TYPES: - raise ValueError('item_type %s not in %s' - % (item_type, ALLOWED_PROPERTY_TYPES)) - self.item_type = item_type - super(SetProperty, self).__init__( - verbose_name=verbose_name, default=default, required=required, - **kwds) - - data_type = set - - def validate(self, value, required=True): - value = super(SetProperty, self).validate(value, required=required) - if value and value is not None: - if not isinstance(value, MutableSet): - raise BadValueError('Property %s must be a set' % self.name) - value = self.validate_set_contents(value) - return value - - def validate_set_contents(self, value): - try: - value = validate_set_content(value, item_type=self.item_type) - except BadValueError: - raise BadValueError( - 'Items of %s set must all be in %s' % - (self.name, ALLOWED_PROPERTY_TYPES)) - return value - - def default_value(self): - """Return default value for set. - - Because the property supplied to 'default' is a static value, - that value must be shallow copied to prevent all fields with - default values from sharing the same instance. - - Returns: - Copy of the default value. - """ - value = super(SetProperty, self).default_value() - if value is None: - return set() - return value.copy() - - def to_python(self, value): - return LazySet(value, item_type=self.item_type) - - def to_json(self, value): - return value_to_json(value, item_type=self.item_type) - - -# structures proxy - -class LazyDict(dict): - """ object to make sure we keep updated of dict - in _doc. We just override a dict and maintain change in - doc reference (doc[keyt] obviously). - - if init_vals is specified, doc is overwritten - with the dict given. Otherwise, the values already in - doc are used. - """ - - def __init__(self, doc, item_type=None, init_vals=None): - dict.__init__(self) - self.item_type = item_type - - self.doc = doc - if init_vals is None: - self._wrap() - else: - for key, value in init_vals.items(): - self[key] = value - - def _wrap(self): - for key, json_value in self.doc.items(): - if isinstance(json_value, dict): - value = LazyDict(json_value, item_type=self.item_type) - elif isinstance(json_value, list): - value = LazyList(json_value, item_type=self.item_type) - else: - value = value_to_python(json_value, self.item_type) - dict.__setitem__(self, key, value) - - def __setitem__(self, key, value): - if isinstance(value, dict): - self.doc[key] = {} - value = LazyDict(self.doc[key], item_type=self.item_type, init_vals=value) - elif isinstance(value, list): - self.doc[key] = [] - value = LazyList(self.doc[key], item_type=self.item_type, init_vals=value) - else: - self.doc.update({key: value_to_json(value, item_type=self.item_type) }) - super(LazyDict, self).__setitem__(key, value) - - def __delitem__(self, key): - del self.doc[key] - super(LazyDict, self).__delitem__(key) - - def pop(self, key, default=None): - del self.doc[key] - return super(LazyDict, self).pop(key, default=default) - - def setdefault(self, key, default): - if key in self: - return self[key] - self.doc.setdefault(key, value_to_json(default, item_type=self.item_type)) - super(LazyDict, self).setdefault(key, default) - return default - - def update(self, value): - for k, v in value.items(): - self[k] = v - - def popitem(self, value): - new_value = super(LazyDict, self).popitem(value) - self.doc.popitem(value_to_json(value, item_type=self.item_type)) - return new_value - - def clear(self): - self.doc.clear() - super(LazyDict, self).clear() - -class LazyList(list): - """ object to make sure we keep update of list - in _doc. We just override a list and maintain change in - doc reference (doc[index] obviously). - - if init_vals is specified, doc is overwritten - with the list given. Otherwise, the values already in - doc are used. - """ - - def __init__(self, doc, item_type=None, init_vals=None): - list.__init__(self) - - self.item_type = item_type - self.doc = doc - if init_vals is None: - # just wrap the current values - self._wrap() - else: - # initialize this list and the underlying list - # with the values given. - del self.doc[:] - for item in init_vals: - self.append(item) - - def _wrap(self): - for json_value in self.doc: - if isinstance(json_value, dict): - value = LazyDict(json_value, item_type=self.item_type) - elif isinstance(json_value, list): - value = LazyList(json_value, item_type=self.item_type) - else: - value = value_to_python(json_value, self.item_type) - list.append(self, value) - - def __delitem__(self, index): - del self.doc[index] - list.__delitem__(self, index) - - def __setitem__(self, index, value): - if isinstance(value, dict): - self.doc[index] = {} - value = LazyDict(self.doc[index], item_type=self.item_type, init_vals=value) - elif isinstance(value, list): - self.doc[index] = [] - value = LazyList(self.doc[index], item_type=self.item_type, init_vals=value) - else: - self.doc[index] = value_to_json(value, item_type=self.item_type) - list.__setitem__(self, index, value) - - - def __delslice__(self, i, j): - del self.doc[i:j] - list.__delslice__(self, i, j) - - def __getslice__(self, i, j): - return LazyList(self.doc[i:j], self.item_type) - - def __setslice__(self, i, j, seq): - self.doc[i:j] = (value_to_json(v, item_type=self.item_type) for v in seq) - list.__setslice__(self, i, j, seq) - - def __contains__(self, value): - jvalue = value_to_json(value) - for m in self.doc: - if m == jvalue: return True - return False - - def append(self, *args, **kwargs): - if args: - assert len(args) == 1 - value = args[0] - else: - value = kwargs - - index = len(self) - if isinstance(value, dict): - self.doc.append({}) - value = LazyDict(self.doc[index], item_type=self.item_type, init_vals=value) - elif isinstance(value, list): - self.doc.append([]) - value = LazyList(self.doc[index], item_type=self.item_type, init_vals=value) - else: - self.doc.append(value_to_json(value, item_type=self.item_type)) - super(LazyList, self).append(value) - - def extend(self, x): - self.doc.extend( - [value_to_json(v, item_type=self.item_type) for v in x]) - super(LazyList, self).extend(x) - - def index(self, x, *args): - x = value_to_json(x, item_type=self.item_type) - return self.doc.index(x) - - def insert(self, i, x): - self.__setslice__(i, i, [x]) - - def pop(self, i=-1): - del self.doc[i] - v = super(LazyList, self).pop(i) - return value_to_python(v, item_type=self.item_type) - - def remove(self, x): - del self[self.index(x)] - - def sort(self, cmp=None, key=None, reverse=False): - self.doc.sort(cmp, key, reverse) - list.sort(self, cmp, key, reverse) - - def reverse(self): - self.doc.reverse() - list.reverse(self) - - -class LazySet(MutableSet): - """Object to make sure that we keep set and _doc synchronized. - - We sub-class MutableSet and maintain changes in doc. - - Note that methods like union that return a set object do not - alter _doc, while methods like update that change a set object - in-place do keep _doc in sync. - """ - def _map_named_operation(opname): - fn = getattr(MutableSet, opname) - if hasattr(fn, 'im_func'): - fn = fn.im_func - def method(self, other, fn=fn): - if not isinstance(other, MutableSet): - other = self._from_iterable(other) - return fn(self, other) - return method - - issubset = _map_named_operation('__le__') - issuperset = _map_named_operation('__ge__') - symmetric_difference = _map_named_operation('__xor__') - - def __init__(self, doc, item_type=None): - self.item_type = item_type - self.doc = doc - self.elements = set(value_to_python(value, self.item_type) - for value in self.doc) - - def __repr__(self): - return '%s(%r)' % (type(self).__name__, list(self)) - - @classmethod - def _from_iterable(cls, it): - return cls(it) - - def __iand__(self, iterator): - for value in (self.elements - iterator): - self.elements.discard(value) - return self - - def __iter__(self): - return iter(element for element in self.elements) - - def __len__(self): - return len(self.elements) - - def __contains__(self, item): - return item in self.elements - - def __xor__(self, other): - if not isinstance(other, MutableSet): - if not is_iterable(Other): - return NotImplemented - other = self._from_iterable(other) - return (self.elements - other) | (other - self.elements) - - def __gt__(self, other): - if not isinstance(other, MutableSet): - return NotImplemented - return other < self.elements - - def __ge__(self, other): - if not isinstance(other, MutableSet): - return NotImplemented - return other <= self.elements - - def __ne__(self, other): - return not (self.elements == other) - - def add(self, value): - self.elements.add(value) - if value not in self.doc: - self.doc.append(value_to_json(value, item_type=self.item_type)) - - def copy(self): - return self.elements.copy() - - def difference(self, other, *args): - return self.elements.difference(other, *args) - - def difference_update(self, other, *args): - for value in other: - self.discard(value) - for arg in args: - self.difference_update(arg) - - def discard(self, value): - self.elements.discard(value) - try: - self.doc.remove(value) - except ValueError: - pass - - def intersection(self, other, *args): - return self.elements.intersection(other, *args) - - def intersection_update(self, other, *args): - if not isinstance(other, MutableSet): - other = set(other) - for value in self.elements - other: - self.discard(value) - for arg in args: - self.intersection_update(arg) - - def symmetric_difference_update(self, other): - if not isinstance(other, MutableSet): - other = set(other) - for value in other: - if value in self.elements: - self.discard(value) - else: - self.add(value) - - def union(self, other, *args): - return self.elements.union(other, *args) - - def update(self, other, *args): - self.elements.update(other, *args) - for element in self.elements: - if element not in self.doc: - self.doc.append( - value_to_json(element, item_type=self.item_type)) - -# some mapping - -MAP_TYPES_PROPERTIES = { - decimal.Decimal: DecimalProperty, - datetime.datetime: DateTimeProperty, - datetime.date: DateProperty, - datetime.time: TimeProperty, - str: StringProperty, - unicode: StringProperty, - bool: BooleanProperty, - int: IntegerProperty, - long: LongProperty, - float: FloatProperty, - list: ListProperty, - dict: DictProperty, - set: SetProperty, -} - -def convert_property(value): - """ convert a value to json from Property._to_json """ - if type(value) in MAP_TYPES_PROPERTIES: - prop = MAP_TYPES_PROPERTIES[type(value)]() - value = prop.to_json(value) - return value - - -def value_to_property(value): - """ Convert value in a Property object """ - if type(value) in MAP_TYPES_PROPERTIES: - prop = MAP_TYPES_PROPERTIES[type(value)]() - return prop - else: - return value - -# utilities functions - -def validate_list_content(value, item_type=None): - """ validate type of values in a list """ - return [validate_content(item, item_type=item_type) for item in value] - -def validate_dict_content(value, item_type=None): - """ validate type of values in a dict """ - return dict([(k, validate_content(v, - item_type=item_type)) for k, v in value.iteritems()]) - -def validate_set_content(value, item_type=None): - """ validate type of values in a set """ - return set(validate_content(item, item_type=item_type) for item in value) - -def validate_content(value, item_type=None): - """ validate a value. test if value is in supported types """ - if isinstance(value, list): - value = validate_list_content(value, item_type=item_type) - elif isinstance(value, dict): - value = validate_dict_content(value, item_type=item_type) - elif item_type is not None and not isinstance(value, item_type): - raise BadValueError( - 'Items must all be in %s' % item_type) - elif type(value) not in ALLOWED_PROPERTY_TYPES: - raise BadValueError( - 'Items must all be in %s' % - (ALLOWED_PROPERTY_TYPES)) - return value - -def dict_to_json(value, item_type=None): - """ convert a dict to json """ - return dict([(k, value_to_json(v, item_type=item_type)) for k, v in value.iteritems()]) - -def list_to_json(value, item_type=None): - """ convert a list to json """ - return [value_to_json(item, item_type=item_type) for item in value] - -def value_to_json(value, item_type=None): - """ convert a value to json using appropriate regexp. - For Dates we use ISO 8601. Decimal are converted to string. - - """ - if isinstance(value, datetime.datetime) and is_type_ok(item_type, datetime.datetime): - value = value.replace(microsecond=0).isoformat() + 'Z' - elif isinstance(value, datetime.date) and is_type_ok(item_type, datetime.date): - value = value.isoformat() - elif isinstance(value, datetime.time) and is_type_ok(item_type, datetime.time): - value = value.replace(microsecond=0).isoformat() - elif isinstance(value, decimal.Decimal) and is_type_ok(item_type, decimal.Decimal): - value = unicode(value) - elif isinstance(value, (list, MutableSet)): - value = list_to_json(value, item_type) - elif isinstance(value, dict): - value = dict_to_json(value, item_type) - return value - -def is_type_ok(item_type, value_type): - return item_type is None or item_type == value_type - - -def value_to_python(value, item_type=None): - """ convert a json value to python type using regexp. values converted - have been put in json via `value_to_json` . - """ - data_type = None - if isinstance(value, basestring): - if re_date.match(value) and is_type_ok(item_type, datetime.date): - data_type = datetime.date - elif re_time.match(value) and is_type_ok(item_type, datetime.time): - data_type = datetime.time - elif re_datetime.match(value) and is_type_ok(item_type, datetime.datetime): - data_type = datetime.datetime - elif re_decimal.match(value) and is_type_ok(item_type, decimal.Decimal): - data_type = decimal.Decimal - if data_type is not None: - prop = MAP_TYPES_PROPERTIES[data_type]() - try: - #sometimes regex fail so return value - value = prop.to_python(value) - except: - pass - elif isinstance(value, (list, MutableSet)): - value = list_to_python(value, item_type=item_type) - elif isinstance(value, dict): - value = dict_to_python(value, item_type=item_type) - return value - -def list_to_python(value, item_type=None): - """ convert a list of json values to python list """ - return [value_to_python(item, item_type=item_type) for item in value] - -def dict_to_python(value, item_type=None): - """ convert a json object values to python dict """ - return dict([(k, value_to_python(v, item_type=item_type)) for k, v in value.iteritems()]) +import functools +from jsonobject.properties import * + +Property = JsonProperty +SchemaProperty = ObjectProperty +SchemaListProperty = ListProperty +StringListProperty = functools.partial(ListProperty, unicode) +SchemaDictProperty = DictProperty + +DecimalProperty = None +TimeProperty = None +SetProperty = None + +dict_to_json = None +list_to_json = None +value_to_json = None +value_to_python = None +dict_to_python = None +list_to_python = None +convert_property = None +value_to_property = None +LazyDict = None +LazyList = None +LazySet = None diff --git a/couchdbkit/schema/properties_proxy.py b/couchdbkit/schema/properties_proxy.py index fb9a92b..0989584 100644 --- a/couchdbkit/schema/properties_proxy.py +++ b/couchdbkit/schema/properties_proxy.py @@ -1,388 +1,5 @@ -# -*- coding: utf-8 - -# -# This file is part of couchdbkit released under the MIT license. -# See the NOTICE for more information. +from jsonobject import ObjectProperty, ListProperty, DictProperty -""" Meta properties """ - - -from ..exceptions import BadValueError - -from .base import DocumentSchema -from .properties import Property - -__all__ = ['SchemaProperty', 'SchemaListProperty', 'SchemaDictProperty'] - -class SchemaProperty(Property): - """ Schema property. It allows you add a DocumentSchema instance - a member of a Document object. It returns a - `schemaDocumentSchema` object. - - Exemple : - - >>> from couchdbkit import * - >>> class Blog(DocumentSchema): - ... title = StringProperty() - ... author = StringProperty(default="me") - ... - >>> class Entry(Document): - ... title = StringProperty() - ... body = StringProperty() - ... blog = SchemaProperty(Blog()) - ... - >>> test = Entry() - >>> test._doc - {'body': None, 'doc_type': 'Entry', 'title': None, 'blog': {'doc_type': 'Blog', 'author': u'me', 'title': None}} - >>> test.blog.title = "Mon Blog" - >>> test._doc - {'body': None, 'doc_type': 'Entry', 'title': None, 'blog': {'doc_type': 'Blog', 'author': u'me', 'title': u'Mon Blog'}} - >>> test.blog.title - u'Mon Blog' - >>> from couchdbkit import Server - >>> s = Server() - >>> db = s.create_db('couchdbkit_test') - >>> Entry._db = db - >>> test.save() - >>> doc = Entry.objects.get(test.id) - >>> doc.blog.title - u'Mon Blog' - >>> del s['simplecouchdb_test'] - - """ - - def __init__(self, schema, verbose_name=None, name=None, - required=False, validators=None, default=None): - - Property.__init__(self, verbose_name=None, - name=None, required=False, validators=None) - - use_instance = True - if isinstance(schema, type): - use_instance = False - - elif not isinstance(schema, DocumentSchema): - raise TypeError('schema should be a DocumentSchema instance') - - elif schema.__class__.__name__ == 'DocumentSchema': - use_instance = False - properties = schema._dynamic_properties.copy() - schema = DocumentSchema.build(**properties) - - self._use_instance = use_instance - self._schema = schema - - def default_value(self): - if not self._use_instance: - return self._schema() - return self._schema.clone() - - def empty(self, value): - if not hasattr(value, '_doc'): - return True - if not value._doc or value._doc is None: - return True - return False - - def validate(self, value, required=True): - value.validate(required=required) - value = super(SchemaProperty, self).validate(value) - - if value is None: - return value - - if not isinstance(value, DocumentSchema): - raise BadValueError( - 'Property %s must be DocumentSchema instance, not a %s' % (self.name, - type(value).__name__)) - - return value - - def to_python(self, value): - if not self._use_instance: - schema = self._schema() - else: - schema = self._schema.clone() - - if not self._use_instance: - schema = self._schema - else: - schema = self._schema.__class__ - return schema.wrap(value) - - def to_json(self, value): - if not isinstance(value, DocumentSchema): - if not self._use_instance: - schema = self._schema() - else: - schema = self._schema.clone() - - if not isinstance(value, dict): - raise BadValueError("%s is not a dict" % str(value)) - value = schema(**value) - - return value._doc - -class SchemaListProperty(Property): - """A property that stores a list of things. - - """ - def __init__(self, schema, verbose_name=None, default=None, - required=False, **kwds): - - Property.__init__(self, verbose_name, default=default, - required=required, **kwds) - - use_instance = True - if isinstance(schema, type): - use_instance = False - - elif not isinstance(schema, DocumentSchema): - raise TypeError('schema should be a DocumentSchema instance') - - elif schema.__class__.__name__ == 'DocumentSchema': - use_instance = False - properties = schema._dynamic_properties.copy() - schema = DocumentSchema.build(**properties) - - self._use_instance = use_instance - self._schema = schema - - def validate(self, value, required=True): - value = super(SchemaListProperty, self).validate(value, required=required) - if value and value is not None: - if not isinstance(value, list): - raise BadValueError('Property %s must be a list' % self.name) - value = self.validate_list_schema(value, required=required) - return value - - def validate_list_schema(self, value, required=True): - for v in value: - v.validate(required=required) - return value - - def default_value(self): - return [] - - def to_python(self, value): - return LazySchemaList(value, self._schema, self._use_instance) - - def to_json(self, value): - return [svalue_to_json(v, self._schema, self._use_instance) for v in value] - - -class LazySchemaList(list): - - def __init__(self, doc, schema, use_instance, init_vals=None): - list.__init__(self) - - self.schema = schema - self.use_instance = use_instance - self.doc = doc - if init_vals is None: - # just wrap the current values - self._wrap() - else: - # initialize this list and the underlying list - # with the values given. - del self.doc[:] - for item in init_vals: - self.append(item) - - def _wrap(self): - for v in self.doc: - if not self.use_instance: - schema = self.schema() - else: - schema = self.schema.clone() - - value = schema.wrap(v) - list.append(self, value) - - def __delitem__(self, index): - del self.doc[index] - list.__delitem__(self, index) - - def __setitem__(self, index, value): - self.doc[index] = svalue_to_json(value, self.schema, - self.use_instance) - list.__setitem__(self, index, value) - - def __delslice__(self, i, j): - del self.doc[i:j] - super(LazySchemaList, self).__delslice__(i, j) - - def __getslice__(self, i, j): - return LazySchemaList(self.doc[i:j], self.schema, self.use_instance) - - def __setslice__(self, i, j, seq): - self.doc[i:j] = (svalue_to_json(v, self.schema, self.use_instance) - for v in seq) - super(LazySchemaList, self).__setslice__(i, j, seq) - - def __contains__(self, value): - for item in self.doc: - if item == value._doc: - return True - return False - - def append(self, *args, **kwargs): - if args: - assert len(args) == 1 - value = args[0] - else: - value = kwargs - - self.doc.append(svalue_to_json(value, self.schema, - self.use_instance)) - super(LazySchemaList, self).append(value) - - def count(self, value): - return sum(1 for item in self.doc if item == value._doc) - - def extend(self, x): - self.doc.extend([svalue_to_json(item, self.schema, self.use_instance) - for item in x]) - super(LazySchemaList, self).extend(x) - - def index(self, value, *args): - try: - i = max(0, args[0]) - except IndexError: - i = 0 - try: - j = min(len(self.doc), args[1]) - except IndexError: - j = len(self.doc) - if j < 0: - j += len(self.doc) - for idx, item in enumerate(self.doc[i:j]): - if item == value._doc: - return idx + i - else: - raise ValueError('list.index(x): x not in list') - - def insert(self, index, value): - self.__setslice__(index, index, [value]) - - def pop(self, index=-1): - del self.doc[index] - return super(LazySchemaList, self).pop(index) - - def remove(self, value): - try: - del self[self.index(value)] - except ValueError: - raise ValueError('list.remove(x): x not in list') - - def reverse(self): - self.doc.reverse() - list.reverse(self) - - def sort(self, cmp=None, key=None, reverse=False): - self.doc.sort(cmp, key, reverse) - list.sort(self, cmp, key, reverse) - - -class SchemaDictProperty(Property): - """A property that stores a dict of things. - - """ - def __init__(self, schema, verbose_name=None, default=None, - required=False, **kwds): - - Property.__init__(self, verbose_name, default=default, - required=required, **kwds) - - use_instance = True - if isinstance(schema, type): - use_instance = False - - elif not isinstance(schema, DocumentSchema): - raise TypeError('schema should be a DocumentSchema instance') - - elif schema.__class__.__name__ == 'DocumentSchema': - use_instance = False - properties = schema._dynamic_properties.copy() - schema = DocumentSchema.build(**properties) - - self._use_instance = use_instance - self._schema = schema - - def validate(self, value, required=True): - value = super(SchemaDictProperty, self).validate(value, required=required) - if value and value is not None: - if not isinstance(value, dict): - raise BadValueError('Property %s must be a dict' % self.name) - value = self.validate_dict_schema(value, required=required) - return value - - def validate_dict_schema(self, value, required=True): - for v in value.values(): - v.validate(required=required) - return value - - def default_value(self): - return {} - - def to_python(self, value): - return LazySchemaDict(value, self._schema, self._use_instance) - - def to_json(self, value): - return dict([(k, svalue_to_json(v, self._schema, self._use_instance)) for k, v in value.items()]) - - -class LazySchemaDict(dict): - - def __init__(self, doc, schema, use_instance, init_vals=None): - dict.__init__(self) - - self.schema = schema - self.use_instance = use_instance - self.doc = doc - if init_vals is None: - # just wrap the current values - self._wrap() - else: - # initialize this dict and the underlying dict - # with the values given. - del self.doc[:] - for k, v in init_vals: - self[k] = self._wrap(v) - - def _wrap(self): - for k, v in self.doc.items(): - if not self.use_instance: - schema = self.schema() - else: - schema = self.schema.clone() - - value = schema.wrap(v) - dict.__setitem__(self, k, value) - - def __delitem__(self, index): - index = str(index) - del self.doc[index] - dict.__delitem__(self, index) - - def __getitem__(self, index): - index = str(index) - return dict.__getitem__(self, index) - - def __setitem__(self, index, value): - index = str(index) - self.doc[index] = svalue_to_json(value, self.schema, - self.use_instance) - dict.__setitem__(self, index, value) - - -def svalue_to_json(value, schema, use_instance): - if not isinstance(value, DocumentSchema): - if not use_instance: - schema = schema() - else: - schema = schema.clone() - - if not isinstance(value, dict): - raise BadValueError("%s is not a dict" % str(value)) - value = schema(**value) - return value._doc +SchemaProperty = ObjectProperty +SchemaListProperty = ListProperty +SchemaDictProperty = DictProperty \ No newline at end of file diff --git a/couchdbkit/utils.py b/couchdbkit/utils.py index 2961a5e..c4858bc 100644 --- a/couchdbkit/utils.py +++ b/couchdbkit/utils.py @@ -206,3 +206,13 @@ def read_json(filename, use_environment=False): return data +import jsonobject.base + + +class ProxyDict(jsonobject.base.SimpleDict): + def __init__(self, parent, *args, **kwargs): + super(ProxyDict, self).__init__(*args, **kwargs) + self.parent = parent + + def __setitem__(self, key, value): + self.parent[key] = value diff --git a/tests/test_schema.py b/tests/test_schema.py index 4b17b15..729a99c 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -15,7 +15,6 @@ from couchdbkit import * - class DocumentTestCase(unittest.TestCase): def setUp(self): self.server = Server() @@ -124,9 +123,9 @@ class Test(Document): string2 = StringProperty() doc = Test() - self.assert_(len(doc) == 3) + self.assert_(len(doc) == 6) doc.string3 = "4" - self.assert_(len(doc) == 4) + self.assert_(len(doc) == 7) def testStore(self): db = self.server.create_db('couchdbkit_test') From 514f91d1366b23a3fbfc71fae73ce131c454b87e Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 21 Aug 2013 09:48:20 -0400 Subject: [PATCH 002/151] fix save --- couchdbkit/client.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 62eff92..4af4061 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -458,7 +458,7 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, if '_attachments' in doc1 and encode_attachments: doc1['_attachments'] = resource.encode_attachments(doc['_attachments']) - if '_id' in doc: + if '_id' in doc1: docid = doc1['_id'] docid1 = resource.escape_docid(doc1['_id']) try: @@ -484,9 +484,8 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, else: doc1.update({'_id': res['id'], '_rev': res['rev']}) - if schema: - doc._doc = doc1 + doc.update(doc.__class__.wrap(doc1)) else: doc.update(doc1) return res From 55d47d3ee9afe19f212b17a12644043fb0763a3c Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 21 Aug 2013 09:48:38 -0400 Subject: [PATCH 003/151] use jsonobject.exceptions.BadValueError --- couchdbkit/exceptions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/couchdbkit/exceptions.py b/couchdbkit/exceptions.py index 3e7def1..e9d5f03 100644 --- a/couchdbkit/exceptions.py +++ b/couchdbkit/exceptions.py @@ -7,6 +7,7 @@ All exceptions used in couchdbkit. """ from restkit.errors import ResourceError +import jsonobject.exceptions class InvalidAttachment(Exception): """ raised when an attachment is invalid """ @@ -15,9 +16,7 @@ class DuplicatePropertyError(Exception): """ exception raised when there is a duplicate property in a model """ -class BadValueError(Exception): - """ exception raised when a value can't be validated - or is required """ +BadValueError = jsonobject.exceptions.BadValueError class MultipleResultsFound(Exception): """ exception raised when more than one object is From 2bcd3f3ae340b750f6858331720f0fd3c323d888 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 21 Aug 2013 14:43:15 -0400 Subject: [PATCH 004/151] use exclude_if_none instead of removing from doc in to_json() --- couchdbkit/schema/base.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 779d955..c08ce99 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -79,19 +79,12 @@ def __delattr__(self, name): class DocumentBase(DocumentSchema): - _id = jsonobject.StringProperty() - _rev = jsonobject.StringProperty() - _attachments = jsonobject.DictProperty() + _id = jsonobject.StringProperty(exclude_if_none=True) + _rev = jsonobject.StringProperty(exclude_if_none=True) + _attachments = jsonobject.DictProperty(exclude_if_none=True) _db = None - def to_json(self): - doc = copy.copy(super(DocumentBase, self).to_json()) - for special in ('_id', '_rev', '_attachments'): - if not doc[special]: - del doc[special] - return doc - # The rest of this class is mostly copied from couchdbkit 0.5.7 @classmethod From 98f47f3c121b8c3362397b5d4ab03d979dcd34bd Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 21 Aug 2013 16:27:06 -0400 Subject: [PATCH 005/151] remove support for clone() --- couchdbkit/schema/base.py | 3 --- tests/test_schema.py | 17 ----------------- 2 files changed, 20 deletions(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index c08ce99..bafb34b 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -61,9 +61,6 @@ def _dynamic_properties(self): from jsonobject.base import get_dynamic_properties return get_dynamic_properties(self) - def clone(self, **kwargs): - cls = self.__class__ - def __delitem__(self, key): try: super(DocumentSchema, self).__delitem__(key) diff --git a/tests/test_schema.py b/tests/test_schema.py index 729a99c..cafe4d2 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -312,23 +312,6 @@ class TestDoc2(TestDoc): self.assert_(len(doc2._dynamic_properties) == 1) - def testClone(self): - class A(DocumentSchema): - s = StringProperty() - - class B(Document): - a = SchemaProperty(A) - s1 = StringProperty() - - b = B() - b.s1 = "test1" - b.a.s = "test" - b1 = b.clone() - - self.assert_(b1.s1 == "test1") - self.assert_('s' in b1._doc['a']) - self.assert_(b1.a.s == "test") - def testView(self): class TestDoc(Document): field1 = StringProperty() From 761aa8f3412d87498c9661c36ce67e8bcbf42ae0 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Thu, 22 Aug 2013 18:55:26 -0400 Subject: [PATCH 006/151] use _validate_lazily --- couchdbkit/schema/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index bafb34b..9dd49d9 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -44,10 +44,13 @@ def __new__(mcs, name, bases, dct): cls._doc_type = doc_type return cls + class DocumentSchema(jsonobject.JsonObject): __metaclass__ = SchemaProperties + _validate_lazily = True + @jsonobject.StringProperty def doc_type(self): return self._doc_type From cbfe07ae2fd374c3d46d621d065beea827f8c4a9 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Thu, 22 Aug 2013 18:59:52 -0400 Subject: [PATCH 007/151] change tests i do not like --- tests/test_schema.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index cafe4d2..36c5c86 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -873,19 +873,18 @@ class Foo( Document ): def bad_value(): doc.bar.foo = "bla" self.assertRaises(BadValueError, bad_value) - - + def testDynamicSchemaProperty(self): from datetime import datetime class A(DocumentSchema): s = StringProperty() - + a = A(s="foo") class B(Document): s1 = StringProperty() s2 = StringProperty() - sm = SchemaProperty(a) + sm = SchemaProperty(A, default=lambda: a) b = B() self.assert_(b._doc == {'doc_type': 'B', 's1': None, 's2': None, @@ -1305,28 +1304,13 @@ class A(Document): self.assert_(len(a.l) == 1) self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10)) self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10Z']}) - a.l.append({ 's': "test"}) - self.assert_(a.l == [datetime(2009, 4, 13, 22, 56, 10), {'s': 'test'}]) - self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10Z', {'s': 'test'}]} - ) - a.save() - + b = A.get(a._id) self.assert_(len(b.l) == 2) self.assert_(b.l[0] == datetime(2009, 4, 13, 22, 56, 10)) - self.assert_(b._doc['l'] == ['2009-04-13T22:56:10Z', {'s': 'test'}]) - - - a = A(l=["a", "b", "c"]) - a.save() - b = self.db.get(a._id, wrapper=A.wrap) - self.assert_(a.l == ["a", "b", "c"]) - b.l = [] - self.assert_(b.l == []) - self.assert_(b.to_json()['l'] == []) - - + self.assert_(b._doc['l'] == ['2009-04-13T22:56:10Z']) + def testListPropertyNotEmpty(self): from datetime import datetime class A(Document): @@ -1570,8 +1554,8 @@ class A(Document): class A2(Document): d = DictProperty() a2 = A2() - self.assertTrue(a2.validate(required=False)) - self.assertTrue(a2.validate()) + # self.assertTrue(a2.validate(required=False)) + self.assertIsNone(a2.validate()) def testDynamicDictProperty(self): from datetime import datetime From 3776f70707103d3546843cd5a8788550a712f374 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 01:46:22 -0400 Subject: [PATCH 008/151] use jsonobject SetProperty --- couchdbkit/schema/properties.py | 1 - 1 file changed, 1 deletion(-) diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index cad833d..fd95c94 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -13,7 +13,6 @@ DecimalProperty = None TimeProperty = None -SetProperty = None dict_to_json = None list_to_json = None From 1eaa0e1a4a4e8a9a55f38678e833fe8d7f04018a Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 17:57:43 -0400 Subject: [PATCH 009/151] rename _validate_lazily to _validate_required_lazily --- couchdbkit/schema/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 9dd49d9..2e316b8 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -49,7 +49,7 @@ class DocumentSchema(jsonobject.JsonObject): __metaclass__ = SchemaProperties - _validate_lazily = True + _validate_required_lazily = True @jsonobject.StringProperty def doc_type(self): From ed205c335ed5a55880ad01ecec98d793329d9b87 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 17:58:10 -0400 Subject: [PATCH 010/151] fix DocumentBase.save --- couchdbkit/schema/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 2e316b8..0ca85ec 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -105,15 +105,15 @@ def save(self, **params): @params db: couchdbkit.core.Database instance """ - # self.validate() + self.validate() db = self.get_db() doc = self.to_json() db.save_doc(doc, **params) if '_id' in doc and '_rev' in doc: - self.update(doc) + self._doc.update(doc) elif '_id' in doc: - self.update({'_id': doc['_id']}) + self._doc.update({'_id': doc['_id']}) store = save From e9956d72a81c63fdad2f373d536ccf8fde68317c Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 17:58:54 -0400 Subject: [PATCH 011/151] use jsonobject's DecimalProperty and TimeProperty --- couchdbkit/schema/properties.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index fd95c94..7c97fd3 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -11,9 +11,6 @@ StringListProperty = functools.partial(ListProperty, unicode) SchemaDictProperty = DictProperty -DecimalProperty = None -TimeProperty = None - dict_to_json = None list_to_json = None value_to_json = None From b76ea1dfb3ea10076b7a3673d200af8880a783d3 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 17:59:09 -0400 Subject: [PATCH 012/151] fix ProxyDict --- couchdbkit/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/utils.py b/couchdbkit/utils.py index c4858bc..eb45217 100644 --- a/couchdbkit/utils.py +++ b/couchdbkit/utils.py @@ -215,4 +215,4 @@ def __init__(self, parent, *args, **kwargs): self.parent = parent def __setitem__(self, key, value): - self.parent[key] = value + self.parent.set_raw_value(key, value) From ae953ec3399d05770bcc6d3a89eda9ec479fb39c Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 18:00:50 -0400 Subject: [PATCH 013/151] remove support for creating schemas as instances of DocumentSchema --- tests/test_schema.py | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 36c5c86..6cee42d 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -839,35 +839,12 @@ class A(Document): self.assert_(b.s is None) self.assert_(b._doc['s'] is None) - def testSchemaBuild(self): - schema = DocumentSchema(i = IntegerProperty()) - C = DocumentSchema.build(**schema._dynamic_properties) - self.assert_('i' in C._properties) - self.assert_(isinstance(C.i, IntegerProperty)) - - c = C() - self.assert_(c._doc_type == 'AnonymousSchema') - self.assert_(c._doc == {'doc_type': 'AnonymousSchema', 'i': - None}) - - - schema2 = DocumentSchema(i = IntegerProperty(default=-1)) - C3 = DocumentSchema.build(**schema2._dynamic_properties) - c3 = C3() - - self.assert_(c3._doc == {'doc_type': 'AnonymousSchema', 'i': - -1}) - self.assert_(c3.i == -1) - - def bad_value(): - c3.i = "test" - - self.assertRaises(BadValueError, bad_value) - self.assert_(c3.i == -1) - def testSchemaPropertyValidation2(self): + class Bar(DocumentSchema): + foo = IntegerProperty() + class Foo( Document ): - bar = SchemaProperty(DocumentSchema(foo=IntegerProperty())) + bar = SchemaProperty(Bar) doc = Foo() def bad_value(): From 6e4dcd95450f829254b7aa18e7991dc9181ea232 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 18:02:01 -0400 Subject: [PATCH 014/151] remove support for using schema instance as item_type --- tests/test_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 6cee42d..87fb95e 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -805,11 +805,11 @@ class DocOne(Document): class DocTwo(Document): name = StringProperty() - one = SchemaProperty(DocOne()) + one = SchemaProperty(DocOne) class DocThree(Document): name = StringProperty() - two = SchemaProperty(DocTwo()) + two = SchemaProperty(DocTwo) one = DocOne(name='one') two = DocTwo(name='two', one=one) From 34cfc62fa18fe4805b1fcda96697c9916465d2ab Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 18:02:40 -0400 Subject: [PATCH 015/151] change error message expected in test --- tests/test_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 87fb95e..d159ae9 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1076,7 +1076,7 @@ class B(Document): self.assertEqual(b.slm.index(a1, 1, -2), 2) with self.assertRaises(ValueError) as cm: b.slm.index(a3) - self.assertEqual(str(cm.exception), 'list.index(x): x not in list') + self.assertEqual(str(cm.exception), '{0} is not in list'.format(a3)) def testSchemaListPropertyInsert(self): @@ -1168,7 +1168,7 @@ class B(Document): }) with self.assertRaises(ValueError) as cm: b.slm.remove(a1) - self.assertEqual(str(cm.exception), 'list.remove(x): x not in list') + self.assertEqual(str(cm.exception), '{0} is not in list'.format(a1)) def testSchemaListPropertyReverse(self): From 3dcf4fee1d5e661a3a11d800f572b53c748d6476 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 18:03:16 -0400 Subject: [PATCH 016/151] finish fixing a test --- tests/test_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index d159ae9..be86948 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1284,7 +1284,7 @@ class A(Document): a.save() b = A.get(a._id) - self.assert_(len(b.l) == 2) + self.assert_(len(b.l) == 1) self.assert_(b.l[0] == datetime(2009, 4, 13, 22, 56, 10)) self.assert_(b._doc['l'] == ['2009-04-13T22:56:10Z']) From 7eb4208fa96d3cf9aaf4c9f3e9465d7b1eb3a11d Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 18:07:18 -0400 Subject: [PATCH 017/151] change tests to expect validate to return None --- tests/test_schema.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index be86948..0c73b49 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1312,9 +1312,9 @@ class A(Document): class A2(Document): l = ListProperty() - a2 = A2() - self.assertTrue(a2.validate(required=False)) - self.assertTrue(a2.validate()) + a2 = A2() + self.assertIsNone(a2.validate(required=False)) + self.assertIsNone(a2.validate()) def testListPropertyWithType(self): @@ -1329,9 +1329,9 @@ class B(Document): ls = StringListProperty() b = B() b.ls.append(u"test") - self.assertTrue(b.validate()) b.ls.append(datetime.utcnow()) self.assertRaises(BadValueError, b.validate) + self.assertIsNone(b.validate()) b1 = B() b1.ls = [u'hello', u'123'] @@ -1531,9 +1531,9 @@ class A(Document): class A2(Document): d = DictProperty() a2 = A2() - # self.assertTrue(a2.validate(required=False)) + self.assertIsNone(a2.validate(required=False)) self.assertIsNone(a2.validate()) - + def testDynamicDictProperty(self): from datetime import datetime class A(Document): From d6378bb419e90a975bb32fc5f157255be75622a6 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 18:09:24 -0400 Subject: [PATCH 018/151] change tests to expect adding the wrong type of element to a typed container to fail fast raises BadValueError --- tests/test_schema.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 0c73b49..8bee372 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1321,17 +1321,17 @@ def testListPropertyWithType(self): from datetime import datetime class A(Document): l = ListProperty(item_type=datetime) - a = A() - a.l.append("test") - self.assertRaises(BadValueError, a.validate) + a = A() + with self.assertRaises(BadValueError): + a.l.append("test") class B(Document): ls = StringListProperty() b = B() b.ls.append(u"test") - b.ls.append(datetime.utcnow()) - self.assertRaises(BadValueError, b.validate) self.assertIsNone(b.validate()) + with self.assertRaises(BadValueError): + b.ls.append(datetime.utcnow()) b1 = B() b1.ls = [u'hello', u'123'] From 8aff64d3ce8ac0195b6e3c4f9fcd041b4afb353e Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 23 Aug 2013 18:10:19 -0400 Subject: [PATCH 019/151] change tests to expect containers to normalize their values eagerly --- tests/test_schema.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 8bee372..1326931 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1543,7 +1543,7 @@ class A(Document): a.d = {} a.d['test'] = { 'a': datetime(2009, 5, 10, 21, 19, 21, 127380) } - self.assert_(a.d == {'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380)}}) + self.assert_(a.d == {'test': {'a': datetime(2009, 5, 10, 21, 19, 21)}}) self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z'}}, 'doc_type': 'A'} ) a.d['test']['b'] = "essai" @@ -1551,7 +1551,7 @@ class A(Document): a.d['essai'] = "test" self.assert_(a.d == {'essai': 'test', - 'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380), + 'test': {'a': datetime(2009, 5, 10, 21, 19, 21), 'b': 'essai'}} ) self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, @@ -1564,7 +1564,7 @@ class A(Document): a.d['test']['essai'] = { "a": datetime(2009, 5, 10, 21, 21, 11, 425782) } self.assert_(a.d == {'essai': 'test', 'test': {'b': 'essai', - 'essai': {'a': datetime(2009, 5, 10, 21, 21, 11, 425782)}}} + 'essai': {'a': datetime(2009, 5, 10, 21, 21, 11)}}} ) self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'b': 'essai', 'essai': {'a': '2009-05-10T21:21:11Z'}}}, @@ -1619,7 +1619,7 @@ class A(Document): a.l.append(1) a.l.append(datetime(2009, 5, 12, 13, 35, 9, 425701)) a.l.append({ 's': "test"}) - self.assert_(a.l == [1, datetime(2009, 5, 12, 13, 35, 9, 425701), {'s': 'test'}]) + self.assert_(a.l == [1, datetime(2009, 5, 12, 13, 35, 9), {'s': 'test'}]) self.assert_(a._doc == {'doc_type': 'A', 'l': [1, '2009-05-12T13:35:09Z', {'s': 'test'}]} ) a.l[2]['date'] = datetime(2009, 5, 12, 13, 35, 9, 425701) @@ -1638,8 +1638,8 @@ class A(Document): a.l[2]['s'] = 'test edited' self.assert_(a.l == [1, - datetime(2009, 5, 12, 13, 35, 9, 425701), - {'date': datetime(2009, 5, 12, 13, 35, 9, 425701), + datetime(2009, 5, 12, 13, 35, 9), + {'date': datetime(2009, 5, 12, 13, 35, 9), 's': 'test edited'}] ) self.assert_(a._doc['l'] == [1, From 56d3549f21434459e526e73eb3fdd6cecef7f7d5 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sat, 24 Aug 2013 12:55:15 -0400 Subject: [PATCH 020/151] expose ALLOWED_PROPERTY_TYPES back --- couchdbkit/schema/properties.py | 1 + 1 file changed, 1 insertion(+) diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index 7c97fd3..7f7f4f5 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -4,6 +4,7 @@ # See the NOTICE for more information. import functools from jsonobject.properties import * +from jsonobject.convert import ALLOWED_PROPERTY_TYPES Property = JsonProperty SchemaProperty = ObjectProperty From f4746981d472e82fe685769f92cc540f04b6b4ed Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sat, 24 Aug 2013 15:08:33 -0400 Subject: [PATCH 021/151] raise AttributeError on __getitem__ --- couchdbkit/schema/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 0ca85ec..31ee95c 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -76,6 +76,12 @@ def __delattr__(self, name): except DeleteNotAllowed: setattr(self, name, None) + def __getitem__(self, item): + try: + return super(DocumentSchema, self).__getitem__(item) + except KeyError as e: + raise AttributeError(e) + class DocumentBase(DocumentSchema): From a7a56d292a3ef3c7586a64fcb2662b35ec12459e Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 25 Aug 2013 17:37:43 -0400 Subject: [PATCH 022/151] have Lazy{Dict,List,Set} point to Json{Dict,Array,Set} --- couchdbkit/schema/properties.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index 7f7f4f5..cdad225 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -20,6 +20,7 @@ list_to_python = None convert_property = None value_to_property = None -LazyDict = None -LazyList = None -LazySet = None + +LazyDict = JsonDict +LazyList = JsonArray +LazySet = JsonSet From fbe0aa82823ff3c893bac67aa8d48bc87cff1b4c Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 26 Aug 2013 15:49:00 -0400 Subject: [PATCH 023/151] expose dynamic_properties() --- couchdbkit/schema/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 31ee95c..2599225 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -64,6 +64,9 @@ def _dynamic_properties(self): from jsonobject.base import get_dynamic_properties return get_dynamic_properties(self) + def dynamic_properties(self): + return self._dynamic_properties.copy() + def __delitem__(self, key): try: super(DocumentSchema, self).__delitem__(key) From d121fd7ca0f7d9af35139f41027e393472b484bc Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 26 Aug 2013 15:50:56 -0400 Subject: [PATCH 024/151] have _attachment default to None --- couchdbkit/schema/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 2599225..bdc8ec4 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -90,7 +90,7 @@ class DocumentBase(DocumentSchema): _id = jsonobject.StringProperty(exclude_if_none=True) _rev = jsonobject.StringProperty(exclude_if_none=True) - _attachments = jsonobject.DictProperty(exclude_if_none=True) + _attachments = jsonobject.DictProperty(exclude_if_none=True, default=None) _db = None From 31d27d3a9eddd302c15146b29d36c7f7d85ae8e9 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 26 Aug 2013 18:17:51 -0400 Subject: [PATCH 025/151] allow to delete properties from document._doc --- couchdbkit/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchdbkit/utils.py b/couchdbkit/utils.py index eb45217..6ef6fa9 100644 --- a/couchdbkit/utils.py +++ b/couchdbkit/utils.py @@ -216,3 +216,6 @@ def __init__(self, parent, *args, **kwargs): def __setitem__(self, key, value): self.parent.set_raw_value(key, value) + + def __delattr__(self, key): + del self.parent[key] From e5e77614b19dbf31affa69011fb6fef0e5d2f514 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 27 Aug 2013 15:39:52 -0400 Subject: [PATCH 026/151] update doc manually on save (doc.update has been removed) --- couchdbkit/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 4af4061..1981021 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -485,7 +485,8 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, doc1.update({'_id': res['id'], '_rev': res['rev']}) if schema: - doc.update(doc.__class__.wrap(doc1)) + for key, value in doc.__class__.wrap(doc1).iteritems(): + doc[key] = value else: doc.update(doc1) return res From c6699e114974e7c7e43a877cff0899b93bbee2cd Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 27 Aug 2013 16:00:58 -0400 Subject: [PATCH 027/151] fix ProxyDict behavior to call super and to define __delitem__ not __delattr__! --- couchdbkit/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/couchdbkit/utils.py b/couchdbkit/utils.py index 6ef6fa9..6081e94 100644 --- a/couchdbkit/utils.py +++ b/couchdbkit/utils.py @@ -216,6 +216,8 @@ def __init__(self, parent, *args, **kwargs): def __setitem__(self, key, value): self.parent.set_raw_value(key, value) + super(ProxyDict, self).__setitem__(key, value) - def __delattr__(self, key): + def __delitem__(self, key): del self.parent[key] + super(ProxyDict, self).__delitem__(key) From 0e791a2350101c46a5bb7fae0052167edb00de99 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 27 Aug 2013 16:01:08 -0400 Subject: [PATCH 028/151] add __copy__ to ProxyDict --- couchdbkit/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchdbkit/utils.py b/couchdbkit/utils.py index 6081e94..05e83a7 100644 --- a/couchdbkit/utils.py +++ b/couchdbkit/utils.py @@ -221,3 +221,6 @@ def __setitem__(self, key, value): def __delitem__(self, key): del self.parent[key] super(ProxyDict, self).__delitem__(key) + + def __copy__(self): + return self.copy() From 3c7b6473f3455fb69b4b483bb616a69948539b61 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 27 Aug 2013 14:29:29 -0400 Subject: [PATCH 029/151] WIP --- couchdbkit/schema/properties.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index cdad225..3d85e6a 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -4,14 +4,35 @@ # See the NOTICE for more information. import functools from jsonobject.properties import * +from jsonobject.base import DefaultProperty from jsonobject.convert import ALLOWED_PROPERTY_TYPES -Property = JsonProperty SchemaProperty = ObjectProperty SchemaListProperty = ListProperty StringListProperty = functools.partial(ListProperty, unicode) SchemaDictProperty = DictProperty + +class Property(DefaultProperty): + def wrap(self, obj): + try: + return self.to_python(obj) + except NotImplementedError: + return super(Property, self).wrap(obj) + + def unwrap(self, obj): + try: + return obj, self.to_json(obj) + except NotImplementedError: + return super(Property, self).unwrap(obj) + + def to_python(self, value): + raise NotImplementedError() + + def to_json(self, value): + raise NotImplementedError() + + dict_to_json = None list_to_json = None value_to_json = None From 97739b60bf614a91960a1962a248cbb8aa1fc9be Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 28 Aug 2013 14:12:01 -0400 Subject: [PATCH 030/151] clean up properties.py --- couchdbkit/schema/properties.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index 3d85e6a..de17659 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -5,12 +5,14 @@ import functools from jsonobject.properties import * from jsonobject.base import DefaultProperty -from jsonobject.convert import ALLOWED_PROPERTY_TYPES +from jsonobject.convert import ( + ALLOWED_PROPERTY_TYPES, + MAP_TYPES_PROPERTIES, + value_to_python, + value_to_property +) -SchemaProperty = ObjectProperty -SchemaListProperty = ListProperty StringListProperty = functools.partial(ListProperty, unicode) -SchemaDictProperty = DictProperty class Property(DefaultProperty): @@ -33,14 +35,15 @@ def to_json(self, value): raise NotImplementedError() -dict_to_json = None -list_to_json = None -value_to_json = None -value_to_python = None -dict_to_python = None -list_to_python = None -convert_property = None -value_to_property = None +def _not_implemented(*args, **kwargs): + raise NotImplementedError() + +dict_to_json = _not_implemented +list_to_json = _not_implemented +value_to_json = _not_implemented +dict_to_python = _not_implemented +list_to_python = _not_implemented +convert_property = _not_implemented LazyDict = JsonDict LazyList = JsonArray From 5a792ae9bde0a6ad3d8ddca7a8c22e9fa471a553 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 28 Aug 2013 15:13:03 -0400 Subject: [PATCH 031/151] add jsonobject dependency --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e202a41..8fc3721 100755 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ install_requires = [ 'restkit>=3.3', + 'jsonobject>=0.2.0', ], entry_points=""" From 0e7ecc3292ca8f0e8e1ec3f501ff3e027c6ffe72 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 1 Sep 2013 19:31:07 -0400 Subject: [PATCH 032/151] add back _doc_type_attr feature --- couchdbkit/schema/base.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index bf973a7..8728448 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -43,10 +43,19 @@ def valid_id(value): class SchemaProperties(jsonobject.JsonObjectMeta): def __new__(mcs, name, bases, dct): - if isinstance(dct.get('doc_type'), basestring): - doc_type = dct.pop('doc_type') + if '_doc_type_attr' in dct: + doc_type_attr = dct['_doc_type_attr'] + else: + doc_type_attr = ( + super(SchemaProperties, mcs).__new__(mcs, '', bases, {}) + )._doc_type_attr + if isinstance(dct.get(doc_type_attr), basestring): + doc_type = dct.pop(doc_type_attr) else: doc_type = name + dct[doc_type_attr] = jsonobject.StringProperty( + default=lambda self: self._doc_type + ) cls = super(SchemaProperties, mcs).__new__(mcs, name, bases, dct) cls._doc_type = doc_type return cls @@ -59,10 +68,6 @@ class DocumentSchema(jsonobject.JsonObject): _validate_required_lazily = True _doc_type_attr = 'doc_type' - @jsonobject.StringProperty - def doc_type(self): - return self._doc_type - @property def _doc(self): return ProxyDict(self, self._obj) From 337f1cf7105a79008ca34b4105386a525c8d287f Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 1 Sep 2013 20:03:26 -0400 Subject: [PATCH 033/151] fix tests for python 2.6 list.index has a different native error message in 2.6 and 2.7 --- tests/test_schema.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 18242d6..42cf3c5 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1140,7 +1140,6 @@ class B(Document): {'doc_type': 'A', 's': unicode(a2.s)}] }) - def testSchemaListPropertyIndex(self): """SchemaListProperty index method """ @@ -1164,8 +1163,10 @@ class B(Document): self.assertEqual(b.slm.index(a1, 1, -2), 2) with self.assertRaises(ValueError) as cm: b.slm.index(a3) - self.assertEqual(str(cm.exception), '{0} is not in list'.format(a3)) - + self.assertIn(str(cm.exception), ( + '{0!r} is not in list'.format(a3), + 'list.index(x): x not in list' + )) def testSchemaListPropertyInsert(self): """SchemaListProperty insert method @@ -1256,8 +1257,10 @@ class B(Document): }) with self.assertRaises(ValueError) as cm: b.slm.remove(a1) - self.assertEqual(str(cm.exception), '{0} is not in list'.format(a1)) - + self.assertIn(str(cm.exception), ( + '{0!r} is not in list'.format(a1), + 'list.index(x): x not in list' + )) def testSchemaListPropertyReverse(self): """SchemaListProperty reverse method From 91bb8e0e5fd5bfe33c952eb24f5991741462fb78 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 1 Oct 2013 18:10:50 -0400 Subject: [PATCH 034/151] set up for pypi jsonboject-couchdbkit --- couchdbkit/version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index e7a28bf..a9f0d53 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5) +version_info = (0, 6, 5, 0) __version__ = ".".join(map(str, version_info)) diff --git a/setup.py b/setup.py index 2b8a025..e0eff49 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( - name = 'couchdbkit', + name = 'jsonobject-couchdbkit', version = version.__version__, description = 'Python couchdb kit', From 109b241b2193ac22b4dfa025f3aca3fc5b83134a Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 1 Oct 2013 18:36:28 -0400 Subject: [PATCH 035/151] update readme --- README.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 25d9a0d..8a7ce83 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,10 @@ +About the jsonobject fork of couchdbkit +--------------------------------------- +`jsonobject-couchdbkit`_ is a fork of couchdbkit that replaces couchdbkit.schema +with a thin wrapper around jsonobject +(which was, incidentally, written as a **way** faster replacement +for couchdbkit.schema.) See `jsonobject`_. + About ----- @@ -155,6 +162,8 @@ greets:: greets = Greeting.view('greeting/all') +.. _jsonobject-couchdbkit: https://github.com/dannyroberts/couchdbkit/tree/jsonobject +.. _jsonobject: http://github.com/dannyroberts/jsonobject .. _Couchdbkit: http://couchdbkit.org .. _API: http://couchdbkit.org/doc/api/ -.. _couchapp: http://github.com/couchapp/couchapp/tree/ +.. _couchapp: http://github.com/couchapp/couchapp/tree/ \ No newline at end of file From 206647494131582430ebc62310061a02e1fe3450 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 6 Oct 2013 14:55:10 -0400 Subject: [PATCH 036/151] declare that jsonobject-couchdbkit provides couchdbkit so it does not need to be installed separately --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e0eff49..68523cb 100755 --- a/setup.py +++ b/setup.py @@ -55,7 +55,9 @@ 'restkit>=4.2.2', 'jsonobject>=0.2.0', ], - + provides=[ + 'couchdbkit', + ], entry_points=""" [couchdbkit.consumers] sync=couchdbkit.consumer.sync:SyncConsumer From 94587cac42fa70278793dcbc846cc2cf50f8f842 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 6 Oct 2013 14:56:13 -0400 Subject: [PATCH 037/151] bump version to 0.6.5.1 --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index a9f0d53..3a81a72 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 0) +version_info = (0, 6, 5, 1) __version__ = ".".join(map(str, version_info)) From 9dab888cee366eced3eb5af913fa1ec6a04d7211 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 6 Oct 2013 16:13:17 -0400 Subject: [PATCH 038/151] declare that jsonobject-couchdbkit obsoletes couchdbkit so both cannot be installed --- setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 68523cb..9d21e1d 100755 --- a/setup.py +++ b/setup.py @@ -55,9 +55,8 @@ 'restkit>=4.2.2', 'jsonobject>=0.2.0', ], - provides=[ - 'couchdbkit', - ], + provides=['couchdbkit'], + obsoletes=['couchdbkit'], entry_points=""" [couchdbkit.consumers] sync=couchdbkit.consumer.sync:SyncConsumer From 7e8450b9e515366f0c2e4bcdf9552974103910fe Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 6 Oct 2013 16:15:09 -0400 Subject: [PATCH 039/151] bump version to 0.6.5.2 --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 3a81a72..20388e1 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 1) +version_info = (0, 6, 5, 2) __version__ = ".".join(map(str, version_info)) From 3bbd1ec5647d79b625915b57c6cdfc60ba6f00ef Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 3 Nov 2013 14:29:09 +0200 Subject: [PATCH 040/151] update jsonobject ref for precise datetimes --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9d21e1d..969c45f 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject>=0.2.0', + 'jsonobject>=0.3.0', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From 53179919a526f3d8a2fa35f4b86e5cdc5643b0d1 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 1 Nov 2013 18:57:20 +0200 Subject: [PATCH 041/151] fix tests to reflect precise datetimes --- tests/test_schema.py | 70 ++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 972e0ec..9615a7d 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -961,7 +961,7 @@ class B(Document): 'sm': {'doc_type': 'A', 's': u'foo'}}) b.created = datetime(2009, 2, 6, 18, 58, 20, 905556) - self.assert_(b._doc == {'created': '2009-02-06T18:58:20Z', + self.assert_(b._doc == {'created': '2009-02-06T18:58:20.905556Z', 'doc_type': 'B', 's1': None, 's2': None, @@ -970,14 +970,14 @@ class B(Document): self.assert_(isinstance(b.created, datetime) == True) a.created = datetime(2009, 2, 6, 20, 58, 20, 905556) - self.assert_(a._doc == {'created': '2009-02-06T20:58:20Z', + self.assert_(a._doc == {'created': '2009-02-06T20:58:20.905556Z', 'doc_type': 'A', 's': u'foo'}) - self.assert_(b._doc == {'created': '2009-02-06T18:58:20Z', + self.assert_(b._doc == {'created': '2009-02-06T18:58:20.905556Z', 'doc_type': 'B', 's1': None, 's2': None, - 'sm': {'created': '2009-02-06T20:58:20Z', 'doc_type': 'A', + 'sm': {'created': '2009-02-06T20:58:20.905556Z', 'doc_type': 'A', 's': u'foo'}}) @@ -1375,14 +1375,14 @@ class A(Document): d = datetime(2009, 4, 13, 22, 56, 10, 967388) a.l.append(d) self.assert_(len(a.l) == 1) - self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10)) - self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10Z']}) + self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10, 967388)) + self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10.967388Z']}) a.save() b = A.get(a._id) self.assert_(len(b.l) == 1) - self.assert_(b.l[0] == datetime(2009, 4, 13, 22, 56, 10)) - self.assert_(b._doc['l'] == ['2009-04-13T22:56:10Z']) + self.assert_(b.l[0] == datetime(2009, 4, 13, 22, 56, 10, 967388)) + self.assert_(b._doc['l'] == ['2009-04-13T22:56:10.967388Z']) def testListPropertyNotEmpty(self): from datetime import datetime @@ -1401,8 +1401,8 @@ class A(Document): d = datetime(2009, 4, 13, 22, 56, 10, 967388) a.l.append(d) self.assert_(len(a.l) == 1) - self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10)) - self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10Z']}) + self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10, 967388)) + self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10.967388Z']}) a.validate() class A2(Document): @@ -1559,18 +1559,18 @@ class A(Document): a = A() a.d['test'] = { 'a': datetime(2009, 5, 10, 21, 19, 21, 127380) } - self.assert_(a.d == { 'test': {'a': datetime(2009, 5, 10, 21, 19, 21)}}) - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z'}}, 'doc_type': 'A'} ) + self.assert_(a.d == { 'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380)}}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z'}}, 'doc_type': 'A'} ) a.d['test']['b'] = "essai" - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, 'doc_type': 'A'}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, 'doc_type': 'A'}) a.d['essai'] = "test" self.assert_(a.d == {'essai': 'test', - 'test': {'a': datetime(2009, 5, 10, 21, 19, 21), + 'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380), 'b': 'essai'}} ) - self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, + self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, 'doc_type': 'A'}) del a.d['test']['a'] @@ -1618,8 +1618,8 @@ class A(Document): d = datetime(2009, 4, 13, 22, 56, 10, 967388) a.d['date'] = d - self.assert_(a.d['date'] == datetime(2009, 4, 13, 22, 56, 10)) - self.assert_(a._doc == {'doc_type': 'A', 'd': { 'date': '2009-04-13T22:56:10Z' }}) + self.assert_(a.d['date'] == datetime(2009, 4, 13, 22, 56, 10, 967388)) + self.assert_(a._doc == {'doc_type': 'A', 'd': { 'date': '2009-04-13T22:56:10.967388Z' }}) a.save() class A2(Document): @@ -1637,18 +1637,18 @@ class A(Document): a.d = {} a.d['test'] = { 'a': datetime(2009, 5, 10, 21, 19, 21, 127380) } - self.assert_(a.d == {'test': {'a': datetime(2009, 5, 10, 21, 19, 21)}}) - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z'}}, 'doc_type': 'A'} ) + self.assert_(a.d == {'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380)}}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z'}}, 'doc_type': 'A'} ) a.d['test']['b'] = "essai" - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, 'doc_type': 'A'}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, 'doc_type': 'A'}) a.d['essai'] = "test" self.assert_(a.d == {'essai': 'test', - 'test': {'a': datetime(2009, 5, 10, 21, 19, 21), + 'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380), 'b': 'essai'}} ) - self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, + self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, 'doc_type': 'A'}) del a.d['test']['a'] @@ -1658,10 +1658,10 @@ class A(Document): a.d['test']['essai'] = { "a": datetime(2009, 5, 10, 21, 21, 11, 425782) } self.assert_(a.d == {'essai': 'test', 'test': {'b': 'essai', - 'essai': {'a': datetime(2009, 5, 10, 21, 21, 11)}}} + 'essai': {'a': datetime(2009, 5, 10, 21, 21, 11, 425782)}}} ) self.assert_(a._doc == {'d': {'essai': 'test', - 'test': {'b': 'essai', 'essai': {'a': '2009-05-10T21:21:11Z'}}}, + 'test': {'b': 'essai', 'essai': {'a': '2009-05-10T21:21:11.425782Z'}}}, 'doc_type': 'A'} ) @@ -1729,32 +1729,32 @@ class A(Document): a.l.append(1) a.l.append(datetime(2009, 5, 12, 13, 35, 9, 425701)) a.l.append({ 's': "test"}) - self.assert_(a.l == [1, datetime(2009, 5, 12, 13, 35, 9), {'s': 'test'}]) - self.assert_(a._doc == {'doc_type': 'A', 'l': [1, '2009-05-12T13:35:09Z', {'s': 'test'}]} + self.assert_(a.l == [1, datetime(2009, 5, 12, 13, 35, 9, 425701), {'s': 'test'}]) + self.assert_(a._doc == {'doc_type': 'A', 'l': [1, '2009-05-12T13:35:09.425701Z', {'s': 'test'}]} ) a.l[2]['date'] = datetime(2009, 5, 12, 13, 35, 9, 425701) - self.assert_(a._doc == {'doc_type': 'A', + self.assertEqual(a._doc, {'doc_type': 'A', 'l': [1, - '2009-05-12T13:35:09Z', - {'date': '2009-05-12T13:35:09Z', 's': 'test'}]} + '2009-05-12T13:35:09.425701Z', + {'date': '2009-05-12T13:35:09.425701Z', 's': 'test'}]} ) a.save() a1 = A.get(a._id) self.assert_(a1.l == [1, - datetime(2009, 5, 12, 13, 35, 9), - {u'date': datetime(2009, 5, 12, 13, 35, 9), u's': u'test'}] + datetime(2009, 5, 12, 13, 35, 9, 425701), + {u'date': datetime(2009, 5, 12, 13, 35, 9, 425701), u's': u'test'}] ) a.l[2]['s'] = 'test edited' self.assert_(a.l == [1, - datetime(2009, 5, 12, 13, 35, 9), - {'date': datetime(2009, 5, 12, 13, 35, 9), + datetime(2009, 5, 12, 13, 35, 9, 425701), + {'date': datetime(2009, 5, 12, 13, 35, 9, 425701), 's': 'test edited'}] ) self.assert_(a._doc['l'] == [1, - '2009-05-12T13:35:09Z', - {'date': '2009-05-12T13:35:09Z', 's': 'test edited'}] + '2009-05-12T13:35:09.425701Z', + {'date': '2009-05-12T13:35:09.425701Z', 's': 'test edited'}] ) From 19ea58d04996a33d9a09d6098dfe01cdb0f74182 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Sun, 3 Nov 2013 14:46:20 +0200 Subject: [PATCH 042/151] update version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 20388e1..5cf965e 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 2) +version_info = (0, 6, 5, 3) __version__ = ".".join(map(str, version_info)) From 306a5b4828eb59c2a43e943e250e501d566b60ca Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 3 Feb 2014 11:26:32 -0600 Subject: [PATCH 043/151] Revert "Merge pull request #3 from dannyroberts/precise-datetime" This reverts commit 8f30aec80092d0f06d6f0b107b4169f732831cfe, reversing changes made to 7e8450b9e515366f0c2e4bcdf9552974103910fe. --- setup.py | 2 +- tests/test_schema.py | 70 ++++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/setup.py b/setup.py index 969c45f..9d21e1d 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject>=0.3.0', + 'jsonobject>=0.2.0', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], diff --git a/tests/test_schema.py b/tests/test_schema.py index 9615a7d..972e0ec 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -961,7 +961,7 @@ class B(Document): 'sm': {'doc_type': 'A', 's': u'foo'}}) b.created = datetime(2009, 2, 6, 18, 58, 20, 905556) - self.assert_(b._doc == {'created': '2009-02-06T18:58:20.905556Z', + self.assert_(b._doc == {'created': '2009-02-06T18:58:20Z', 'doc_type': 'B', 's1': None, 's2': None, @@ -970,14 +970,14 @@ class B(Document): self.assert_(isinstance(b.created, datetime) == True) a.created = datetime(2009, 2, 6, 20, 58, 20, 905556) - self.assert_(a._doc == {'created': '2009-02-06T20:58:20.905556Z', + self.assert_(a._doc == {'created': '2009-02-06T20:58:20Z', 'doc_type': 'A', 's': u'foo'}) - self.assert_(b._doc == {'created': '2009-02-06T18:58:20.905556Z', + self.assert_(b._doc == {'created': '2009-02-06T18:58:20Z', 'doc_type': 'B', 's1': None, 's2': None, - 'sm': {'created': '2009-02-06T20:58:20.905556Z', 'doc_type': 'A', + 'sm': {'created': '2009-02-06T20:58:20Z', 'doc_type': 'A', 's': u'foo'}}) @@ -1375,14 +1375,14 @@ class A(Document): d = datetime(2009, 4, 13, 22, 56, 10, 967388) a.l.append(d) self.assert_(len(a.l) == 1) - self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10, 967388)) - self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10.967388Z']}) + self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10)) + self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10Z']}) a.save() b = A.get(a._id) self.assert_(len(b.l) == 1) - self.assert_(b.l[0] == datetime(2009, 4, 13, 22, 56, 10, 967388)) - self.assert_(b._doc['l'] == ['2009-04-13T22:56:10.967388Z']) + self.assert_(b.l[0] == datetime(2009, 4, 13, 22, 56, 10)) + self.assert_(b._doc['l'] == ['2009-04-13T22:56:10Z']) def testListPropertyNotEmpty(self): from datetime import datetime @@ -1401,8 +1401,8 @@ class A(Document): d = datetime(2009, 4, 13, 22, 56, 10, 967388) a.l.append(d) self.assert_(len(a.l) == 1) - self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10, 967388)) - self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10.967388Z']}) + self.assert_(a.l[0] == datetime(2009, 4, 13, 22, 56, 10)) + self.assert_(a._doc == {'doc_type': 'A', 'l': ['2009-04-13T22:56:10Z']}) a.validate() class A2(Document): @@ -1559,18 +1559,18 @@ class A(Document): a = A() a.d['test'] = { 'a': datetime(2009, 5, 10, 21, 19, 21, 127380) } - self.assert_(a.d == { 'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380)}}) - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z'}}, 'doc_type': 'A'} ) + self.assert_(a.d == { 'test': {'a': datetime(2009, 5, 10, 21, 19, 21)}}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z'}}, 'doc_type': 'A'} ) a.d['test']['b'] = "essai" - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, 'doc_type': 'A'}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, 'doc_type': 'A'}) a.d['essai'] = "test" self.assert_(a.d == {'essai': 'test', - 'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380), + 'test': {'a': datetime(2009, 5, 10, 21, 19, 21), 'b': 'essai'}} ) - self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, + self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, 'doc_type': 'A'}) del a.d['test']['a'] @@ -1618,8 +1618,8 @@ class A(Document): d = datetime(2009, 4, 13, 22, 56, 10, 967388) a.d['date'] = d - self.assert_(a.d['date'] == datetime(2009, 4, 13, 22, 56, 10, 967388)) - self.assert_(a._doc == {'doc_type': 'A', 'd': { 'date': '2009-04-13T22:56:10.967388Z' }}) + self.assert_(a.d['date'] == datetime(2009, 4, 13, 22, 56, 10)) + self.assert_(a._doc == {'doc_type': 'A', 'd': { 'date': '2009-04-13T22:56:10Z' }}) a.save() class A2(Document): @@ -1637,18 +1637,18 @@ class A(Document): a.d = {} a.d['test'] = { 'a': datetime(2009, 5, 10, 21, 19, 21, 127380) } - self.assert_(a.d == {'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380)}}) - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z'}}, 'doc_type': 'A'} ) + self.assert_(a.d == {'test': {'a': datetime(2009, 5, 10, 21, 19, 21)}}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z'}}, 'doc_type': 'A'} ) a.d['test']['b'] = "essai" - self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, 'doc_type': 'A'}) + self.assert_(a._doc == {'d': {'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, 'doc_type': 'A'}) a.d['essai'] = "test" self.assert_(a.d == {'essai': 'test', - 'test': {'a': datetime(2009, 5, 10, 21, 19, 21, 127380), + 'test': {'a': datetime(2009, 5, 10, 21, 19, 21), 'b': 'essai'}} ) - self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21.127380Z', 'b': 'essai'}}, + self.assert_(a._doc == {'d': {'essai': 'test', 'test': {'a': '2009-05-10T21:19:21Z', 'b': 'essai'}}, 'doc_type': 'A'}) del a.d['test']['a'] @@ -1658,10 +1658,10 @@ class A(Document): a.d['test']['essai'] = { "a": datetime(2009, 5, 10, 21, 21, 11, 425782) } self.assert_(a.d == {'essai': 'test', 'test': {'b': 'essai', - 'essai': {'a': datetime(2009, 5, 10, 21, 21, 11, 425782)}}} + 'essai': {'a': datetime(2009, 5, 10, 21, 21, 11)}}} ) self.assert_(a._doc == {'d': {'essai': 'test', - 'test': {'b': 'essai', 'essai': {'a': '2009-05-10T21:21:11.425782Z'}}}, + 'test': {'b': 'essai', 'essai': {'a': '2009-05-10T21:21:11Z'}}}, 'doc_type': 'A'} ) @@ -1729,32 +1729,32 @@ class A(Document): a.l.append(1) a.l.append(datetime(2009, 5, 12, 13, 35, 9, 425701)) a.l.append({ 's': "test"}) - self.assert_(a.l == [1, datetime(2009, 5, 12, 13, 35, 9, 425701), {'s': 'test'}]) - self.assert_(a._doc == {'doc_type': 'A', 'l': [1, '2009-05-12T13:35:09.425701Z', {'s': 'test'}]} + self.assert_(a.l == [1, datetime(2009, 5, 12, 13, 35, 9), {'s': 'test'}]) + self.assert_(a._doc == {'doc_type': 'A', 'l': [1, '2009-05-12T13:35:09Z', {'s': 'test'}]} ) a.l[2]['date'] = datetime(2009, 5, 12, 13, 35, 9, 425701) - self.assertEqual(a._doc, {'doc_type': 'A', + self.assert_(a._doc == {'doc_type': 'A', 'l': [1, - '2009-05-12T13:35:09.425701Z', - {'date': '2009-05-12T13:35:09.425701Z', 's': 'test'}]} + '2009-05-12T13:35:09Z', + {'date': '2009-05-12T13:35:09Z', 's': 'test'}]} ) a.save() a1 = A.get(a._id) self.assert_(a1.l == [1, - datetime(2009, 5, 12, 13, 35, 9, 425701), - {u'date': datetime(2009, 5, 12, 13, 35, 9, 425701), u's': u'test'}] + datetime(2009, 5, 12, 13, 35, 9), + {u'date': datetime(2009, 5, 12, 13, 35, 9), u's': u'test'}] ) a.l[2]['s'] = 'test edited' self.assert_(a.l == [1, - datetime(2009, 5, 12, 13, 35, 9, 425701), - {'date': datetime(2009, 5, 12, 13, 35, 9, 425701), + datetime(2009, 5, 12, 13, 35, 9), + {'date': datetime(2009, 5, 12, 13, 35, 9), 's': 'test edited'}] ) self.assert_(a._doc['l'] == [1, - '2009-05-12T13:35:09.425701Z', - {'date': '2009-05-12T13:35:09.425701Z', 's': 'test edited'}] + '2009-05-12T13:35:09Z', + {'date': '2009-05-12T13:35:09Z', 's': 'test edited'}] ) From a652891cb484b13e637a17e423a7fbab50dbeae3 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 3 Feb 2014 11:28:09 -0600 Subject: [PATCH 044/151] bump version this is really just a revert of version 3 to version 2 but want this to be the highest default --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 5cf965e..e35f4c3 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 3) +version_info = (0, 6, 5, 4) __version__ = ".".join(map(str, version_info)) From 0994bbdad12c319c90000c734aa584a3c28d83cb Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 9 Apr 2014 11:55:27 -0400 Subject: [PATCH 045/151] use jsonobject>=0.4.0, bump version to 0.6.5.5 --- couchdbkit/version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index e35f4c3..b85302e 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 4) +version_info = (0, 6, 5, 5) __version__ = ".".join(map(str, version_info)) diff --git a/setup.py b/setup.py index 9d21e1d..277e6cf 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject>=0.2.0', + 'jsonobject>=0.4.0', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From 63185272253db8d7f5f940e212e2f11cc397d984 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 9 Sep 2014 18:17:32 -0400 Subject: [PATCH 046/151] change tests to reflect minor change in error message --- tests/test_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 972e0ec..281fb40 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -44,7 +44,7 @@ class Test(Document): try: doc.bar="bla" except AttributeError, e: - self.assert_(str(e) == "bar is not defined in schema (not a valid property)") + self.assertEqual(str(e), "'bar' is not defined in schema (not a valid property)") doc.save() self.assert_(not hasattr(doc, "bar")) assert doc._doc['foo'] == "test" @@ -59,7 +59,7 @@ class Test(StaticDocument): try: doc.bar="bla" except AttributeError, e: - self.assert_(str(e) == "bar is not defined in schema (not a valid property)") + self.assertEqual(str(e), "'bar' is not defined in schema (not a valid property)") doc.save() self.assert_(not hasattr(doc, "bar")) self.assert_(doc._doc['foo'] == "test") From 12163558d1bf19adc914a26f533134ddb4f3945e Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 8 Sep 2014 14:55:21 -0400 Subject: [PATCH 047/151] make changes to accomodate jsonobject type config changes --- couchdbkit/__init__.py | 2 +- couchdbkit/ext/django/schema.py | 6 +++--- couchdbkit/schema/__init__.py | 4 ---- couchdbkit/schema/base.py | 6 +++--- couchdbkit/schema/properties.py | 6 ------ 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/couchdbkit/__init__.py b/couchdbkit/__init__.py index 600ebff..f309e1a 100644 --- a/couchdbkit/__init__.py +++ b/couchdbkit/__init__.py @@ -22,7 +22,7 @@ Property, IntegerProperty, DecimalProperty, BooleanProperty, FloatProperty, StringProperty, DateTimeProperty, DateProperty, TimeProperty, dict_to_json, dict_to_json, dict_to_json, - value_to_python, dict_to_python, + dict_to_python, DocumentSchema, DocumentBase, Document, StaticDocument, contain, QueryMixin, AttachmentMixin, SchemaProperty, SchemaListProperty, SchemaDictProperty, diff --git a/couchdbkit/ext/django/schema.py b/couchdbkit/ext/django/schema.py index ddd2334..237c0c7 100644 --- a/couchdbkit/ext/django/schema.py +++ b/couchdbkit/ext/django/schema.py @@ -34,7 +34,7 @@ 'DecimalProperty', 'BooleanProperty', 'FloatProperty', 'DateTimeProperty', 'DateProperty', 'TimeProperty', 'dict_to_json', 'list_to_json', 'value_to_json', - 'value_to_python', 'dict_to_python', 'list_to_python', + 'dict_to_python', 'list_to_python', 'convert_property', 'DocumentSchema', 'Document', 'SchemaProperty', 'SchemaListProperty', 'ListProperty', 'DictProperty', 'StringDictProperty', 'StringListProperty', @@ -42,7 +42,8 @@ DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', - 'app_label') + 'app_label', 'string_conversions', 'properties', + 'update_properties') class Options(object): """ class based on django.db.models.options. We only keep @@ -180,7 +181,6 @@ def get_db(cls): dict_to_json = schema.dict_to_json list_to_json = schema.list_to_json value_to_json = schema.value_to_json -value_to_python = schema.value_to_python dict_to_python = schema.dict_to_python list_to_python = schema.list_to_python convert_property = schema.convert_property diff --git a/couchdbkit/schema/__init__.py b/couchdbkit/schema/__init__.py index 03b5399..b6fb5e5 100644 --- a/couchdbkit/schema/__init__.py +++ b/couchdbkit/schema/__init__.py @@ -156,7 +156,6 @@ class A(Dcoument): threadsafe. """ from .properties import ( - ALLOWED_PROPERTY_TYPES, Property, StringProperty, IntegerProperty, @@ -174,12 +173,9 @@ class A(Dcoument): dict_to_json, list_to_json, value_to_json, - MAP_TYPES_PROPERTIES, - value_to_python, dict_to_python, list_to_python, convert_property, - value_to_property, LazyDict, LazyList) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 8728448..50a23b8 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -13,14 +13,14 @@ from couchdbkit.utils import ProxyDict from ..exceptions import ResourceNotFound, ReservedWordError from . import properties as p -from .properties import value_to_python, \ -convert_property, MAP_TYPES_PROPERTIES, ALLOWED_PROPERTY_TYPES, \ +from .properties import \ +convert_property, \ LazyDict, LazyList from ..exceptions import DuplicatePropertyError, ResourceNotFound, \ ReservedWordError -__all__ = ['ReservedWordError', 'ALLOWED_PROPERTY_TYPES', 'DocumentSchema', +__all__ = ['ReservedWordError', 'DocumentSchema', 'SchemaProperties', 'DocumentBase', 'QueryMixin', 'AttachmentMixin', 'Document', 'StaticDocument', 'valid_id'] diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index 56d5361..8758f5a 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -5,12 +5,6 @@ import functools from jsonobject.properties import * from jsonobject.base import DefaultProperty -from jsonobject.convert import ( - ALLOWED_PROPERTY_TYPES, - MAP_TYPES_PROPERTIES, - value_to_python, - value_to_property -) try: from collections import MutableSet, Iterable From 30cf2b94c5627038e7307035565a1fbc63937652 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Mon, 8 Sep 2014 14:59:55 -0400 Subject: [PATCH 048/151] update jsonobject requirement --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 277e6cf..d6aea57 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject>=0.4.0', + 'jsonobject>=0.6.0', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From f2cfeea3aa1b4ac566f5274bb6a9f7a97aece8ac Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 9 Sep 2014 18:04:48 -0400 Subject: [PATCH 049/151] change jsonobject version to 0.6.0b1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d6aea57..7650af7 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject>=0.6.0', + 'jsonobject>=0.6.0b1', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From f0c5332ca3f2bda4527df323efbd5cca31a89467 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Tue, 9 Sep 2014 18:40:56 -0400 Subject: [PATCH 050/151] drop support for python 2.6 --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c85e3f1..97c1777 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,7 @@ language: python services: - couchdb -python: - - 2.6 - - 2.7 +python: 2.7 install: - pip install -r requirements_dev.txt --use-mirrors From 268b2f6ac1e76a193319aa413e93c5dee518772f Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 12 Sep 2014 16:25:39 -0400 Subject: [PATCH 051/151] update version to 0.6.5.6.b1 --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index b85302e..0564e7f 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 5) +version_info = (0, 6, 5, 6, 'b1') __version__ = ".".join(map(str, version_info)) From fc4739fe887ece2ca404e709e3b5161cefa3ed3d Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Fri, 3 Oct 2014 18:01:39 -0400 Subject: [PATCH 052/151] fix for jsonobject==0.6.0.b2 --- couchdbkit/utils.py | 2 +- couchdbkit/version.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/couchdbkit/utils.py b/couchdbkit/utils.py index 97f0c22..6086d80 100644 --- a/couchdbkit/utils.py +++ b/couchdbkit/utils.py @@ -214,7 +214,7 @@ def read_json(filename, use_environment=False): import jsonobject.base -class ProxyDict(jsonobject.base.SimpleDict): +class ProxyDict(jsonobject.utils.SimpleDict): def __init__(self, parent, *args, **kwargs): super(ProxyDict, self).__init__(*args, **kwargs) self.parent = parent diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 0564e7f..006d282 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 6, 'b1') +version_info = (0, 6, 5, 6, 'b2') __version__ = ".".join(map(str, version_info)) diff --git a/setup.py b/setup.py index 7650af7..de3be2c 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject>=0.6.0b1', + 'jsonobject>=0.6.0b2', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From 5dd4d31dcdebcce5316f2142bb5c887fc49ef4a5 Mon Sep 17 00:00:00 2001 From: Daniel Roberts Date: Wed, 15 Oct 2014 14:04:44 -0400 Subject: [PATCH 053/151] release 0.6.5.6 --- couchdbkit/version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 006d282..964b8f2 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 6, 'b2') +version_info = (0, 6, 5, 6) __version__ = ".".join(map(str, version_info)) diff --git a/setup.py b/setup.py index de3be2c..d6aea57 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject>=0.6.0b2', + 'jsonobject>=0.6.0', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From 366c09d7bd0e3a9e47e229f215ef4dc2c065b00e Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Jul 2015 11:43:33 -0400 Subject: [PATCH 054/151] Update README.rst --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 8a7ce83..667595c 100644 --- a/README.rst +++ b/README.rst @@ -162,8 +162,8 @@ greets:: greets = Greeting.view('greeting/all') -.. _jsonobject-couchdbkit: https://github.com/dannyroberts/couchdbkit/tree/jsonobject -.. _jsonobject: http://github.com/dannyroberts/jsonobject +.. _jsonobject-couchdbkit: https://github.com/dimagi/couchdbkit/tree/jsonobject +.. _jsonobject: http://github.com/dimagi/jsonobject .. _Couchdbkit: http://couchdbkit.org .. _API: http://couchdbkit.org/doc/api/ -.. _couchapp: http://github.com/couchapp/couchapp/tree/ \ No newline at end of file +.. _couchapp: http://github.com/couchapp/couchapp/tree/ From 8eade7f329773dda84a0c65fc0e6e3b95122a063 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Jul 2015 13:33:30 -0400 Subject: [PATCH 055/151] Bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 964b8f2..770e79e 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 6) +version_info = (0, 6, 5, 7) __version__ = ".".join(map(str, version_info)) From 63dabffb68c80c491ca3e81bce2129a01fefa6d2 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 28 Aug 2015 11:57:14 -0400 Subject: [PATCH 056/151] SortedDict -> OrderedDict --- couchdbkit/ext/django/forms.py | 6 +++--- couchdbkit/ext/django/loading.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/couchdbkit/ext/django/forms.py b/couchdbkit/ext/django/forms.py index 8b5e4ab..964a2e9 100644 --- a/couchdbkit/ext/django/forms.py +++ b/couchdbkit/ext/django/forms.py @@ -78,8 +78,8 @@ """ +from collections import OrderedDict from django.utils.text import capfirst -from django.utils.datastructures import SortedDict from django.forms.util import ErrorList from django.forms.forms import BaseForm, get_declared_fields from django.forms import fields as f @@ -120,7 +120,7 @@ def document_to_dict(instance, properties=None, exclude=None): def fields_for_document(document, properties=None, exclude=None): """ - Returns a ``SortedDict`` containing form fields for the given document. + Returns a ``OrderedDict`` containing form fields for the given document. ``properties`` is an optional list of properties names. If provided, only the named properties will be included in the returned properties. @@ -162,7 +162,7 @@ def fields_for_document(document, properties=None, exclude=None): field_list.append((prop.name, FIELDS_PROPERTES_MAPPING[property_class_name](**defaults))) - return SortedDict(field_list) + return OrderedDict(field_list) class DocumentFormOptions(object): def __init__(self, options=None): diff --git a/couchdbkit/ext/django/loading.py b/couchdbkit/ext/django/loading.py index e89bcd4..f097dbf 100644 --- a/couchdbkit/ext/django/loading.py +++ b/couchdbkit/ext/django/loading.py @@ -21,6 +21,7 @@ import sys import os +from collections import OrderedDict from restkit import BasicAuth from couchdbkit import Server @@ -28,7 +29,6 @@ from couchdbkit.resource import CouchdbResource from couchdbkit.exceptions import ResourceNotFound from django.conf import settings -from django.utils.datastructures import SortedDict COUCHDB_DATABASES = getattr(settings, "COUCHDB_DATABASES", []) COUCHDB_TIMEOUT = getattr(settings, "COUCHDB_TIMEOUT", 300) @@ -39,7 +39,7 @@ class CouchdbkitHandler(object): # share state between instances __shared_state__ = dict( _databases = {}, - app_schema = SortedDict() + app_schema = OrderedDict() ) def __init__(self, databases): @@ -183,7 +183,7 @@ def register_schema(self, app_label, *schema): """ register a Document object""" for s in schema: schema_name = schema[0].__name__.lower() - schema_dict = self.app_schema.setdefault(app_label, SortedDict()) + schema_dict = self.app_schema.setdefault(app_label, OrderedDict()) if schema_name in schema_dict: fname1 = os.path.abspath(sys.modules[s.__module__].__file__) fname2 = os.path.abspath(sys.modules[schema_dict[schema_name].__module__].__file__) @@ -193,7 +193,7 @@ def register_schema(self, app_label, *schema): def get_schema(self, app_label, schema_name): """ retriev Document object from its name and app name """ - return self.app_schema.get(app_label, SortedDict()).get(schema_name.lower()) + return self.app_schema.get(app_label, OrderedDict()).get(schema_name.lower()) couchdbkit_handler = CouchdbkitHandler(COUCHDB_DATABASES) register_schema = couchdbkit_handler.register_schema From cce58bc202ffca0729b25e9afddcee1a2fe37b04 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 28 Aug 2015 11:57:31 -0400 Subject: [PATCH 057/151] require python 2.7 --- setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index d6aea57..5280b06 100755 --- a/setup.py +++ b/setup.py @@ -7,8 +7,8 @@ import os import sys -if not hasattr(sys, 'version_info') or sys.version_info < (2, 6, 0, 'final'): - raise SystemExit("couchdbkit requires Python 2.6 or later.") +if not hasattr(sys, 'version_info') or sys.version_info < (2, 7, 0, 'final'): + raise SystemExit("couchdbkit requires Python 2.7 or later.") from setuptools import setup, find_packages @@ -40,8 +40,6 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Database', 'Topic :: Utilities', From b243233001d2675f6b779dfc8bc4ef93b39fc6a9 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Mon, 31 Aug 2015 10:41:38 -0400 Subject: [PATCH 058/151] version 0.7.0 --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 770e79e..cec3a5e 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 6, 5, 7) +version_info = (0, 7, 0, 0) __version__ = ".".join(map(str, version_info)) From 568253eb9f5afca67ad6fdcbde801b5274524c24 Mon Sep 17 00:00:00 2001 From: Cory Zue Date: Fri, 18 Sep 2015 14:19:03 -0400 Subject: [PATCH 059/151] make logging its own module --- couchdbkit/__init__.py | 28 +--------------------------- couchdbkit/logging.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 27 deletions(-) create mode 100644 couchdbkit/logging.py diff --git a/couchdbkit/__init__.py b/couchdbkit/__init__.py index f309e1a..29c6bdd 100644 --- a/couchdbkit/__init__.py +++ b/couchdbkit/__init__.py @@ -29,30 +29,4 @@ ListProperty, DictProperty, StringDictProperty, StringListProperty, SetProperty ) -import logging - -LOG_LEVELS = { - "critical": logging.CRITICAL, - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, - "debug": logging.DEBUG -} - -def set_logging(level, handler=None): - """ - Set level of logging, and choose where to display/save logs - (file or standard output). - """ - if not handler: - handler = logging.StreamHandler() - - loglevel = LOG_LEVELS.get(level, logging.INFO) - logger = logging.getLogger('couchdbkit') - logger.setLevel(loglevel) - format = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s" - datefmt = r"%Y-%m-%d %H:%M:%S" - - handler.setFormatter(logging.Formatter(format, datefmt)) - logger.addHandler(handler) - +from .logging import (LOG_LEVELS, set_logging) diff --git a/couchdbkit/logging.py b/couchdbkit/logging.py new file mode 100644 index 0000000..ff00f5c --- /dev/null +++ b/couchdbkit/logging.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +import logging + +LOG_LEVELS = { + "critical": logging.CRITICAL, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, + "debug": logging.DEBUG +} + +def set_logging(level, handler=None): + """ + Set level of logging, and choose where to display/save logs + (file or standard output). + """ + if not handler: + handler = logging.StreamHandler() + + loglevel = LOG_LEVELS.get(level, logging.INFO) + logger = logging.getLogger('couchdbkit') + logger.setLevel(loglevel) + format = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s" + datefmt = r"%Y-%m-%d %H:%M:%S" + + handler.setFormatter(logging.Formatter(format, datefmt)) + logger.addHandler(handler) From 94f71780b2325f2299b92b81e87a5e37bce230c1 Mon Sep 17 00:00:00 2001 From: Cory Zue Date: Fri, 18 Sep 2015 14:20:25 -0400 Subject: [PATCH 060/151] expose logger object --- couchdbkit/__init__.py | 2 +- couchdbkit/logging.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/couchdbkit/__init__.py b/couchdbkit/__init__.py index 29c6bdd..1d7a35a 100644 --- a/couchdbkit/__init__.py +++ b/couchdbkit/__init__.py @@ -29,4 +29,4 @@ ListProperty, DictProperty, StringDictProperty, StringListProperty, SetProperty ) -from .logging import (LOG_LEVELS, set_logging) +from .logging import (LOG_LEVELS, set_logging, logger) diff --git a/couchdbkit/logging.py b/couchdbkit/logging.py index ff00f5c..c990b1a 100644 --- a/couchdbkit/logging.py +++ b/couchdbkit/logging.py @@ -9,6 +9,10 @@ "debug": logging.DEBUG } + +logger = logging.getLogger('couchdbkit') + + def set_logging(level, handler=None): """ Set level of logging, and choose where to display/save logs @@ -18,7 +22,6 @@ def set_logging(level, handler=None): handler = logging.StreamHandler() loglevel = LOG_LEVELS.get(level, logging.INFO) - logger = logging.getLogger('couchdbkit') logger.setLevel(loglevel) format = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s" datefmt = r"%Y-%m-%d %H:%M:%S" From c822ff7b2fb86d86dbce6367db748b1704efca10 Mon Sep 17 00:00:00 2001 From: Cory Zue Date: Fri, 18 Sep 2015 16:48:10 -0400 Subject: [PATCH 061/151] add request logger and log requests --- couchdbkit/logging.py | 1 + couchdbkit/resource.py | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/couchdbkit/logging.py b/couchdbkit/logging.py index c990b1a..e817f3f 100644 --- a/couchdbkit/logging.py +++ b/couchdbkit/logging.py @@ -11,6 +11,7 @@ logger = logging.getLogger('couchdbkit') +request_logger = logging.getLogger('couchdbkit.request') def set_logging(level, handler=None): diff --git a/couchdbkit/resource.py b/couchdbkit/resource.py index c0a6470..0526aa7 100644 --- a/couchdbkit/resource.py +++ b/couchdbkit/resource.py @@ -21,6 +21,7 @@ """ import base64 import re +from datetime import datetime from restkit import Resource, ClientResponse from restkit.errors import ResourceError, RequestFailed, RequestError @@ -30,6 +31,7 @@ from .exceptions import ResourceNotFound, ResourceConflict, \ PreconditionFailed from .utils import json +from .logging import request_logger USER_AGENT = 'couchdbkit/%s' % __version__ @@ -94,6 +96,10 @@ def request(self, method, path=None, payload=None, headers=None, **params): @return: tuple (data, resp), where resp is an `httplib2.Response` object and data a python object (often a dict). """ + # logging information + start_time = datetime.utcnow() + error_status = end_time = None + has_error = False headers = headers or {} headers.setdefault('Accept', 'application/json') @@ -109,7 +115,7 @@ def request(self, method, path=None, payload=None, headers=None, **params): try: resp = Resource.request(self, method, path=path, payload=payload, headers=headers, **params) - + end_time = datetime.utcnow() except ResourceError, e: msg = getattr(e, 'msg', '') if e.response and msg: @@ -124,6 +130,9 @@ def request(self, method, path=None, payload=None, headers=None, **params): else: error = msg + end_time = datetime.utcnow() + has_error = True + error_status = e.status_int if e.status_int == 404: raise ResourceNotFound(error, http_code=404, response=e.response) @@ -138,6 +147,19 @@ def request(self, method, path=None, payload=None, headers=None, **params): raise except: raise + finally: + duration = end_time-start_time + logging_context = dict( + method=method, + path=path, + params=params, + start_time=start_time, + end_time=end_time, + has_error=has_error, + error_status=error_status, + duration=duration, + ) + request_logger.debug('{} to {} took {}'.format(method, path, duration), extra=logging_context) return resp From 0ac5d9b69fabf1bf7763e7304e0d19d681bf2e6e Mon Sep 17 00:00:00 2001 From: Cory Zue Date: Fri, 18 Sep 2015 16:49:23 -0400 Subject: [PATCH 062/151] bump version number --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index cec3a5e..0055aaf 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 0, 0) +version_info = (0, 7, 0, 1) __version__ = ".".join(map(str, version_info)) From 1072443299d627b47badb8c19c0bc9dc291124ee Mon Sep 17 00:00:00 2001 From: Cory Zue Date: Fri, 18 Sep 2015 16:56:01 -0400 Subject: [PATCH 063/151] fix end_time reference in error cases --- couchdbkit/resource.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/couchdbkit/resource.py b/couchdbkit/resource.py index 0526aa7..dafc94f 100644 --- a/couchdbkit/resource.py +++ b/couchdbkit/resource.py @@ -98,7 +98,7 @@ def request(self, method, path=None, payload=None, headers=None, **params): """ # logging information start_time = datetime.utcnow() - error_status = end_time = None + error_status = None has_error = False headers = headers or {} @@ -115,7 +115,6 @@ def request(self, method, path=None, payload=None, headers=None, **params): try: resp = Resource.request(self, method, path=path, payload=payload, headers=headers, **params) - end_time = datetime.utcnow() except ResourceError, e: msg = getattr(e, 'msg', '') if e.response and msg: @@ -148,7 +147,8 @@ def request(self, method, path=None, payload=None, headers=None, **params): except: raise finally: - duration = end_time-start_time + end_time = datetime.utcnow() + duration = end_time - start_time logging_context = dict( method=method, path=path, From c3d7696e6251c055b851aeeb7e94573a584e2475 Mon Sep 17 00:00:00 2001 From: Cory Zue Date: Sun, 20 Sep 2015 10:43:34 -0400 Subject: [PATCH 064/151] remove redundant assignment --- couchdbkit/resource.py | 1 - 1 file changed, 1 deletion(-) diff --git a/couchdbkit/resource.py b/couchdbkit/resource.py index dafc94f..c89362f 100644 --- a/couchdbkit/resource.py +++ b/couchdbkit/resource.py @@ -129,7 +129,6 @@ def request(self, method, path=None, payload=None, headers=None, **params): else: error = msg - end_time = datetime.utcnow() has_error = True error_status = e.status_int if e.status_int == 404: From 7485c88a1f62152acbd31c35b5b224b4431ebd9d Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Fri, 25 Sep 2015 18:59:18 -0400 Subject: [PATCH 065/151] log bulk save errors --- couchdbkit/client.py | 7 +++++++ couchdbkit/logging.py | 1 + 2 files changed, 8 insertions(+) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 28100f7..1a896dd 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -26,6 +26,7 @@ >>> del server['simplecouchdb_test'] """ +from couchdbkit.logging import error_logger UNKOWN_INFO = {} @@ -593,6 +594,12 @@ def is_id(doc): for i, res in enumerate(results): if 'error' in res: errors.append(res) + logging_context = dict( + method='save_docs', + params=params, + error=res['error'], + ) + error_logger.error("save_docs error", extra=logging_context) else: if docs_schema[i]: docs[i]._doc.update({ diff --git a/couchdbkit/logging.py b/couchdbkit/logging.py index e817f3f..a2a6f7f 100644 --- a/couchdbkit/logging.py +++ b/couchdbkit/logging.py @@ -12,6 +12,7 @@ logger = logging.getLogger('couchdbkit') request_logger = logging.getLogger('couchdbkit.request') +error_logger = logging.getLogger('couchdbkit.error') def set_logging(level, handler=None): From 85fcbd9f990c578bed2e6cbe556d12031538fe0f Mon Sep 17 00:00:00 2001 From: Daniel Miller Date: Thu, 19 Nov 2015 15:19:06 -0500 Subject: [PATCH 066/151] Add support for abstract meta property for mixins --- couchdbkit/ext/django/schema.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchdbkit/ext/django/schema.py b/couchdbkit/ext/django/schema.py index e1c4a2a..ac5f6e4 100644 --- a/couchdbkit/ext/django/schema.py +++ b/couchdbkit/ext/django/schema.py @@ -124,6 +124,9 @@ def __new__(cls, name, bases, attrs): else: meta = attr_meta + if getattr(meta, 'abstract', False): + return new_class + if getattr(meta, 'app_label', None) is None: document_module = sys.modules[new_class.__module__] app_label = document_module.__name__.split('.')[-2] From efb8f5937734b320c65a88a183bd34bb2de60b7c Mon Sep 17 00:00:00 2001 From: Daniel Miller Date: Fri, 20 Nov 2015 12:16:29 -0500 Subject: [PATCH 067/151] Bump version to 0.7.1.0 Changes: - Add support for couchdbkit.ext.django.schema.Document.Meta.abstract --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 0055aaf..cb53fdb 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 0, 1) +version_info = (0, 7, 1, 0) __version__ = ".".join(map(str, version_info)) From 5b9e30c81e41dcb87cb594b71f112d3da3e09c8c Mon Sep 17 00:00:00 2001 From: Ethan Soergel Date: Mon, 1 Feb 2016 17:42:42 -0500 Subject: [PATCH 068/151] Add Document.bulk_delete, analagous to bulk_save --- couchdbkit/schema/base.py | 24 +++++++++++++++++++++--- tests/test_schema.py | 23 +++++++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 50a23b8..d5bf3ed 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -152,13 +152,31 @@ def save_docs(cls, docs, use_uuids=True, all_or_nothing=False): """ db = cls.get_db() - docs_to_save= [doc for doc in docs if doc._doc_type == cls._doc_type] - if not len(docs_to_save) == len(docs): + if any(doc._doc_type != cls._doc_type for doc in docs): raise ValueError("one of your documents does not have the correct type") - db.bulk_save(docs_to_save, use_uuids=use_uuids, all_or_nothing=all_or_nothing) + db.bulk_save(docs, use_uuids=use_uuids, all_or_nothing=all_or_nothing) bulk_save = save_docs + @classmethod + def delete_docs(cls, docs, all_or_nothing=False, empty_on_delete=False): + """ Bulk delete documents in a database + + @params docs: list of couchdbkit.schema.Document instance + @param all_or_nothing: In the case of a power failure, when the database + restarts either all the changes will have been saved or none of them. + However, it does not do conflict checking, so the documents will + be committed even if this creates conflicts. + @param empty_on_delete: default is False if you want to make + """ + db = cls.get_db() + if any(doc._doc_type != cls._doc_type for doc in docs): + raise ValueError("one of your documents does not have the correct type") + db.bulk_delete(docs, all_or_nothing=all_or_nothing, + empty_on_delete=empty_on_delete) + + bulk_delete = delete_docs + @classmethod def get(cls, docid, rev=None, db=None, dynamic_properties=True): """ get document with `docid` diff --git a/tests/test_schema.py b/tests/test_schema.py index 281fb40..5d33a4e 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -213,8 +213,6 @@ class Test2(Document): self.server.delete_db('couchdbkit_test') - - def testGet(self): db = self.server.create_db('couchdbkit_test') class Test(Document): @@ -637,6 +635,27 @@ class Test(Document): self.server.delete_db('couchdbkit_test') + def testDocumentBulkDelete(self): + db = self.server.create_db('couchdbkit_test') + + class Test(Document): + string = StringProperty() + Test._db = db + + doc = Test() + doc.string = "test" + doc.save() + doc2 = Test() + doc2.string = "test2" + doc2.save() + + Test.bulk_delete([doc, doc2]) + + with self.assertRaises(ResourceNotFound): + Test.get(doc._id) + + self.server.delete_db('couchdbkit_test') + class PropertyTestCase(unittest.TestCase): From dab2b278c0abac53102b307f65826cdf7b63985a Mon Sep 17 00:00:00 2001 From: Ethan Soergel Date: Mon, 1 Feb 2016 17:50:08 -0500 Subject: [PATCH 069/151] bump version number to update via pip --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index cb53fdb..78d1081 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 1, 0) +version_info = (0, 7, 2, 0) __version__ = ".".join(map(str, version_info)) From a501109fea1f0e2f95c7c11159e4092ba4297cb5 Mon Sep 17 00:00:00 2001 From: Ethan Soergel Date: Tue, 2 Feb 2016 09:30:54 -0500 Subject: [PATCH 070/151] Finish docstring --- couchdbkit/schema/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index d5bf3ed..83de279 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -168,6 +168,8 @@ def delete_docs(cls, docs, all_or_nothing=False, empty_on_delete=False): However, it does not do conflict checking, so the documents will be committed even if this creates conflicts. @param empty_on_delete: default is False if you want to make + sure the doc is emptied and will not be stored as is in Apache + CouchDB. """ db = cls.get_db() if any(doc._doc_type != cls._doc_type for doc in docs): From 00396bfd3f76d037306e1c32710c95e3e0a4b0dc Mon Sep 17 00:00:00 2001 From: Ethan Soergel Date: Tue, 2 Feb 2016 09:33:20 -0500 Subject: [PATCH 071/151] Check that both docs are deleted --- tests/test_schema.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 5d33a4e..79a32fd 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -651,8 +651,9 @@ class Test(Document): Test.bulk_delete([doc, doc2]) - with self.assertRaises(ResourceNotFound): - Test.get(doc._id) + for doc_id in [doc._id, doc2._id]: + with self.assertRaises(ResourceNotFound): + Test.get(doc_id) self.server.delete_db('couchdbkit_test') From 50a9789dc10642e179c3dd485bb7a68cb4a7e870 Mon Sep 17 00:00:00 2001 From: Cal Ellowitz Date: Wed, 23 Mar 2016 11:22:11 -0400 Subject: [PATCH 072/151] making sure doc list for bulk save is accessable by index --- couchdbkit/schema/base.py | 2 +- couchdbkit/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 83de279..8b6cd3a 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -154,7 +154,7 @@ def save_docs(cls, docs, use_uuids=True, all_or_nothing=False): db = cls.get_db() if any(doc._doc_type != cls._doc_type for doc in docs): raise ValueError("one of your documents does not have the correct type") - db.bulk_save(docs, use_uuids=use_uuids, all_or_nothing=all_or_nothing) + db.bulk_save(tuple(docs), use_uuids=use_uuids, all_or_nothing=all_or_nothing) bulk_save = save_docs diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 78d1081..a29c906 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 2, 0) +version_info = (0, 7, 3, 0) __version__ = ".".join(map(str, version_info)) From 116f2de3c2ab6955b93d7d090cae749deeebad3d Mon Sep 17 00:00:00 2001 From: Cal Ellowitz Date: Wed, 23 Mar 2016 14:07:56 -0400 Subject: [PATCH 073/151] moving indexing check to inside save_docs --- couchdbkit/client.py | 2 ++ couchdbkit/schema/base.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 1a896dd..d3c3081 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -558,6 +558,8 @@ def save_docs(self, docs, use_uuids=True, all_or_nothing=False, new_edits=None, """ + if not isinstance(docs, ('list', 'tuple')): + docs = tuple(docs) docs1 = [] docs_schema = [] for doc in docs: diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 8b6cd3a..83de279 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -154,7 +154,7 @@ def save_docs(cls, docs, use_uuids=True, all_or_nothing=False): db = cls.get_db() if any(doc._doc_type != cls._doc_type for doc in docs): raise ValueError("one of your documents does not have the correct type") - db.bulk_save(tuple(docs), use_uuids=use_uuids, all_or_nothing=all_or_nothing) + db.bulk_save(docs, use_uuids=use_uuids, all_or_nothing=all_or_nothing) bulk_save = save_docs From d380cd7158abc3afa69306b70ebdc559636ed0f3 Mon Sep 17 00:00:00 2001 From: Cal Ellowitz Date: Wed, 23 Mar 2016 14:43:57 -0400 Subject: [PATCH 074/151] using actual classes instead of strings --- couchdbkit/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index d3c3081..b01d225 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -558,7 +558,7 @@ def save_docs(self, docs, use_uuids=True, all_or_nothing=False, new_edits=None, """ - if not isinstance(docs, ('list', 'tuple')): + if not isinstance(docs, (list, tuple)): docs = tuple(docs) docs1 = [] docs_schema = [] From 75e678ab669806fa01f2fe83970bf1474f8c5809 Mon Sep 17 00:00:00 2001 From: Daniel Miller Date: Thu, 14 Apr 2016 14:49:52 -0400 Subject: [PATCH 075/151] Do not propagate Meta.abstract to subclasses --- couchdbkit/ext/django/schema.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/couchdbkit/ext/django/schema.py b/couchdbkit/ext/django/schema.py index ac5f6e4..961a3eb 100644 --- a/couchdbkit/ext/django/schema.py +++ b/couchdbkit/ext/django/schema.py @@ -48,6 +48,7 @@ DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 'app_label', 'string_conversions', 'properties', 'update_properties') +DISCARD_NAMES = ('abstract',) class Options(object): """ class based on django.db.models.options. We only keep @@ -75,7 +76,7 @@ def contribute_to_class(self, cls, name): # Ignore any private attributes that Django doesn't care about. # NOTE: We can't modify a dictionary's contents while looping # over it, so we loop over the *original* dictionary instead. - if name.startswith('_'): + if name.startswith('_') or name in DISCARD_NAMES: del meta_attrs[name] for attr_name in DEFAULT_NAMES: if attr_name in meta_attrs: @@ -122,11 +123,10 @@ def __new__(cls, name, bases, attrs): if not attr_meta: meta = getattr(new_class, 'Meta', None) else: + if getattr(attr_meta, 'abstract', False): + return new_class meta = attr_meta - if getattr(meta, 'abstract', False): - return new_class - if getattr(meta, 'app_label', None) is None: document_module = sys.modules[new_class.__module__] app_label = document_module.__name__.split('.')[-2] From 7aa640f1317259b0f8e30ba53d767b01a6f96e28 Mon Sep 17 00:00:00 2001 From: Daniel Miller Date: Thu, 14 Apr 2016 14:53:56 -0400 Subject: [PATCH 076/151] Bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index a29c906..cf30b63 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 3, 0) +version_info = (0, 7, 4, 0) __version__ = ".".join(map(str, version_info)) From 9c91b29272d7398b2dc828dc828a5e8d67fa55c8 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 1 Sep 2016 19:29:50 -0400 Subject: [PATCH 077/151] use post_migrate for django 1.7 --- couchdbkit/ext/django/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/couchdbkit/ext/django/__init__.py b/couchdbkit/ext/django/__init__.py index 0897262..34ec4f3 100644 --- a/couchdbkit/ext/django/__init__.py +++ b/couchdbkit/ext/django/__init__.py @@ -89,6 +89,7 @@ def home(request): It won't destroy your datas, just synchronize views. """ +import django from django.db.models import signals def syncdb(app, created_models, verbosity=2, **kwargs): @@ -96,4 +97,7 @@ def syncdb(app, created_models, verbosity=2, **kwargs): from couchdbkit.ext.django.loading import couchdbkit_handler couchdbkit_handler.sync(app, verbosity=verbosity) -signals.post_syncdb.connect(syncdb) +if django.VERSION >= (1, 7): + signals.post_migrate.connect(syncdb) +else: + signals.post_syncdb.connect(syncdb) From 5870146d4ec13caffe7bb82c1d80bd0498a5e198 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 1 Sep 2016 19:32:48 -0400 Subject: [PATCH 078/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index cf30b63..2f6d3c1 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 4, 0) +version_info = (0, 7, 4, 1) __version__ = ".".join(map(str, version_info)) From 182e98afa279045b4e24ef4d7ea7714fcbaa627c Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 2 Sep 2016 10:36:46 -0400 Subject: [PATCH 079/151] get rid of removed --use-mirrors flag --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 97c1777..34488c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ services: python: 2.7 install: - - pip install -r requirements_dev.txt --use-mirrors + - pip install -r requirements_dev.txt - python setup.py install script: python setup.py test From f795498bd6fadfe56dc537f84adb27dfe6785095 Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Sun, 18 Sep 2016 15:55:53 +0200 Subject: [PATCH 080/151] remove legacy (and unused) arg --- couchdbkit/ext/django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/ext/django/__init__.py b/couchdbkit/ext/django/__init__.py index 34ec4f3..eaaf5c1 100644 --- a/couchdbkit/ext/django/__init__.py +++ b/couchdbkit/ext/django/__init__.py @@ -92,7 +92,7 @@ def home(request): import django from django.db.models import signals -def syncdb(app, created_models, verbosity=2, **kwargs): +def syncdb(app, verbosity=2, **kwargs): """ function used by syncdb signal """ from couchdbkit.ext.django.loading import couchdbkit_handler couchdbkit_handler.sync(app, verbosity=verbosity) From baebe128b71dfd0a55133d81fd2913d9c76c7281 Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Mon, 19 Sep 2016 07:57:09 -0400 Subject: [PATCH 081/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 2f6d3c1..cbced7e 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 4, 1) +version_info = (0, 7, 4, 2) __version__ = ".".join(map(str, version_info)) From 72993673f623288569a0af1fb4d3126c1832799c Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Mon, 19 Sep 2016 14:55:41 -0400 Subject: [PATCH 082/151] drop support for django < 1.7 --- couchdbkit/ext/django/__init__.py | 12 ++++-------- couchdbkit/ext/django/loading.py | 6 +++--- .../ext/django/management/commands/sync_couchdb.py | 4 ++-- .../management/commands/sync_finish_couchdb.py | 4 ++-- .../management/commands/sync_prepare_couchdb.py | 4 ++-- couchdbkit/version.py | 2 +- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/couchdbkit/ext/django/__init__.py b/couchdbkit/ext/django/__init__.py index eaaf5c1..ba3c040 100644 --- a/couchdbkit/ext/django/__init__.py +++ b/couchdbkit/ext/django/__init__.py @@ -88,16 +88,12 @@ def home(request): To create databases and sync views, just run the usual `syncdb` command. It won't destroy your datas, just synchronize views. """ - -import django from django.db.models import signals -def syncdb(app, verbosity=2, **kwargs): + +def syncdb(app_config, verbosity=2, **kwargs): """ function used by syncdb signal """ from couchdbkit.ext.django.loading import couchdbkit_handler - couchdbkit_handler.sync(app, verbosity=verbosity) + couchdbkit_handler.sync(app_config, verbosity=verbosity) -if django.VERSION >= (1, 7): - signals.post_migrate.connect(syncdb) -else: - signals.post_syncdb.connect(syncdb) +signals.post_migrate.connect(syncdb) diff --git a/couchdbkit/ext/django/loading.py b/couchdbkit/ext/django/loading.py index f097dbf..88f8261 100644 --- a/couchdbkit/ext/django/loading.py +++ b/couchdbkit/ext/django/loading.py @@ -88,7 +88,7 @@ def sync(self, app, verbosity=2, temp=None): When temp is specified, it is appended to the app's name on the docid. It can then be updated in the background and copied over the existing design docs to reduce blocking time of view updates """ - app_name = app.__name__.rsplit('.', 1)[0] + app_name = app.name.rsplit('.', 1)[0] app_labels = set() schema_list = self.app_schema.values() for schema_dict in schema_list: @@ -103,7 +103,7 @@ def sync(self, app, verbosity=2, temp=None): print "sync `%s` in CouchDB" % app_name db = self.get_db(app_label) - app_path = os.path.abspath(os.path.join(sys.modules[app.__name__].__file__, "..")) + app_path = app.path design_path = "%s/%s" % (app_path, "_design") if not os.path.isdir(design_path): if settings.DEBUG: @@ -136,7 +136,7 @@ def copy_designs(self, app, temp, verbosity=2, delete=True): This is used to reduce the waiting time for blocking view updates """ - app_name = app.__name__.rsplit('.', 1)[0] + app_name = app.name.rsplit('.', 1)[0] app_labels = set() schema_list = self.app_schema.values() for schema_dict in schema_list: diff --git a/couchdbkit/ext/django/management/commands/sync_couchdb.py b/couchdbkit/ext/django/management/commands/sync_couchdb.py index faa9e6c..7835af0 100644 --- a/couchdbkit/ext/django/management/commands/sync_couchdb.py +++ b/couchdbkit/ext/django/management/commands/sync_couchdb.py @@ -1,4 +1,4 @@ -from django.db.models import get_apps +from django.apps import apps from django.core.management.base import BaseCommand from couchdbkit.ext.django.loading import couchdbkit_handler @@ -6,5 +6,5 @@ class Command(BaseCommand): help = 'Sync couchdb views.' def handle(self, *args, **options): - for app in get_apps(): + for app in apps.get_apps(): couchdbkit_handler.sync(app, verbosity=2) diff --git a/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py b/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py index e6bbd05..89e2fa6 100644 --- a/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py +++ b/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py @@ -1,4 +1,4 @@ -from django.db.models import get_apps +from django.apps import apps from django.core.management.base import BaseCommand from couchdbkit.ext.django.loading import couchdbkit_handler @@ -6,5 +6,5 @@ class Command(BaseCommand): help = 'Copy temporary design docs over existing ones' def handle(self, *args, **options): - for app in get_apps(): + for app in apps.get_apps(): couchdbkit_handler.copy_designs(app, temp='tmp', verbosity=2) diff --git a/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py b/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py index c86e18a..5cc76ba 100644 --- a/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py +++ b/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py @@ -1,4 +1,4 @@ -from django.db.models import get_apps +from django.apps import apps from django.core.management.base import BaseCommand from couchdbkit.ext.django.loading import couchdbkit_handler @@ -6,5 +6,5 @@ class Command(BaseCommand): help = 'Sync design docs to temporary ids' def handle(self, *args, **options): - for app in get_apps(): + for app in apps.get_apps(): couchdbkit_handler.sync(app, verbosity=2, temp='tmp') diff --git a/couchdbkit/version.py b/couchdbkit/version.py index cbced7e..cfc21fb 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 7, 4, 2) +version_info = (0, 8, 0, 0) __version__ = ".".join(map(str, version_info)) From 17806bc82c73fce4ccc38766108d28db1f7962d5 Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Wed, 12 Oct 2016 16:52:44 +0200 Subject: [PATCH 083/151] add more info to request logging --- couchdbkit/resource.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/couchdbkit/resource.py b/couchdbkit/resource.py index c89362f..50e595b 100644 --- a/couchdbkit/resource.py +++ b/couchdbkit/resource.py @@ -98,6 +98,7 @@ def request(self, method, path=None, payload=None, headers=None, **params): """ # logging information start_time = datetime.utcnow() + resp = None error_status = None has_error = False @@ -154,8 +155,9 @@ def request(self, method, path=None, payload=None, headers=None, **params): params=params, start_time=start_time, end_time=end_time, + status_code=resp.status_int if resp else error_status, + content_length=resp.headers.get('content-length') if resp else None, has_error=has_error, - error_status=error_status, duration=duration, ) request_logger.debug('{} to {} took {}'.format(method, path, duration), extra=logging_context) From 3ac8ffc67eaad40df922b85b04c7f06377e269ca Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Wed, 12 Oct 2016 17:00:57 +0200 Subject: [PATCH 084/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index cfc21fb..3d299c5 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 8, 0, 0) +version_info = (0, 8, 0, 1) __version__ = ".".join(map(str, version_info)) From d12562a103acaa2b1f0eb107f12e0a1e2b33bc6d Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Thu, 4 May 2017 13:08:38 +0200 Subject: [PATCH 085/151] add database name to request logger --- couchdbkit/resource.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/couchdbkit/resource.py b/couchdbkit/resource.py index 50e595b..5bff83e 100644 --- a/couchdbkit/resource.py +++ b/couchdbkit/resource.py @@ -24,8 +24,8 @@ from datetime import datetime from restkit import Resource, ClientResponse -from restkit.errors import ResourceError, RequestFailed, RequestError -from restkit.util import url_quote +from restkit.errors import ResourceError, RequestFailed +from restkit.util import url_quote, make_uri from . import __version__ from .exceptions import ResourceNotFound, ResourceConflict, \ @@ -147,6 +147,7 @@ def request(self, method, path=None, payload=None, headers=None, **params): except: raise finally: + database = _get_db_from_uri(self.uri, path) end_time = datetime.utcnow() duration = end_time - start_time logging_context = dict( @@ -159,8 +160,9 @@ def request(self, method, path=None, payload=None, headers=None, **params): content_length=resp.headers.get('content-length') if resp else None, has_error=has_error, duration=duration, + database=database ) - request_logger.debug('{} to {} took {}'.format(method, path, duration), extra=logging_context) + request_logger.debug('{} to {}/{} took {}'.format(method, database, path, duration), extra=logging_context) return resp @@ -195,3 +197,11 @@ def encode_attachments(attachments): else: v['data'] = re_sp.sub('', base64.b64encode(v['data'])) return attachments + + +def _get_db_from_uri(uri, path): + full_uri = make_uri(uri, path) + try: + return full_uri.split('/')[3] + except IndexError: + return 'unknown' From 5a3bd2e908a3ffe68f872a4800b78259fc46d0a1 Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Thu, 4 May 2017 13:13:30 +0200 Subject: [PATCH 086/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 3d299c5..563db4b 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 8, 0, 1) +version_info = (0, 8, 0, 2) __version__ = ".".join(map(str, version_info)) From 590c300ce5265e285a1c47186c4490360a72e276 Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Wed, 8 Nov 2017 13:37:29 +0200 Subject: [PATCH 087/151] only push docs if there are changes --- couchdbkit/designer/fs.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/couchdbkit/designer/fs.py b/couchdbkit/designer/fs.py index 1d78272..bc57bee 100644 --- a/couchdbkit/designer/fs.py +++ b/couchdbkit/designer/fs.py @@ -86,13 +86,18 @@ def create(self): def push(self, dbs, atomic=True, force=False): """Push a doc to a list of database `dburls`. If noatomic is true each attachments will be sent one by one.""" + pushed = False for db in dbs: + db_push = False if atomic: doc = self.doc(db, force=force) - db.save_doc(doc, force_update=True) + if not self._compare_with_current_version(db, doc): + db_push = True + db.save_doc(doc, force_update=True) else: doc = self.doc(db, with_attachments=False, force=force) db.save_doc(doc, force_update=True) + db_push = True attachments = doc.get('_attachments') or {} @@ -102,9 +107,23 @@ def push(self, dbs, atomic=True, force=False): db.put_attachment(doc, open(filepath, "r"), name=name) - logger.debug("%s/%s had been pushed from %s" % (db.uri, - self.docid, self.docdir)) + if db_push: + logger.debug("%s/%s had been pushed from %s" % (db.uri, + self.docid, self.docdir)) + pushed |= db_push + return pushed + + def _compare_with_current_version(self, db, doc): + """:returns: True if docs match otherwise False""" + try: + olddoc = db.open_doc(self._doc['_id']) + except ResourceNotFound: + return False + + if '_attachments' not in olddoc: + olddoc['_attachments'] = {} + return doc == olddoc def attachment_stub(self, name, filepath): att = {} @@ -391,10 +410,12 @@ def push(path, dbs, atomic=True, force=False, docid=None): dbs = [dbs] doc = document(path, create=False, docid=docid) - doc.push(dbs, atomic=atomic, force=force) + pushed = doc.push(dbs, atomic=atomic, force=force) docspath = os.path.join(path, '_docs') if os.path.exists(docspath): + pushed = True pushdocs(docspath, dbs, atomic=atomic) + return pushed def pushapps(path, dbs, atomic=True, export=False, couchapprc=False): """ push all couchapps in one folder like couchapp pushapps command From 6a87d1b5b3865414df126cc892f6dc66c273e418 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Thu, 9 Nov 2017 17:37:17 -0500 Subject: [PATCH 088/151] test against couch 2 Fix docker --- .travis.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 34488c4..362bceb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,28 @@ language: python services: - - couchdb + - docker python: 2.7 -install: - - pip install -r requirements_dev.txt - - python setup.py install +install: + - pip install -r requirements_dev.txt + - python setup.py install + +before_script: + - "docker run -d --name couchdb-cluster \ + -p 5984:5984 \ + -v $(pwd)/data:/usr/src/couchdb/dev/lib/ \ + klaemo/couchdb:2.0-dev \ + --with-admin-party-please \ + --with-haproxy + -n 3" + - | + while : + do + curl http://localhost:5984/${db_name} -sv 2>&1 | grep '^< HTTP/.* 200 OK' && break || continue + sleep 1 + done + script: python setup.py test From a2df967c7b2d714e235b67a424b76063effed6f1 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 10 Nov 2017 17:01:43 -0500 Subject: [PATCH 089/151] Update git ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index dc8ca9d..88b1570 100755 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ couchdbkit.egg-info dist/ doc/_build/ distribute-* +data +jsonobject_couchdbkit.egg-info From 084e4d5bfd7cbd32cf28b119ba5bb52c1dbe8d77 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 10 Nov 2017 17:01:57 -0500 Subject: [PATCH 090/151] couch 2 doesn't support all_or_nothing --- couchdbkit/client.py | 17 +++--------- couchdbkit/schema/base.py | 18 +++---------- tests/client_test.py | 55 --------------------------------------- 3 files changed, 7 insertions(+), 83 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index b01d225..e80b742 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -541,15 +541,11 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, doc.update(doc1) return res - def save_docs(self, docs, use_uuids=True, all_or_nothing=False, new_edits=None, - **params): + def save_docs(self, docs, use_uuids=True, new_edits=None, **params): """ bulk save. Modify Multiple Documents With a Single Request @param docs: list of docs @param use_uuids: add _id in doc who don't have it already set. - @param all_or_nothing: In the case of a power failure, when the database - restarts either all the changes will have been saved or none of them. - However, it does not do conflict checking, so the documents will @param new_edits: When False, this saves existing revisions instead of creating new ones. Used in the replication Algorithm. Each document should have a _revisions property that lists its revision history. @@ -583,8 +579,6 @@ def is_id(doc): doc['_id'] = nextid payload = { "docs": docs1 } - if all_or_nothing: - payload["all_or_nothing"] = True if new_edits is not None: payload["new_edits"] = new_edits @@ -618,17 +612,13 @@ def is_id(doc): return results bulk_save = save_docs - def delete_docs(self, docs, all_or_nothing=False, - empty_on_delete=False, **params): + def delete_docs(self, docs, empty_on_delete=False, **params): """ bulk delete. It adds '_deleted' member to doc then uses bulk_save to save them. @param empty_on_delete: default is False if you want to make sure the doc is emptied and will not be stored as is in Apache CouchDB. - @param all_or_nothing: In the case of a power failure, when the database - restarts either all the changes will have been saved or none of them. - However, it does not do conflict checking, so the documents will .. seealso:: `HTTP Bulk Document API ` @@ -646,8 +636,7 @@ def delete_docs(self, docs, all_or_nothing=False, for doc in docs: doc['_deleted'] = True - return self.bulk_save(docs, use_uuids=False, - all_or_nothing=all_or_nothing, **params) + return self.bulk_save(docs, use_uuids=False, **params) bulk_delete = delete_docs diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 83de279..0b1800e 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -140,33 +140,24 @@ def save(self, **params): store = save @classmethod - def save_docs(cls, docs, use_uuids=True, all_or_nothing=False): + def save_docs(cls, docs, use_uuids=True): """ Save multiple documents in database. @params docs: list of couchdbkit.schema.Document instance @param use_uuids: add _id in doc who don't have it already set. - @param all_or_nothing: In the case of a power failure, when the database - restarts either all the changes will have been saved or none of them. - However, it does not do conflict checking, so the documents will - be committed even if this creates conflicts. - """ db = cls.get_db() if any(doc._doc_type != cls._doc_type for doc in docs): raise ValueError("one of your documents does not have the correct type") - db.bulk_save(docs, use_uuids=use_uuids, all_or_nothing=all_or_nothing) + db.bulk_save(docs, use_uuids=use_uuids) bulk_save = save_docs @classmethod - def delete_docs(cls, docs, all_or_nothing=False, empty_on_delete=False): + def delete_docs(cls, docs, empty_on_delete=False): """ Bulk delete documents in a database @params docs: list of couchdbkit.schema.Document instance - @param all_or_nothing: In the case of a power failure, when the database - restarts either all the changes will have been saved or none of them. - However, it does not do conflict checking, so the documents will - be committed even if this creates conflicts. @param empty_on_delete: default is False if you want to make sure the doc is emptied and will not be stored as is in Apache CouchDB. @@ -174,8 +165,7 @@ def delete_docs(cls, docs, all_or_nothing=False, empty_on_delete=False): db = cls.get_db() if any(doc._doc_type != cls._doc_type for doc in docs): raise ValueError("one of your documents does not have the correct type") - db.bulk_delete(docs, all_or_nothing=all_or_nothing, - empty_on_delete=empty_on_delete) + db.bulk_delete(docs, empty_on_delete=empty_on_delete) bulk_delete = delete_docs diff --git a/tests/client_test.py b/tests/client_test.py index 803e941..8861501 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -497,61 +497,6 @@ def testDeleteMultipleDocs(self): del self.Server['couchdbkit_test'] - def testMultipleDocCOnflict(self): - db = self.Server.create_db('couchdbkit_test') - docs = [ - { 'string': 'test', 'number': 4 }, - { 'string': 'test', 'number': 5 }, - { 'string': 'test', 'number': 4 }, - { 'string': 'test', 'number': 6 } - ] - db.bulk_save(docs) - self.assert_(len(db) == 4) - docs1 = [ - docs[0], - docs[1], - {'_id': docs[2]['_id'], 'string': 'test', 'number': 4 }, - {'_id': docs[3]['_id'], 'string': 'test', 'number': 6 } - ] - - self.assertRaises(BulkSaveError, db.bulk_save, docs1) - - docs2 = [ - docs1[0], - docs1[1], - {'_id': docs[2]['_id'], 'string': 'test', 'number': 4 }, - {'_id': docs[3]['_id'], 'string': 'test', 'number': 6 } - ] - doc23 = docs2[3].copy() - all_errors = [] - try: - db.bulk_save(docs2) - except BulkSaveError, e: - all_errors = e.errors - - self.assert_(len(all_errors) == 2) - self.assert_(all_errors[0]['error'] == 'conflict') - self.assert_(doc23 == docs2[3]) - - docs3 = [ - docs2[0], - docs2[1], - {'_id': docs[2]['_id'], 'string': 'test', 'number': 4 }, - {'_id': docs[3]['_id'], 'string': 'test', 'number': 6 } - ] - - doc33 = docs3[3].copy() - all_errors2 = [] - try: - db.bulk_save(docs3, all_or_nothing=True) - except BulkSaveError, e: - all_errors2 = e.errors - - self.assert_(len(all_errors2) == 0) - self.assert_(doc33 != docs3[3]) - del self.Server['couchdbkit_test'] - - def testCopy(self): db = self.Server.create_db('couchdbkit_test') doc = { "f": "a" } From 11b3bf4db60273a6b5651cfaa99c5929b5714325 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 10 Nov 2017 17:10:05 -0500 Subject: [PATCH 091/151] Couch 2 doesn't have temp views. --- couchdbkit/client.py | 8 -------- couchdbkit/schema/base.py | 24 ++---------------------- tests/client_test.py | 20 -------------------- tests/test_schema.py | 25 ------------------------- 4 files changed, 2 insertions(+), 75 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index e80b742..6c6cfed 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -720,10 +720,6 @@ def raw_view(self, view_path, params): else: return self.res.get(view_path, **params) - def raw_temp_view(db, design, params): - return db.res.post('_temp_view', payload=design, - headers={"Content-Type": "application/json"}, **params) - def view(self, view_name, schema=None, wrapper=None, **params): """ get view results from database. viewname is generally a string like `designname/viewname". It return an ViewResults @@ -756,10 +752,6 @@ def view(self, view_name, schema=None, wrapper=None, **params): return ViewResults(self.raw_view, view_path, wrapper, schema, params) - def temp_view(self, design, schema=None, wrapper=None, **params): - """ get adhoc view results. Like view it reeturn a ViewResult object.""" - return ViewResults(self.raw_temp_view, design, wrapper, schema, params) - def search( self, view_name, handler='_fti/_design', wrapper=None, schema=None, **params): """ Search. Return results from search. Use couchdb-lucene with its default settings by default.""" diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 0b1800e..92b054c 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -301,36 +301,16 @@ def view(cls, view_name, wrapper=None, dynamic_properties=None, dynamic_properties=dynamic_properties, wrap_doc=wrap_doc, wrapper=wrapper, schema=classes, **params) - @classmethod - def temp_view(cls, design, wrapper=None, dynamic_properties=None, - wrap_doc=True, classes=None, **params): - """ Slow view. Like in view method, - results are automatically wrapped to - Document object. - - @params design: design object, See `simplecouchd.client.Database` - @dynamic_properties: do we handle properties which aren't in - the schema ? - @wrap_doc: If True, if a doc is present in the row it will be - used for wrapping. Default is True. - @params params: params of view - - @return: Like view, return a :class:`simplecouchdb.core.ViewResults` - instance. All results are wrapped to current document instance. - """ - db = cls.get_db() - return db.temp_view(design, - dynamic_properties=dynamic_properties, wrap_doc=wrap_doc, - wrapper=wrapper, schema=classes or cls, **params) class Document(DocumentBase, QueryMixin, AttachmentMixin): """ Full featured document object implementing the following : - :class:`QueryMixin` for view & temp_view that wrap results to this object + :class:`QueryMixin` for view that wrap results to this object :class `AttachmentMixin` for attachments function """ + class StaticDocument(Document): """ Shorthand for a document that disallow dynamic properties. diff --git a/tests/client_test.py b/tests/client_test.py index 8861501..93d2dd4 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -637,26 +637,6 @@ def testCount(self): self.assert_(count == 2) del self.Server['couchdbkit_test'] - def testTemporaryView(self): - db = self.Server.create_db('couchdbkit_test') - # save 2 docs - doc1 = { '_id': 'test', 'string': 'test', 'number': 4, - 'docType': 'test' } - db.save_doc(doc1) - doc2 = { '_id': 'test2', 'string': 'test', 'number': 2, - 'docType': 'test'} - db.save_doc(doc2) - - design_doc = { - "map": """function(doc) { if (doc.docType == "test") { emit(doc._id, doc); -}}""" - } - - results = db.temp_view(design_doc) - self.assert_(len(results) == 2) - del self.Server['couchdbkit_test'] - - def testView2(self): db = self.Server.create_db('couchdbkit_test') # save 2 docs diff --git a/tests/test_schema.py b/tests/test_schema.py index 79a32fd..990ba35 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -530,31 +530,6 @@ class TestDoc(Document): self.assert_(len(results) == 2) self.server.delete_db('couchdbkit_test') - - def testTempView(self): - class TestDoc(Document): - field1 = StringProperty() - field2 = StringProperty() - - design_doc = { - "map": """function(doc) { if (doc.doc_type == "TestDoc") { emit(doc._id, doc); -}}""" - } - - doc = TestDoc(field1="a", field2="b") - doc1 = TestDoc(field1="c", field2="d") - - db = self.server.create_db('couchdbkit_test') - TestDoc._db = db - - doc.save() - doc1.save() - results = TestDoc.temp_view(design_doc) - self.assert_(len(results) == 2) - doc3 = list(results)[0] - self.assert_(hasattr(doc3, "field1")) - self.server.delete_db('couchdbkit_test') - def testDocumentAttachments(self): db = self.server.create_db('couchdbkit_test') From 25755b4d7cbb4d5f9a7a8f6419a160a623a6dc92 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 10 Nov 2017 17:19:32 -0500 Subject: [PATCH 092/151] couch 2 has different seq numbers --- tests/test_consumer.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_consumer.py b/tests/test_consumer.py index 8397999..054c29c 100644 --- a/tests/test_consumer.py +++ b/tests/test_consumer.py @@ -31,19 +31,18 @@ def _delete_db(self): except: pass - def test_fetch(self): res1 = self.consumer.fetch() - self.assert_("last_seq" in res1) - self.assert_(res1["last_seq"] == 0) - self.assert_(res1["results"] == []) + self.assertTrue("last_seq" in res1) + self.assertTrue(res1["last_seq"].startswith("0")) + self.assertEqual(res1["results"], []) doc = {} self.db.save_doc(doc) res2 = self.consumer.fetch() - self.assert_(res2["last_seq"] == 1) - self.assert_(len(res2["results"]) == 1) + self.assertTrue(res2["last_seq"].startswith("1")) + self.assertEqual(len(res2["results"]), 1) line = res2["results"][0] - self.assert_(line["id"] == doc["_id"]) + self.assertEqual(line["id"], doc["_id"]) def test_longpoll(self): From 4164e75a3c4b2c9c7c6823914b76f5107d1a39c0 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 10 Nov 2017 17:25:03 -0500 Subject: [PATCH 093/151] Reduce nodes to 1 because 'eventual consistency' --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 362bceb..954575d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ before_script: klaemo/couchdb:2.0-dev \ --with-admin-party-please \ --with-haproxy - -n 3" + -n 1" - | while : do From 136f531d7877d13d22bc08ad57e933f59c3210e3 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Mon, 13 Nov 2017 12:32:00 -0500 Subject: [PATCH 094/151] test whitespace --- tests/test_schema.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 990ba35..15aa4f7 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -15,6 +15,7 @@ from couchdbkit import * from couchdbkit.schema.properties import support_setproperty + class DocumentTestCase(unittest.TestCase): def setUp(self): self.server = Server() @@ -715,8 +716,6 @@ def ftest(): value = test.field self.assert_(isinstance(value, datetime.datetime)) - - def testDateProperty(self): class Test(Document): field = DateProperty() @@ -731,7 +730,6 @@ def ftest(): value = test.field self.assert_(isinstance(value, datetime.date)) - def testTimeProperty(self): class Test(Document): field = TimeProperty() @@ -789,7 +787,6 @@ class Test(Document): self.assert_(isinstance(v1, datetime.datetime)) self.assert_(isinstance(vd, basestring)) - def testSchemaProperty1(self): class MySchema(DocumentSchema): astring = StringProperty() @@ -813,7 +810,6 @@ class MyDoc(Document): self.assert_(doc2.schema.astring == u"test") self.assert_(doc2._doc['schema']['astring'] == u"test") - def testSchemaPropertyWithRequired(self): class B( Document ): class b_schema(DocumentSchema): @@ -1042,7 +1038,6 @@ class B(Document): self.assert_(len(b1.slm) == 2) self.assert_(b1.slm[0].s == "test") - def testSchemaListPropertySlice(self): """SchemaListProperty slice methods """ @@ -1079,7 +1074,6 @@ class B(Document): 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}] }) - def testSchemaListPropertyContains(self): """SchemaListProperty contains method """ @@ -1098,7 +1092,6 @@ class B(Document): self.assertTrue(a1 in b.slm) self.assertFalse(a2 in b.slm) - def testSchemaListPropertyCount(self): """SchemaListProperty count method """ @@ -1116,7 +1109,6 @@ class B(Document): b.slm = [a1, a2, a1] self.assertEqual(b.slm.count(a1), 2) - def testSchemaListPropertyExtend(self): """SchemaListProperty extend method """ @@ -1196,7 +1188,6 @@ class B(Document): {'doc_type': 'A', 's': unicode(a3.s)}] }) - def testSchemaListPropertyPop(self): """SchemaListProperty pop method """ @@ -1232,7 +1223,6 @@ class B(Document): 'slm': [{'doc_type': 'A', 's': unicode(a2.s)}] }) - def testSchemaListPropertyRemove(self): """SchemaListProperty remove method """ @@ -1285,7 +1275,6 @@ class B(Document): {'doc_type': 'A', 's': unicode(a1.s)}] }) - def testSchemaListPropertySort(self): """SchemaListProperty sort method """ @@ -1323,7 +1312,6 @@ class B(Document): {'doc_type': 'A', 's': unicode(a2.s)}] }) - def testSchemaDictProperty(self): class A(DocumentSchema): i = IntegerProperty() @@ -1352,7 +1340,6 @@ class B(Document): self.assert_(b1.d['v1'].i == 123) self.assert_(b1.d[23].i == 42) - def testListProperty(self): from datetime import datetime class A(Document): @@ -1431,7 +1418,6 @@ class B(Document): b1.ls.remove(u'hello') self.assert_(u'hello' not in b1.ls) - def testListPropertyExtend(self): """list extend method for property w/o type """ @@ -1443,7 +1429,6 @@ class A(Document): self.assert_(a.l == [42, 24]) self.assert_(a._doc == {'doc_type': 'A', 'l': [42, 24]}) - def testListPropertyExtendWithType(self): """list extend method for property w/ type """ @@ -1461,7 +1446,6 @@ class A(Document): 'l': ['2011-03-11T21:31:01Z', '2011-11-03T13:12:02Z'] }) - def testListPropertyInsert(self): """list insert method for property w/o type """ @@ -1474,7 +1458,6 @@ class A(Document): self.assertEqual(a.l, [42, 4224, 24]) self.assertEqual(a._doc, {'doc_type': 'A', 'l': [42, 4224, 24]}) - def testListPropertyInsertWithType(self): """list insert method for property w/ type """ @@ -1496,7 +1479,6 @@ class A(Document): '2010-01-12T03:02:03Z'] }) - def testListPropertyPop(self): """list pop method for property w/o type """ @@ -1514,7 +1496,6 @@ class A(Document): self.assert_(a.l == [24]) self.assert_(a._doc == {'doc_type': 'A', 'l': [24]}) - def testListPropertyPopWithType(self): """list pop method for property w/ type """ @@ -1531,7 +1512,6 @@ class A(Document): self.assertEqual(v, d3) self.assertEqual(a.l, [d1, d2]) - def testDictProperty(self): from datetime import datetime class A(Document): @@ -1800,7 +1780,6 @@ class A2(Document): self.assert_(b.to_json()['d'] == {}) - if support_setproperty: class SetPropertyTestCase(unittest.TestCase): def testSetPropertyConstructor(self): From 8544e945d145f5b6ac7b8cd3452be1982355e120 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Mon, 13 Nov 2017 12:32:13 -0500 Subject: [PATCH 095/151] add doc conflict test --- tests/client_test.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/client_test.py b/tests/client_test.py index 93d2dd4..dad1fd6 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -497,6 +497,43 @@ def testDeleteMultipleDocs(self): del self.Server['couchdbkit_test'] + def testMultipleDocCOnflict(self): + db = self.Server.create_db('couchdbkit_test') + docs = [ + {'string': 'test', 'number': 4}, + {'string': 'test', 'number': 5}, + {'string': 'test', 'number': 4}, + {'string': 'test', 'number': 6} + ] + db.bulk_save(docs) + self.assertEqual(len(db), 4) + docs1 = [ + docs[0], + docs[1], + {'_id': docs[2]['_id'], 'string': 'test', 'number': 4}, + {'_id': docs[3]['_id'], 'string': 'test', 'number': 6} + ] + + self.assertRaises(BulkSaveError, db.bulk_save, docs1) + + docs2 = [ + docs1[0], + docs1[1], + {'_id': docs[2]['_id'], 'string': 'test', 'number': 4}, + {'_id': docs[3]['_id'], 'string': 'test', 'number': 6} + ] + doc23 = docs2[3].copy() + all_errors = [] + try: + db.bulk_save(docs2) + except BulkSaveError, e: + all_errors = e.errors + + self.assertEqual(len(all_errors), 2) + self.assertEqual(all_errors[0]['error'], 'conflict') + self.assertEqual(doc23, docs2[3]) + del self.Server['couchdbkit_test'] + def testCopy(self): db = self.Server.create_db('couchdbkit_test') doc = { "f": "a" } From 35601e575736cfd11de8e015d6ee1ccc1d59f093 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 17:23:12 -0500 Subject: [PATCH 096/151] Bump version to 0.9.0.0 --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 563db4b..0eb6f75 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,5 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -version_info = (0, 8, 0, 2) +version_info = (0, 9, 0, 0) __version__ = ".".join(map(str, version_info)) From 990835b21144b2cc9ec9eca1f45b6186d1608586 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Sun, 17 Sep 2017 15:19:00 +0530 Subject: [PATCH 097/151] we don't use mercuruial --- .hgignore | 20 -------------------- .hgtags | 38 -------------------------------------- 2 files changed, 58 deletions(-) delete mode 100755 .hgignore delete mode 100644 .hgtags diff --git a/.hgignore b/.hgignore deleted file mode 100755 index 85a1737..0000000 --- a/.hgignore +++ /dev/null @@ -1,20 +0,0 @@ -syntax: glob -*.orig -*.rej -*~ -*.o -*.pyc -*.pyo -tests/*.err -*.swp -store/* -*.DS_Store -*.beam -.coverage -couchdbkit.egg-info -dist -examples/djangoapp/test.db - -syntax: regexp -.*\#.*\#$ - diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 1e6a042..0000000 --- a/.hgtags +++ /dev/null @@ -1,38 +0,0 @@ -042d6587fe290b158f6779145a96651a894fc5f7 0.1.1 -99a25564cc09004aca0c921c0a94e92c3972c033 0.1.2 -69bc244110249a0b310dfde31b719dc7472bb3d7 0.1.3 -eb07f2cd97b65a5a3ff9ddaac0435effee589fe8 0.1.4 -9d8a3b20aa4e41565513ce1593701e1658c44efc 0.1.6 -7e5da2149fa5864a0390094d9c5dee88589233eb 0.1.6.1 -d9831b7c3d6d21a9827c171e92e84eb60889c481 0.1.7 -d8acae3e42268fd765e5b7c348435ba707d10636 0.1.7.1 -aedb19fb01cb8f4afc79027b20cdd6ebc275825f 0.1.7.2 -9e169d80ffb1e1fdf9131c2824c1ebc5037c5d95 0.1.8 -f9fa696b672130cf15a2d16d97c0cc862507d1fc 0.1.9 -c28f80517104b7a4dc402974959449ccfa60ab08 0.1.9.1 -67dddbdb8ae5d668662f2dcbcc7bf484adf50533 0.2 -ffa06885b498666efe9c069a53f640751a38d4e5 0.2.1 -e9316c36f8e73b3a016debcfd1d130f4c6df3a0d 0.2.2 -e9316c36f8e73b3a016debcfd1d130f4c6df3a0d 0.2.2 -d7e31f3829a8021d50b5f19f33f4a4ec5253a728 0.2.2 -7214410b4514880b8f6b09e6d7cbb5f5e746efa2 0.2.3 -7214410b4514880b8f6b09e6d7cbb5f5e746efa2 0.2.3 -be307befe36be7ff5b102f2fff36105db9a9f871 0.2.3 -903cd60cec4b11d1171e88a045a9a16c2fb03024 0.2.4 -903cd60cec4b11d1171e88a045a9a16c2fb03024 0.2.4 -3dc59a843ce0bad21a16bca4b4a9ec3d8397fd75 0.2.4 -d5c211047a6d2d98daa403c7ba7ceaa24b5a0585 0.3 -d5c211047a6d2d98daa403c7ba7ceaa24b5a0585 0.3 -65bb003bde529a1fc6669ccfbdef3abe5835e283 0.3 -57879431a4e200318378327af6dfe04884822e3e 0.3.1 -062d4b680c600c9cfbf2d96b90af80a291add1e3 0.4 -062d4b680c600c9cfbf2d96b90af80a291add1e3 0.4 -f8ddd60879ef6ed24a69b76ac1927c3fa1bdbb54 0.4 -f8ddd60879ef6ed24a69b76ac1927c3fa1bdbb54 0.4 -4eb125217ca6800e25deb185a6ced3f813f5727e 0.4 -4eb125217ca6800e25deb185a6ced3f813f5727e 0.4 -a1907763e888440b17353208de0fb78740587735 0.4 -b157f94ff805b10f6ec4118b78d25b9695d10fbf 0.4.1 -b157f94ff805b10f6ec4118b78d25b9695d10fbf 0.4.1 -c43c2a19151952af90d3b26a6ba4f4f726123e27 0.4.1 -631c681601e1be76a94dda6ffc7faa90e55ce9f1 0.4.2 From 238c77700c5062f8d3a1ec6dce6a02d0fa94de49 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 17:38:40 -0500 Subject: [PATCH 098/151] Don't use file --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 5280b06..7d2d2ae 100755 --- a/setup.py +++ b/setup.py @@ -22,11 +22,11 @@ version = version.__version__, description = 'Python couchdb kit', - long_description = file( + long_description = open( os.path.join( os.path.dirname(__file__), 'README.rst' - ) + ), 'rt' ).read(), author = 'Benoit Chesneau', author_email = 'benoitc@e-engura.com', From 3da35893ff427fbff6b43a6afb2e90146a4820a0 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Sun, 17 Sep 2017 15:20:17 +0530 Subject: [PATCH 099/151] Added cloudant's couch library. --- requirements.txt | 2 ++ setup.py | 1 + 2 files changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index c9da5f5..34f0eaa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ restkit>=4.2.2 +jsonobject>=0.6.0 +cloudant==2.7.0 diff --git a/setup.py b/setup.py index 5280b06..3ef5aec 100755 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ install_requires = [ 'restkit>=4.2.2', 'jsonobject>=0.6.0', + 'cloudant==2.7.0', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From 952f0420ef9541f312e9c6efc71f3f6493212251 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 18:18:16 -0500 Subject: [PATCH 100/151] Added six as dependency. --- requirements.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 34f0eaa..29c9513 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ restkit>=4.2.2 jsonobject>=0.6.0 cloudant==2.7.0 +six==1.11.0 diff --git a/setup.py b/setup.py index 3ef5aec..43cb205 100755 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ 'restkit>=4.2.2', 'jsonobject>=0.6.0', 'cloudant==2.7.0', + 'six==1.11.0', ], provides=['couchdbkit'], obsoletes=['couchdbkit'], From 998744b82681756ee8484d278e29d97575a3760b Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Sun, 17 Sep 2017 15:36:27 +0530 Subject: [PATCH 101/151] add couchdb server Comment about the admin party --- couchdbkit/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 6c6cfed..1ec8c25 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -36,6 +36,7 @@ from mimetypes import guess_type import time +from cloudant.client import CouchDB from restkit.util import url_quote from .exceptions import InvalidAttachment, NoResultFound, \ @@ -104,6 +105,8 @@ def __init__(self, uri='http://127.0.0.1:5984', else: self.res = self.resource_class(uri, **client_opts) self._uuids = deque() + # admin_party is true, because the username/pass is passed in uri for now + self.cloudant_client = CouchDB('', '', url=uri, admin_party=True, connect=True) def info(self): """ info of server From 1f5d0b649dc73633104fdb1fda7f18874e9dda9a Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Sun, 17 Sep 2017 16:51:39 +0530 Subject: [PATCH 102/151] Some cloudant endpoints more cloudant Fix name fix variable --- couchdbkit/client.py | 62 +++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 1ec8c25..2f3e722 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -37,7 +37,10 @@ import time from cloudant.client import CouchDB +from cloudant.database import CouchDatabase +from cloudant.document import Document from restkit.util import url_quote +from six.moves.urllib.parse import urljoin, unquote from .exceptions import InvalidAttachment, NoResultFound, \ ResourceNotFound, ResourceConflict, BulkSaveError, MultipleResultsFound @@ -108,6 +111,10 @@ def __init__(self, uri='http://127.0.0.1:5984', # admin_party is true, because the username/pass is passed in uri for now self.cloudant_client = CouchDB('', '', url=uri, admin_party=True, connect=True) + @property + def _request_session(self): + return self.cloudant_client.r_session + def info(self): """ info of server @@ -115,17 +122,18 @@ def info(self): """ try: - resp = self.res.get() + resp = self._request_session.get(self.uri) + resp.raise_for_status() except Exception: return UNKOWN_INFO - return resp.json_body + return resp.json() def all_dbs(self): """ get list of databases in CouchDb host """ - return self.res.get('/_all_dbs').json_body + return self.cloudant_client.all_dbs() def get_db(self, dbname, **params): """ @@ -210,14 +218,12 @@ def __getitem__(self, dbname): return Database(self._db_uri(dbname), server=self) def __delitem__(self, dbname): - ret = self.res.delete('/%s/' % url_quote(dbname, - safe=":")).json_body - return ret + self.cloudant_client.delete_database(dbname) def __contains__(self, dbname): try: - self.res.head('/%s/' % url_quote(dbname, safe=":")) - except: + self.cloudant_client[dbname] + except KeyError: return False return True @@ -254,6 +260,7 @@ def __init__(self, uri, create=False, server=None, **params): """ self.uri = uri.rstrip('/') self.server_uri, self.dbname = self.uri.rsplit("/", 1) + self.cloudant_dbname = unquote(self.dbname) if server is not None: if not hasattr(server, 'next_uuid'): @@ -263,14 +270,15 @@ def __init__(self, uri, create=False, server=None, **params): else: self.server = server = Server(self.server_uri, **params) + self.cloudant_client = self.server.cloudant_client + validate_dbname(self.dbname) + self.cloudant_database = CouchDatabase(self.cloudant_client, self.cloudant_dbname) if create: - try: - self.server.res.head('/%s/' % self.dbname) - except ResourceNotFound: - self.server.res.put('/%s/' % self.dbname, **params).json_body + self.cloudant_database.create() self.res = server.res(self.dbname) + self._request_session = self.server._request_session def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.dbname) @@ -281,15 +289,15 @@ def info(self): @return: dict """ - return self.res.get().json_body + return self.cloudant_database.metadata() def set_security(self, secobj): """ set database securrity object """ - return self.res.put("/_security", payload=secobj).json_body + return self._request_session.put(urljoin(self.uri, "/_security"), data=secobj).json() def get_security(self): """ get database secuirity object """ - return self.res.get("/_security").json_body + return self._request_session.get(urljoin(self.uri, "/_security"), data=secobj).json() def compact(self, dname=None): """ compact database @@ -299,14 +307,13 @@ def compact(self, dname=None): path = "/_compact" if dname is not None: path = "%s/%s" % (path, resource.escape_docid(dname)) - res = self.res.post(path, headers={"Content-Type": + path = urljoin(self.uri, path) + res = self._request_session.post(path, headers={"Content-Type": "application/json"}) - return res.json_body + return res.json() def view_cleanup(self): - res = self.res.post('/_view_cleanup', headers={"Content-Type": - "application/json"}) - return res.json_body + return self.cloudant_database.view_cleanup() def flush(self): """ Remove all docs from a database @@ -339,9 +346,7 @@ def flush(self): # we let a chance to the system to sync times = 0 while times < 10: - try: - self.server.res.head('/%s/' % self.dbname) - except ResourceNotFound: + if self.dbname in self.server: break time.sleep(0.2) times += 1 @@ -356,12 +361,8 @@ def doc_exist(self, docid): @param docid: str, document id @return: boolean, True if document exist """ - - try: - self.res.head(resource.escape_docid(docid)) - except ResourceNotFound: - return False - return True + doc = Document(self.cloudant_database, docid) + return doc.exists() def open_doc(self, docid, **params): """Get document from database @@ -1089,6 +1090,3 @@ def __len__(self): def __nonzero__(self): return bool(len(self)) - - - From 69b845dbd4a8f26af8fcaff086b11b61952bf6a6 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Sun, 17 Sep 2017 18:08:53 +0530 Subject: [PATCH 103/151] More cloudant lib stuff --- couchdbkit/client.py | 24 ++++++++++++++++-------- tests/client_test.py | 6 +++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 2f3e722..2295501 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -39,6 +39,7 @@ from cloudant.client import CouchDB from cloudant.database import CouchDatabase from cloudant.document import Document +from cloudant.security_document import SecurityDocument from restkit.util import url_quote from six.moves.urllib.parse import urljoin, unquote @@ -279,10 +280,14 @@ def __init__(self, uri, create=False, server=None, **params): self.res = server.res(self.dbname) self._request_session = self.server._request_session + self.database_url = self.cloudant_database.database_url def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.dbname) + def _database_path(self, path): + return '/'.join([self.database_url, path]) + def info(self): """ Get database information @@ -293,11 +298,17 @@ def info(self): def set_security(self, secobj): """ set database securrity object """ - return self._request_session.put(urljoin(self.uri, "/_security"), data=secobj).json() + with SecurityDocument(self.cloudant_database) as sec_doc: + # context manager saves + for key in sec_doc: + del sec_doc[key] + for k, v in secobj.items(): + sec_doc[k] = v + return self.get_security() def get_security(self): """ get database secuirity object """ - return self._request_session.get(urljoin(self.uri, "/_security"), data=secobj).json() + return self.cloudant_database.get_security_document() def compact(self, dname=None): """ compact database @@ -307,9 +318,8 @@ def compact(self, dname=None): path = "/_compact" if dname is not None: path = "%s/%s" % (path, resource.escape_docid(dname)) - path = urljoin(self.uri, path) - res = self._request_session.post(path, headers={"Content-Type": - "application/json"}) + path = self._database_path(path) + res = self._request_session.post(path, headers={"Content-Type": "application/json"}) return res.json() def view_cleanup(self): @@ -320,9 +330,7 @@ def flush(self): except design docs.""" # save ddocs - all_ddocs = self.all_docs(startkey="_design", - endkey="_design/"+u"\u9999", - include_docs=True) + all_ddocs = self.all_docs(startkey="_design", endkey="_design/"+u"\u9999", include_docs=True) ddocs = [] for ddoc in all_ddocs: doc = ddoc['doc'] diff --git a/tests/client_test.py b/tests/client_test.py index dad1fd6..dac8b0d 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -566,8 +566,9 @@ def testCopy(self): def testSetSecurity(self): db = self.Server.create_db('couchdbkit_test') - res = db.set_security({"meta": "test"}) - self.assert_(res['ok'] == True) + sec_doc = {"meta": "test"} + res = db.set_security(sec_doc) + self.assertEquals(res, sec_doc) del self.Server['couchdbkit_test'] def testGetSecurity(self): @@ -824,4 +825,3 @@ class B(Document): if __name__ == '__main__': unittest.main() - From 8cbcb10a653361975d34824aa214ca7fcdc507d4 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Sun, 17 Sep 2017 18:16:38 +0530 Subject: [PATCH 104/151] more cloudant --- couchdbkit/client.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 2295501..6d98765 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -36,6 +36,7 @@ from mimetypes import guess_type import time +import cloudant from cloudant.client import CouchDB from cloudant.database import CouchDatabase from cloudant.document import Document @@ -184,21 +185,19 @@ def replicate(self, source, target, **params): http://wiki.apache.org/couchdb/Replication """ - payload = { - "source": source, - "target": target, - } - payload.update(params) - resp = self.res.post('/_replicate', payload=payload) - return resp.json_body + replicator = cloudant.replicator.Replication(self.cloudant_client) + source_db = Database(self.cloudant_client, source) + target_db = Database(self.cloudant_client, target) + return replicator.create_replication(source_db, target_db, **params) def active_tasks(self): """ return active tasks """ - resp = self.res.get('/_active_tasks') - return resp.json_body + resp = self._request_session.get(urljoin(self.uri, '/_active_tasks')) + return resp.json() def uuids(self, count=1): - return self.res.get('/_uuids', count=count).json_body + resp = self._request_session.get(urljoin(self.uri, '/_uuids'), params={'count': count}) + return resp.json() def next_uuid(self, count=None): """ From 1a4890bf7e6b7e0432cdcf546e884f1e20d6ba69 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Mon, 18 Sep 2017 08:25:23 +0530 Subject: [PATCH 105/151] open_doc uses cloudant --- couchdbkit/client.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 6d98765..df34e21 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -41,8 +41,10 @@ from cloudant.database import CouchDatabase from cloudant.document import Document from cloudant.security_document import SecurityDocument +from requests.exceptions import HTTPError from restkit.util import url_quote -from six.moves.urllib.parse import urljoin, unquote +import six +from six.moves.urllib.parse import quote, urljoin, unquote from .exceptions import InvalidAttachment, NoResultFound, \ ResourceNotFound, ResourceConflict, BulkSaveError, MultipleResultsFound @@ -393,8 +395,15 @@ def open_doc(self, docid, **params): raise TypeError("invalid schema") wrapper = schema.wrap - docid = resource.escape_docid(docid) - doc = self.res.get(docid, **params).json_body + if isinstance(docid, six.text_type): + docid = docid.encode('utf-8') + doc = Document(self.cloudant_database, docid) + try: + doc.fetch() + except HTTPError as e: + if e.response.status_code == 404: + raise ResourceNotFound + raise if wrapper is not None: if not callable(wrapper): raise TypeError("wrapper isn't a callable") From 19a325dde476f19f31b3d5aea95eb4ead00874e3 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 00:02:27 -0500 Subject: [PATCH 106/151] cloudant ensure full commit Fix full commit --- couchdbkit/client.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index df34e21..c3b935f 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -875,9 +875,7 @@ def delete_attachment(self, doc, name, headers=None): doc.update(new_doc) return res['ok'] - - def fetch_attachment(self, id_or_doc, name, stream=False, - headers=None): + def fetch_attachment(self, id_or_doc, name, stream=False, headers=None): """ get attachment in a document @param id_or_doc: str or dict, doc id or document dict @@ -903,9 +901,9 @@ def fetch_attachment(self, id_or_doc, name, stream=False, def ensure_full_commit(self): """ commit all docs in memory """ - return self.res.post('_ensure_full_commit', headers={ - "Content-Type": "application/json" - }).json_body + path = self._database_path('_ensure_full_commit') + res = self._request_session.post(path, headers={"Content-Type": "application/json"}) + return res.json() def __len__(self): return self.info()['doc_count'] @@ -920,9 +918,8 @@ def __setitem__(self, docid, doc): doc['_id'] = docid self.save_doc(doc) - def __delitem__(self, docid): - self.delete_doc(docid) + self.delete_doc(docid) def __iter__(self): return self.documents().iterator() @@ -930,6 +927,7 @@ def __iter__(self): def __nonzero__(self): return (len(self) > 0) + class ViewResults(object): """ Object to retrieve view results. From 506a327686bc6f5018fb20570a65ef277277c4f8 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 00:48:56 -0500 Subject: [PATCH 107/151] save_doc(s) uses cloudant lib half of save_doc clean up save_doc save_docs uses cloudant lib --- couchdbkit/client.py | 52 ++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index c3b935f..5d24816 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -32,7 +32,9 @@ from collections import deque +from copy import deepcopy from itertools import groupby +import json from mimetypes import guess_type import time @@ -56,6 +58,7 @@ DEFAULT_UUID_BATCH_COUNT = 1000 + def _maybe_serialize(doc): if hasattr(doc, "to_json"): # try to validate doc first @@ -70,6 +73,7 @@ def _maybe_serialize(doc): return doc, False + class Server(object): """ Server object that allows you to access and manage a couchdb node. A Server object can be used like any `dict` object. @@ -246,6 +250,7 @@ def _db_uri(self, dbname): dbname = url_quote(dbname, safe=":") return "/".join([self.uri, dbname]) + class Database(object): """ Object that abstract access to a CouchDB database A Database object can act as a Dict object. @@ -529,30 +534,28 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, doc1['_attachments'] = resource.encode_attachments(doc['_attachments']) if '_id' in doc1: - docid = doc1['_id'] - docid1 = resource.escape_docid(doc1['_id']) + docid = doc1['_id'].encode('utf-8') + couch_doc = Document(self.cloudant_database, docid) + couch_doc.update(doc1) try: - res = self.res.put(docid1, payload=doc1, - **params).json_body - except ResourceConflict: + couch_doc.save() + except HTTPError as e: + if e.response.status_code != 409: + raise + if force_update: - doc1['_rev'] = self.get_rev(docid) - res =self.res.put(docid1, payload=doc1, - **params).json_body + couch_doc['_rev'] = self.get_rev(docid) + couch_doc.save() else: - raise + raise ResourceConflict + res = couch_doc else: - try: - doc['_id'] = self.server.next_uuid() - res = self.res.put(doc['_id'], payload=doc1, - **params).json_body - except: - res = self.res.post(payload=doc1, **params).json_body + res = self.cloudant_database.create_document(doc1) - if 'batch' in params and 'id' in res: - doc1.update({ '_id': res['id']}) + if 'batch' in params and ('id' in res or '_id' in res): + doc1.update({ '_id': res.get('_id')}) else: - doc1.update({'_id': res['id'], '_rev': res['rev']}) + doc1.update({'_id': res.get('_id'), '_rev': res.get('_rev')}) if schema: for key, value in doc.__class__.wrap(doc1).iteritems(): @@ -598,13 +601,14 @@ def is_id(doc): if nextid: doc['_id'] = nextid - payload = { "docs": docs1 } + payload = {"docs": docs1} if new_edits is not None: payload["new_edits"] = new_edits # update docs - results = self.res.post('/_bulk_docs', - payload=payload, **params).json_body + results = self._request_session.post( + self._database_path('_bulk_docs'), data=json.dumps(payload), + headers={"Content-Type": "application/json"}, **params).json() errors = [] for i, res in enumerate(results): @@ -787,8 +791,6 @@ def documents(self, schema=None, wrapper=None, **params): wrapper=wrapper, schema=schema, params=params) iterdocuments = documents - - def put_attachment(self, doc, content, name=None, content_type=None, content_length=None, headers=None): """ Add attachement to a document. All attachments are streamed. @@ -828,11 +830,13 @@ def put_attachment(self, doc, content, name=None, content_type=None, if not content: content = "" content_length = 0 + if name is None: if hasattr(content, "name"): name = content.name else: raise InvalidAttachment('You should provide a valid attachment name') + name = url_quote(name, safe="") if content_type is None: content_type = ';'.join(filter(None, guess_type(name))) @@ -841,7 +845,7 @@ def put_attachment(self, doc, content, name=None, content_type=None, headers['Content-Type'] = content_type # add appropriate headers - if content_length and content_length is not None: + if content_length: headers['Content-Length'] = content_length doc1, schema = _maybe_serialize(doc) From 9f4451c6429eadd1d80049d79fd07d86016b6df0 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 10:36:18 -0500 Subject: [PATCH 108/151] Fix importer lint --- couchdbkit/client.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 5d24816..e9dc8da 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -26,11 +26,6 @@ >>> del server['simplecouchdb_test'] """ -from couchdbkit.logging import error_logger - -UNKOWN_INFO = {} - - from collections import deque from copy import deepcopy from itertools import groupby @@ -46,10 +41,11 @@ from requests.exceptions import HTTPError from restkit.util import url_quote import six -from six.moves.urllib.parse import quote, urljoin, unquote +from six.moves.urllib.parse import urljoin, unquote +from couchdbkit.logging import error_logger from .exceptions import InvalidAttachment, NoResultFound, \ -ResourceNotFound, ResourceConflict, BulkSaveError, MultipleResultsFound + ResourceNotFound, ResourceConflict, BulkSaveError, MultipleResultsFound from . import resource from .utils import validate_dbname @@ -57,6 +53,7 @@ DEFAULT_UUID_BATCH_COUNT = 1000 +UNKOWN_INFO = {} def _maybe_serialize(doc): From 3ea6072c75c3461127214e6dcd53e7d8df6e887b Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 10:46:27 -0500 Subject: [PATCH 109/151] cloudant lib get rev --- couchdbkit/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index e9dc8da..7413a63 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -502,8 +502,8 @@ def get_rev(self, docid): @return rev: str, the last revision of document. """ - response = self.res.head(resource.escape_docid(docid)) - return response['etag'].strip('"') + response = self._request_session.head(self._database_path(docid)) + return response.headers['ETag'].strip('"') def save_doc(self, doc, encode_attachments=True, force_update=False, **params): From 79d953909070ab983c39a3a682caf969b6aec4bb Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 11:04:36 -0500 Subject: [PATCH 110/151] delete doc cloudant lib --- couchdbkit/client.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 7413a63..a3ddcea 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -673,16 +673,22 @@ def delete_doc(self, doc, **params): result = { 'ok': False } doc1, schema = _maybe_serialize(doc) + if isinstance(doc1, dict): if not '_id' or not '_rev' in doc1: raise KeyError('_id and _rev are required to delete a doc') - docid = resource.escape_docid(doc1['_id']) - result = self.res.delete(docid, rev=doc1['_rev'], **params).json_body + couch_doc = Document(self.cloudant_database, doc1['_id']) + couch_doc['_rev'] = doc1['_rev'] elif isinstance(doc1, basestring): # we get a docid - rev = self.get_rev(doc1) - docid = resource.escape_docid(doc1) - result = self.res.delete(docid, rev=rev, **params).json_body + couch_doc = Document(self.cloudant_database, doc1) + couch_doc['_rev'] = self.get_rev(doc1) + + # manual request because cloudant library doesn't return result + result = self._request_session.delete( + couch_doc.document_url, + params={"rev": couch_doc["_rev"]}, + ).json() if schema: doc._doc.update({ From 8bf4554f28a118545ee8fddc81a5ac1d0f320892 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 11:11:39 -0500 Subject: [PATCH 111/151] raise for status --- couchdbkit/client.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index a3ddcea..d0e2ac3 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -196,10 +196,12 @@ def replicate(self, source, target, **params): def active_tasks(self): """ return active tasks """ resp = self._request_session.get(urljoin(self.uri, '/_active_tasks')) + resp.raise_for_status() return resp.json() def uuids(self, count=1): resp = self._request_session.get(urljoin(self.uri, '/_uuids'), params={'count': count}) + resp.raise_for_status() return resp.json() def next_uuid(self, count=None): @@ -323,6 +325,7 @@ def compact(self, dname=None): path = "%s/%s" % (path, resource.escape_docid(dname)) path = self._database_path(path) res = self._request_session.post(path, headers={"Content-Type": "application/json"}) + res.raise_for_status() return res.json() def view_cleanup(self): @@ -503,6 +506,7 @@ def get_rev(self, docid): @return rev: str, the last revision of document. """ response = self._request_session.head(self._database_path(docid)) + response.raise_for_status() return response.headers['ETag'].strip('"') def save_doc(self, doc, encode_attachments=True, force_update=False, @@ -603,9 +607,11 @@ def is_id(doc): payload["new_edits"] = new_edits # update docs - results = self._request_session.post( + res = self._request_session.post( self._database_path('_bulk_docs'), data=json.dumps(payload), - headers={"Content-Type": "application/json"}, **params).json() + headers={"Content-Type": "application/json"}, **params) + res.raise_for_status() + results = res.json() errors = [] for i, res in enumerate(results): @@ -685,10 +691,12 @@ def delete_doc(self, doc, **params): couch_doc['_rev'] = self.get_rev(doc1) # manual request because cloudant library doesn't return result - result = self._request_session.delete( + res = self._request_session.delete( couch_doc.document_url, params={"rev": couch_doc["_rev"]}, - ).json() + ) + res.raise_for_status() + result = res.json() if schema: doc._doc.update({ @@ -910,6 +918,7 @@ def ensure_full_commit(self): """ commit all docs in memory """ path = self._database_path('_ensure_full_commit') res = self._request_session.post(path, headers={"Content-Type": "application/json"}) + res.raise_for_status() return res.json() def __len__(self): From 158d95277d32057e7cba5368b6f75e289a2cd1ff Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Fri, 17 Nov 2017 11:21:33 -0500 Subject: [PATCH 112/151] cloudant lib copy doc --- couchdbkit/client.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index d0e2ac3..b1141d7 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -723,7 +723,7 @@ def copy_doc(self, doc, dest=None, headers=None): if isinstance(doc1, basestring): docid = doc1 else: - if not '_id' in doc1: + if '_id' not in doc1: raise KeyError('_id is required to copy a doc') docid = doc1['_id'] @@ -743,10 +743,11 @@ def copy_doc(self, doc, dest=None, headers=None): if destination: headers.update({"Destination": str(destination)}) - result = self.res.copy('/%s' % docid, headers=headers).json_body - return result + resp = self._request_session.request('copy', self._database_path(docid), headers=headers) + resp.raise_for_status() + return resp.json() - return { 'ok': False } + return {'ok': False} def raw_view(self, view_path, params): if 'keys' in params: From d4673d9e03c2b948e98aa2dfecc1910cc7bcee8a Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Mon, 20 Nov 2017 13:09:43 -0500 Subject: [PATCH 113/151] Removed unittest2. --- requirements_dev.txt | 1 - tests/client_test.py | 5 +---- tests/test_changes.py | 5 +---- tests/test_consumer.py | 5 +---- tests/test_loaders.py | 5 +---- tests/test_resource.py | 5 +---- tests/test_schema.py | 5 +---- 7 files changed, 6 insertions(+), 25 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index c38e143..3d8259b 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,3 +1,2 @@ -unittest2 nose restkit>=4.2.2 diff --git a/tests/client_test.py b/tests/client_test.py index dad1fd6..c0f8b43 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -6,10 +6,7 @@ __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' import copy -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest from couchdbkit import ResourceNotFound, RequestFailed, \ ResourceConflict diff --git a/tests/test_changes.py b/tests/test_changes.py index 7f95adc..c48793f 100644 --- a/tests/test_changes.py +++ b/tests/test_changes.py @@ -7,10 +7,7 @@ import threading import time -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest from couchdbkit import * from couchdbkit.changes import ChangesStream, fold, foreach diff --git a/tests/test_consumer.py b/tests/test_consumer.py index 054c29c..d6d5980 100644 --- a/tests/test_consumer.py +++ b/tests/test_consumer.py @@ -7,10 +7,7 @@ import threading import time -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest from couchdbkit import * diff --git a/tests/test_loaders.py b/tests/test_loaders.py index 54edd68..94c0586 100644 --- a/tests/test_loaders.py +++ b/tests/test_loaders.py @@ -9,10 +9,7 @@ import os import shutil import tempfile -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest from restkit import ResourceNotFound, RequestFailed diff --git a/tests/test_resource.py b/tests/test_resource.py index e2b20d7..dcb9e9c 100644 --- a/tests/test_resource.py +++ b/tests/test_resource.py @@ -5,10 +5,7 @@ # __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest from restkit.errors import RequestFailed, RequestError from couchdbkit.resource import CouchdbResource diff --git a/tests/test_schema.py b/tests/test_schema.py index 15aa4f7..50f3b16 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -7,10 +7,7 @@ import datetime import decimal -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest from couchdbkit import * from couchdbkit.schema.properties import support_setproperty From bb68b30baca784f3e70de8fbc6d07db51145b834 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 8 Feb 2018 10:40:52 -0500 Subject: [PATCH 114/151] python-modernize . -w --- couchdbkit/client.py | 13 ++-- couchdbkit/consumer/__init__.py | 1 + couchdbkit/consumer/ceventlet.py | 1 + couchdbkit/consumer/cgevent.py | 1 + couchdbkit/designer/fs.py | 29 ++++---- couchdbkit/designer/macros.py | 14 ++-- couchdbkit/exceptions.py | 1 + couchdbkit/ext/django/__init__.py | 1 + couchdbkit/ext/django/forms.py | 7 +- couchdbkit/ext/django/loading.py | 21 +++--- .../management/commands/sync_couchdb.py | 1 + .../commands/sync_finish_couchdb.py | 1 + .../commands/sync_prepare_couchdb.py | 1 + couchdbkit/ext/django/schema.py | 7 +- couchdbkit/ext/django/testrunner.py | 10 +-- couchdbkit/ext/pylons/auth/adapters.py | 1 + couchdbkit/ext/pylons/auth/basic.py | 1 + couchdbkit/ext/pylons/auth/model.py | 6 +- couchdbkit/ext/pylons/commands.py | 1 + couchdbkit/ext/pylons/db.py | 1 + couchdbkit/ext/pylons/test.py | 1 + couchdbkit/external.py | 1 + couchdbkit/loaders.py | 6 +- couchdbkit/resource.py | 10 +-- couchdbkit/schema/base.py | 10 +-- couchdbkit/schema/properties.py | 6 +- couchdbkit/schema/properties_proxy.py | 1 + couchdbkit/schema/util.py | 3 +- couchdbkit/utils.py | 16 +++-- couchdbkit/version.py | 2 + couchdbkit/wsgi/handler.py | 3 +- couchdbkit/wsgi/proxy.py | 5 +- distribute_setup.py | 3 +- doc/couchdbkit.org/buildweb.py | 29 ++++---- doc/couchdbkit.org/conf.py | 1 + examples/django_blogapp/blog_app/models.py | 1 + examples/django_blogapp/blog_app/tests.py | 1 + examples/django_blogapp/blog_app/views.py | 3 +- examples/django_blogapp/settings.py | 1 + examples/django_blogapp/urls.py | 1 + examples/django_blogapp/wsgi.py | 1 + examples/djangoapp/greeting/models.py | 1 + examples/djangoapp/greeting/views.py | 1 + examples/djangoapp/manage.py | 3 +- examples/djangoapp/run.py | 1 + examples/djangoapp/settings.py | 1 + examples/djangoapp/urls.py | 1 + .../pyramid_couchdb_example/__init__.py | 1 + .../pyramid_couchdb_example/tests.py | 1 + .../pyramid_couchdb_example/views.py | 1 + examples/pyramidapp/setup.py | 1 + examples/wsgi/test.py | 1 + setup.py | 1 + tests/client_test.py | 8 ++- tests/test_changes.py | 2 + tests/test_consumer.py | 2 + tests/test_loaders.py | 1 + tests/test_resource.py | 3 +- tests/test_schema.py | 71 ++++++++++--------- 59 files changed, 201 insertions(+), 124 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index b1141d7..01c5d81 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -26,6 +26,7 @@ >>> del server['simplecouchdb_test'] """ +from __future__ import absolute_import from collections import deque from copy import deepcopy from itertools import groupby @@ -559,7 +560,7 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, doc1.update({'_id': res.get('_id'), '_rev': res.get('_rev')}) if schema: - for key, value in doc.__class__.wrap(doc1).iteritems(): + for key, value in six.iteritems(doc.__class__.wrap(doc1)): doc[key] = value else: doc.update(doc1) @@ -686,7 +687,7 @@ def delete_doc(self, doc, **params): couch_doc = Document(self.cloudant_database, doc1['_id']) couch_doc['_rev'] = doc1['_rev'] - elif isinstance(doc1, basestring): # we get a docid + elif isinstance(doc1, six.string_types): # we get a docid couch_doc = Document(self.cloudant_database, doc1) couch_doc['_rev'] = self.get_rev(doc1) @@ -720,7 +721,7 @@ def copy_doc(self, doc, dest=None, headers=None): headers = {} doc1, schema = _maybe_serialize(doc) - if isinstance(doc1, basestring): + if isinstance(doc1, six.string_types): docid = doc1 else: if '_id' not in doc1: @@ -729,7 +730,7 @@ def copy_doc(self, doc, dest=None, headers=None): if dest is None: destination = self.server.next_uuid(count=1) - elif isinstance(dest, basestring): + elif isinstance(dest, six.string_types): if dest in self: dest = self.get(dest) destination = "%s?rev=%s" % (dest['_id'], dest['_rev']) @@ -851,7 +852,7 @@ def put_attachment(self, doc, content, name=None, content_type=None, name = url_quote(name, safe="") if content_type is None: - content_type = ';'.join(filter(None, guess_type(name))) + content_type = ';'.join([_f for _f in guess_type(name) if _f]) if content_type: headers['Content-Type'] = content_type @@ -900,7 +901,7 @@ def fetch_attachment(self, id_or_doc, name, stream=False, headers=None): @return: `restkit.httpc.Response` object """ - if isinstance(id_or_doc, basestring): + if isinstance(id_or_doc, six.string_types): docid = id_or_doc else: doc, schema = _maybe_serialize(id_or_doc) diff --git a/couchdbkit/consumer/__init__.py b/couchdbkit/consumer/__init__.py index 32a877b..7b437b4 100644 --- a/couchdbkit/consumer/__init__.py +++ b/couchdbkit/consumer/__init__.py @@ -4,6 +4,7 @@ # See the NOTICE for more information. +from __future__ import absolute_import from .base import ConsumerBase OLD_CONSUMER_URIS = dict( diff --git a/couchdbkit/consumer/ceventlet.py b/couchdbkit/consumer/ceventlet.py index 3459287..94c5483 100644 --- a/couchdbkit/consumer/ceventlet.py +++ b/couchdbkit/consumer/ceventlet.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import traceback import eventlet diff --git a/couchdbkit/consumer/cgevent.py b/couchdbkit/consumer/cgevent.py index 019d868..1241e39 100644 --- a/couchdbkit/consumer/cgevent.py +++ b/couchdbkit/consumer/cgevent.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import traceback import gevent diff --git a/couchdbkit/designer/fs.py b/couchdbkit/designer/fs.py index bc57bee..0b05812 100644 --- a/couchdbkit/designer/fs.py +++ b/couchdbkit/designer/fs.py @@ -4,6 +4,7 @@ # See the NOTICE for more information. from __future__ import with_statement +from __future__ import absolute_import import base64 import copy from hashlib import md5 @@ -18,6 +19,7 @@ BulkSaveError from .macros import package_shows, package_views from .. import utils +import six if os.name == 'nt': def _replace_backslash(name): @@ -131,8 +133,7 @@ def attachment_stub(self, name, filepath): re_sp = re.compile('\s') att = { "data": re_sp.sub('',base64.b64encode(f.read())), - "content_type": ';'.join(filter(None, - mimetypes.guess_type(name))) + "content_type": ';'.join([_f for _f in mimetypes.guess_type(name) if _f]) } return att @@ -230,7 +231,7 @@ def doc(self, db=None, with_attachments=True, force=False): name = name[:-1] dmanifest[name] = i - for vname, value in self._doc['views'].iteritems(): + for vname, value in six.iteritems(self._doc['views']): if value and isinstance(value, dict): views[vname] = value else: @@ -446,7 +447,7 @@ def pushapps(path, dbs, atomic=True, export=False, couchapprc=False): docs = [doc.doc(db) for doc in apps] try: db.save_docs(docs) - except BulkSaveError, e: + except BulkSaveError as e: docs1 = [] for doc in e.errors: try: @@ -511,7 +512,7 @@ def pushdocs(path, dbs, atomic=True, export=False): docs1.append(newdoc) try: db.save_docs(docs1) - except BulkSaveError, e: + except BulkSaveError as e: # resolve conflicts docs1 = [] for doc in e.errors: @@ -583,7 +584,7 @@ def clone(db, docid, dest=None, rev=None): break - if isinstance(content, basestring): + if isinstance(content, six.string_types): _ref = md5(utils.to_bytestring(content)).hexdigest() if objects and _ref in objects: content = objects[_ref] @@ -615,7 +616,7 @@ def clone(db, docid, dest=None, rev=None): # second pass for missing key or in case # manifest isn't in app - for key in doc.iterkeys(): + for key in six.iterkeys(doc): if key.startswith('_'): continue elif key in ('couchapp'): @@ -635,11 +636,11 @@ def clone(db, docid, dest=None, rev=None): vs_dir = os.path.join(path, key) if not os.path.isdir(vs_dir): os.makedirs(vs_dir) - for vsname, vs_item in doc[key].iteritems(): + for vsname, vs_item in six.iteritems(doc[key]): vs_item_dir = os.path.join(vs_dir, vsname) if not os.path.isdir(vs_item_dir): os.makedirs(vs_item_dir) - for func_name, func in vs_item.iteritems(): + for func_name, func in six.iteritems(vs_item): filename = os.path.join(vs_item_dir, '%s.js' % func_name) utils.write_content(filename, func) @@ -648,7 +649,7 @@ def clone(db, docid, dest=None, rev=None): showpath = os.path.join(path, key) if not os.path.isdir(showpath): os.makedirs(showpath) - for func_name, func in doc[key].iteritems(): + for func_name, func in six.iteritems(doc[key]): filename = os.path.join(showpath, '%s.js' % func_name) utils.write_content(filename, func) @@ -665,9 +666,9 @@ def clone(db, docid, dest=None, rev=None): elif isinstance(doc[key], dict): if not os.path.isdir(filedir): os.makedirs(filedir) - for field, value in doc[key].iteritems(): + for field, value in six.iteritems(doc[key]): fieldpath = os.path.join(filedir, field) - if isinstance(value, basestring): + if isinstance(value, six.string_types): if value.startswith('base64-encoded;'): value = base64.b64decode(content[15:]) utils.write_content(fieldpath, value) @@ -675,7 +676,7 @@ def clone(db, docid, dest=None, rev=None): utils.write_json(fieldpath + '.json', value) else: value = doc[key] - if not isinstance(value, basestring): + if not isinstance(value, six.string_types): value = str(value) utils.write_content(filedir, value) @@ -690,7 +691,7 @@ def clone(db, docid, dest=None, rev=None): if not os.path.isdir(attachdir): os.makedirs(attachdir) - for filename in doc['_attachments'].iterkeys(): + for filename in six.iterkeys(doc['_attachments']): if filename.startswith('vendor'): attach_parts = utils.split_path(filename) vendor_attachdir = os.path.join(path, attach_parts.pop(0), diff --git a/couchdbkit/designer/macros.py b/couchdbkit/designer/macros.py index 10f20d9..d9fad95 100644 --- a/couchdbkit/designer/macros.py +++ b/couchdbkit/designer/macros.py @@ -31,6 +31,7 @@ hash when views are updated. """ +from __future__ import absolute_import import glob from hashlib import md5 import logging @@ -39,6 +40,7 @@ from ..exceptions import MacroError from ..utils import read_file, read_json, to_bytestring, json +import six logger = logging.getLogger(__name__) @@ -47,13 +49,13 @@ def package_shows(doc, funcs, app_dir, objs): apply_lib(doc, funcs, app_dir, objs) def package_views(doc, views, app_dir, objs): - for view, funcs in views.iteritems(): + for view, funcs in six.iteritems(views): if hasattr(funcs, "items"): apply_lib(doc, funcs, app_dir, objs) def apply_lib(doc, funcs, app_dir, objs): for k, v in funcs.items(): - if not isinstance(v, basestring): + if not isinstance(v, six.string_types): continue else: logger.debug("process function: %s" % k) @@ -61,7 +63,7 @@ def apply_lib(doc, funcs, app_dir, objs): try: funcs[k] = run_json_macros(doc, run_code_macros(v, app_dir), app_dir) - except ValueError, e: + except ValueError as e: raise MacroError( "Error running !code or !json on function \"%s\": %s" % (k, e)) if old_v != funcs[k]: @@ -80,7 +82,7 @@ def rreq(mo): if cnt.find("!code") >= 0: cnt = run_code_macros(cnt, app_dir) library += cnt - except IOError, e: + except IOError as e: raise MacroError(str(e)) filenum += 1 @@ -109,7 +111,7 @@ def rjson(mo): library = read_json(filename) else: library = read_file(filename) - except IOError, e: + except IOError as e: raise MacroError(str(e)) filenum += 1 current_file = filename.split(app_dir)[1] @@ -154,7 +156,7 @@ def rjson2(mo): if not included: return f_string - for k, v in included.iteritems(): + for k, v in six.iteritems(included): varstrings.append("var %s = %s;" % (k, json.dumps(v).encode('utf-8'))) return re_json.sub(rjson2, f_string) diff --git a/couchdbkit/exceptions.py b/couchdbkit/exceptions.py index cb7fac0..575377b 100644 --- a/couchdbkit/exceptions.py +++ b/couchdbkit/exceptions.py @@ -6,6 +6,7 @@ """ All exceptions used in couchdbkit. """ +from __future__ import absolute_import from restkit.errors import ResourceError import jsonobject.exceptions diff --git a/couchdbkit/ext/django/__init__.py b/couchdbkit/ext/django/__init__.py index ba3c040..b5bc1e2 100644 --- a/couchdbkit/ext/django/__init__.py +++ b/couchdbkit/ext/django/__init__.py @@ -88,6 +88,7 @@ def home(request): To create databases and sync views, just run the usual `syncdb` command. It won't destroy your datas, just synchronize views. """ +from __future__ import absolute_import from django.db.models import signals diff --git a/couchdbkit/ext/django/forms.py b/couchdbkit/ext/django/forms.py index 964a2e9..8c9ef1d 100644 --- a/couchdbkit/ext/django/forms.py +++ b/couchdbkit/ext/django/forms.py @@ -78,12 +78,14 @@ """ +from __future__ import absolute_import from collections import OrderedDict from django.utils.text import capfirst from django.forms.util import ErrorList from django.forms.forms import BaseForm, get_declared_fields from django.forms import fields as f from django.forms.widgets import media_property +import six FIELDS_PROPERTES_MAPPING = { "StringProperty": f.CharField, @@ -136,7 +138,7 @@ def fields_for_document(document, properties=None, exclude=None): values = [document._properties[prop] for prop in properties if \ prop in document._properties] else: - values = document._properties.values() + values = list(document._properties.values()) values.sort(lambda a, b: cmp(a.creation_counter, b.creation_counter)) for prop in values: @@ -263,6 +265,5 @@ def save(self, commit=True, dynamic=True): return self.instance -class DocumentForm(BaseDocumentForm): +class DocumentForm(six.with_metaclass(DocumentFormMetaClass, BaseDocumentForm)): """ The document form object """ - __metaclass__ = DocumentFormMetaClass diff --git a/couchdbkit/ext/django/loading.py b/couchdbkit/ext/django/loading.py index 88f8261..d867e45 100644 --- a/couchdbkit/ext/django/loading.py +++ b/couchdbkit/ext/django/loading.py @@ -19,6 +19,8 @@ and manage db sessions """ +from __future__ import absolute_import +from __future__ import print_function import sys import os from collections import OrderedDict @@ -29,6 +31,7 @@ from couchdbkit.resource import CouchdbResource from couchdbkit.exceptions import ResourceNotFound from django.conf import settings +import six COUCHDB_DATABASES = getattr(settings, "COUCHDB_DATABASES", []) COUCHDB_TIMEOUT = getattr(settings, "COUCHDB_TIMEOUT", 300) @@ -55,7 +58,7 @@ def __init__(self, databases): ) # create databases sessions - for app_name, app_setting in databases.iteritems(): + for app_name, app_setting in six.iteritems(databases): uri = app_setting['URL'] # Do not send credentials when they are both None as admin party will give a 401 @@ -90,7 +93,7 @@ def sync(self, app, verbosity=2, temp=None): design docs to reduce blocking time of view updates """ app_name = app.name.rsplit('.', 1)[0] app_labels = set() - schema_list = self.app_schema.values() + schema_list = list(self.app_schema.values()) for schema_dict in schema_list: for schema in schema_dict.values(): app_module = schema.__module__.rsplit(".", 1)[0] @@ -100,14 +103,14 @@ def sync(self, app, verbosity=2, temp=None): if not app_label in self._databases: continue if verbosity >=1: - print "sync `%s` in CouchDB" % app_name + print("sync `%s` in CouchDB" % app_name) db = self.get_db(app_label) app_path = app.path design_path = "%s/%s" % (app_path, "_design") if not os.path.isdir(design_path): if settings.DEBUG: - print >>sys.stderr, "%s don't exists, no ddoc synchronized" % design_path + print("%s don't exists, no ddoc synchronized" % design_path, file=sys.stderr) return if temp: @@ -122,10 +125,10 @@ def sync(self, app, verbosity=2, temp=None): if temp: ddoc = db[docid] - view_names = ddoc.get('views', {}).keys() + view_names = list(ddoc.get('views', {}).keys()) if len(view_names) > 0: if verbosity >= 1: - print 'Triggering view rebuild' + print('Triggering view rebuild') view = '%s/%s' % (design_name, view_names[0]) list(db.view(view, limit=0)) @@ -138,7 +141,7 @@ def copy_designs(self, app, temp, verbosity=2, delete=True): app_name = app.name.rsplit('.', 1)[0] app_labels = set() - schema_list = self.app_schema.values() + schema_list = list(self.app_schema.values()) for schema_dict in schema_list: for schema in schema_dict.values(): app_module = schema.__module__.rsplit(".", 1)[0] @@ -148,7 +151,7 @@ def copy_designs(self, app, temp, verbosity=2, delete=True): if not app_label in self._databases: continue if verbosity >=1: - print "Copy prepared design docs for `%s`" % app_name + print("Copy prepared design docs for `%s`" % app_name) db = self.get_db(app_label) tmp_name = '%s-%s' % (app_label, temp) @@ -163,7 +166,7 @@ def copy_designs(self, app, temp, verbosity=2, delete=True): del db[from_id] except ResourceNotFound: - print '%s not found.' % (from_id, ) + print('%s not found.' % (from_id, )) return diff --git a/couchdbkit/ext/django/management/commands/sync_couchdb.py b/couchdbkit/ext/django/management/commands/sync_couchdb.py index 7835af0..cfdfd64 100644 --- a/couchdbkit/ext/django/management/commands/sync_couchdb.py +++ b/couchdbkit/ext/django/management/commands/sync_couchdb.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.apps import apps from django.core.management.base import BaseCommand from couchdbkit.ext.django.loading import couchdbkit_handler diff --git a/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py b/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py index 89e2fa6..4a45008 100644 --- a/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py +++ b/couchdbkit/ext/django/management/commands/sync_finish_couchdb.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.apps import apps from django.core.management.base import BaseCommand from couchdbkit.ext.django.loading import couchdbkit_handler diff --git a/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py b/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py index 5cc76ba..487756a 100644 --- a/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py +++ b/couchdbkit/ext/django/management/commands/sync_prepare_couchdb.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.apps import apps from django.core.management.base import BaseCommand from couchdbkit.ext.django.loading import couchdbkit_handler diff --git a/couchdbkit/ext/django/schema.py b/couchdbkit/ext/django/schema.py index 961a3eb..16d5d15 100644 --- a/couchdbkit/ext/django/schema.py +++ b/couchdbkit/ext/django/schema.py @@ -17,8 +17,10 @@ """ Wrapper of couchdbkit Document and Properties for django. It also add possibility to a document to register itself in CouchdbkitHandler """ +from __future__ import absolute_import import re import sys +import six try: from django.db.models.options import get_verbose_name @@ -90,7 +92,7 @@ def contribute_to_class(self, cls, name): # Any leftover attributes must be invalid. if meta_attrs != {}: - raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())) + raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(list(meta_attrs.keys()))) else: self.verbose_name_plural = string_concat(self.verbose_name, 's') del self.meta @@ -145,9 +147,8 @@ def add_to_class(cls, name, value): else: setattr(cls, name, value) -class Document(schema.Document): +class Document(six.with_metaclass(DocumentMeta, schema.Document)): """ Document object for django extension """ - __metaclass__ = DocumentMeta get_id = property(lambda self: self['_id']) get_rev = property(lambda self: self['_rev']) diff --git a/couchdbkit/ext/django/testrunner.py b/couchdbkit/ext/django/testrunner.py index cd96d79..4a59161 100644 --- a/couchdbkit/ext/django/testrunner.py +++ b/couchdbkit/ext/django/testrunner.py @@ -3,6 +3,8 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import +from __future__ import print_function from django.test.simple import DjangoTestSuiteRunner from django.conf import settings @@ -32,7 +34,7 @@ def get_test_db(self, db): return test_db def setup_databases(self, **kwargs): - print "overridding the couchdbkit database settings to use a test database!" + print("overridding the couchdbkit database settings to use a test database!") # first pass: just implement this as a monkey-patch to the loading module # overriding all the existing couchdb settings @@ -75,9 +77,9 @@ def teardown_databases(self, old_config, **kwargs): try: db.server.delete_db(db.dbname) deleted_databases.append(db.dbname) - print "deleted database %s for %s" % (db.dbname, app_label) + print("deleted database %s for %s" % (db.dbname, app_label)) except ResourceNotFound: - print "database %s not found for %s! it was probably already deleted." % (db.dbname, app_label) + print("database %s not found for %s! it was probably already deleted." % (db.dbname, app_label)) if skipcount: - print "skipped deleting %s app databases that were already deleted" % skipcount + print("skipped deleting %s app databases that were already deleted" % skipcount) return super(CouchDbKitTestSuiteRunner, self).teardown_databases(old_config, **kwargs) diff --git a/couchdbkit/ext/pylons/auth/adapters.py b/couchdbkit/ext/pylons/auth/adapters.py index 7215502..0269340 100644 --- a/couchdbkit/ext/pylons/auth/adapters.py +++ b/couchdbkit/ext/pylons/auth/adapters.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import from repoze.what.adapters import BaseSourceAdapter from repoze.who.interfaces import IAuthenticator from repoze.who.interfaces import IMetadataProvider diff --git a/couchdbkit/ext/pylons/auth/basic.py b/couchdbkit/ext/pylons/auth/basic.py index 66278ce..0fa6dc3 100644 --- a/couchdbkit/ext/pylons/auth/basic.py +++ b/couchdbkit/ext/pylons/auth/basic.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import logging from paste.request import parse_dict_querystring, parse_formvars from paste.httpexceptions import HTTPUnauthorized diff --git a/couchdbkit/ext/pylons/auth/model.py b/couchdbkit/ext/pylons/auth/model.py index 2203e66..2a3c097 100644 --- a/couchdbkit/ext/pylons/auth/model.py +++ b/couchdbkit/ext/pylons/auth/model.py @@ -3,12 +3,14 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import from hashlib import sha256 import os from .... import Document, SchemaListProperty, StringProperty, \ StringListProperty +import six class Permission(Document): name = StringProperty(required=True) @@ -28,7 +30,7 @@ class User(Document): @staticmethod def _hash_password(cleartext): - if isinstance(cleartext, unicode): + if isinstance(cleartext, six.text_type): password_8bit = cleartext.encode('UTF-8') else: password_8bit = cleartext @@ -39,7 +41,7 @@ def _hash_password(cleartext): hash.update(password_8bit + salt.hexdigest()) hashed_password = salt.hexdigest() + hash.hexdigest() - if not isinstance(hashed_password, unicode): + if not isinstance(hashed_password, six.text_type): hashed_password = hashed_password.decode('UTF-8') return hashed_password diff --git a/couchdbkit/ext/pylons/commands.py b/couchdbkit/ext/pylons/commands.py index 925575c..2b46ae4 100644 --- a/couchdbkit/ext/pylons/commands.py +++ b/couchdbkit/ext/pylons/commands.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import os from paste.deploy import loadapp from paste.script.command import Command diff --git a/couchdbkit/ext/pylons/db.py b/couchdbkit/ext/pylons/db.py index f56c7e2..ef6a67c 100644 --- a/couchdbkit/ext/pylons/db.py +++ b/couchdbkit/ext/pylons/db.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import os.path from ...client import Server diff --git a/couchdbkit/ext/pylons/test.py b/couchdbkit/ext/pylons/test.py index b0785ac..ea93b64 100644 --- a/couchdbkit/ext/pylons/test.py +++ b/couchdbkit/ext/pylons/test.py @@ -5,6 +5,7 @@ from __future__ import with_statement +from __future__ import absolute_import import os import unittest diff --git a/couchdbkit/external.py b/couchdbkit/external.py index 9a83989..97a382a 100644 --- a/couchdbkit/external.py +++ b/couchdbkit/external.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import sys from .utils import json diff --git a/couchdbkit/loaders.py b/couchdbkit/loaders.py index e57355f..cd574a8 100644 --- a/couchdbkit/loaders.py +++ b/couchdbkit/loaders.py @@ -20,7 +20,9 @@ """ from __future__ import with_statement +from __future__ import absolute_import from .designer import document, push, pushapps, pushdocs +import six class BaseDocsLoader(object): """Baseclass for all doc loaders. """ @@ -46,13 +48,13 @@ class FileSystemDocsLoader(BaseDocsLoader): """ def __init__(self, designpath, docpath=None): - if isinstance(designpath, basestring): + if isinstance(designpath, six.string_types): self.designpaths = [designpath] else: self.designpaths = designpath docpath = docpath or [] - if isinstance(docpath, basestring): + if isinstance(docpath, six.string_types): docpath = [docpath] self.docpaths = docpath diff --git a/couchdbkit/resource.py b/couchdbkit/resource.py index 5bff83e..8b6c288 100644 --- a/couchdbkit/resource.py +++ b/couchdbkit/resource.py @@ -19,6 +19,7 @@ u'Welcome' """ +from __future__ import absolute_import import base64 import re from datetime import datetime @@ -32,6 +33,7 @@ PreconditionFailed from .utils import json from .logging import request_logger +import six USER_AGENT = 'couchdbkit/%s' % __version__ @@ -108,7 +110,7 @@ def request(self, method, path=None, payload=None, headers=None, **params): if payload is not None: #TODO: handle case we want to put in payload json file. - if not hasattr(payload, 'read') and not isinstance(payload, basestring): + if not hasattr(payload, 'read') and not isinstance(payload, six.string_types): payload = json.dumps(payload).encode('utf-8') headers.setdefault('Content-Type', 'application/json') @@ -116,7 +118,7 @@ def request(self, method, path=None, payload=None, headers=None, **params): try: resp = Resource.request(self, method, path=path, payload=payload, headers=headers, **params) - except ResourceError, e: + except ResourceError as e: msg = getattr(e, 'msg', '') if e.response and msg: if e.response.headers.get('content-type') == 'application/json': @@ -175,7 +177,7 @@ def encode_params(params): value = json.dumps(value) elif value is None: continue - elif not isinstance(value, basestring): + elif not isinstance(value, six.string_types): value = json.dumps(value) _params[name] = value return _params @@ -191,7 +193,7 @@ def escape_docid(docid): re_sp = re.compile('\s') def encode_attachments(attachments): - for k, v in attachments.iteritems(): + for k, v in six.iteritems(attachments): if v.get('stub', False): continue else: diff --git a/couchdbkit/schema/base.py b/couchdbkit/schema/base.py index 92b054c..84fb60c 100644 --- a/couchdbkit/schema/base.py +++ b/couchdbkit/schema/base.py @@ -6,6 +6,7 @@ """ module that provides a Document object that allows you to map CouchDB document in Python statically, dynamically or both """ +from __future__ import absolute_import import copy import jsonobject @@ -18,6 +19,7 @@ LazyDict, LazyList from ..exceptions import DuplicatePropertyError, ResourceNotFound, \ ReservedWordError +import six __all__ = ['ReservedWordError', 'DocumentSchema', @@ -36,7 +38,7 @@ def check_reserved_words(attr_name): locals()) def valid_id(value): - if isinstance(value, basestring) and not value.startswith('_'): + if isinstance(value, six.string_types) and not value.startswith('_'): return value raise TypeError('id "%s" is invalid' % value) @@ -49,7 +51,7 @@ def __new__(mcs, name, bases, dct): doc_type_attr = ( super(SchemaProperties, mcs).__new__(mcs, '', bases, {}) )._doc_type_attr - if isinstance(dct.get(doc_type_attr), basestring): + if isinstance(dct.get(doc_type_attr), six.string_types): doc_type = dct.pop(doc_type_attr) else: doc_type = name @@ -61,9 +63,7 @@ def __new__(mcs, name, bases, dct): return cls -class DocumentSchema(jsonobject.JsonObject): - - __metaclass__ = SchemaProperties +class DocumentSchema(six.with_metaclass(SchemaProperties, jsonobject.JsonObject)): _validate_required_lazily = True _doc_type_attr = 'doc_type' diff --git a/couchdbkit/schema/properties.py b/couchdbkit/schema/properties.py index 8758f5a..de0d8f1 100644 --- a/couchdbkit/schema/properties.py +++ b/couchdbkit/schema/properties.py @@ -2,9 +2,11 @@ # # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import functools from jsonobject.properties import * from jsonobject.base import DefaultProperty +import six try: from collections import MutableSet, Iterable @@ -16,8 +18,8 @@ def is_iterable(c): except ImportError: support_setproperty = False -StringListProperty = functools.partial(ListProperty, unicode) -StringDictProperty = functools.partial(DictProperty, unicode) +StringListProperty = functools.partial(ListProperty, six.text_type) +StringDictProperty = functools.partial(DictProperty, six.text_type) class Property(DefaultProperty): diff --git a/couchdbkit/schema/properties_proxy.py b/couchdbkit/schema/properties_proxy.py index ba06484..1847c10 100644 --- a/couchdbkit/schema/properties_proxy.py +++ b/couchdbkit/schema/properties_proxy.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from jsonobject import ObjectProperty, ListProperty, DictProperty SchemaProperty = ObjectProperty diff --git a/couchdbkit/schema/util.py b/couchdbkit/schema/util.py index 2069336..64a31ea 100644 --- a/couchdbkit/schema/util.py +++ b/couchdbkit/schema/util.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from couchdbkit.exceptions import DocTypeError @@ -23,7 +24,7 @@ def doctype_attr_of(classes): def get_multi_wrapper(classes): - doctype_attr = doctype_attr_of(classes.values()) + doctype_attr = doctype_attr_of(list(classes.values())) def wrap(doc): doc_type = doc.get(doctype_attr) diff --git a/couchdbkit/utils.py b/couchdbkit/utils.py index 6086d80..5c25cca 100644 --- a/couchdbkit/utils.py +++ b/couchdbkit/utils.py @@ -10,13 +10,17 @@ """ from __future__ import with_statement +from __future__ import absolute_import +from __future__ import print_function import codecs import string from hashlib import md5 import os import re import sys -import urllib +import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error +import six +from six.moves import range try: @@ -123,15 +127,15 @@ def validate_dbname(name): """ validate dbname """ if name in SPECIAL_DBS: return True - elif not VALID_DB_NAME.match(urllib.unquote(name)): + elif not VALID_DB_NAME.match(six.moves.urllib.parse.unquote(name)): raise ValueError("Invalid db name: '%s'" % name) return True def to_bytestring(s): """ convert to bytestring an unicode """ - if not isinstance(s, basestring): + if not isinstance(s, six.string_types): return s - if isinstance(s, unicode): + if isinstance(s, six.text_type): return s.encode('utf-8') else: return s @@ -195,7 +199,7 @@ def read_json(filename, use_environment=False): """ try: data = read_file(filename, force_read=True) - except IOError, e: + except IOError as e: if e[0] == 2: return {} raise @@ -206,7 +210,7 @@ def read_json(filename, use_environment=False): try: data = json.loads(data) except ValueError: - print >>sys.stderr, "Json is invalid, can't load %s" % filename + print("Json is invalid, can't load %s" % filename, file=sys.stderr) raise return data diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 0eb6f75..f83fa5a 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -3,5 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import +from six.moves import map version_info = (0, 9, 0, 0) __version__ = ".".join(map(str, version_info)) diff --git a/couchdbkit/wsgi/handler.py b/couchdbkit/wsgi/handler.py index fafdb1f..20e8d1d 100644 --- a/couchdbkit/wsgi/handler.py +++ b/couchdbkit/wsgi/handler.py @@ -3,10 +3,11 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import import sys import StringIO import traceback -from urllib import unquote +from six.moves.urllib.parse import unquote from restkit.util import url_encode diff --git a/couchdbkit/wsgi/proxy.py b/couchdbkit/wsgi/proxy.py index 0849975..a858286 100644 --- a/couchdbkit/wsgi/proxy.py +++ b/couchdbkit/wsgi/proxy.py @@ -3,7 +3,8 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. -import urlparse +from __future__ import absolute_import +import six.moves.urllib.parse from restkit.contrib.wsgi_proxy import HostProxy, ALLOWED_METHODS from webob import Request @@ -34,7 +35,7 @@ def __call__(self, environ, start_response): req = Request(environ) if 'RAW_URI' in req.environ: # gunicorn so we can use real path non encoded - u = urlparse.urlparse(req.environ['RAW_URI']) + u = six.moves.urllib.parse.urlparse(req.environ['RAW_URI']) req.environ['PATH_INFO'] = u.path resp = self.do_proy(req, environ, start_response) diff --git a/distribute_setup.py b/distribute_setup.py index cfb3bbe..ef014ed 100644 --- a/distribute_setup.py +++ b/distribute_setup.py @@ -13,6 +13,7 @@ This file can also be run as a script to install or upgrade setuptools. """ +from __future__ import absolute_import import os import sys import time @@ -178,7 +179,7 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, try: from urllib.request import urlopen except ImportError: - from urllib2 import urlopen + from six.moves.urllib.request import urlopen tgz_name = "distribute-%s.tar.gz" % version url = download_base + tgz_name saveto = os.path.join(to_dir, tgz_name) diff --git a/doc/couchdbkit.org/buildweb.py b/doc/couchdbkit.org/buildweb.py index ea538bf..e01d473 100755 --- a/doc/couchdbkit.org/buildweb.py +++ b/doc/couchdbkit.org/buildweb.py @@ -17,6 +17,8 @@ from __future__ import with_statement +from __future__ import absolute_import +from __future__ import print_function import codecs import datetime import os @@ -27,6 +29,7 @@ from jinja2 import Environment from jinja2.loaders import FileSystemLoader from jinja2.utils import open_if_exists +import six try: import markdown except ImportError: @@ -106,7 +109,7 @@ def process_directory(self, current_dir, files, target_path): files = [f for f in files if os.path.splitext(f)[1] in conf.EXTENSIONS] blog = None for f in files: - print "process %s" % f + print("process %s" % f) page = Page(self, f, current_dir, target_path) if page.is_blog() and f == "index.txt" or f == "archives.txt": continue @@ -119,14 +122,14 @@ def process_directory(self, current_dir, files, target_path): if not source_newer(page.finput, page.foutput) and f != "index.txt": continue - print "write %s" % page.foutput + print("write %s" % page.foutput) try: f = codecs.open(page.foutput, 'w', 'utf-8') try: f.write(page.render()) finally: f.close() - except (IOError, OSError), err: + except (IOError, OSError) as err: raise self.sitemap.append(page) if blog is not None: @@ -218,7 +221,7 @@ def render(self): archives_page = None if not os.path.isfile(index_page.finput): - raise IOError, "index.txt isn't found in %s" % self.current_dir + raise IOError("index.txt isn't found in %s" % self.current_dir) self.pages.sort(lambda a, b: a.headers['pubDate'] - b.headers['pubDate'], reverse=True) @@ -249,7 +252,7 @@ def render(self): f.write(page.render()) finally: f.close() - except (IOError, OSError), err: + except (IOError, OSError) as err: raise self.site.sitemap.append(page) @@ -294,20 +297,20 @@ def parse(self): (header_lines,body) = raw.split("\n\n", 1) for header in header_lines.split("\n"): (name, value) = header.split(": ", 1) - headers[name.lower()] = unicode(value.strip()) + headers[name.lower()] = six.text_type(value.strip()) self.headers = headers self.headers['pubDate'] = os.stat(self.finput)[ST_CTIME] self.headers['published'] = datetime.datetime.fromtimestamp(self.headers['pubDate']) self.body = body content_type = self.headers.get('content_type', conf.CONTENT_TYPE) - if content_type in self.content_types.keys(): + if content_type in list(self.content_types.keys()): self.foutput = os.path.join(self.target_path, "%s.%s" % (os.path.splitext(self.filename)[0], self.files_ext[content_type])) self.url = self.get_url() else: - raise TypeError, "Unknown content_type" + raise TypeError("Unknown content_type") except: - raise TypeError, "Invalid page file format for %s" % self.finput + raise TypeError("Invalid page file format for %s" % self.finput) self.parsed = True def is_blog(self): @@ -320,11 +323,11 @@ def render(self): self.parse() template = self.headers.get('template', conf.DEFAULT_TEMPLATE) content_type = self.headers.get('content_type', conf.CONTENT_TYPE) - if content_type in self.content_types.keys(): + if content_type in list(self.content_types.keys()): fun = getattr(self, "render_%s" % content_type) return fun(template) else: - raise TypeError, "Unknown content_type" + raise TypeError("Unknown content_type") def _render_html(self, template, body): kwargs = { @@ -341,13 +344,13 @@ def render_html(self, template): def render_markdown(self, template): if markdown is None: - raise TypeError, "markdown isn't suported" + raise TypeError("markdown isn't suported") body = convert_markdown(self.body) return self._render_html(template, body) def render_textile(self, template): if textile is None: - raise TypeError, "textile isn't suported" + raise TypeError("textile isn't suported") body = convert_textile(self.body) return self._render_html(template, body) diff --git a/doc/couchdbkit.org/conf.py b/doc/couchdbkit.org/conf.py index e788353..bcbc502 100644 --- a/doc/couchdbkit.org/conf.py +++ b/doc/couchdbkit.org/conf.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import import os, platform # options diff --git a/examples/django_blogapp/blog_app/models.py b/examples/django_blogapp/blog_app/models.py index ed5ccc8..6499c9b 100644 --- a/examples/django_blogapp/blog_app/models.py +++ b/examples/django_blogapp/blog_app/models.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from datetime import datetime from couchdbkit.ext.django.schema import Document, StringProperty, \ diff --git a/examples/django_blogapp/blog_app/tests.py b/examples/django_blogapp/blog_app/tests.py index 501deb7..e585808 100644 --- a/examples/django_blogapp/blog_app/tests.py +++ b/examples/django_blogapp/blog_app/tests.py @@ -5,6 +5,7 @@ Replace this with more appropriate tests for your application. """ +from __future__ import absolute_import from django.test import TestCase diff --git a/examples/django_blogapp/blog_app/views.py b/examples/django_blogapp/blog_app/views.py index 8834e63..997e86b 100644 --- a/examples/django_blogapp/blog_app/views.py +++ b/examples/django_blogapp/blog_app/views.py @@ -1,10 +1,11 @@ +from __future__ import absolute_import from couchdbkit.ext.django.forms import DocumentForm from django.forms.fields import CharField from django.forms.widgets import HiddenInput from django.shortcuts import render_to_response from django.template import RequestContext -from models import Post, Comment +from .models import Post, Comment class PostForm(DocumentForm): diff --git a/examples/django_blogapp/settings.py b/examples/django_blogapp/settings.py index d1fa5ba..da59a5b 100644 --- a/examples/django_blogapp/settings.py +++ b/examples/django_blogapp/settings.py @@ -1,4 +1,5 @@ # Django settings for django_blogapp project. +from __future__ import absolute_import import os PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) diff --git a/examples/django_blogapp/urls.py b/examples/django_blogapp/urls.py index f8efdee..4bbb5b1 100644 --- a/examples/django_blogapp/urls.py +++ b/examples/django_blogapp/urls.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.conf.urls import patterns, url diff --git a/examples/django_blogapp/wsgi.py b/examples/django_blogapp/wsgi.py index ab90313..8cfdede 100644 --- a/examples/django_blogapp/wsgi.py +++ b/examples/django_blogapp/wsgi.py @@ -13,6 +13,7 @@ framework. """ +from __future__ import absolute_import import os # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks diff --git a/examples/djangoapp/greeting/models.py b/examples/djangoapp/greeting/models.py index 9f41e09..c8205ee 100755 --- a/examples/djangoapp/greeting/models.py +++ b/examples/djangoapp/greeting/models.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from datetime import datetime from django.db import models diff --git a/examples/djangoapp/greeting/views.py b/examples/djangoapp/greeting/views.py index a11b58c..ff21698 100755 --- a/examples/djangoapp/greeting/views.py +++ b/examples/djangoapp/greeting/views.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import from datetime import datetime from django.shortcuts import render_to_response as render from django.template import RequestContext, loader, Context diff --git a/examples/djangoapp/manage.py b/examples/djangoapp/manage.py index 5e78ea9..727a271 100755 --- a/examples/djangoapp/manage.py +++ b/examples/djangoapp/manage.py @@ -1,7 +1,8 @@ #!/usr/bin/env python +from __future__ import absolute_import from django.core.management import execute_manager try: - import settings # Assumed to be in the same directory. + from . import settings # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) diff --git a/examples/djangoapp/run.py b/examples/djangoapp/run.py index 182487c..8907764 100755 --- a/examples/djangoapp/run.py +++ b/examples/djangoapp/run.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import absolute_import from couchdbkit.wsgi.handler import WSGIHandler import os import sys diff --git a/examples/djangoapp/settings.py b/examples/djangoapp/settings.py index a6a6d77..03ef6be 100755 --- a/examples/djangoapp/settings.py +++ b/examples/djangoapp/settings.py @@ -1,5 +1,6 @@ # Django settings for testapp project. +from __future__ import absolute_import import os, platform PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) diff --git a/examples/djangoapp/urls.py b/examples/djangoapp/urls.py index 4a09ece..bb0cc79 100755 --- a/examples/djangoapp/urls.py +++ b/examples/djangoapp/urls.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.conf.urls.defaults import * urlpatterns = patterns('', diff --git a/examples/pyramidapp/pyramid_couchdb_example/__init__.py b/examples/pyramidapp/pyramid_couchdb_example/__init__.py index d15a2c0..1ed361a 100644 --- a/examples/pyramidapp/pyramid_couchdb_example/__init__.py +++ b/examples/pyramidapp/pyramid_couchdb_example/__init__.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from pyramid.config import Configurator from pyramid.events import subscriber, ApplicationCreated from couchdbkit import * diff --git a/examples/pyramidapp/pyramid_couchdb_example/tests.py b/examples/pyramidapp/pyramid_couchdb_example/tests.py index b51e540..2bfb564 100644 --- a/examples/pyramidapp/pyramid_couchdb_example/tests.py +++ b/examples/pyramidapp/pyramid_couchdb_example/tests.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import unittest from pyramid import testing diff --git a/examples/pyramidapp/pyramid_couchdb_example/views.py b/examples/pyramidapp/pyramid_couchdb_example/views.py index 859f991..694c788 100644 --- a/examples/pyramidapp/pyramid_couchdb_example/views.py +++ b/examples/pyramidapp/pyramid_couchdb_example/views.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import datetime from pyramid.view import view_config diff --git a/examples/pyramidapp/setup.py b/examples/pyramidapp/setup.py index 774d729..1707acd 100644 --- a/examples/pyramidapp/setup.py +++ b/examples/pyramidapp/setup.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import os from setuptools import setup, find_packages diff --git a/examples/wsgi/test.py b/examples/wsgi/test.py index 5a79960..f894fc4 100755 --- a/examples/wsgi/test.py +++ b/examples/wsgi/test.py @@ -15,6 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import absolute_import import couchdbkit from couchdbkit.contrib import WSGIHandler import json diff --git a/setup.py b/setup.py index ff771fa..efd40ee 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import from imp import load_source import os import sys diff --git a/tests/client_test.py b/tests/client_test.py index 55deeeb..1447f08 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -3,6 +3,8 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. # +from __future__ import absolute_import +import six __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' import copy @@ -28,7 +30,7 @@ def tearDown(self): def testGetInfo(self): info = self.Server.info() - self.assert_(info.has_key('version')) + self.assert_('version' in info) def testCreateDb(self): res = self.Server.create_db('couchdbkit_test') @@ -76,7 +78,7 @@ def testServerContain(self): def testGetUUIDS(self): uuid = self.Server.next_uuid() - self.assert_(isinstance(uuid, basestring) == True) + self.assert_(isinstance(uuid, six.string_types) == True) self.assert_(len(self.Server._uuids) == 999) uuid2 = self.Server.next_uuid() self.assert_(uuid != uuid2) @@ -523,7 +525,7 @@ def testMultipleDocCOnflict(self): all_errors = [] try: db.bulk_save(docs2) - except BulkSaveError, e: + except BulkSaveError as e: all_errors = e.errors self.assertEqual(len(all_errors), 2) diff --git a/tests/test_changes.py b/tests/test_changes.py index c48793f..203458e 100644 --- a/tests/test_changes.py +++ b/tests/test_changes.py @@ -3,6 +3,8 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. # +from __future__ import absolute_import +from six.moves import range __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' import threading diff --git a/tests/test_consumer.py b/tests/test_consumer.py index d6d5980..cb7a2b9 100644 --- a/tests/test_consumer.py +++ b/tests/test_consumer.py @@ -3,6 +3,8 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. # +from __future__ import absolute_import +from six.moves import range __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' import threading diff --git a/tests/test_loaders.py b/tests/test_loaders.py index 94c0586..416e490 100644 --- a/tests/test_loaders.py +++ b/tests/test_loaders.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. # +from __future__ import absolute_import __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' import base64 diff --git a/tests/test_resource.py b/tests/test_resource.py index dcb9e9c..9ae12f5 100644 --- a/tests/test_resource.py +++ b/tests/test_resource.py @@ -3,6 +3,7 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. # +from __future__ import absolute_import __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' import unittest @@ -28,7 +29,7 @@ def tearDown(self): def testGetInfo(self): info = self.couchdb.get().json_body - self.assert_(info.has_key('version')) + self.assert_('version' in info) def testCreateDb(self): res = self.couchdb.put('/couchdkbit_test').json_body diff --git a/tests/test_schema.py b/tests/test_schema.py index 50f3b16..b85847d 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -3,6 +3,9 @@ # This file is part of couchdbkit released under the MIT license. # See the NOTICE for more information. +from __future__ import absolute_import +from __future__ import print_function +import six __author__ = 'benoitc@e-engura.com (Benoît Chesneau)' import datetime @@ -41,7 +44,7 @@ class Test(Document): doc.foo="test" try: doc.bar="bla" - except AttributeError, e: + except AttributeError as e: self.assertEqual(str(e), "'bar' is not defined in schema (not a valid property)") doc.save() self.assert_(not hasattr(doc, "bar")) @@ -56,7 +59,7 @@ class Test(StaticDocument): doc.foo="test" try: doc.bar="bla" - except AttributeError, e: + except AttributeError as e: self.assertEqual(str(e), "'bar' is not defined in schema (not a valid property)") doc.save() self.assert_(not hasattr(doc, "bar")) @@ -178,14 +181,14 @@ class Test2(Document): try: Test.bulk_save( [doc1, doc2, doc3] ) - except TypeError, e: + except TypeError as e: self.assert_(str(e)== "doc database required to save document" ) Test.set_db( db ) bad_doc = Test2(string="bad_doc") try: Test.bulk_save( [doc1, doc2, doc3, bad_doc] ) - except ValueError, e: + except ValueError as e: self.assert_(str(e) == "one of your documents does not have the correct type" ) Test.bulk_save( [doc1, doc2, doc3] ) @@ -252,7 +255,7 @@ class Test(Document): doc1 = Test.get(doc._id) self.server.delete_db('couchdbkit_test') - self.assert_(isinstance(doc1.field, basestring)) + self.assert_(isinstance(doc1.field, six.string_types)) self.assert_(isinstance(doc1.field1, datetime.datetime)) self.assert_(isinstance(doc1.field2, datetime.date)) self.assert_(isinstance(doc1.field3, datetime.time)) @@ -423,7 +426,7 @@ class TestDoc(Document): results3 = TestDoc.view('test/all', include_docs=True, wrapper=lambda row: row['doc']['field1']) self.assert_(len(results3) == 2) - self.assert_(isinstance(results3.first(), unicode) == True) + self.assert_(isinstance(results3.first(), six.text_type) == True) self.server.delete_db('couchdbkit_test') def test_wrong_doc_type(self): @@ -602,7 +605,7 @@ class Test(Document): db.bulk_delete([doc1, doc2, doc3]) - print list(db.all_docs(include_docs=True)) + print(list(db.all_docs(include_docs=True))) self.assert_(len(db) == 0) self.assert_(db.info()['doc_del_count'] == 3) @@ -704,7 +707,7 @@ def ftest(): test_dates = [ ([2008, 11, 10, 8, 0, 0], "2008-11-10T08:00:00Z"), ([9999, 12, 31, 23, 59, 59], '9999-12-31T23:59:59Z'), - ([0001, 1, 1, 0, 0, 1], '0001-01-01T00:00:01Z'), + ([0o001, 1, 1, 0, 0, 1], '0001-01-01T00:00:01Z'), ] for date, date_str in test_dates: @@ -752,7 +755,7 @@ class Test(Document): self.assert_(test._doc['field'] == "test") self.assert_(test._doc['field1'] == "2008-11-10T08:00:00Z") - self.assert_(isinstance(test.field, basestring)) + self.assert_(isinstance(test.field, six.string_types)) self.assert_(isinstance(test.field1, datetime.datetime)) Test._db = self.db test.save() @@ -760,7 +763,7 @@ class Test(Document): v = doc2.field v1 = doc2.field1 - self.assert_(isinstance(v, basestring)) + self.assert_(isinstance(v, six.string_types)) self.assert_(isinstance(v1, datetime.datetime)) def testMixDynamicProperties(self): @@ -782,7 +785,7 @@ class Test(Document): vd = doc2.dynamic_field self.assert_(isinstance(v1, datetime.datetime)) - self.assert_(isinstance(vd, basestring)) + self.assert_(isinstance(vd, six.string_types)) def testSchemaProperty1(self): class MySchema(DocumentSchema): @@ -899,7 +902,7 @@ class DocTwo(Document): def testSchemaWithPythonTypes(self): class A(Document): - c = unicode() + c = six.text_type() i = int(4) a = A() self.assert_(a._doc == {'c': u'', 'doc_type': 'A', 'i': 4}) @@ -1056,8 +1059,8 @@ class B(Document): self.assertEqual([b.slm[0].s, b.slm[1].s], [a1.s, a2.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}, - {'doc_type': 'A', 's': unicode(a2.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a1.s)}, + {'doc_type': 'A', 's': six.text_type(a2.s)}] }) b.slm.append(a3) c = b.slm[1:3] @@ -1068,7 +1071,7 @@ class B(Document): self.assertEqual(b.slm[0].s, a1.s) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a1.s)}] }) def testSchemaListPropertyContains(self): @@ -1125,8 +1128,8 @@ class B(Document): self.assertEqual([b.slm[0].s, b.slm[1].s], [a1.s, a2.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}, - {'doc_type': 'A', 's': unicode(a2.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a1.s)}, + {'doc_type': 'A', 's': six.text_type(a2.s)}] }) def testSchemaListPropertyIndex(self): @@ -1180,9 +1183,9 @@ class B(Document): [b.slm[0].s, b.slm[1].s, b.slm[2].s], [a1.s, a2.s, a3.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}, - {'doc_type': 'A', 's': unicode(a2.s)}, - {'doc_type': 'A', 's': unicode(a3.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a1.s)}, + {'doc_type': 'A', 's': six.text_type(a2.s)}, + {'doc_type': 'A', 's': six.text_type(a3.s)}] }) def testSchemaListPropertyPop(self): @@ -1208,8 +1211,8 @@ class B(Document): self.assertEqual([b.slm[0].s, b.slm[1].s], [a1.s, a2.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}, - {'doc_type': 'A', 's': unicode(a2.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a1.s)}, + {'doc_type': 'A', 's': six.text_type(a2.s)}] }) v = b.slm.pop(0) self.assertEqual(v.s, a1.s) @@ -1217,7 +1220,7 @@ class B(Document): self.assertEqual(b.slm[0].s, a2.s) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a2.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a2.s)}] }) def testSchemaListPropertyRemove(self): @@ -1240,7 +1243,7 @@ class B(Document): self.assertEqual(b.slm[0].s, a2.s) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a2.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a2.s)}] }) with self.assertRaises(ValueError) as cm: b.slm.remove(a1) @@ -1268,8 +1271,8 @@ class B(Document): self.assertEqual([b.slm[0].s, b.slm[1].s], [a2.s, a1.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a2.s)}, - {'doc_type': 'A', 's': unicode(a1.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a2.s)}, + {'doc_type': 'A', 's': six.text_type(a1.s)}] }) def testSchemaListPropertySort(self): @@ -1291,22 +1294,22 @@ class B(Document): self.assertEqual([b.slm[0].s, b.slm[1].s], [a1.s, a2.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}, - {'doc_type': 'A', 's': unicode(a2.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a1.s)}, + {'doc_type': 'A', 's': six.text_type(a2.s)}] }) b.slm.sort(key=lambda item: item['s'], reverse=True) self.assertEqual([b.slm[0].s, b.slm[1].s], [a2.s, a1.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a2.s)}, - {'doc_type': 'A', 's': unicode(a1.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a2.s)}, + {'doc_type': 'A', 's': six.text_type(a1.s)}] }) b.slm.sort(cmp=lambda x, y: cmp(x['s'].lower(), y['s'].lower())) self.assertEqual([b.slm[0].s, b.slm[1].s], [a1.s, a2.s]) self.assertEqual(b._doc, { 'doc_type': 'B', - 'slm': [{'doc_type': 'A', 's': unicode(a1.s)}, - {'doc_type': 'A', 's': unicode(a2.s)}] + 'slm': [{'doc_type': 'A', 's': six.text_type(a1.s)}, + {'doc_type': 'A', 's': six.text_type(a2.s)}] }) def testSchemaDictProperty(self): @@ -1373,7 +1376,7 @@ class A(Document): self.assertRaises(BadValueError, a.save) try: a.validate() - except BadValueError, e: + except BadValueError as e: pass self.assert_(str(e) == 'Property l is required.') @@ -1584,7 +1587,7 @@ class A(Document): self.assertRaises(BadValueError, a.save) try: a.save() - except BadValueError, e: + except BadValueError as e: pass self.assert_(str(e) == 'Property d is required.') From 04ce94f90f2cdce0a6d6c23fc72a221f59fe6afe Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 8 Feb 2018 10:45:03 -0500 Subject: [PATCH 115/151] keep using the filter keyword --- couchdbkit/client.py | 3 ++- couchdbkit/designer/fs.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 01c5d81..ef118ab 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -42,6 +42,7 @@ from requests.exceptions import HTTPError from restkit.util import url_quote import six +from six.moves import filter from six.moves.urllib.parse import urljoin, unquote from couchdbkit.logging import error_logger @@ -852,7 +853,7 @@ def put_attachment(self, doc, content, name=None, content_type=None, name = url_quote(name, safe="") if content_type is None: - content_type = ';'.join([_f for _f in guess_type(name) if _f]) + content_type = ';'.join(filter(None, guess_type(name))) if content_type: headers['Content-Type'] = content_type diff --git a/couchdbkit/designer/fs.py b/couchdbkit/designer/fs.py index 0b05812..a6a3aab 100644 --- a/couchdbkit/designer/fs.py +++ b/couchdbkit/designer/fs.py @@ -20,6 +20,7 @@ from .macros import package_shows, package_views from .. import utils import six +from six.moves import filter if os.name == 'nt': def _replace_backslash(name): @@ -133,7 +134,8 @@ def attachment_stub(self, name, filepath): re_sp = re.compile('\s') att = { "data": re_sp.sub('',base64.b64encode(f.read())), - "content_type": ';'.join([_f for _f in mimetypes.guess_type(name) if _f]) + "content_type": ';'.join(filter(None, + mimetypes.guess_type(name))) } return att From 880c0cd54043dae8b4079be56f6cff8f3a0c67e0 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 8 Feb 2018 10:49:44 -0500 Subject: [PATCH 116/151] don't use octal --- tests/test_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index b85847d..e84faa3 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -707,7 +707,7 @@ def ftest(): test_dates = [ ([2008, 11, 10, 8, 0, 0], "2008-11-10T08:00:00Z"), ([9999, 12, 31, 23, 59, 59], '9999-12-31T23:59:59Z'), - ([0o001, 1, 1, 0, 0, 1], '0001-01-01T00:00:01Z'), + ([1, 1, 1, 0, 0, 1], '0001-01-01T00:00:01Z'), ] for date, date_str in test_dates: From 26c27442a159c175ba0346d4ccb99b1ecb7f3ef5 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 21 Jun 2018 16:31:52 -0400 Subject: [PATCH 117/151] bump to version 0.9.0.1 --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index f83fa5a..a9943e3 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 0) +version_info = (0, 9, 0, 1) __version__ = ".".join(map(str, version_info)) From 38d965feca00a9a90ffcb59b182ba726406804e1 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 21 Jun 2018 20:39:30 -0400 Subject: [PATCH 118/151] try pinning requests --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index efd40ee..61ffb32 100755 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ zip_safe = False, install_requires = [ + 'requests==2.18.4', 'restkit>=4.2.2', 'jsonobject>=0.6.0', 'cloudant==2.7.0', From 35eedb5fd10b5eb8a5b60b631e868cb5cdb2a0a4 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 21 Jun 2018 20:43:12 -0400 Subject: [PATCH 119/151] pin jsonobject --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 61ffb32..02c632a 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ install_requires = [ 'requests==2.18.4', 'restkit>=4.2.2', - 'jsonobject>=0.6.0', + 'jsonobject==0.8.0', 'cloudant==2.7.0', 'six==1.11.0', ], From 4eb140c42be318638f4133cba0450d070443a4c4 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 21 Jun 2018 20:46:52 -0400 Subject: [PATCH 120/151] Revert "try pinning requests" This reverts commit 38d965feca00a9a90ffcb59b182ba726406804e1. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 02c632a..95d5d08 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,6 @@ zip_safe = False, install_requires = [ - 'requests==2.18.4', 'restkit>=4.2.2', 'jsonobject==0.8.0', 'cloudant==2.7.0', From cc054903e2453d7ac5533b244ea8596678b9697d Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 21 Jun 2018 20:51:47 -0400 Subject: [PATCH 121/151] jsonobject to 0.9.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 95d5d08..614cad3 100755 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject==0.8.0', + 'jsonobject==0.9.0', 'cloudant==2.7.0', 'six==1.11.0', ], From e369620fea7ee1069ea7404454e09bcd34ea73a0 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 21 Jun 2018 21:00:12 -0400 Subject: [PATCH 122/151] modify test --- tests/test_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index e84faa3..57bcdf0 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1799,7 +1799,7 @@ class B(Document): class C(Document): s = SetProperty(item_type=tuple) self.assertIn( - "item_type not in set([", str(cm.exception)) + "item_type not in ", str(cm.exception)) def testSetPropertyAssignment(self): From 511521f4f8542e7cfbd5ef0579e2ae04e741649f Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 21 Jun 2018 21:03:22 -0400 Subject: [PATCH 123/151] update to latest jsonobject --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 614cad3..7470657 100755 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ install_requires = [ 'restkit>=4.2.2', - 'jsonobject==0.9.0', + 'jsonobject>=0.9.1', 'cloudant==2.7.0', 'six==1.11.0', ], From 131b16d150e2fe9f3943c723997fab7ecde71bf0 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Sat, 30 Jun 2018 13:17:07 -0400 Subject: [PATCH 124/151] raise ResourceNotFound when deleting database raises CloudantClientException --- couchdbkit/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index ef118ab..31a41b5 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -38,6 +38,7 @@ from cloudant.client import CouchDB from cloudant.database import CouchDatabase from cloudant.document import Document +from cloudant.error import CloudantClientException from cloudant.security_document import SecurityDocument from requests.exceptions import HTTPError from restkit.util import url_quote @@ -175,7 +176,10 @@ def delete_db(self, dbname): """ Delete database """ - del self[dbname] + try: + del self[dbname] + except CloudantClientException: + raise ResourceNotFound #TODO: maintain list of replications def replicate(self, source, target, **params): From 6dd4a694b5a3f72c907b6f06cd33e5361c1dddcc Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 5 Jul 2018 13:13:35 -0400 Subject: [PATCH 125/151] preserve exception message --- couchdbkit/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 31a41b5..c4803e3 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -178,8 +178,8 @@ def delete_db(self, dbname): """ try: del self[dbname] - except CloudantClientException: - raise ResourceNotFound + except CloudantClientException as e: + raise ResourceNotFound(six.text_type(e)) #TODO: maintain list of replications def replicate(self, source, target, **params): From dd2707786c3648d3cd91ef3e3c740c013d309d6d Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 09:10:09 -0400 Subject: [PATCH 126/151] open_doc should return a dict, not a cloudant Document --- couchdbkit/client.py | 1 + couchdbkit/version.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index c4803e3..ec978cb 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -415,6 +415,7 @@ def open_doc(self, docid, **params): if e.response.status_code == 404: raise ResourceNotFound raise + doc = json.loads(doc.json()) if wrapper is not None: if not callable(wrapper): raise TypeError("wrapper isn't a callable") diff --git a/couchdbkit/version.py b/couchdbkit/version.py index a9943e3..e4aa3be 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 1) +version_info = (0, 9, 0, 3) __version__ = ".".join(map(str, version_info)) From 9546b72c8af2976f63185ff84579e41fa6e54732 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 09:36:45 -0400 Subject: [PATCH 127/151] use dict instead of json.loads --- couchdbkit/client.py | 2 +- couchdbkit/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index ec978cb..d83129c 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -415,7 +415,7 @@ def open_doc(self, docid, **params): if e.response.status_code == 404: raise ResourceNotFound raise - doc = json.loads(doc.json()) + doc = dict(doc) if wrapper is not None: if not callable(wrapper): raise TypeError("wrapper isn't a callable") diff --git a/couchdbkit/version.py b/couchdbkit/version.py index e4aa3be..a497b55 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3) +version_info = (0, 9, 0, 3, 1) __version__ = ".".join(map(str, version_info)) From 6503264420aa3fdd21da849cd028ac76540f73fa Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 11:01:57 -0400 Subject: [PATCH 128/151] raise ResourceNotFound in get_rev if document is not found --- couchdbkit/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index d83129c..4200b37 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -513,6 +513,8 @@ def get_rev(self, docid): @return rev: str, the last revision of document. """ response = self._request_session.head(self._database_path(docid)) + if response.status_code == 404: + raise ResourceNotFound response.raise_for_status() return response.headers['ETag'].strip('"') From 7b20d3684abcd5451fdd15ef0da5b281c2170c66 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 11:02:04 -0400 Subject: [PATCH 129/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index a497b55..00272b1 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 1) +version_info = (0, 9, 0, 3, 2) __version__ = ".".join(map(str, version_info)) From 4ed38206fe8b8b8210c837294192dafd05edb81c Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 11:19:44 -0400 Subject: [PATCH 130/151] use try-except --- couchdbkit/client.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 4200b37..db1957a 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -513,9 +513,12 @@ def get_rev(self, docid): @return rev: str, the last revision of document. """ response = self._request_session.head(self._database_path(docid)) - if response.status_code == 404: - raise ResourceNotFound - response.raise_for_status() + try: + response.raise_for_status() + except HTTPError as e: + if e.response.status_code == 404: + raise ResourceNotFound + raise return response.headers['ETag'].strip('"') def save_doc(self, doc, encode_attachments=True, force_update=False, From 1ac374d12db4e982605cd5ef7f870a9293db3425 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 13:58:29 -0400 Subject: [PATCH 131/151] include reason in ResourceNotFound --- couchdbkit/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index db1957a..a88ef3e 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -413,7 +413,7 @@ def open_doc(self, docid, **params): doc.fetch() except HTTPError as e: if e.response.status_code == 404: - raise ResourceNotFound + raise ResourceNotFound(json.loads(e.response.content)['reason']) raise doc = dict(doc) if wrapper is not None: From 7095db47342d2b217ce6be5321dc0448b1d98f42 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 13:58:46 -0400 Subject: [PATCH 132/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 00272b1..cbe5d02 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 2) +version_info = (0, 9, 0, 3, 3) __version__ = ".".join(map(str, version_info)) From 9f9f919014f8ac173f5152ac31237e6e94fd55f1 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 15:21:46 -0400 Subject: [PATCH 133/151] make save_doc keep the same return values --- couchdbkit/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index a88ef3e..f15b109 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -575,7 +575,11 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, doc[key] = value else: doc.update(doc1) - return res + return { + 'id': res['_id'], + 'rev': res['_rev'], + 'ok': True, + } def save_docs(self, docs, use_uuids=True, new_edits=None, **params): """ bulk save. Modify Multiple Documents With a Single Request From 0e7477b710ae23cbd1797f134a24e1c6dd59f9fb Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Fri, 6 Jul 2018 15:21:52 -0400 Subject: [PATCH 134/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index cbe5d02..c41fbd3 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 3) +version_info = (0, 9, 0, 3, 4) __version__ = ".".join(map(str, version_info)) From fb7f92e39bf92b21c267d9e65095061b646f8862 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Tue, 10 Jul 2018 17:22:32 -0400 Subject: [PATCH 135/151] allow getting attachments with doc.get(doc_id, attachments=True) --- couchdbkit/client.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index f15b109..213a3be 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -27,6 +27,8 @@ """ from __future__ import absolute_import + +import base64 from collections import deque from copy import deepcopy from itertools import groupby @@ -405,6 +407,7 @@ def open_doc(self, docid, **params): if not hasattr(schema, "wrap"): raise TypeError("invalid schema") wrapper = schema.wrap + attachments = params.get('attachments', False) if isinstance(docid, six.text_type): docid = docid.encode('utf-8') @@ -415,14 +418,22 @@ def open_doc(self, docid, **params): if e.response.status_code == 404: raise ResourceNotFound(json.loads(e.response.content)['reason']) raise - doc = dict(doc) + doc_dict = dict(doc) + + if attachments and '_attachments' in doc_dict: + for attachment_name in doc_dict['_attachments']: + attachment_data = doc.get_attachment(attachment_name, attachment_type='binary') + doc_dict['_attachments'][attachment_name]['data'] = base64.b64encode(attachment_data) + del doc_dict['_attachments'][attachment_name]['stub'] + del doc_dict['_attachments'][attachment_name]['length'] + if wrapper is not None: if not callable(wrapper): raise TypeError("wrapper isn't a callable") - return wrapper(doc) + return wrapper(doc_dict) - return doc + return doc_dict get = open_doc def list(self, list_name, view_name, **params): From 77eb65876ad3788bed9ea565928c442350718091 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Tue, 10 Jul 2018 17:49:20 -0400 Subject: [PATCH 136/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index c41fbd3..75c243a 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 4) +version_info = (0, 9, 0, 3, 5) __version__ = ".".join(map(str, version_info)) From 35bb5a542bca4583de9c21213263cd342a9ede9d Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Wed, 11 Jul 2018 16:36:00 -0400 Subject: [PATCH 137/151] remove prefix 'W/"' from _rev --- couchdbkit/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 213a3be..4055005 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -530,7 +530,7 @@ def get_rev(self, docid): if e.response.status_code == 404: raise ResourceNotFound raise - return response.headers['ETag'].strip('"') + return response.headers['ETag'].strip('"').strip('W/"') def save_doc(self, doc, encode_attachments=True, force_update=False, **params): From 6f0e2152ef4ae997252cc4509be9e4b95e2e6b22 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Wed, 11 Jul 2018 16:41:55 -0400 Subject: [PATCH 138/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 75c243a..56e9570 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 5) +version_info = (0, 9, 0, 3, 6) __version__ = ".".join(map(str, version_info)) From af2c36691b4b90f8fa8331a85219d91b8c48da1d Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 12 Jul 2018 13:29:25 -0400 Subject: [PATCH 139/151] use lstrip --- couchdbkit/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 4055005..4e8037d 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -530,7 +530,7 @@ def get_rev(self, docid): if e.response.status_code == 404: raise ResourceNotFound raise - return response.headers['ETag'].strip('"').strip('W/"') + return response.headers['ETag'].strip('"').lstrip('W/"') def save_doc(self, doc, encode_attachments=True, force_update=False, **params): From 74cd0a741ede1e8c12fe85a281d1c9158185acdf Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 12 Jul 2018 13:29:40 -0400 Subject: [PATCH 140/151] add comment --- couchdbkit/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index 4e8037d..cb6b261 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -530,6 +530,7 @@ def get_rev(self, docid): if e.response.status_code == 404: raise ResourceNotFound raise + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag return response.headers['ETag'].strip('"').lstrip('W/"') def save_doc(self, doc, encode_attachments=True, force_update=False, From 6444260ce6a31280ccbe8c982df2999f00f9fb35 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 12 Jul 2018 13:39:26 -0400 Subject: [PATCH 141/151] make sure that attempting to save a document after deletion raises ResoureConflict --- couchdbkit/client.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index cb6b261..a9b0231 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -563,7 +563,17 @@ def save_doc(self, doc, encode_attachments=True, force_update=False, couch_doc = Document(self.cloudant_database, docid) couch_doc.update(doc1) try: - couch_doc.save() + # Copied from Document.save to ensure that a deleted doc cannot be saved. + headers = {} + headers.setdefault('Content-Type', 'application/json') + put_resp = couch_doc.r_session.put( + couch_doc.document_url, + data=couch_doc.json(), + headers=headers + ) + put_resp.raise_for_status() + data = put_resp.json() + super(Document, couch_doc).__setitem__('_rev', data['rev']) except HTTPError as e: if e.response.status_code != 409: raise From 3909a0b5fb76f0629b5b0a04320e3c5e3d6780b7 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 12 Jul 2018 13:41:30 -0400 Subject: [PATCH 142/151] maintain behavior of raising ResourceConflict when attempting to delete previously deleted doc --- couchdbkit/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index a9b0231..be10b9b 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -726,7 +726,10 @@ def delete_doc(self, doc, **params): couch_doc['_rev'] = doc1['_rev'] elif isinstance(doc1, six.string_types): # we get a docid couch_doc = Document(self.cloudant_database, doc1) - couch_doc['_rev'] = self.get_rev(doc1) + try: + couch_doc['_rev'] = self.get_rev(doc1) + except ResourceNotFound: + raise ResourceConflict # manual request because cloudant library doesn't return result res = self._request_session.delete( From c33146942895ca0c5a8145c1e31705913fa27766 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Thu, 12 Jul 2018 13:41:44 -0400 Subject: [PATCH 143/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 56e9570..4175157 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 6) +version_info = (0, 9, 0, 3, 7) __version__ = ".".join(map(str, version_info)) From 9167e4eb6090f02ecf0e290cbeedf498cabc53c6 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Mon, 16 Jul 2018 12:13:17 -0400 Subject: [PATCH 144/151] raise ResourceNotFound when trying to delete an already deleted doc --- couchdbkit/client.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/couchdbkit/client.py b/couchdbkit/client.py index be10b9b..a9b0231 100644 --- a/couchdbkit/client.py +++ b/couchdbkit/client.py @@ -726,10 +726,7 @@ def delete_doc(self, doc, **params): couch_doc['_rev'] = doc1['_rev'] elif isinstance(doc1, six.string_types): # we get a docid couch_doc = Document(self.cloudant_database, doc1) - try: - couch_doc['_rev'] = self.get_rev(doc1) - except ResourceNotFound: - raise ResourceConflict + couch_doc['_rev'] = self.get_rev(doc1) # manual request because cloudant library doesn't return result res = self._request_session.delete( From 6df4106b3bdaa78870aee0465ceabdbaa98ec4a3 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Mon, 16 Jul 2018 12:13:25 -0400 Subject: [PATCH 145/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 4175157..b871467 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 7) +version_info = (0, 9, 0, 3, 8) __version__ = ".".join(map(str, version_info)) From 05d9d6b066a4c6e1a49ac66a653ea481898f5d71 Mon Sep 17 00:00:00 2001 From: Jonathan Emord Date: Sun, 18 Mar 2018 16:28:35 +0530 Subject: [PATCH 146/151] WSGI is unused --- couchdbkit/wsgi/__init__.py | 6 -- couchdbkit/wsgi/handler.py | 133 ------------------------------------ couchdbkit/wsgi/proxy.py | 42 ------------ 3 files changed, 181 deletions(-) delete mode 100644 couchdbkit/wsgi/__init__.py delete mode 100644 couchdbkit/wsgi/handler.py delete mode 100644 couchdbkit/wsgi/proxy.py diff --git a/couchdbkit/wsgi/__init__.py b/couchdbkit/wsgi/__init__.py deleted file mode 100644 index 21d0b7c..0000000 --- a/couchdbkit/wsgi/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 - -# -# This file is part of couchdbkit released under the MIT license. -# See the NOTICE for more information. - - diff --git a/couchdbkit/wsgi/handler.py b/couchdbkit/wsgi/handler.py deleted file mode 100644 index 20e8d1d..0000000 --- a/couchdbkit/wsgi/handler.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 - -# -# This file is part of couchdbkit released under the MIT license. -# See the NOTICE for more information. - -from __future__ import absolute_import -import sys -import StringIO -import traceback -from six.moves.urllib.parse import unquote - -from restkit.util import url_encode - -from .. import __version__ -from ..external import External - -def _normalize_name(name): - return "-".join([w.lower().capitalize() for w in name.split("-")]) - -class WSGIRequest(object): - - SERVER_VERSION = "couchdbkit/%s" % __version__ - - def __init__(self, line): - self.line = line - self.response_status = 200 - self.response_headers = {} - self.start_response_called = False - - def read(self): - headers = self.parse_headers() - - length = headers.get("CONTENT_LENGTH") - if self.line["body"] and self.line["body"] != "undefined": - length = len(self.line["body"]) - body = StringIO.StringIO(self.line["body"]) - - else: - body = StringIO.StringIO() - - # path - script_name, path_info = self.line['path'][:2], self.line['path'][2:] - if path_info: - path_info = "/%s" % "/".join(path_info) - else: - path_info = "" - script_name = "/%s" % "/".join(script_name) - - # build query string - args = [] - query_string = None - for k, v in self.line["query"].items(): - if v is None: - continue - else: - args.append((k,v)) - if args: query_string = url_encode(dict(args)) - - # raw path could be useful - path = "%s%s" % (path_info, query_string) - - # get server address - if ":" in self.line["headers"]["Host"]: - server_address = self.line["headers"]["Host"].split(":") - else: - server_address = (self.line["headers"]["Host"], 80) - - environ = { - "wsgi.url_scheme": 'http', - "wsgi.input": body, - "wsgi.errors": StringIO.StringIO(), - "wsgi.version": (1, 0), - "wsgi.multithread": False, - "wsgi.multiprocess": True, - "wsgi.run_once": False, - "SCRIPT_NAME": script_name, - "SERVER_SOFTWARE": self.SERVER_VERSION, - "COUCHDB_INFO": self.line["info"], - "COUCHDB_REQUEST": self.line, - "REQUEST_METHOD": self.line["verb"].upper(), - "PATH_INFO": unquote(path_info), - "QUERY_STRING": query_string, - "RAW_URI": path, - "CONTENT_TYPE": headers.get('CONTENT-TYPE', ''), - "CONTENT_LENGTH": length, - "REMOTE_ADDR": self.line['peer'], - "REMOTE_PORT": 0, - "SERVER_NAME": server_address[0], - "SERVER_PORT": int(server_address[1]), - "SERVER_PROTOCOL": "HTTP/1.1" - } - - for key, value in headers.items(): - key = 'HTTP_' + key.replace('-', '_') - if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'): - environ[key] = value - - return environ - - def start_response(self, status, response_headers): - self.response_status = int(status.split(" ")[0]) - for name, value in response_headers: - name = _normalize_name(name) - self.response_headers[name] = value.strip() - self.start_response_called = True - - def parse_headers(self): - headers = {} - for name, value in self.line.get("headers", {}).items(): - name = name.strip().upper().encode("utf-8") - headers[name] = value.strip().encode("utf-8") - return headers - -class WSGIHandler(External): - - def __init__(self, application, stdin=sys.stdin, - stdout=sys.stdout): - External.__init__(self, stdin=stdin, stdout=stdout) - self.app = application - - def handle_line(self, line): - try: - req = WSGIRequest(line) - response = self.app(req.read(), req.start_response) - except: - self.send_response(500, "".join(traceback.format_exc()), - {"Content-Type": "text/plain"}) - return - - content = "".join(response).encode("utf-8") - self.send_response(req.response_status, content, req.response_headers) - - diff --git a/couchdbkit/wsgi/proxy.py b/couchdbkit/wsgi/proxy.py deleted file mode 100644 index a858286..0000000 --- a/couchdbkit/wsgi/proxy.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 - -# -# This file is part of couchdbkit released under the MIT license. -# See the NOTICE for more information. - -from __future__ import absolute_import -import six.moves.urllib.parse - -from restkit.contrib.wsgi_proxy import HostProxy, ALLOWED_METHODS -from webob import Request - -class CouchdbProxy(object): - """\ - WSGI application to proxy a couchdb server. - - Simple usage to proxy a CouchDB server on default url:: - - from couchdbkit.wsgi import CouchdbProxy - application = CouchdbProxy() - """ - - def __init__(self, uri="http://127.0.0.1:5984", - allowed_method=ALLOWED_METHODS, **kwargs): - self.proxy = HostProxy(uri, allowed_methods=allowed_method, - **kwargs) - - def do_proxy(self, req, environ, start_response): - """\ - return proxy response. Can be overrided to add authentification and - such. It's better to override do_proxy method than the __call__ - """ - return req.get_response(self.proxy) - - def __call__(self, environ, start_response): - req = Request(environ) - if 'RAW_URI' in req.environ: - # gunicorn so we can use real path non encoded - u = six.moves.urllib.parse.urlparse(req.environ['RAW_URI']) - req.environ['PATH_INFO'] = u.path - - resp = self.do_proy(req, environ, start_response) - return resp(environ, start_response) From 24dcd3e8e497b3f5af631fa2717ec1e5f98cf531 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Mon, 16 Jul 2018 21:12:36 -0400 Subject: [PATCH 147/151] bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index b871467..3b83e52 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 3, 8) +version_info = (0, 9, 0, 4, 0) __version__ = ".".join(map(str, version_info)) From bfbbf2e58133b30702cca0236a1493f0a80deec3 Mon Sep 17 00:00:00 2001 From: Nick Pellegrino Date: Mon, 16 Jul 2018 21:56:24 -0400 Subject: [PATCH 148/151] bump to 0.9.1 --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index 3b83e52..e5f66a3 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -5,5 +5,5 @@ from __future__ import absolute_import from six.moves import map -version_info = (0, 9, 0, 4, 0) +version_info = (0, 9, 1) __version__ = ".".join(map(str, version_info)) From e074e00b5aa2c79cd814bd776b5479e65886d711 Mon Sep 17 00:00:00 2001 From: Preethi Vaidyanathan Date: Fri, 20 Jul 2018 16:44:19 -0400 Subject: [PATCH 149/151] Make sure a wheel file shows up on pypi --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 46c0ec2..ddb812b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,3 +2,6 @@ exclude=(django) with-coverage=1 cover-package=couchdbkit + +[bdist_wheel] +universal = 1 From 4e310223fd8668fd4fdcae6fa6d09290539bfee4 Mon Sep 17 00:00:00 2001 From: Preethi Vaidyanathan Date: Fri, 20 Jul 2018 16:53:26 -0400 Subject: [PATCH 150/151] Use list comprehension to get version number --- couchdbkit/version.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index e5f66a3..ab70757 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -4,6 +4,5 @@ # See the NOTICE for more information. from __future__ import absolute_import -from six.moves import map version_info = (0, 9, 1) -__version__ = ".".join(map(str, version_info)) +__version__ = ".".join([str(vi) for vi in version_info]) From d3af5bf1793a4d6084ba1ef694454a5468625c9a Mon Sep 17 00:00:00 2001 From: Preethi Vaidyanathan Date: Fri, 20 Jul 2018 16:53:38 -0400 Subject: [PATCH 151/151] Bump version --- couchdbkit/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchdbkit/version.py b/couchdbkit/version.py index ab70757..2102051 100644 --- a/couchdbkit/version.py +++ b/couchdbkit/version.py @@ -4,5 +4,5 @@ # See the NOTICE for more information. from __future__ import absolute_import -version_info = (0, 9, 1) +version_info = (0, 9, 2) __version__ = ".".join([str(vi) for vi in version_info])