Source code for swimlane.core.resources.record

from functools import total_ordering

import pendulum
import six

from swimlane.core.resources.base import APIResource
from swimlane.exceptions import UnknownField, ValidationError


@total_ordering
[docs]class Record(APIResource): """A single Swimlane Record instance Attributes: id (str): Full Record ID tracking_id (str): Record tracking ID created (pendulum.Pendulum): Pendulum datetime for Record created date modified (pendulum.Pendulum): Pendulum datetime for Record last modified date is_new (bool): True if Record does not yet exist on server. Other values may be temporarily None if True """ _type = 'Core.Models.Record.Record, Core' def __init__(self, app, raw): super(Record, self).__init__(app._swimlane, raw) self._app = app self.is_new = self._raw.get('isNew', False) # Protect against creation from generic raw data not yet containing server-generated values if self.is_new: self.id = self.tracking_id = self.created = self.modified = None else: self.id = self._raw['id'] # Combine app acronym + trackingId instead of using trackingFull raw # for guaranteed value (not available through report results) self.tracking_id = '-'.join([ self._app.acronym, str(int(self._raw['trackingId'])) ]) self.created = pendulum.parse(self._raw['createdDate']) self.modified = pendulum.parse(self._raw['modifiedDate']) self._fields = {} self.__premap_fields() def __str__(self): if self.is_new: return '{} - New'.format(self._app.acronym) return str(self.tracking_id) def __setitem__(self, field_name, value): self.get_field(field_name).set_python(value) def __getitem__(self, field_name): return self.get_field(field_name).get_python() def __delitem__(self, field_name): self[field_name] = None def __iter__(self): for field_name, field in six.iteritems(self._fields): yield field_name, field.get_python() def __hash__(self): return hash((self.id, self._app)) def __eq__(self, other): return isinstance(other, self.__class__) and hash(self) == hash(other) def __lt__(self, other): if not isinstance(other, self.__class__): raise TypeError("Comparisons not supported between instances of '{}' and '{}'".format( other.__class__.__name__, self.__class__.__name__ )) tracking_number_self = int(self.tracking_id.split('-')[1]) tracking_number_other = int(other.tracking_id.split('-')[1]) return (self._app.name, tracking_number_self) < (other._app.name, tracking_number_other) def __premap_fields(self): """Build field instances using field definitions in app manifest Map raw record field data into appropriate field instances with their correct respective types """ # Circular imports from swimlane.core.fields import resolve_field_class for field_definition in self._app._raw['fields']: field_class = resolve_field_class(field_definition) field_instance = field_class(field_definition['name'], self) value = self._raw['values'].get(field_instance.id) field_instance.set_swimlane(value) self._fields[field_instance.name] = field_instance
[docs] def get_field(self, field_name): """Get field instance used to get, set, and serialize internal field value Returns: Field: Requested field instance Raises: UnknownField: Raised if `field_name` not found in parent App """ try: return self._fields[field_name] except KeyError: raise UnknownField(self._app, field_name, self._fields.keys())
[docs] def validate(self): """Explicitly validate field data Notes: Called automatically during save call before sending data to server Raises: ValidationError: If any fields fail validation """ for field in (_field for _field in six.itervalues(self._fields) if _field.required): if field.get_swimlane() is None: raise ValidationError(self, 'Required field "{}" is not set'.format(field.name))
[docs] def save(self): """Persist record changes on Swimlane server Updates internal raw data with response content from server to guarantee calculated field values match values on server Raises: ValidationError: If any fields fail validation """ if self.is_new: method = 'post' else: method = 'put' self.validate() response = self._swimlane.request( method, 'app/{}/record'.format(self._app.id), json=self._raw ) # Reinitialize record with new raw content returned from server to update any calculated fields self.__init__(self._app, response.json())
[docs]def record_factory(app, fields=None): """Return a temporary Record instance to be used for field validation and value parsing Args: app (App): Target App to create a transient Record instance for fields (dict): Optional dict of fields and values to set on new Record instance before returning Returns: Record: Unsaved Record instance to be used for validation, creation, etc. """ # pylint: disable=line-too-long record = Record(app, { '$type': Record._type, 'isNew': True, 'applicationId': app.id, 'comments': { '$type': 'System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Collections.Generic.List`1[[Core.Models.Record.Comments, Core]], mscorlib]], mscorlib' }, 'values': { '$type': 'System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib' } }) fields = fields or {} for name, value in six.iteritems(fields): record[name] = value return record