# Path: ckan_api_client/objects/base.py
# class BaseObject(object):
#     """
#     Base for the other objects, dispatching get/set/deletes
#     to ``BaseField`` instances, if available.
#     """
# 
#     _values = None
#     _updates = None
# 
#     def __init__(self, values=None):
#         if values is None:
#             values = {}
#         if not isinstance(values, MAPPING_TYPES):
#             raise TypeError("Initial values must be a dict (or None). "
#                             "Got {0!r} instead".format(type(values)))
# 
#         # Prepare variables to hold initial / updated values
#         self._values = {}
#         self._updates = {}
# 
#         # Set initial field values, by calling set_initial()
#         # on the fields themselves.
#         self.set_initial(values)
# 
#     @classmethod
#     def from_dict(cls, data):
#         warnings.warn("from_dict() is deprecated -- use normal constructor",
#                       DeprecationWarning)
#         return cls(data)
# 
#     def set_initial(self, values):
#         """Set initial values for all fields"""
#         for name, field in self.iter_fields():
#             if name in values:
#                 field.set_initial(self, name, values[name])
# 
#     def to_dict(self):
#         warnings.warn("to_dict() is deprecated -- use serialize()",
#                       DeprecationWarning)
#         return self.serialize()
# 
#     def __getattribute__(self, key):
#         """
#         Custom attribute handling.
#         If the attribute is a field, return the value returned
#         from its .get() method. Otherwise, return it directly.
#         """
#         attr = object.__getattribute__(self, key)
#         if isinstance(attr, BaseField):
#             return attr.get(self, key)
#         return attr
# 
#     def __setattr__(self, key, value):
#         """
#         Custom attribute handling.
#         If the attribute is a field, pass the value to its
#         .set() method. Otherwise, set it directly on the object.
#         """
#         v = object.__getattribute__(self, key)
#         if isinstance(v, BaseField):
#             return v.set(self, key, value)
#         return object.__setattr__(self, key, value)
# 
#     def __delattr__(self, key):
#         """
#         Custom attribute handling.
#         If the attribute is a field, call its .del() method.
#         Otherwise, perform the action directly on the object.
#         """
#         v = object.__getattribute__(self, key)
#         if isinstance(v, BaseField):
#             return v.delete(self, key)
#         return object.__delattr__(self, key)
# 
#     def serialize(self):
#         """
#         Create a serializable representation of the object.
#         """
#         serialized = {}
#         for name, field in self.iter_fields():
#             serialized[name] = field.serialize(self, name)
#         return serialized
# 
#     def iter_fields(self):
#         """
#         Iterate over fields in this objects, yielding
#         (name, field) pairs.
#         """
#         for name in dir(self):
#             attr = object.__getattribute__(self, name)
#             if isinstance(attr, BaseField):
#                 yield name, attr
# 
#     def is_equivalent(self, other, ignore_key=True):
#         """
#         Equivalency check between objects.
#         Will make sure that values in all the non-key
#         fields match.
# 
#         :param other:
#             other object to compare
#         :param ignore_key:
#             if set to True (the default), it will ignore
#             "key" fields during comparison
#         """
# 
#         if type(self) != type(other):
#             # We want to make sure the objects are of the
#             # exact same type, not of some sub-type.
#             return False
# 
#         for name, field in self.iter_fields():
#             # Ignore key fields if we are required to
#             # ignore keys.
#             if ignore_key and field.is_key:
#                 continue
# 
#             # Use the equivalency check for fields
#             if not field.is_equivalent(
#                     self, name, other, ignore_key=ignore_key):
#                 return False
# 
#         return True
# 
#     def compare(self, other):
#         """Compare differences between this object and another"""
#         from differ import compare_objects
#         return compare_objects(self.serialize(), other.serialize())
# 
#     def __eq__(self, other):
#         return self.is_equivalent(other, ignore_key=False)
# 
#     def __ne__(self, other):
#         return not self.__eq__(other)
# 
#     def is_modified(self):
#         """
#         The object is modified if any of its fields reports
#         itself as modified.
#         """
# 
#         for name, field in self.iter_fields():
#             if field.is_modified(self, name):
#                 return True
#         return False
# 
#     def __repr__(self):
#         return u'{0}({1})'.format(self.__class__.__name__, self.serialize())
# 
# Path: ckan_api_client/objects/fields.py
# class StringField(BaseField):
#     default = None
# 
#     def validate(self, instance, name, value):
#         if value is None:
#             if self.required:
#                 raise TypeError("Required field cannot be None")
#             return None
# 
#         if not isinstance(value, basestring):
#             raise TypeError("Invalid type for string field {0!r}: {1!r}"
#                             .format(name, type(value)))
# 
#         return value
# 
# class ListField(MutableFieldMixin, BaseField):
#     default = staticmethod(lambda: [])
# 
#     def validate(self, instance, name, value):
#         value = super(ListField, self).validate(instance, name, value)
#         if not isinstance(value, SEQUENCE_TYPES):
#             raise ValueError("{0} must be a list".format(name))
#         return value


import pytest
from ckan_api_client.objects.base import BaseObject
from ckan_api_client.objects.fields import StringField, ListField



def test_string_field():
    # --------------------------------------------------
    # Field without default value

    class MyObject(BaseObject):
        field = StringField()

    # --- Test #1: no initial values
    obj = MyObject()
    assert obj.field is None
    assert obj.is_modified() is False

    # --- Test #2: initial values
    obj = MyObject({'field': 'initial_value'})
    assert obj.field == 'initial_value'
    assert obj.is_modified() is False

    # --- Test #3: empty initial values
    obj = MyObject({})

# Path: ckan_api_client/objects/base.py
# class BaseObject(object):
#     """
#     Base for the other objects, dispatching get/set/deletes
#     to ``BaseField`` instances, if available.
#     """
# 
#     _values = None
#     _updates = None
# 
#     def __init__(self, values=None):
#         if values is None:
#             values = {}
#         if not isinstance(values, MAPPING_TYPES):
#             raise TypeError("Initial values must be a dict (or None). "
#                             "Got {0!r} instead".format(type(values)))
# 
#         # Prepare variables to hold initial / updated values
#         self._values = {}
#         self._updates = {}
# 
#         # Set initial field values, by calling set_initial()
#         # on the fields themselves.
#         self.set_initial(values)
# 
#     @classmethod
#     def from_dict(cls, data):
#         warnings.warn("from_dict() is deprecated -- use normal constructor",
#                       DeprecationWarning)
#         return cls(data)
# 
#     def set_initial(self, values):
#         """Set initial values for all fields"""
#         for name, field in self.iter_fields():
#             if name in values:
#                 field.set_initial(self, name, values[name])
# 
#     def to_dict(self):
#         warnings.warn("to_dict() is deprecated -- use serialize()",
#                       DeprecationWarning)
#         return self.serialize()
# 
#     def __getattribute__(self, key):
#         """
#         Custom attribute handling.
#         If the attribute is a field, return the value returned
#         from its .get() method. Otherwise, return it directly.
#         """
#         attr = object.__getattribute__(self, key)
#         if isinstance(attr, BaseField):
#             return attr.get(self, key)
#         return attr
# 
#     def __setattr__(self, key, value):
#         """
#         Custom attribute handling.
#         If the attribute is a field, pass the value to its
#         .set() method. Otherwise, set it directly on the object.
#         """
#         v = object.__getattribute__(self, key)
#         if isinstance(v, BaseField):
#             return v.set(self, key, value)
#         return object.__setattr__(self, key, value)
# 
#     def __delattr__(self, key):
#         """
#         Custom attribute handling.
#         If the attribute is a field, call its .del() method.
#         Otherwise, perform the action directly on the object.
#         """
#         v = object.__getattribute__(self, key)
#         if isinstance(v, BaseField):
#             return v.delete(self, key)
#         return object.__delattr__(self, key)
# 
#     def serialize(self):
#         """
#         Create a serializable representation of the object.
#         """
#         serialized = {}
#         for name, field in self.iter_fields():
#             serialized[name] = field.serialize(self, name)
#         return serialized
# 
#     def iter_fields(self):
#         """
#         Iterate over fields in this objects, yielding
#         (name, field) pairs.
#         """
#         for name in dir(self):
#             attr = object.__getattribute__(self, name)
#             if isinstance(attr, BaseField):
#                 yield name, attr
# 
#     def is_equivalent(self, other, ignore_key=True):
#         """
#         Equivalency check between objects.
#         Will make sure that values in all the non-key
#         fields match.
# 
#         :param other:
#             other object to compare
#         :param ignore_key:
#             if set to True (the default), it will ignore
#             "key" fields during comparison
#         """
# 
#         if type(self) != type(other):
#             # We want to make sure the objects are of the
#             # exact same type, not of some sub-type.
#             return False
# 
#         for name, field in self.iter_fields():
#             # Ignore key fields if we are required to
#             # ignore keys.
#             if ignore_key and field.is_key:
#                 continue
# 
#             # Use the equivalency check for fields
#             if not field.is_equivalent(
#                     self, name, other, ignore_key=ignore_key):
#                 return False
# 
#         return True
# 
#     def compare(self, other):
#         """Compare differences between this object and another"""
#         from differ import compare_objects
#         return compare_objects(self.serialize(), other.serialize())
# 
#     def __eq__(self, other):
#         return self.is_equivalent(other, ignore_key=False)
# 
#     def __ne__(self, other):
#         return not self.__eq__(other)
# 
#     def is_modified(self):
#         """
#         The object is modified if any of its fields reports
#         itself as modified.
#         """
# 
#         for name, field in self.iter_fields():
#             if field.is_modified(self, name):
#                 return True
#         return False
# 
#     def __repr__(self):
#         return u'{0}({1})'.format(self.__class__.__name__, self.serialize())
# 
# Path: ckan_api_client/objects/fields.py
# class StringField(BaseField):
#     default = None
# 
#     def validate(self, instance, name, value):
#         if value is None:
#             if self.required:
#                 raise TypeError("Required field cannot be None")
#             return None
# 
#         if not isinstance(value, basestring):
#             raise TypeError("Invalid type for string field {0!r}: {1!r}"
#                             .format(name, type(value)))
# 
#         return value
# 
# class ListField(MutableFieldMixin, BaseField):
#     default = staticmethod(lambda: [])
# 
#     def validate(self, instance, name, value):
#         value = super(ListField, self).validate(instance, name, value)
#         if not isinstance(value, SEQUENCE_TYPES):
#             raise ValueError("{0} must be a list".format(name))
#         return value


# Path: ckan_api_client/tests/unit/test_fields.py
import pytest
from ckan_api_client.objects.base import BaseObject
from ckan_api_client.objects.fields import StringField, ListField




def test_string_field():
    # --------------------------------------------------
    # Field without default value

    class MyObject(BaseObject):
        field = StringField()

    # --- Test #1: no initial values
    obj = MyObject()
    assert obj.field is None
    assert obj.is_modified() is False

    # --- Test #2: initial values
    obj = MyObject({'field': 'initial_value'})
    assert obj.field == 'initial_value'
    assert obj.is_modified() is False

    # --- Test #3: empty initial values
    obj = MyObject({})

    assert obj.field is None