diff --git a/gitmodel/conf.py b/gitmodel/conf.py index 1af6db3..1bba326 100644 --- a/gitmodel/conf.py +++ b/gitmodel/conf.py @@ -1,8 +1,8 @@ DEFAULTS = { - 'DEFAULT_SERIALIZER': 'gitmodel.serializers.json', - 'LOCK_WAIT_TIMEOUT': 30, # in seconds - 'LOCK_WAIT_INTERVAL': 1000, # in milliseconds - 'DEFAULT_GIT_USER': ('gitmodel', 'gitmodel@local'), + "DEFAULT_SERIALIZER": "gitmodel.serializers.json", + "LOCK_WAIT_TIMEOUT": 30, # in seconds + "LOCK_WAIT_INTERVAL": 1000, # in milliseconds + "DEFAULT_GIT_USER": ("gitmodel", "gitmodel@local"), } @@ -24,4 +24,5 @@ def __getattr__(self, name): def __setattr__(self, name, value): self[name] = value + defaults = Config(DEFAULTS) diff --git a/gitmodel/exceptions.py b/gitmodel/exceptions.py index 47df369..844cb72 100644 --- a/gitmodel/exceptions.py +++ b/gitmodel/exceptions.py @@ -1,10 +1,12 @@ class GitModelError(Exception): """A base exception for other gitmodel-related errors.""" + pass class ConfigurationError(GitModelError): """Raised during configuration errors""" + pass @@ -12,6 +14,7 @@ class UnsupportedFormat(GitModelError): """ Raised when an unsupported serialization format is requested. """ + pass @@ -19,6 +22,7 @@ class FieldError(GitModelError): """ Raised when there is a configuration error with a ``Field``. """ + pass @@ -26,6 +30,7 @@ class DoesNotExist(GitModelError): """ Raised when the object in question can't be found. """ + pass @@ -33,6 +38,7 @@ class RepositoryError(GitModelError): """ Raises during an error while operating with the repository """ + pass @@ -46,13 +52,14 @@ class ValidationError(GitModelError): """ Raised when an invalid value is encountered """ + def __init__(self, msg_or_code, field=None, **kwargs): self.field = field self.msg_or_code = msg_or_code if self.field: - msg = self.field.get_error_message(msg_or_code, - default=msg_or_code, - **kwargs) + msg = self.field.get_error_message( + msg_or_code, default=msg_or_code, **kwargs + ) else: msg = msg_or_code super(ValidationError, self).__init__(msg) @@ -62,6 +69,7 @@ class IntegrityError(GitModelError): """ Raised when a save results in duplicate values """ + pass @@ -69,4 +77,5 @@ class ModelNotFound(Exception): """ Raised during deserialization if the model class no longer exists """ + pass diff --git a/gitmodel/fields.py b/gitmodel/fields.py index 9dd7a2a..9bad1b4 100644 --- a/gitmodel/fields.py +++ b/gitmodel/fields.py @@ -12,27 +12,37 @@ from gitmodel.utils import isodate, json from gitmodel.exceptions import ValidationError, FieldError -INVALID_PATH_CHARS = ('/', '\000') +INVALID_PATH_CHARS = ("/", "\000") class NOT_PROVIDED: def __str__(self): - return 'No default provided.' + return "No default provided." class Field(object): """The base implementation of a field used by a GitModel class.""" + creation_counter = 0 default_error_messages = { - 'required': 'is required', - 'invalid_path': 'may only contain valid path characters', + "required": "is required", + "invalid_path": "may only contain valid path characters", } serializable = True empty_value = None - def __init__(self, name=None, id=False, default=NOT_PROVIDED, - required=True, readonly=False, unique=False, serialize=True, - autocreated=False, error_messages=None): + def __init__( + self, + name=None, + id=False, + default=NOT_PROVIDED, + required=True, + readonly=False, + unique=False, + serialize=True, + autocreated=False, + error_messages=None, + ): self.model = None self.name = name @@ -46,10 +56,10 @@ def __init__(self, name=None, id=False, default=NOT_PROVIDED, self.autocreated = autocreated # update error_messages using default_error_messages from all parents - #NEEDS-TEST + # NEEDS-TEST messages = {} for c in reversed(type(self).__mro__): - messages.update(getattr(c, 'default_error_messages', {})) + messages.update(getattr(c, "default_error_messages", {})) messages.update(error_messages or {}) self.error_messages = messages @@ -109,10 +119,10 @@ def validate(self, value, model_instance): ValidationError if invalid. """ if self.required and self.empty(value): - raise ValidationError('required', self) + raise ValidationError("required", self) if self.id and any(c in value for c in INVALID_PATH_CHARS): - raise ValidationError('invalid_path', self) + raise ValidationError("invalid_path", self) def clean(self, value, model_instance): """ @@ -138,9 +148,9 @@ def deserialize(self, data, value): """ return self.to_python(value) - def get_error_message(self, error_code, default='', **kwargs): + def get_error_message(self, error_code, default="", **kwargs): msg = self.error_messages.get(error_code, default) - kwargs['field'] = self + kwargs["field"] = self msg = msg.format(**kwargs) return '"{name}" {err}'.format(name=self.name, err=msg) @@ -156,7 +166,8 @@ class CharField(Field): """ A text field of arbitrary length. """ - empty_value = '' + + empty_value = "" def to_python(self, value): if value is None and not self.required: @@ -170,21 +181,20 @@ def to_python(self, value): class SlugField(CharField): default_error_messages = { - 'invalid_slug': ('must contain only letters, numbers, underscores and ' - 'dashes') + "invalid_slug": ( + "must contain only letters, numbers, underscores and " "dashes" + ) } def validate(self, value, model_instance): super(SlugField, self).validate(value, model_instance) - slug_re = re.compile(r'^[-\w]+$') + slug_re = re.compile(r"^[-\w]+$") if not slug_re.match(value): - raise ValidationError('invalid_slug', self) + raise ValidationError("invalid_slug", self) class EmailField(CharField): - default_error_messages = { - 'invalid_email': 'must be a valid e-mail address' - } + default_error_messages = {"invalid_email": "must be a valid e-mail address"} def validate(self, value, model_instance): super(EmailField, self).validate(value, model_instance) @@ -194,18 +204,19 @@ def validate(self, value, model_instance): r"(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]' r'|\\[\001-011\013\014\016-\177])*"' # quoted-string - r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}' - r'[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', # domain - re.IGNORECASE) + r")@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}" + r"[A-Z0-9])?\.)+[A-Z]{2,6}\.?$", # domain + re.IGNORECASE, + ) if not email_re.match(value): - raise ValidationError('invalid_email', self) + raise ValidationError("invalid_email", self) class URLField(CharField): default_error_messages = { - 'invalid_url': 'must be a valid URL', - 'invalid_scheme': 'scheme must be one of {schemes}' + "invalid_url": "must be a valid URL", + "invalid_scheme": "scheme must be one of {schemes}", } def __init__(self, **kwargs): @@ -214,7 +225,7 @@ def __init__(self, **kwargs): restricted. Raises validation error if url scheme is not in this list. Otherwise, any scheme is allowed. """ - self.schemes = kwargs.pop('schemes', None) + self.schemes = kwargs.pop("schemes", None) super(URLField, self).__init__(self, **kwargs) def validate(self, value, model_instance): @@ -223,10 +234,10 @@ def validate(self, value, model_instance): return parsed = urlparse(value) if not all((parsed.scheme, parsed.hostname)): - raise ValidationError('invalid_url', self) + raise ValidationError("invalid_url", self) if self.schemes and parsed.scheme.lower() not in self.schemes: - schemes = ', '.join(self.schemes) - raise ValidationError('invalid_scheme', self, schemes=schemes) + schemes = ", ".join(self.schemes) + raise ValidationError("invalid_scheme", self, schemes=schemes) class BlobFieldDescriptor(object): @@ -251,7 +262,7 @@ def __set__(self, instance, value): self.data = None elif value is None: self.data = None - elif hasattr(value, 'read'): + elif hasattr(value, "read"): self.data = StringIO(value.read()) else: self.data = StringIO(value) @@ -263,10 +274,11 @@ class BlobField(Field): its own git blob within the repository, and added as a file entry under the same path as the data.json for that instance. """ + serializable = False def to_python(self, value): - if hasattr(value, 'read'): + if hasattr(value, "read"): return value if value is None: return None @@ -284,7 +296,7 @@ def post_save(self, value, instance, commit=False): def get_data_path(self, instance): path = os.path.dirname(instance.get_data_path()) path = os.path.join(path, self.name) - return '{0}.data'.format(path) + return "{0}.data".format(path) def contribute_to_class(self, cls, name): super(BlobField, self).contribute_to_class(cls, name) @@ -298,9 +310,8 @@ class IntegerField(Field): """ An integer field. """ - default_error_messages = { - 'invalid_int': 'must be an integer' - } + + default_error_messages = {"invalid_int": "must be an integer"} def to_python(self, value): if value is None: @@ -310,9 +321,9 @@ def to_python(self, value): try: value = float(value) except ValueError: - raise ValidationError('invalid_int', self) + raise ValidationError("invalid_int", self) if value % 1 != 0: - raise ValidationError('invalid_int', self) + raise ValidationError("invalid_int", self) return int(value) @@ -320,15 +331,14 @@ class UUIDField(CharField): """ A CharField which uses a globally-unique identifier as its default value """ + @property def default(self): return uuid.uuid4().hex class FloatField(Field): - default_error_messages = { - 'invalid_float': 'must be a floating-point number' - } + default_error_messages = {"invalid_float": "must be a floating-point number"} def to_python(self, value): if value is None: @@ -336,12 +346,12 @@ def to_python(self, value): try: return float(value) except ValueError: - raise ValidationError('invalid_float', self) + raise ValidationError("invalid_float", self) class DecimalField(Field): default_error_messages = { - 'invalid_decimal': 'must be a numeric value', + "invalid_decimal": "must be a numeric value", } def __init__(self, max_digits=None, decimal_places=None, **kwargs): @@ -357,7 +367,7 @@ def to_python(self, value): try: return decimal.Decimal(value) except decimal.InvalidOperation: - raise ValidationError('invalid_decimal', self) + raise ValidationError("invalid_decimal", self) class BooleanField(Field): @@ -373,8 +383,8 @@ def to_python(self, value): class DateField(Field): default_error_messages = { - 'invalid_format': 'must be in the format of YYYY-MM-DD', - 'invalid': 'must be a valid date', + "invalid_format": "must be in the format of YYYY-MM-DD", + "invalid": "must be a valid date", } def to_python(self, value): @@ -389,15 +399,15 @@ def to_python(self, value): try: return isodate.parse_iso_date(value) except isodate.InvalidFormat: - raise ValidationError('invalid_format', self) + raise ValidationError("invalid_format", self) except isodate.InvalidDate: - raise ValidationError('invalid', self) + raise ValidationError("invalid", self) class DateTimeField(Field): default_error_messages = { - 'invalid_format': 'must be in the format of YYYY-MM-DD HH:MM[:SS]', - 'invalid': 'must be a valid date/time' + "invalid_format": "must be in the format of YYYY-MM-DD HH:MM[:SS]", + "invalid": "must be a valid date/time", } def to_python(self, value): @@ -416,15 +426,15 @@ def to_python(self, value): try: return isodate.parse_iso_date(value) except isodate.InvalidFormat: - raise ValidationError('invalid_format', self) + raise ValidationError("invalid_format", self) except isodate.InvalidDate: - raise ValidationError('invalid', self) + raise ValidationError("invalid", self) class TimeField(Field): default_error_messages = { - 'invalid_format': 'must be in the format of HH:MM[:SS]', - 'invalid': 'must be a valid time' + "invalid_format": "must be in the format of HH:MM[:SS]", + "invalid": "must be a valid time", } def to_python(self, value): @@ -437,9 +447,9 @@ def to_python(self, value): try: return isodate.parse_iso_time(value) except isodate.InvalidFormat: - raise ValidationError('invalid_format', self) + raise ValidationError("invalid_format", self) except isodate.InvalidDate: - raise ValidationError('invalid', self) + raise ValidationError("invalid", self) class RelatedFieldDescriptor(object): @@ -449,6 +459,7 @@ def __init__(self, field): def __get__(self, instance, instance_type=None): from gitmodel import models + if instance is None: return self value = instance.__dict__[self.field.name] @@ -478,7 +489,7 @@ def to_model(self): return self.workspace.models[self._to_model] # if the model has already been registered with a workspace, use as-is - if hasattr(self._to_model, '_meta'): + if hasattr(self._to_model, "_meta"): return self._to_model # otherwise, check on our own workspace @@ -491,6 +502,7 @@ def to_model(self): def to_python(self, value): from gitmodel import models + if isinstance(value, models.GitModel): return value.get_id() return value @@ -501,7 +513,7 @@ def serialize(self, obj): def contribute_to_class(self, cls, name): super(RelatedField, self).contribute_to_class(cls, name) - if hasattr(cls, '_meta'): + if hasattr(cls, "_meta"): self.workspace = cls._meta.workspace setattr(cls, name, RelatedFieldDescriptor(self)) @@ -532,9 +544,10 @@ class GitObjectField(CharField): Acts as a reference to a git object. This field stores the OID of the object. Returns the actual object when accessed as a property. """ + default_error_messages = { - 'invalid_oid': "must be a valid git OID or pygit2 Object", - 'invalid_type': "must point to a {type}", + "invalid_oid": "must be a valid git OID or pygit2 Object", + "invalid_type": "must point to a {type}", } def __init__(self, **kwargs): @@ -546,12 +559,12 @@ def __init__(self, **kwargs): pygit2.Commit, or pygit2.Tree. Any object type that can be resolved from a git oid is valid. """ - self.type = kwargs.pop('type', None) + self.type = kwargs.pop("type", None) super(GitObjectField, self).__init__(**kwargs) def to_python(self, value): if not isinstance(value, (basestring, pygit2.Oid, pygit2.Object)): - raise ValidationError('invalid_object', self) + raise ValidationError("invalid_object", self) if isinstance(value, pygit2.Oid): return value.hex return value @@ -577,10 +590,9 @@ def validate(self, value, model_instance): try: obj = model_instance._meta.workspace.repo[oid] except (ValueError, KeyError): - raise ValidationError('invalid_oid', self) + raise ValidationError("invalid_oid", self) if self.type and not isinstance(obj, self.type): - raise ValidationError('invalid_type', self, - type=self.type.__name__) + raise ValidationError("invalid_type", self, type=self.type.__name__) class JSONField(CharField): diff --git a/gitmodel/models.py b/gitmodel/models.py index b95e0a4..c5a7176 100644 --- a/gitmodel/models.py +++ b/gitmodel/models.py @@ -14,11 +14,12 @@ class GitModelOptions(object): """ An options class for ``GitModel``. """ + # attributes that can be overridden in a model's options ("Meta" class) - meta_opts = ('abstract', 'data_filename', 'get_data_path', 'id_attr') + meta_opts = ("abstract", "data_filename", "get_data_path", "id_attr") # reserved attributes - reserved = ('oid',) + reserved = ("oid",) def __init__(self, meta, workspace): self.meta = meta @@ -29,13 +30,14 @@ def __init__(self, meta, workspace): self.model_name = None self.parents = [] self.id_attr = None - self.data_filename = 'data.json' + self.data_filename = "data.json" self._serializer = None @property def serializer(self): if self._serializer is None: from gitmodel.conf import defaults + default_serializer = defaults.DEFAULT_SERIALIZER if self.workspace is not None: default_serializer = self.workspace.config.DEFAULT_SERIALIZER @@ -52,12 +54,12 @@ def contribute_to_class(self, cls, name): if self.meta: # Ignore private attributes for name in dir(self.meta): - if name.startswith('_') or name not in self.meta_opts: + if name.startswith("_") or name not in self.meta_opts: continue value = getattr(self.meta, name) # if attr is a function, bind it to this instance - if not isinstance(value, type) and hasattr(value, '__call__'): + if not isinstance(value, type) and hasattr(value, "__call__"): value = value.__get__(self) setattr(self, name, value) @@ -75,17 +77,18 @@ def get_data_path(self, object_id): return os.path.join(model_name, unicode(object_id), self.data_filename) def add_field(self, field): - """ Insert a field into the fields list in correct order """ + """Insert a field into the fields list in correct order""" if field.name in self.reserved: - raise exceptions.FieldError("{} is a reserved name and cannot be" - "used as a field name.") + raise exceptions.FieldError( + "{} is a reserved name and cannot be" "used as a field name." + ) # bisect calls field.__cmp__ which uses field.creation_counter to # maintain the correct order position = bisect(self.local_fields, field) self.local_fields.insert(position, field) # invalidate the field cache - if hasattr(self, '_field_cache'): + if hasattr(self, "_field_cache"): del self._field_cache @property @@ -98,7 +101,7 @@ def fields(self): to this instance (not a copy) """ # get cached field names. if not cached, then fill the cache. - if not hasattr(self, '_field_cache'): + if not hasattr(self, "_field_cache"): self._fill_fields_cache() return self._field_cache @@ -141,8 +144,8 @@ def _prepare(self, model): auto = fields.UUIDField(id=True, autocreated=True) # add to the beginning of the fields list auto.creation_counter = -1 - model.add_to_class('id', auto) - self.id_attr = 'id' + model.add_to_class("id", auto) + self.id_attr = "id" class DeclarativeMetaclass(type): @@ -157,11 +160,11 @@ def __new__(cls, name, bases, attrs): return super_new(cls, name, bases, attrs) # workspace that will be passed to GitModelOptions - workspace = attrs.pop('__workspace__', None) + workspace = attrs.pop("__workspace__", None) # inherit parent workspace if not provided if not workspace: - if len(parents) > 0 and hasattr(parents[0], '_meta'): + if len(parents) > 0 and hasattr(parents[0], "_meta"): workspace = parents[0]._meta.workspace # don't do anything special for GitModels without a workspace @@ -170,14 +173,14 @@ def __new__(cls, name, bases, attrs): # Create the new class, while leaving out the declared attributes # which will be added later - module = attrs.pop('__module__') - options_cls = attrs.pop('__optclass__', None) - new_class = super_new(cls, name, bases, {'__module__': module}) + module = attrs.pop("__module__") + options_cls = attrs.pop("__optclass__", None) + new_class = super_new(cls, name, bases, {"__module__": module}) # grab the declared Meta - meta = attrs.pop('Meta', None) + meta = attrs.pop("Meta", None) base_meta = None - if parents and hasattr(parents[0], '_meta'): + if parents and hasattr(parents[0], "_meta"): base_meta = parents[0]._meta # Add _meta to the new class. The _meta property is an instance of @@ -190,15 +193,21 @@ def __new__(cls, name, bases, attrs): if meta is None: # if meta is not declared, use the closest parent's meta - meta = next((p._meta._declared_meta for p in parents if - hasattr(p, '_meta') and p._meta._declared_meta), None) + meta = next( + ( + p._meta._declared_meta + for p in parents + if hasattr(p, "_meta") and p._meta._declared_meta + ), + None, + ) # don't inherit the abstract property - if hasattr(meta, 'abstract'): + if hasattr(meta, "abstract"): meta.abstract = False opts = options_cls(meta, workspace) - new_class.add_to_class('_meta', opts) + new_class.add_to_class("_meta", opts) # Add all attributes to the class for obj_name, obj in attrs.items(): @@ -206,7 +215,7 @@ def __new__(cls, name, bases, attrs): # Handle parents for parent in parents: - if not hasattr(parent, '_meta'): + if not hasattr(parent, "_meta"): # Ignore parents that have no _meta continue new_class._check_parent_fields(parent) @@ -230,8 +239,10 @@ def _check_parent_fields(cls, parent, child=None): # Check for duplicate field definitions in parent for field in parent._meta.local_fields: if not field.autocreated and field.name in local_field_names: - msg = ('Duplicate field name "{0}" in {1!r} already exists in ' - 'parent model {2!r}') + msg = ( + 'Duplicate field name "{0}" in {1!r} already exists in ' + "parent model {2!r}" + ) msg = msg.format(field.name, child.__name__, parent.__name__) raise exceptions.FieldError(msg) @@ -248,7 +259,7 @@ def _prepare(cls): # Give the class a docstring if cls.__doc__ is None: - fields = ', '.join(f.name for f in opts.fields) + fields = ", ".join(f.name for f in opts.fields) cls.__doc__ = "{}({})".format(cls.__name__, fields) def add_to_class(cls, name, value): @@ -257,7 +268,7 @@ def add_to_class(cls, name, value): be called. Otherwise, this is an alias to setattr. This allows objects to have control over how they're added to a class during its creation. """ - if hasattr(value, 'contribute_to_class'): + if hasattr(value, "contribute_to_class"): value.contribute_to_class(cls, name) else: setattr(cls, name, value) @@ -272,9 +283,11 @@ def concrete(func, self, *args, **kwargs): model = self if not isinstance(model, type): model = type(self) - if not hasattr(model, '_meta'): - msg = ("Cannot call {0.__name__}.{1.__name__}() because {0!r} " - "has not been registered with a workspace") + if not hasattr(model, "_meta"): + msg = ( + "Cannot call {0.__name__}.{1.__name__}() because {0!r} " + "has not been registered with a workspace" + ) raise exceptions.GitModelError(msg.format(model, func)) if model._meta.abstract: msg = "Cannot call {1.__name__}() on abstract model {0.__name__} " @@ -296,7 +309,7 @@ def __init__(self, **kwargs): that this model is being instantiated from an existing instance in the git repository. Deserializing a model will automatically set this oid. """ - self._oid = kwargs.pop('oid', None) + self._oid = kwargs.pop("oid", None) # To keep things simple, we only accept attribute values as kwargs # Check for fields in kwargs @@ -331,13 +344,13 @@ def __repr__(self): try: u = unicode(self) except (UnicodeEncodeError, UnicodeDecodeError): - u = '[Bad Unicode Data]' - return u'<{0}: {1}>'.format(self._meta.model_name, u) + u = "[Bad Unicode Data]" + return u"<{0}: {1}>".format(self._meta.model_name, u) def __str__(self): - if hasattr(self, '__unicode__'): - return unicode(self).encode('utf-8') - return '{0} object'.format(self._meta.model_name) + if hasattr(self, "__unicode__"): + return unicode(self).encode("utf-8") + return "{0} object".format(self._meta.model_name) def save(self, commit=False, **commit_info): # make sure model has clean data @@ -353,7 +366,8 @@ def save(self, commit=False, **commit_info): pass else: err = 'A {} instance already exists with id "{}"'.format( - type(self).__name__, self.get_id()) + type(self).__name__, self.get_id() + ) raise exceptions.IntegrityError(err) serialized = self._meta.serializer.serialize(self) @@ -363,8 +377,10 @@ def save(self, commit=False, **commit_info): # only allow commit-during-save if workspace doesn't have pending # changes. if commit and workspace.has_changes(): - msg = "Repository has pending changes. Cannot save-commit until "\ - "pending changes have been comitted." + msg = ( + "Repository has pending changes. Cannot save-commit until " + "pending changes have been comitted." + ) raise exceptions.RepositoryError(msg) # create the git object and set the instance oid @@ -377,7 +393,7 @@ def save(self, commit=False, **commit_info): # if our path has changed, remove the old path. This generally only # happens with a custom mutable id_attr. - old_path = getattr(self, '_current_path', None) + old_path = getattr(self, "_current_path", None) new_path = self.get_data_path() if old_path and old_path != new_path: rmpath = os.path.dirname(old_path) @@ -449,7 +465,7 @@ def get(cls, id, treeish=None): msg = "{} with id {}{} does not exist." name = cls._meta.model_name - revname = treeish and '@{}'.format(treeish) or '' + revname = treeish and "@{}".format(treeish) or "" msg = msg.format(name, id, revname) if not tree: @@ -469,7 +485,7 @@ def all(cls): """ Returns a generator for all instances of this model. """ - pattern = cls._meta.get_data_path('*') + pattern = cls._meta.get_data_path("*") workspace = cls._meta.workspace repo = workspace.repo @@ -502,6 +518,7 @@ class ModelSet(object): """ A read-only container type initailized with a generator """ + def __init__(self, gen): self._gen = gen diff --git a/gitmodel/serializers/json.py b/gitmodel/serializers/json.py index a58d020..b570ef6 100644 --- a/gitmodel/serializers/json.py +++ b/gitmodel/serializers/json.py @@ -55,11 +55,12 @@ class GitModelJSONEncoder(json.JSONEncoder): """ JSONEncoder subclass that knows how to encode date/time and decimal types. """ + def default(self, o): if isinstance(o, datetime.datetime): return o.isoformat() elif isinstance(o, datetime.date): - return o.isoformat().split('T')[0] + return o.isoformat().split("T")[0] elif isinstance(o, datetime.time): return o.isoformat() elif isinstance(o, decimal.Decimal): diff --git a/gitmodel/serializers/python.py b/gitmodel/serializers/python.py index 41a4d67..5699b58 100644 --- a/gitmodel/serializers/python.py +++ b/gitmodel/serializers/python.py @@ -25,10 +25,7 @@ def serialize(obj, fields=None, invalid=ABORT): serialization may still fail with IGNORE if a value is not serializable. """ - pyobj = OrderedDict({ - 'model': obj._meta.model_name, - 'fields': {} - }) + pyobj = OrderedDict({"model": obj._meta.model_name, "fields": {}}) for field in obj._meta.fields: if fields is None or field.name in fields: if field.serializable: @@ -41,7 +38,7 @@ def serialize(obj, fields=None, invalid=ABORT): value = getattr(obj, field.name) else: raise - pyobj['fields'][field.name] = value + pyobj["fields"][field.name] = value return pyobj @@ -59,13 +56,13 @@ def deserialize(workspace, data, oid, invalid=IGNORE): this exception is re-raised. ``SET_EMPTY`` causes the value to be set to an empty value. ``IGNORE`` simply uses the raw value. """ - attrs = {'oid': oid} + attrs = {"oid": oid} try: - model = workspace.models[data['model']] + model = workspace.models[data["model"]] except KeyError: - raise ModelNotFound(data['model']) + raise ModelNotFound(data["model"]) for field in model._meta.fields: - value = data['fields'].get(field.name) + value = data["fields"].get(field.name) # field.deserialize() calls field.to_python(). If a serialized value # cannot be coerced into the correct type for its field, just assign # the raw value. diff --git a/gitmodel/test/__init__.py b/gitmodel/test/__init__.py index 0e37c85..6ea9d84 100644 --- a/gitmodel/test/__init__.py +++ b/gitmodel/test/__init__.py @@ -8,7 +8,7 @@ class GitModelTestCase(unittest.TestCase): - """ Sets up a temporary git repository for each test """ + """Sets up a temporary git repository for each test""" def setUp(self): # For tests, it's easier to use global_config so that we don't @@ -21,7 +21,7 @@ def setUp(self): self.utils = utils # Create temporary repo to work from - self.repo_path = tempfile.mkdtemp(prefix='python-gitmodel-') + self.repo_path = tempfile.mkdtemp(prefix="python-gitmodel-") pygit2.init_repository(self.repo_path, False) self.workspace = Workspace(self.repo_path) @@ -36,7 +36,7 @@ def get_module_suite(mod): subclasses are gethered automatically into a TestSuite """ # modules may provide a suite() function, - if hasattr(mod, 'suite'): + if hasattr(mod, "suite"): return mod.suite() else: # gather all testcases in this module into a suite @@ -46,31 +46,32 @@ def get_module_suite(mod): if inspect.isclass(obj) and issubclass(obj, unittest.TestCase): suite.addTest(unittest.makeSuite(obj)) # Set a name attribute so we can find it later - if mod.__name__.endswith('tests'): - name = mod.__name__.split('.')[-2] + if mod.__name__.endswith("tests"): + name = mod.__name__.split(".")[-2] else: - name = mod.__name__.split('.')[-1] - name = re.sub(r'^test_', '', name) + name = mod.__name__.split(".")[-1] + name = re.sub(r"^test_", "", name) suite.name = name suite.module = mod return suite def get_all_suites(): - """ Yields all testsuites """ + """Yields all testsuites""" # Tests can be one of: # - test/suitename/tests.py # - test/test_suitename.py test_dir = os.path.dirname(__file__) for f in os.listdir(test_dir): mod = None - if os.path.exists(os.path.join(test_dir, f, 'tests.py')): - p = __import__('gitmodel.test.{}'.format(f), globals(), locals(), - ['tests'], -1) + if os.path.exists(os.path.join(test_dir, f, "tests.py")): + p = __import__( + "gitmodel.test.{}".format(f), globals(), locals(), ["tests"], -1 + ) mod = p.tests - elif re.match(r'^test_\w+.py$', f): - modname = f.replace('.py', '') - p = __import__('gitmodel.test', globals(), locals(), [modname], -1) + elif re.match(r"^test_\w+.py$", f): + modname = f.replace(".py", "") + p = __import__("gitmodel.test", globals(), locals(), [modname], -1) mod = getattr(p, modname) if mod: suite = get_module_suite(mod) @@ -78,7 +79,7 @@ def get_all_suites(): def default_suite(): - """ Sets up the default test suite """ + """Sets up the default test suite""" suite = unittest.TestSuite() for other_suite in get_all_suites(): suite.addTest(other_suite) @@ -86,25 +87,25 @@ def default_suite(): class TestLoader(unittest.TestLoader): - """ Allows tests to be referenced by name """ + """Allows tests to be referenced by name""" + def loadTestsFromName(self, name, module=None): - if name == 'suite': + if name == "suite": return default_suite() testcase = None - if '.' in name: - name, testcase = name.split('.', 1) + if "." in name: + name, testcase = name.split(".", 1) for suite in get_all_suites(): if suite.name == name: if testcase is None: return suite - return super(TestLoader, self).loadTestsFromName(testcase, - suite.module) + return super(TestLoader, self).loadTestsFromName(testcase, suite.module) raise LookupError('could not find test case for "{}"'.format(name)) def main(): - """ Runs the default test suite as a command line application. """ - unittest.main(__name__, testLoader=TestLoader(), defaultTest='suite') + """Runs the default test suite as a command line application.""" + unittest.main(__name__, testLoader=TestLoader(), defaultTest="suite") diff --git a/gitmodel/test/fields/models.py b/gitmodel/test/fields/models.py index 5dff8b9..2b29e83 100644 --- a/gitmodel/test/fields/models.py +++ b/gitmodel/test/fields/models.py @@ -22,8 +22,8 @@ class Author(models.GitModel): first_name = fields.CharField() last_name = fields.CharField() email = fields.EmailField() - language = fields.CharField(default='en-US') - url = fields.URLField(schemes=('http', 'https'), required=False) + language = fields.CharField(default="en-US") + url = fields.URLField(schemes=("http", "https"), required=False) class Post(models.GitModel): diff --git a/gitmodel/test/fields/tests.py b/gitmodel/test/fields/tests.py index f841801..a7fb5b8 100644 --- a/gitmodel/test/fields/tests.py +++ b/gitmodel/test/fields/tests.py @@ -10,32 +10,33 @@ def setUp(self): super(TestInstancesMixin, self).setUp() from gitmodel.test.fields import models + self.models = self.workspace.import_models(models) self.person = self.models.Person( - slug='john-doe', - first_name='John', - last_name='Doe', - email='jdoe@example.com', + slug="john-doe", + first_name="John", + last_name="Doe", + email="jdoe@example.com", ) self.author = self.models.Author( - email='jdoe@example.com', - first_name='John', - last_name='Doe', + email="jdoe@example.com", + first_name="John", + last_name="Doe", ) self.post = self.models.Post( - slug='test-post', - title='Test Post', - body='Lorem ipsum dolor sit amet', + slug="test-post", + title="Test Post", + body="Lorem ipsum dolor sit amet", ) class FieldValidationTest(TestInstancesMixin, GitModelTestCase): def test_validate_not_empty(self): # empty string on required field should trigger validationerror - self.person.last_name = '' + self.person.last_name = "" with self.assertRaises(self.exceptions.ValidationError): self.person.save() @@ -45,12 +46,12 @@ def test_validate_not_empty(self): self.person.save() def test_validate_email(self): - self.person.email = 'foo_at_example.com' + self.person.email = "foo_at_example.com" with self.assertRaises(self.exceptions.ValidationError): self.person.save() def test_validate_slug(self): - self.person.slug = 'Foo Bar' + self.person.slug = "Foo Bar" with self.assertRaises(self.exceptions.ValidationError): self.person.save() @@ -58,52 +59,51 @@ def test_validate_integer(self): self.person.age = 20.5 with self.assertRaises(self.exceptions.ValidationError): self.person.save() - self.person.age = 'twenty-one' + self.person.age = "twenty-one" with self.assertRaises(self.exceptions.ValidationError): self.person.save() def test_validate_float(self): - self.person.tax_rate = '5%' + self.person.tax_rate = "5%" with self.assertRaises(self.exceptions.ValidationError): self.person.save() - self.person.tax_rate = '1.2.3' + self.person.tax_rate = "1.2.3" with self.assertRaises(self.exceptions.ValidationError): self.person.save() def test_validate_decimal(self): - self.person.account_balance = 'one.two' + self.person.account_balance = "one.two" with self.assertRaises(self.exceptions.ValidationError): self.person.save() - self.person.account_balance = '1.2.3' + self.person.account_balance = "1.2.3" with self.assertRaises(self.exceptions.ValidationError): self.person.save() def test_validate_date(self): # valid iso-8601 date - self.person.birth_date = '1978-12-07' + self.person.birth_date = "1978-12-07" self.person.save() # not a valid iso-8601 date - self.person.birth_date = '12/7/1978' + self.person.birth_date = "12/7/1978" with self.assertRaises(self.exceptions.ValidationError): self.person.save() def test_validate_datetime(self): # not a valid iso-8601 datetime - self.person.date_joined = '12/8/2012 4:53pm' + self.person.date_joined = "12/8/2012 4:53pm" with self.assertRaises(self.exceptions.ValidationError): self.person.save() def test_validate_time(self): - self.person.wake_up_call = '9am' + self.person.wake_up_call = "9am" with self.assertRaises(self.exceptions.ValidationError): self.person.save() - self.person.wake_up_call = '2012-08-10 09:00' + self.person.wake_up_call = "2012-08-10 09:00" with self.assertRaises(self.exceptions.ValidationError): self.person.save() class FieldTypeCheckingTest(TestInstancesMixin, GitModelTestCase): - def assertTypesMatch(self, field, test_values, type): for value, eq_value in test_values.iteritems(): setattr(self.person, field, value) @@ -114,64 +114,60 @@ def assertTypesMatch(self, field, test_values, type): def test_char(self): from datetime import datetime + test_values = { - 'John': 'John', - .007: '0.007', - datetime(2012, 12, 12): '2012-12-12 00:00:00' + "John": "John", + 0.007: "0.007", + datetime(2012, 12, 12): "2012-12-12 00:00:00", } - self.assertTypesMatch('first_name', test_values, basestring) + self.assertTypesMatch("first_name", test_values, basestring) def test_integer(self): - test_values = {33: 33, '33': 33} - self.assertTypesMatch('age', test_values, int) + test_values = {33: 33, "33": 33} + self.assertTypesMatch("age", test_values, int) def test_float(self): - test_values = {.825: .825, '0.825': .825} - self.assertTypesMatch('tax_rate', test_values, float) + test_values = {0.825: 0.825, "0.825": 0.825} + self.assertTypesMatch("tax_rate", test_values, float) def test_decimal(self): from decimal import Decimal + test_values = { - '1.23': Decimal('1.23'), - '12.300': Decimal('12.3'), - 1: Decimal('1.0') + "1.23": Decimal("1.23"), + "12.300": Decimal("12.3"), + 1: Decimal("1.0"), } - self.assertTypesMatch('account_balance', test_values, Decimal) + self.assertTypesMatch("account_balance", test_values, Decimal) def test_boolean(self): - test_values = { - True: True, - False: False, - 1: True, - 0: False, - None: False - } - self.assertTypesMatch('active', test_values, bool) + test_values = {True: True, False: False, 1: True, 0: False, None: False} + self.assertTypesMatch("active", test_values, bool) def test_date(self): from datetime import date + test_values = { - '1978-12-7': date(1978, 12, 7), - '1850-05-05': date(1850, 5, 5), + "1978-12-7": date(1978, 12, 7), + "1850-05-05": date(1850, 5, 5), } - self.assertTypesMatch('birth_date', test_values, date) + self.assertTypesMatch("birth_date", test_values, date) def test_datetime(self): from datetime import datetime from dateutil import tz + utc = tz.tzutc() utc_offset = tz.tzoffset(None, -1 * 4 * 60 * 60) test_values = { - '2012-05-30 14:32': datetime(2012, 5, 30, 14, 32), - '1820-8-13 9:23:48Z': datetime(1820, 8, 13, 9, 23, 48, 0, utc), - '2001-9-11 8:46:00-0400': datetime(2001, 9, 11, 8, 46, 0, 0, - utc_offset), - '2012-05-05 14:32:02.012345': datetime(2012, 5, 5, 14, 32, 2, - 12345), + "2012-05-30 14:32": datetime(2012, 5, 30, 14, 32), + "1820-8-13 9:23:48Z": datetime(1820, 8, 13, 9, 23, 48, 0, utc), + "2001-9-11 8:46:00-0400": datetime(2001, 9, 11, 8, 46, 0, 0, utc_offset), + "2012-05-05 14:32:02.012345": datetime(2012, 5, 5, 14, 32, 2, 12345), } - self.assertTypesMatch('date_joined', test_values, datetime) + self.assertTypesMatch("date_joined", test_values, datetime) # test a normal date - self.person.date_joined = '2012-01-01' + self.person.date_joined = "2012-01-01" self.person.save() person = self.models.Person.get(self.person.id) self.assertEqual(type(person.date_joined), datetime) @@ -180,14 +176,15 @@ def test_datetime(self): def test_time(self): from datetime import time from dateutil import tz + utc = tz.tzutc() utc_offset = tz.tzoffset(None, -1 * 4 * 60 * 60) test_values = { - '14:32': time(14, 32), - '9:23:48Z': time(9, 23, 48, 0, utc), - '8:46:00-0400': time(8, 46, 0, 0, utc_offset) + "14:32": time(14, 32), + "9:23:48Z": time(9, 23, 48, 0, utc), + "8:46:00-0400": time(8, 46, 0, 0, utc_offset), } - self.assertTypesMatch('wake_up_call', test_values, time) + self.assertTypesMatch("wake_up_call", test_values, time) class RelatedFieldTest(TestInstancesMixin, GitModelTestCase): @@ -202,47 +199,45 @@ def test_related(self): class BlobFieldTest(TestInstancesMixin, GitModelTestCase): def test_blob_field(self): - fd = open(os.path.join(os.path.dirname(__file__), - 'git-logo-2color.png')) + fd = open(os.path.join(os.path.dirname(__file__), "git-logo-2color.png")) self.author.save() self.post.author = self.author self.post.image = fd self.post.save() - #make sure stored file and original file are identical + # make sure stored file and original file are identical post = self.models.Post.get(self.post.get_id()) saved_content = post.image.read() fd.seek(0) control = fd.read() - self.assertEqual(saved_content, control, - "Saved blob does not match file") + self.assertEqual(saved_content, control, "Saved blob does not match file") class InheritedFieldTest(TestInstancesMixin, GitModelTestCase): def test_inherited_local_fields(self): user = self.models.User( - slug='john-doe', - first_name='John', - last_name='Doe', - email='jdoe@example.com', - password='secret' + slug="john-doe", + first_name="John", + last_name="Doe", + email="jdoe@example.com", + password="secret", ) user.save() # get user user_retreived = self.models.User.get(user.id) - self.assertEqual(user_retreived.password, 'secret') + self.assertEqual(user_retreived.password, "secret") def test_inherited_related_fields(self): self.author.save() self.post.author = self.author self.post.save() user = self.models.User( - slug='john-doe', - first_name='John', - last_name='Doe', - email='jdoe@example.com', - password='secret', - last_read=self.post + slug="john-doe", + first_name="John", + last_name="Doe", + email="jdoe@example.com", + password="secret", + last_read=self.post, ) user.save() # get user @@ -252,10 +247,7 @@ def test_inherited_related_fields(self): class JSONFieldTest(TestInstancesMixin, GitModelTestCase): def test_json_field(self): - metadata = { - 'foo': 'bar', - 'baz': 'qux' - } + metadata = {"foo": "bar", "baz": "qux"} self.author.save() self.post.author = self.author self.post.metadata = metadata @@ -268,14 +260,12 @@ def test_json_field(self): class GitObjectFieldTest(TestInstancesMixin, GitModelTestCase): def test_gitobject_field(self): repo = self.workspace.repo - test_commit = self.person.save(commit=True, message='Test Commit') + test_commit = self.person.save(commit=True, message="Test Commit") test_blob = repo[self.workspace.index[self.person.get_data_path()].oid] test_tree = repo[test_commit].tree obj = self.models.GitObjectTestModel( - blob=test_blob.oid, - commit=test_commit, - tree=test_tree.oid + blob=test_blob.oid, commit=test_commit, tree=test_tree.oid ) obj.save() @@ -288,7 +278,7 @@ def test_gitobject_field(self): err = '"commit" must be a valid git OID' with self.assertRaisesRegexp(self.exceptions.ValidationError, err): - obj.commit = 'foo' + obj.commit = "foo" obj.save() err = '"commit" must point to a Commit' @@ -302,15 +292,15 @@ def test_email_field(self): invalid = '"email" must be a valid e-mail address' with self.assertRaisesRegexp(self.exceptions.ValidationError, invalid): - self.author.email = 'jdoe[at]example.com' + self.author.email = "jdoe[at]example.com" self.author.save() - self.author.email = 'jdoe@example.com' + self.author.email = "jdoe@example.com" self.author.save() id = self.author.id author = self.models.Author.get(id) - self.assertEqual(author.email, 'jdoe@example.com') + self.assertEqual(author.email, "jdoe@example.com") class URLFieldTest(TestInstancesMixin, GitModelTestCase): @@ -319,17 +309,16 @@ def test_url_field(self): invalid_scheme = '"url" scheme must be one of http, https' with self.assertRaisesRegexp(self.exceptions.ValidationError, invalid): - self.author.url = 'http//example.com/foo' + self.author.url = "http//example.com/foo" self.author.save() - with self.assertRaisesRegexp(self.exceptions.ValidationError, - invalid_scheme): - self.author.url = 'ftp://example.com/foo' + with self.assertRaisesRegexp(self.exceptions.ValidationError, invalid_scheme): + self.author.url = "ftp://example.com/foo" self.author.save() - self.author.url = 'http://example.com/foo' + self.author.url = "http://example.com/foo" self.author.save() id = self.author.id author = self.models.Author.get(id) - self.assertEqual(author.url, 'http://example.com/foo') + self.assertEqual(author.url, "http://example.com/foo") diff --git a/gitmodel/test/model/models.py b/gitmodel/test/model/models.py index 711737c..c581493 100644 --- a/gitmodel/test/model/models.py +++ b/gitmodel/test/model/models.py @@ -6,7 +6,7 @@ class Author(GitModel): first_name = fields.CharField() last_name = fields.CharField() email = fields.CharField() - language = fields.CharField(default='en-US') + language = fields.CharField(default="en-US") class Post(GitModel): @@ -22,7 +22,7 @@ class Person(GitModel): email = fields.EmailField() class Meta: - data_filename = 'person_data.json' + data_filename = "person_data.json" class User(Person): @@ -32,10 +32,11 @@ class User(Person): def get_path_custom(opts, object_id): import os + # kinda silly, but good for testing that the override works model_name = opts.model_name.lower() - model_name = model_name.replace('alternate', '-alt') - return os.path.join(model_name, unicode(object_id), 'data.json') + model_name = model_name.replace("alternate", "-alt") + return os.path.join(model_name, unicode(object_id), "data.json") class PostAlternate(GitModel): @@ -43,7 +44,7 @@ class PostAlternate(GitModel): title = fields.CharField() class Meta: - id_attr = 'slug' + id_attr = "slug" get_data_path = get_path_custom @@ -51,7 +52,7 @@ class PostAlternateSub(PostAlternate): foo = fields.CharField() class Meta(PostAlternate.Meta): - id_attr = 'foo' + id_attr = "foo" class AbstractBase(GitModel): diff --git a/gitmodel/test/model/tests.py b/gitmodel/test/model/tests.py index ea91fb5..cbc3693 100644 --- a/gitmodel/test/model/tests.py +++ b/gitmodel/test/model/tests.py @@ -17,69 +17,61 @@ def setUp(self): self.models = self.workspace.models self.author = self.models.Author( - email='jdoe@example.com', - first_name='John', - last_name='Doe', + email="jdoe@example.com", + first_name="John", + last_name="Doe", ) self.post = self.models.Post( - slug='test-post', - title='Test Post', - body='Lorem ipsum dolor sit amet', + slug="test-post", + title="Test Post", + body="Lorem ipsum dolor sit amet", ) class GitModelBasicTest(TestInstancesMixin, GitModelTestCase): - def test_type(self): author = self.models.Author() self.assertIsInstance(author, self.models.GitModel) def test_meta(self): self.assertIsNotNone(self.models.Author._meta) - self.assertEqual(self.models.Person._meta.data_filename, - "person_data.json") - self.assertEqual(self.models.User._meta.data_filename, - "person_data.json") + self.assertEqual(self.models.Person._meta.data_filename, "person_data.json") + self.assertEqual(self.models.User._meta.data_filename, "person_data.json") def test_workspace_in_model_meta(self): from gitmodel.workspace import Workspace + self.assertIsInstance(self.models.Author._meta.workspace, Workspace) def test_fields_added_to_meta(self): fields = [f.name for f in self.models.Author._meta.fields] - self.assertEqual(fields, [ - 'id', - 'first_name', - 'last_name', - 'email', - 'language' - ]) + self.assertEqual(fields, ["id", "first_name", "last_name", "email", "language"]) def test_has_id_attr(self): self.assertIsNotNone(self.author._meta.id_attr) def test_id(self): - self.assertTrue(hasattr(self.author, 'id')) + self.assertTrue(hasattr(self.author, "id")) def test_create_from_kwargs(self): - self.assertEqual(self.author.first_name, 'John') + self.assertEqual(self.author.first_name, "John") def test_property_assignment(self): author = self.models.Author() - author.first_name = 'John' - self.assertEqual(author.first_name, 'John') + author.first_name = "John" + self.assertEqual(author.first_name, "John") def test_get_data_path(self): self.author.save() path = self.author.get_data_path() - test_path = 'author/{}/data.json'.format(self.author.get_id()) + test_path = "author/{}/data.json".format(self.author.get_id()) self.assertEqual(path, test_path) def test_subclass_meta(self): - obj = self.models.PostAlternateSub(foo='bar') + obj = self.models.PostAlternateSub(foo="bar") path = obj.get_data_path() - test_path = 'post-altsub/bar/data.json' + test_path = "post-altsub/bar/data.json" self.assertEqual(path, test_path) def test_save_oid(self): @@ -95,7 +87,7 @@ def test_get_oid(self): self.assertEqual(obj.oid, test_oid) def test_field_default(self): - self.assertEqual(self.author.language, 'en-US') + self.assertEqual(self.author.language, "en-US") def test_save(self): # save without adding to index or commit @@ -107,16 +99,19 @@ def test_save(self): # verify data data = json.loads(blob.data) - self.assertItemsEqual(data, { - 'model': 'Author', - 'fields': { - 'id': self.author.get_id(), - 'first_name': 'John', - 'last_name': 'Doe', - 'email': 'jdoe@example.com', - 'language': '', - } - }) + self.assertItemsEqual( + data, + { + "model": "Author", + "fields": { + "id": self.author.get_id(), + "first_name": "John", + "last_name": "Doe", + "email": "jdoe@example.com", + "language": "", + }, + }, + ) def test_delete(self): self.author.save() @@ -134,16 +129,16 @@ def test_delete(self): def test_save_commit(self): commit_info = { - 'author': ('John Doe', 'jdoe@example.com'), - 'message': 'Testing save with commit' + "author": ("John Doe", "jdoe@example.com"), + "message": "Testing save with commit", } commit_id = self.author.save(commit=True, **commit_info) commit = self.workspace.repo[commit_id] # verify commit - self.assertEqual(commit.author.name, 'John Doe') - self.assertEqual(commit.author.email, 'jdoe@example.com') - self.assertEqual(commit.message, 'Testing save with commit') + self.assertEqual(commit.author.name, "John Doe") + self.assertEqual(commit.author.email, "jdoe@example.com") + self.assertEqual(commit.message, "Testing save with commit") # get json from the returned tree using pygit2 code entry = commit.tree[self.author.get_data_path()] @@ -151,16 +146,19 @@ def test_save_commit(self): # verify data data = json.loads(blob.data) - self.assertItemsEqual(data, { - 'model': 'Author', - 'fields': { - 'id': self.author.get_id(), - 'first_name': 'John', - 'last_name': 'Doe', - 'email': 'jdoe@example.com', - 'language': '', - } - }) + self.assertItemsEqual( + data, + { + "model": "Author", + "fields": { + "id": self.author.get_id(), + "first_name": "John", + "last_name": "Doe", + "email": "jdoe@example.com", + "language": "", + }, + }, + ) def test_diff_nobranch(self): # Tests a diff when a save is made with no previous commits @@ -168,10 +166,10 @@ def test_diff_nobranch(self): self.author.save() self.assertTrue(self.workspace.has_changes()) blob_hash = self.workspace.index[self.author.get_data_path()].hex[:7] - diff = open(os.path.join(os.path.dirname(__file__), - 'diff_nobranch.diff')).read() - diff = diff.format(self.author.get_data_path(), blob_hash, - self.author.id) + diff = open( + os.path.join(os.path.dirname(__file__), "diff_nobranch.diff") + ).read() + diff = diff.format(self.author.get_data_path(), blob_hash, self.author.id) self.assertMultiLineEqual(diff, self.workspace.diff().patch) def test_diff_branch(self): @@ -179,25 +177,27 @@ def test_diff_branch(self): self.maxDiff = None self.author.save(commit=True, message="Test first commit") blob_hash_1 = self.workspace.index[self.author.get_data_path()].hex[:7] - self.author.first_name = 'Jane' + self.author.first_name = "Jane" self.author.save() blob_hash_2 = self.workspace.index[self.author.get_data_path()].hex[:7] - diff = open(os.path.join(os.path.dirname(__file__), - 'diff_branch.diff')).read() - diff = diff.format(self.author.get_data_path(), blob_hash_1, - blob_hash_2, self.author.id) + diff = open(os.path.join(os.path.dirname(__file__), "diff_branch.diff")).read() + diff = diff.format( + self.author.get_data_path(), blob_hash_1, blob_hash_2, self.author.id + ) self.assertMultiLineEqual(diff, self.workspace.diff().patch) def test_save_commit_history(self): # Test that commited models save correctly import pygit2 + commit1 = self.author.save(commit=True, message="Test first commit") - self.author.first_name = 'Jane' + self.author.first_name = "Jane" commit2 = self.author.save(commit=True, message="Changed name to Jane") self.assertEqual(self.workspace.branch.commit.oid, commit2) self.assertEqual(self.workspace.repo[commit2].parents[0].oid, commit1) - walktree = self.workspace.repo.walk(self.workspace.branch.oid, - pygit2.GIT_SORT_TIME) + walktree = self.workspace.repo.walk( + self.workspace.branch.oid, pygit2.GIT_SORT_TIME + ) commits = [c for c in walktree] self.assertEqual(commits[0].oid, commit2) self.assertEqual(commits[1].oid, commit1) @@ -207,29 +207,29 @@ def test_get_simple_object(self): id = self.author.id author = self.models.Author.get(self.author.get_id()) self.assertEqual(author.id, id) - self.assertEqual(author.first_name, 'John') - self.assertEqual(author.last_name, 'Doe') - self.assertEqual(author.email, 'jdoe@example.com') + self.assertEqual(author.first_name, "John") + self.assertEqual(author.last_name, "Doe") + self.assertEqual(author.email, "jdoe@example.com") def test_save_custom_id(self): self.post.save(commit=True) - post = self.models.Post.get('test-post') - self.assertEqual(post.get_id(), 'test-post') - self.assertEqual(post.slug, 'test-post') - self.assertEqual(post.title, 'Test Post') + post = self.models.Post.get("test-post") + self.assertEqual(post.get_id(), "test-post") + self.assertEqual(post.slug, "test-post") + self.assertEqual(post.title, "Test Post") def test_id_validator(self): # "/" and "\0" are both invalid characters - self.author.id = 'foo/bar' + self.author.id = "foo/bar" with self.assertRaises(self.exceptions.ValidationError): self.author.save() - self.author.id = 'foo\000bar' + self.author.id = "foo\000bar" with self.assertRaises(self.exceptions.ValidationError): self.author.save() def test_require_fields(self): - test_author = self.models.Author(first_name='Jane') + test_author = self.models.Author(first_name="Jane") with self.assertRaises(self.exceptions.ValidationError): test_author.save() @@ -247,21 +247,24 @@ class Resource(self.models.GitModel): path = self.fields.CharField() class Meta: - id_attr = 'path' + id_attr = "path" - fields_named_id = (f for f in Resource._meta.fields if f.name == 'id') + fields_named_id = (f for f in Resource._meta.fields if f.name == "id") self.assertEqual(len(tuple(fields_named_id)), 1) def test_basic_inheritance(self): fields = [f.name for f in self.models.User._meta.fields] - self.assertEqual(fields, [ - 'id', - 'first_name', - 'last_name', - 'email', - 'password', - 'date_joined', - ]) + self.assertEqual( + fields, + [ + "id", + "first_name", + "last_name", + "email", + "password", + "date_joined", + ], + ) def test_inherited_field_clash(self): with self.assertRaises(self.exceptions.FieldError): @@ -272,16 +275,16 @@ class User(self.models.Person): date_joined = self.fields.DateField() def test_meta_overrides(self): - self.assertEqual(self.models.PostAlternate._meta.id_attr, 'slug') + self.assertEqual(self.models.PostAlternate._meta.id_attr, "slug") def test_custom_path_override(self): - post = self.models.PostAlternate(slug='foobar', title='Foobar') + post = self.models.PostAlternate(slug="foobar", title="Foobar") post.save() - self.assertEqual(post.get_data_path(), 'post-alt/foobar/data.json') + self.assertEqual(post.get_data_path(), "post-alt/foobar/data.json") def test_commit_when_pending_changes(self): self.author.save() - self.author.first_name = 'Jane' + self.author.first_name = "Jane" with self.assertRaises(self.exceptions.RepositoryError): self.author.save(commit=True) @@ -300,12 +303,11 @@ def test_concrete(self): self.author.save() id = self.author.get_id() - err = ('Cannot call .*? because .*? has not been registered with a ' - 'workspace') + err = "Cannot call .*? because .*? has not been registered with a " "workspace" # try to init an unregistered model with self.assertRaisesRegexp(self.exceptions.GitModelError, err): - Author(first_name='John', last_name='Doe') + Author(first_name="John", last_name="Doe") # try to use .get() on the unregistered model with self.assertRaisesRegexp(self.exceptions.GitModelError, err): @@ -317,30 +319,27 @@ def test_abstract(self): self.models.AbstractBase.get() with self.assertRaises(self.exceptions.GitModelError): - self.models.AbstractBase.get('1') + self.models.AbstractBase.get("1") with self.assertRaises(self.exceptions.GitModelError): self.models.AbstractBase.all() - concrete = self.models.Concrete(field_one='1', field_two='2', - field_three='3') + concrete = self.models.Concrete(field_one="1", field_two="2", field_three="3") concrete.save() test_concrete = self.models.Concrete.get(id=concrete.id) - self.assertEqual(test_concrete.field_one, '1') - self.assertEqual(test_concrete.field_two, '2') - self.assertEqual(test_concrete.field_three, '3') + self.assertEqual(test_concrete.field_one, "1") + self.assertEqual(test_concrete.field_two, "2") + self.assertEqual(test_concrete.field_three, "3") def test_all(self): author1 = self.author author2 = self.models.Author( - first_name='Zeb', - last_name='Doe', - email='zdoe@example.com' + first_name="Zeb", last_name="Doe", email="zdoe@example.com" ) author1.save() author2.save() authors = self.models.Author.all() - self.assertTrue(hasattr(authors, '__iter__')) + self.assertTrue(hasattr(authors, "__iter__")) authors = list(authors) authors.sort(key=lambda a: a.first_name) self.assertEqual(authors[0].id, author1.id) @@ -349,20 +348,20 @@ def test_all(self): def test_unique_id(self): self.post.save() p2 = self.models.Post( - slug='test-post', - title='A duplicate test post', - body='Lorem ipsum dupor sit amet', + slug="test-post", + title="A duplicate test post", + body="Lorem ipsum dupor sit amet", ) - err = 'A .*? instance already exists with id .*?' + err = "A .*? instance already exists with id .*?" with self.assertRaisesRegexp(self.exceptions.IntegrityError, err): p2.save() def test_moved_path(self): self.post.save() old_path = self.post.get_data_path() - self.post.slug = 'updated-post' + self.post.slug = "updated-post" self.post.save() self.assertNotEqual(old_path, self.post.get_data_path()) with self.assertRaises(KeyError): self.workspace.index[old_path] - self.models.Post.get('updated-post') + self.models.Post.get("updated-post") diff --git a/gitmodel/test/test_utils.py b/gitmodel/test/test_utils.py index 62999e6..907ada4 100644 --- a/gitmodel/test/test_utils.py +++ b/gitmodel/test/test_utils.py @@ -21,31 +21,34 @@ def _get_test_tree(self): test2_txt = repo.create_blob("TEST 2") test3_text = repo.create_blob("TEST 3") baz_tb = repo.TreeBuilder() - baz_tb.insert('test2.txt', test2_txt, pygit2.GIT_FILEMODE_BLOB) + baz_tb.insert("test2.txt", test2_txt, pygit2.GIT_FILEMODE_BLOB) baz = baz_tb.write() bar_tb = repo.TreeBuilder() - bar_tb.insert('test.txt', test_txt, pygit2.GIT_FILEMODE_BLOB) - bar_tb.insert('test3.txt', test3_text, pygit2.GIT_FILEMODE_BLOB) - bar_tb.insert('baz', baz, pygit2.GIT_FILEMODE_TREE) + bar_tb.insert("test.txt", test_txt, pygit2.GIT_FILEMODE_BLOB) + bar_tb.insert("test3.txt", test3_text, pygit2.GIT_FILEMODE_BLOB) + bar_tb.insert("baz", baz, pygit2.GIT_FILEMODE_TREE) bar = bar_tb.write() foo_tb = repo.TreeBuilder() - foo_tb.insert('bar', bar, pygit2.GIT_FILEMODE_TREE) + foo_tb.insert("bar", bar, pygit2.GIT_FILEMODE_TREE) foo = foo_tb.write() root_tb = repo.TreeBuilder() - root_tb.insert('foo', foo, pygit2.GIT_FILEMODE_TREE) + root_tb.insert("foo", foo, pygit2.GIT_FILEMODE_TREE) root = root_tb.write() return root def test_describe_tree(self): from gitmodel import utils + root = self._get_test_tree() desc = utils.path.describe_tree(self.repo, root) - test_desc = ('foo/\n' - ' bar/\n' - ' baz/\n' - ' test2.txt\n' - ' test.txt\n' - ' test3.txt') + test_desc = ( + "foo/\n" + " bar/\n" + " baz/\n" + " test2.txt\n" + " test.txt\n" + " test3.txt" + ) self.assertMultiLineEqual(desc, test_desc) def test_make_signature(self): @@ -57,60 +60,72 @@ def test_make_signature(self): # Get local offset timestamp = time() dt = datetime.fromtimestamp(timestamp) - aware = datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, - dt.second, dt.microsecond, tzinfo=tzlocal()) + aware = datetime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + tzinfo=tzlocal(), + ) seconds = aware.utcoffset().days * 86400 seconds += aware.utcoffset().seconds offset = seconds / 60 - test_sig = utils.make_signature('Tester Test', 'test@example.com', - timestamp=timestamp) - self.assertEqual(test_sig.name, 'Tester Test') - self.assertEqual(test_sig.email, 'test@example.com') + test_sig = utils.make_signature( + "Tester Test", "test@example.com", timestamp=timestamp + ) + self.assertEqual(test_sig.name, "Tester Test") + self.assertEqual(test_sig.email, "test@example.com") self.assertEqual(test_sig.offset, offset) self.assertAlmostEqual(test_sig.time, timestamp, -1) # since we defined passed timestamp earlier, test that timestamp is # automatically created - test_sig = utils.make_signature('Tester Test', 'test@example.com') + test_sig = utils.make_signature("Tester Test", "test@example.com") self.assertAlmostEqual(test_sig.time, timestamp, delta=10) def test_build_path_empty(self): # Test building a path from an empty tree from gitmodel import utils - path = '/foo/bar/baz/' # path sep should be stripped + + path = "/foo/bar/baz/" # path sep should be stripped # create dummy entry blob_oid = self.repo.create_blob("TEST CONTENT") - entries = [('qux.txt', blob_oid, pygit2.GIT_FILEMODE_BLOB)] + entries = [("qux.txt", blob_oid, pygit2.GIT_FILEMODE_BLOB)] oid = utils.path.build_path(self.repo, path, entries) desc = utils.path.describe_tree(self.repo, oid) - test_desc = 'foo/\n bar/\n baz/\n qux.txt' + test_desc = "foo/\n bar/\n baz/\n qux.txt" self.assertMultiLineEqual(desc, test_desc) def test_build_path_update(self): # Test building a path from an existing tree, updating the path from gitmodel import utils - path = '/foo/bar/baz/' # path sep should be stripped + + path = "/foo/bar/baz/" # path sep should be stripped # build initial tree blob_oid = self.repo.create_blob("TEST CONTENT") - entries = [('qux.txt', blob_oid, pygit2.GIT_FILEMODE_BLOB)] + entries = [("qux.txt", blob_oid, pygit2.GIT_FILEMODE_BLOB)] tree1 = utils.path.build_path(self.repo, path, entries) # build the same path, but this time with a new blob blob_oid = self.repo.create_blob("UPDATED CONTENT") - entries = [('qux.txt', blob_oid, pygit2.GIT_FILEMODE_BLOB)] + entries = [("qux.txt", blob_oid, pygit2.GIT_FILEMODE_BLOB)] tree2 = utils.path.build_path(self.repo, path, entries, tree1) - entry = self.repo[tree2]['foo/bar/baz/qux.txt'] + entry = self.repo[tree2]["foo/bar/baz/qux.txt"] new_content = self.repo[entry.oid].data desc = utils.path.describe_tree(self.repo, tree2) - test_desc = 'foo/\n bar/\n baz/\n qux.txt' - self.assertEqual(new_content, 'UPDATED CONTENT') + test_desc = "foo/\n bar/\n baz/\n qux.txt" + self.assertEqual(new_content, "UPDATED CONTENT") self.assertMultiLineEqual(desc, test_desc) def test_glob(self): from gitmodel import utils + tree = self._get_test_tree() - files = utils.path.glob(self.repo, tree, 'foo/*/*.txt') - test = ['foo/bar/test.txt', 'foo/bar/test3.txt'] + files = utils.path.glob(self.repo, tree, "foo/*/*.txt") + test = ["foo/bar/test.txt", "foo/bar/test3.txt"] self.assertEqual(list(files), test) diff --git a/gitmodel/test/test_workspace.py b/gitmodel/test/test_workspace.py index eaf2cb1..9584f07 100644 --- a/gitmodel/test/test_workspace.py +++ b/gitmodel/test/test_workspace.py @@ -10,14 +10,15 @@ def setUp(self): def test_workspace_init(self): from gitmodel.conf import Config import pygit2 + self.assertIsInstance(self.workspace.config, Config) self.assertIsInstance(self.workspace.repo, pygit2.Repository) def test_base_gitmodel(self): from gitmodel.models import GitModel, DeclarativeMetaclass + self.assertIsInstance(GitModel, DeclarativeMetaclass) - self.assertIsInstance(self.workspace.models.GitModel, - DeclarativeMetaclass) + self.assertIsInstance(self.workspace.models.GitModel, DeclarativeMetaclass) def test_register_model(self): from gitmodel.models import GitModel, DeclarativeMetaclass @@ -28,7 +29,7 @@ class TestModel(GitModel): bar = fields.CharField() self.workspace.register_model(TestModel) - self.assertIsNotNone(self.workspace.models.get('TestModel')) + self.assertIsNotNone(self.workspace.models.get("TestModel")) test_model = self.workspace.models.TestModel() self.assertIsInstance(test_model, self.workspace.models.TestModel) self.assertIsInstance(type(test_model), DeclarativeMetaclass) @@ -36,98 +37,99 @@ class TestModel(GitModel): def test_init_existing_branch(self): from gitmodel.workspace import Workspace + # Test init of workspace with existing branch # create a commit on existing workspace - self.workspace.add_blob('test.txt', 'Test') - self.workspace.commit('initial commit') + self.workspace.add_blob("test.txt", "Test") + self.workspace.commit("initial commit") new_workspace = Workspace(self.workspace.repo.path) - self.assertEqual(new_workspace.branch.ref.name, 'refs/heads/master') - self.assertEqual(new_workspace.branch.commit.message, 'initial commit') + self.assertEqual(new_workspace.branch.ref.name, "refs/heads/master") + self.assertEqual(new_workspace.branch.commit.message, "initial commit") def test_getitem(self): - self.workspace.add_blob('test.txt', 'Test') - entry = self.workspace.index['test.txt'] - self.assertEqual(self.repo[entry.oid].data, 'Test') + self.workspace.add_blob("test.txt", "Test") + entry = self.workspace.index["test.txt"] + self.assertEqual(self.repo[entry.oid].data, "Test") def test_branch_property(self): self.assertIsNone(self.workspace.branch) - self.workspace.add_blob('test.txt', 'Test') - self.workspace.commit('initial commit') + self.workspace.add_blob("test.txt", "Test") + self.workspace.commit("initial commit") self.assertIsNotNone(self.workspace.branch) - self.assertEqual(self.workspace.branch.ref.name, 'refs/heads/master') - self.assertEqual(self.workspace.branch.commit.message, - 'initial commit') + self.assertEqual(self.workspace.branch.ref.name, "refs/heads/master") + self.assertEqual(self.workspace.branch.commit.message, "initial commit") def test_set_branch(self): # create intial master branch - self.workspace.add_blob('test.txt', 'Test') - self.workspace.commit('initial commit') + self.workspace.add_blob("test.txt", "Test") + self.workspace.commit("initial commit") # create a new branch - self.workspace.create_branch('testbranch') + self.workspace.create_branch("testbranch") # set_branch will automatically update the index - self.workspace.set_branch('testbranch') - self.workspace.add_blob('test.txt', 'Test 2') - self.workspace.commit('test branch commit') + self.workspace.set_branch("testbranch") + self.workspace.add_blob("test.txt", "Test 2") + self.workspace.commit("test branch commit") - entry = self.workspace.index['test.txt'] + entry = self.workspace.index["test.txt"] test_content = self.repo[entry.oid].data - self.assertEqual(test_content, 'Test 2') + self.assertEqual(test_content, "Test 2") - self.workspace.set_branch('master') - entry = self.workspace.index['test.txt'] + self.workspace.set_branch("master") + entry = self.workspace.index["test.txt"] test_content = self.repo[entry.oid].data - self.assertEqual(test_content, 'Test') + self.assertEqual(test_content, "Test") def test_set_nonexistant_branch(self): with self.assertRaises(KeyError): - self.workspace.set_branch('foobar') + self.workspace.set_branch("foobar") def test_update_index_with_pending_changes(self): - self.workspace.add_blob('test.txt', 'Test') - self.workspace.commit('initial commit') - with self.assertRaisesRegexp(exceptions.RepositoryError, r'pending'): - self.workspace.add_blob('test.txt', 'Test 2') - self.workspace.create_branch('testbranch') - self.workspace.set_branch('testbranch') + self.workspace.add_blob("test.txt", "Test") + self.workspace.commit("initial commit") + with self.assertRaisesRegexp(exceptions.RepositoryError, r"pending"): + self.workspace.add_blob("test.txt", "Test 2") + self.workspace.create_branch("testbranch") + self.workspace.set_branch("testbranch") def test_add_blob(self): - self.workspace.add_blob('test.txt', 'Test') - entry = self.workspace.index['test.txt'] - self.assertEqual(self.repo[entry.oid].data, 'Test') + self.workspace.add_blob("test.txt", "Test") + entry = self.workspace.index["test.txt"] + self.assertEqual(self.repo[entry.oid].data, "Test") def test_remove(self): - self.workspace.add_blob('test.txt', 'Test') - entry = self.workspace.index['test.txt'] - self.assertEqual(self.repo[entry.oid].data, 'Test') - self.workspace.remove('test.txt') + self.workspace.add_blob("test.txt", "Test") + entry = self.workspace.index["test.txt"] + self.assertEqual(self.repo[entry.oid].data, "Test") + self.workspace.remove("test.txt") with self.assertRaises(KeyError): - self.workspace.index['test.txt'] + self.workspace.index["test.txt"] def test_commit_on_success(self): - with self.workspace.commit_on_success('Test commit'): - self.workspace.add_blob('test.txt', 'Test') - self.assertEqual(self.workspace.branch.commit.message, 'Test commit') + with self.workspace.commit_on_success("Test commit"): + self.workspace.add_blob("test.txt", "Test") + self.assertEqual(self.workspace.branch.commit.message, "Test commit") def test_commit_on_success_with_error(self): # make an exception we can catch class TestException(Exception): pass + try: - with self.workspace.commit_on_success('Test commit'): - self.workspace.add_blob('test.txt', 'Test') - raise TestException('dummy error') + with self.workspace.commit_on_success("Test commit"): + self.workspace.add_blob("test.txt", "Test") + raise TestException("dummy error") except TestException: pass # since commit should have failed, current branch should be nonexistent self.assertEqual(self.workspace.branch, None) def test_commit_on_success_with_pending_changes(self): - self.workspace.add_blob('foo.txt', 'Foobar') - with self.assertRaisesRegexp(exceptions.RepositoryError, r'pending'): - with self.workspace.commit_on_success('Test commit'): - self.workspace.add_blob('test.txt', 'Test') + self.workspace.add_blob("foo.txt", "Foobar") + with self.assertRaisesRegexp(exceptions.RepositoryError, r"pending"): + with self.workspace.commit_on_success("Test commit"): + self.workspace.add_blob("test.txt", "Test") self.assertEqual(self.workspace.branch, None) def test_has_changes(self): - self.workspace.add_blob('foo.txt', 'Foobar') + self.workspace.add_blob("foo.txt", "Foobar") self.assertTrue(self.workspace.has_changes()) diff --git a/gitmodel/utils/__init__.py b/gitmodel/utils/__init__.py index 5083909..b63a2b0 100644 --- a/gitmodel/utils/__init__.py +++ b/gitmodel/utils/__init__.py @@ -10,14 +10,13 @@ # We disable c_make_encoder for python between versions 2.7 and 2.7.3, so that # we can use collections.OrderedDict when encoding. -if 0x20700f0 <= sys.hexversion < 0x20703f0: +if 0x20700F0 <= sys.hexversion < 0x20703F0: json.encoder.c_make_encoder = None -__all__ = ['json', 'make_signature', 'path'] +__all__ = ["json", "make_signature", "path"] -def make_signature(name, email, timestamp=None, offset=None, - default_offset=None): +def make_signature(name, email, timestamp=None, offset=None, default_offset=None): """ Creates a pygit2.Signature while making time and offset optional. By default, uses current time, and local offset as determined by @@ -29,8 +28,16 @@ def make_signature(name, email, timestamp=None, offset=None, if offset is None and default_offset is None: # Get local offset dt = datetime.fromtimestamp(timestamp) - aware = datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, - dt.second, dt.microsecond, tzinfo=tzlocal()) + aware = datetime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + tzinfo=tzlocal(), + ) seconds = aware.utcoffset().days * 86400 seconds += aware.utcoffset().seconds offset = seconds / 60 diff --git a/gitmodel/utils/encoding.py b/gitmodel/utils/encoding.py index 593f70d..c1cc150 100644 --- a/gitmodel/utils/encoding.py +++ b/gitmodel/utils/encoding.py @@ -13,7 +13,7 @@ def __init__(self, obj, *args): def __str__(self): original = UnicodeDecodeError.__str__(self) - msg = '{0}. You passed in {1!r} ({2})' + msg = "{0}. You passed in {1!r} ({2})" return msg.format(original, self.obj, type(self.obj)) @@ -23,15 +23,22 @@ def is_protected_type(obj): Objects of protected types are preserved as-is when passed to force_unicode(strings_only=True). """ - return isinstance(obj, ( - types.NoneType, - int, long, - datetime.datetime, datetime.date, datetime.time, - float, Decimal) + return isinstance( + obj, + ( + types.NoneType, + int, + long, + datetime.datetime, + datetime.date, + datetime.time, + float, + Decimal, + ), ) -def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): +def force_unicode(s, encoding="utf-8", strings_only=False, errors="strict"): """ Similar to smart_unicode, except that lazy instances are resolved to strings, rather than kept as lazy objects. @@ -46,8 +53,11 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): if strings_only and is_protected_type(s): return s try: - if not isinstance(s, basestring,): - if hasattr(s, '__unicode__'): + if not isinstance( + s, + basestring, + ): + if hasattr(s, "__unicode__"): s = unicode(s) else: try: @@ -61,8 +71,12 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # without raising a further exception. We do an # approximation to what the Exception's standard str() # output should be. - s = u' '.join([force_unicode(arg, encoding, strings_only, - errors) for arg in s]) + s = u" ".join( + [ + force_unicode(arg, encoding, strings_only, errors) + for arg in s + ] + ) elif not isinstance(s, unicode): # Note: We use .decode() here, instead of unicode(s, encoding, # errors), so that if s is a SafeString, it ends up being a @@ -77,6 +91,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # working unicode method. Try to handle this without raising a # further exception by individually forcing the exception args # to unicode. - s = u' '.join([force_unicode(arg, encoding, strings_only, - errors) for arg in s]) + s = u" ".join( + [force_unicode(arg, encoding, strings_only, errors) for arg in s] + ) return s diff --git a/gitmodel/utils/isodate.py b/gitmodel/utils/isodate.py index a0d0b94..a3d53af 100644 --- a/gitmodel/utils/isodate.py +++ b/gitmodel/utils/isodate.py @@ -3,13 +3,16 @@ from datetime import datetime, time as dt_time from dateutil import tz -ISO_DATE_RE = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$') -ISO_TIME_RE = re.compile(r'^(\d{1,2}:\d{2})(:(\d{2})(\.\d{1,5})?)?' - r'(Z|[+-]\d{1,2}:?\d{2}?)?$') -ISO_DATETIME_RE = re.compile(r'^(\d{4}-\d{1,2}-\d{1,2}[T\s]\d{1,2}:\d{2})(:' - r'(\d{2})(\.\d{1,6})?)?(Z|[+-]\d{1,2}:?' - r'\d{2}?)?$') -TZ_RE = re.compile(r'([+-])(\d{1,2}):?(\d{2})?') +ISO_DATE_RE = re.compile(r"^\d{4}-\d{1,2}-\d{1,2}$") +ISO_TIME_RE = re.compile( + r"^(\d{1,2}:\d{2})(:(\d{2})(\.\d{1,5})?)?" r"(Z|[+-]\d{1,2}:?\d{2}?)?$" +) +ISO_DATETIME_RE = re.compile( + r"^(\d{4}-\d{1,2}-\d{1,2}[T\s]\d{1,2}:\d{2})(:" + r"(\d{2})(\.\d{1,6})?)?(Z|[+-]\d{1,2}:?" + r"\d{2}?)?$" +) +TZ_RE = re.compile(r"([+-])(\d{1,2}):?(\d{2})?") class InvalidFormat(Exception): @@ -21,35 +24,35 @@ class InvalidDate(Exception): def parse_iso_date(value): - #NEEDS-TEST + # NEEDS-TEST if not ISO_DATE_RE.match(value): raise InvalidFormat('invalid ISO-8601 date: "{}"'.format(value)) try: - return datetime(*time.strptime(value, '%Y-%m-%d')[:3]).date() + return datetime(*time.strptime(value, "%Y-%m-%d")[:3]).date() except ValueError: raise InvalidDate('invalid date: "{}"'.format(value)) def parse_tz(tzstr): - #NEEDS-TEST + # NEEDS-TEST # get tz data if tzstr is None: tzinfo = None - elif tzstr == 'Z': + elif tzstr == "Z": tzinfo = tz.tzutc() else: # parse offset string s, h, m = TZ_RE.match(tzstr).groups() tzseconds = int(m and m or 0) * 60 tzseconds += int(h) * 60 * 60 - if s == '-': + if s == "-": tzseconds = tzseconds * -1 tzinfo = tz.tzoffset(None, tzseconds) return tzinfo def parse_iso_datetime(value): - #NEEDS-TEST + # NEEDS-TEST match = ISO_DATETIME_RE.match(value) if not match: raise InvalidFormat('invalid ISO-8601 date/time: "{}"'.format(value)) @@ -61,15 +64,15 @@ def parse_iso_datetime(value): tzstr = match.group(5) # replace the "T" if given - dtstr = dtstr.replace('T', ' ') + dtstr = dtstr.replace("T", " ") try: - dt_args = time.strptime(dtstr, '%Y-%m-%d %H:%M')[:5] + dt_args = time.strptime(dtstr, "%Y-%m-%d %H:%M")[:5] except ValueError: raise InvalidDate('invalid date: "{}"'.format(value)) # append seconds, usecs, and tz dt_args += (int(secs) if secs else 0,) - dt_args += (int(usecs.lstrip('.')) if usecs else 0,) + dt_args += (int(usecs.lstrip(".")) if usecs else 0,) dt_args += (parse_tz(tzstr),) try: @@ -79,7 +82,7 @@ def parse_iso_datetime(value): def parse_iso_time(value): - #NEEDS-TEST + # NEEDS-TEST match = ISO_TIME_RE.match(value) if not match: raise InvalidFormat('invalid ISO-8601 time: "{}"'.format(value)) @@ -91,7 +94,7 @@ def parse_iso_time(value): tzstr = match.group(5) try: - dt_args = time.strptime(tmstr, '%H:%M')[3:5] + dt_args = time.strptime(tmstr, "%H:%M")[3:5] except ValueError: raise InvalidDate('invalid time: "{}"'.format(value)) diff --git a/gitmodel/utils/path.py b/gitmodel/utils/path.py index 2f51712..4a1ecf0 100644 --- a/gitmodel/utils/path.py +++ b/gitmodel/utils/path.py @@ -6,7 +6,7 @@ import pygit2 -__all__ = ['describe_tree', 'build_path', 'glob'] +__all__ = ["describe_tree", "build_path", "glob"] def build_path(repo, path, entries=None, root=None): @@ -26,7 +26,7 @@ def build_path(repo, path, entries=None, root=None): or stage. """ path = path.strip(os.path.sep) - if path is not None and path != '': + if path is not None and path != "": parent, name = os.path.split(path) else: parent, name = None, None @@ -64,9 +64,9 @@ def build_path(repo, path, entries=None, root=None): entry = (name, oid, pygit2.GIT_FILEMODE_TREE) - if parent == '': + if parent == "": # parent is the root tree - return build_path(repo, '', (entry,), root) + return build_path(repo, "", (entry,), root) return build_path(repo, parent, (entry,), root) @@ -79,15 +79,15 @@ def describe_tree(repo, tree, indent=2, lvl=0): if isinstance(tree, pygit2.Oid): tree = repo[tree] for e in tree: - i = ' ' * indent * lvl + i = " " * indent * lvl is_tree = repo[e.oid].type == pygit2.GIT_OBJ_TREE - slash = is_tree and '/' or '' - output.append('{}{}{}'.format(i, e.name, slash)) + slash = is_tree and "/" or "" + output.append("{}{}{}".format(i, e.name, slash)) if is_tree: sub_items = describe_tree(repo, e.oid, indent, lvl + 1) output.extend(sub_items) if lvl == 0: - return '\n'.join(output) + return "\n".join(output) return output @@ -101,7 +101,7 @@ def glob(repo, tree, pathname): if isinstance(tree, pygit2.Oid): tree = repo[tree] - pathname = pathname.strip('/') + pathname = pathname.strip("/") if not has_magic(pathname): if path_exists(tree, pathname): yield pathname @@ -127,6 +127,7 @@ def glob(repo, tree, pathname): for name in glob_in_dir(repo, tree, dirname, basename): yield os.path.join(dirname, name) + # These 2 helper functions non-recursively glob inside a literal directory. # They return a list of basenames. `glob1` accepts a pattern while `glob0` # takes a literal basename (so it only has to check for its existence). @@ -136,21 +137,22 @@ def glob1(repo, tree, dirname, pattern): if not dirname: dirname = os.curdir if isinstance(pattern, unicode) and not isinstance(dirname, unicode): - dirname = unicode(dirname, sys.getfilesystemencoding() or - sys.getdefaultencoding()) + dirname = unicode( + dirname, sys.getfilesystemencoding() or sys.getdefaultencoding() + ) if dirname != os.curdir: try: tree = repo[tree[dirname].oid] except KeyError: return [] names = [e.name for e in tree] - if pattern[0] != '.': - names = filter(lambda n: n[0] != '.', names) + if pattern[0] != ".": + names = filter(lambda n: n[0] != ".", names) return fnmatch.filter(names, pattern) def glob0(repo, tree, dirname, basename): - if basename == '': + if basename == "": # `os.path.split()` returns an empty basename for paths ending with a # directory separator. 'q*x/' should match only directories. if path_exists(tree, dirname): @@ -163,7 +165,7 @@ def glob0(repo, tree, dirname, basename): return [] -magic_check = re.compile('[*?[]') +magic_check = re.compile("[*?[]") def has_magic(s): diff --git a/gitmodel/workspace.py b/gitmodel/workspace.py index f61aa74..317ceb5 100644 --- a/gitmodel/workspace.py +++ b/gitmodel/workspace.py @@ -24,12 +24,14 @@ class Workspace(object): Passing initial_branch will set the default head for the workspace. """ - def __init__(self, repo_path, initial_branch='refs/heads/master'): + + def __init__(self, repo_path, initial_branch="refs/heads/master"): self.config = conf.Config() # set up a model registry class ModelRegistry(dict): """This class acts like a so-called AttrDict""" + def __init__(self): self.__dict__ = self @@ -57,7 +59,7 @@ def __init__(self): self.update_index(self.head) # add a base GitModel which can be extended if needed - self.register_model(models.GitModel, 'GitModel') + self.register_model(models.GitModel, "GitModel") self.log = logging.getLogger(__name__) @@ -82,7 +84,7 @@ def register_model(self, cls, name=None): if self.models.get(name): return self.models[name] - if hasattr(cls, '_meta'): + if hasattr(cls, "_meta"): if cls._meta.workspace != self: msg = "{0} is already registered with a different workspace" raise ValueError(msg.format(cls.__name__)) @@ -92,14 +94,17 @@ def register_model(self, cls, name=None): return cls metaclass = models.DeclarativeMetaclass - attrs = dict(cls.__dict__, **{ - '__workspace__': self, - }) - if not attrs.get('__module__'): - attrs['__module__'] == __name__ - - if attrs.get('__dict__'): - del attrs['__dict__'] + attrs = dict( + cls.__dict__, + **{ + "__workspace__": self, + } + ) + if not attrs.get("__module__"): + attrs["__module__"] == __name__ + + if attrs.get("__dict__"): + del attrs["__dict__"] # the cloned model must subclass the original so as not to break # type-checking operations @@ -107,8 +112,7 @@ def register_model(self, cls, name=None): # parents must also be registered with the workspace for base in cls.__bases__: - if issubclass(base, models.GitModel) and \ - not hasattr(base, '_meta'): + if issubclass(base, models.GitModel) and not hasattr(base, "_meta"): base = self.models.get(name) or self.register_model(base) bases.append(base) @@ -128,8 +132,7 @@ def import_models(self, path_or_module): for name in dir(mod): item = getattr(mod, name) - if isinstance(item, type) and \ - issubclass(item, models.GitModel): + if isinstance(item, type) and issubclass(item, models.GitModel): self.register_model(item, name) return self.models @@ -148,9 +151,9 @@ def create_branch(self, name, start_point=None): start_point_ref = self.repo.lookup_reference(start_point) if start_point_ref.type != pygit2.GIT_OBJ_COMMIT: - raise ValueError('Given reference must point to a commit') + raise ValueError("Given reference must point to a commit") - branch_ref = 'refs/heads/{}'.format(name) + branch_ref = "refs/heads/{}".format(name) self.repo.create_reference(branch_ref, start_point_ref.target) def get_branch(self, ref_name): @@ -161,7 +164,7 @@ def set_branch(self, name): Sets the current head ref to the given branch name """ # make sure the branch is a valid head ref - ref = 'refs/heads/{}'.format(name) + ref = "refs/heads/{}".format(name) self.repo.lookup_reference(ref) self.update_index(ref) @@ -181,7 +184,7 @@ def update_index(self, treeish): tree = utils.treeish_to_tree(self.repo, treeish) - if treeish.startswith('refs/heads'): + if treeish.startswith("refs/heads"): # if treeish is a head ref, update head self.head = treeish else: @@ -223,7 +226,7 @@ def add_blob(self, path, content, mode=pygit2.GIT_FILEMODE_BLOB): return blob @contextmanager - def commit_on_success(self, message='', author=None, committer=None): + def commit_on_success(self, message="", author=None, committer=None): """ A context manager that allows you to wrap a block of changes and commit those changes if no exceptions occur. This also ensures that the @@ -232,8 +235,10 @@ def commit_on_success(self, message='', author=None, committer=None): """ # ensure a clean state if self.has_changes(): - msg = "Repository has pending changes. Cannot auto-commit until "\ - "pending changes have been comitted." + msg = ( + "Repository has pending changes. Cannot auto-commit until " + "pending changes have been comitted." + ) raise exceptions.RepositoryError(msg) yield @@ -258,18 +263,20 @@ def has_changes(self): # if there are no changes, so we check the iterable length instead. return len(tuple(self.diff())) > 0 - def commit(self, message='', author=None, committer=None): + def commit(self, message="", author=None, committer=None): """Commits the current tree to the current branch.""" if not self.has_changes(): return None parents = [] if self.branch: parents = [self.branch.commit.oid] - return self.create_commit(self.head, self.index, message, author, - committer, parents) + return self.create_commit( + self.head, self.index, message, author, committer, parents + ) - def create_commit(self, ref, tree, message='', author=None, - committer=None, parents=None): + def create_commit( + self, ref, tree, message="", author=None, committer=None, parents=None + ): """ Create a commit with the given ref, tree, and message. If parent commits are not given, the commit pointed to by the given ref is used @@ -281,10 +288,9 @@ def create_commit(self, ref, tree, message='', author=None, if not committer: committer = author - default_offset = self.config.get('DEFAULT_TZ_OFFSET', None) + default_offset = self.config.get("DEFAULT_TZ_OFFSET", None) author = utils.make_signature(*author, default_offset=default_offset) - committer = utils.make_signature(*committer, - default_offset=default_offset) + committer = utils.make_signature(*committer, default_offset=default_offset) if parents is None: try: @@ -297,12 +303,13 @@ def create_commit(self, ref, tree, message='', author=None, # FIXME: create_commit updates the HEAD ref. HEAD isn't used in # gitmodel, however it would be prudent to make sure it doesn't # get changed. Possibly need to just restore it after the commit - return self.repo.create_commit(ref, author, committer, message, - tree.oid, parents) + return self.repo.create_commit( + ref, author, committer, message, tree.oid, parents + ) def walk(self, sort=pygit2.GIT_SORT_TIME): """Iterate through commits on the current branch""" - #NEEDS-TEST + # NEEDS-TEST for commit in self.repo.walk(self.branch.oid, sort): yield commit @@ -315,20 +322,22 @@ def lock(self, id): start_time = time() while self.locked(id): if time() - start_time > self.config.LOCK_WAIT_TIMEOUT: - msg = ("Lock wait timeout exceeded while trying to acquire " - "lock '{}' on {}").format(id, self.path) + msg = ( + "Lock wait timeout exceeded while trying to acquire " + "lock '{}' on {}" + ).format(id, self.path) raise exceptions.LockWaitTimeoutExceeded(msg) time.sleep(self.config.LOCK_WAIT_INTERVAL) # The blob itself is not important, just the fact that the ref exists - emptyblob = self.create_blob('') - ref = self.repo.create_reference('refs/locks/{}'.format(id), emptyblob) + emptyblob = self.create_blob("") + ref = self.repo.create_reference("refs/locks/{}".format(id), emptyblob) yield ref.delete() def locked(self, id): try: - self.repo.lookup_reference('refs/locks/{}'.format(id)) + self.repo.lookup_reference("refs/locks/{}".format(id)) except KeyError: return False return True @@ -344,7 +353,7 @@ def sync_repo_index(self, checkout=True): This function acquires a workspace-level INDEX lock. """ - with self.lock('INDEX'): + with self.lock("INDEX"): self.repo.index.read_tree(self.index.oid) if checkout: self.repo.checkout() @@ -355,6 +364,7 @@ class Branch(object): A representation of a git branch that provides quick access to the ref, commit, and commit tree. """ + def __init__(self, repo, ref_name): try: self.ref = repo.lookup_reference(ref_name) diff --git a/run-tests.py b/run-tests.py index 163dac9..823a4cd 100755 --- a/run-tests.py +++ b/run-tests.py @@ -6,5 +6,5 @@ from gitmodel.test import main -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/setup.py b/setup.py index fdda3b0..33ab834 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,15 @@ from distutils.core import setup setup( - name='python-gitmodel', - version='0.1dev', - test_suite='gitmodel.test', + name="python-gitmodel", + version="0.1dev", + test_suite="gitmodel.test", packages=[ - 'gitmodel', - 'gitmodel.serializers', - 'gitmodel.utils', + "gitmodel", + "gitmodel.serializers", + "gitmodel.utils", ], - install_requires=['pygit2', 'python-dateutil', 'decorator'], - license='Creative Commons Attribution-Noncommercial-Share Alike license', - long_description=open('README.rst').read(), + install_requires=["pygit2", "python-dateutil", "decorator"], + license="Creative Commons Attribution-Noncommercial-Share Alike license", + long_description=open("README.rst").read(), )