def _validate_entity_data(self, entity_type, data):
        if "id" in data or "type" in data:
            raise ShotgunError("Can't set id or type on create or update")

        self._validate_entity_fields(entity_type, data.keys())

        for field, item in data.items():
            
            if item is None:
                # none is always ok
                continue
            
            field_info = self._schema[entity_type][field]

            if field_info["data_type"]["value"] == "multi_entity":
                if not isinstance(item, list):
                    raise ShotgunError("%s.%s is of type multi_entity, but data %s is not a list" % (entity_type, field, item))
                elif item and any(not isinstance(sub_item, dict) for sub_item in item):
                    raise ShotgunError("%s.%s is of type multi_entity, but data %s contains a non-dictionary" % (entity_type, field, item))
                elif item and any("id" not in sub_item or "type" not in sub_item for sub_item in item):
                    raise ShotgunError("%s.%s is of type multi-entity, but an item in data %s does not contain 'type' and 'id'" % (entity_type, field, item))
                elif item and any(sub_item["type"] not in field_info["properties"]["valid_types"]["value"] for sub_item in item):
                    raise ShotgunError("%s.%s is of multi-type entity, but an item in data %s has an invalid type (expected one of %s)" % (entity_type, field, item, field_info["properties"]["valid_types"]["value"]))
                
                
            elif field_info["data_type"]["value"] == "entity":
                if not isinstance(item, dict):
                    raise ShotgunError("%s.%s is of type entity, but data %s is not a dictionary" % (entity_type, field, item))
                elif "id" not in item or "type" not in item:
                    raise ShotgunError("%s.%s is of type entity, but data %s does not contain 'type' and 'id'" % (entity_type, field, item))
                #elif item["type"] not in field_info["properties"]["valid_types"]["value"]:
                #    raise ShotgunError("%s.%s is of type entity, but data %s has an invalid type (expected one of %s)" % (entity_type, field, item, field_info["properties"]["valid_types"]["value"]))

            else:
                try:
                    sg_type = field_info["data_type"]["value"]
                    python_type = {"number": int,
                                   "float": float,
                                   "checkbox": bool,
                                   "text": basestring,
                                   "serializable": dict,
                                   "date": datetime.date,
                                   "date_time": datetime.datetime,
                                   "url": dict}[sg_type]
                except KeyError:
                    raise ShotgunError("Field %s.%s: Handling for Shotgun type %s is not implemented" % (entity_type, field, sg_type)) 
                
                if not isinstance(item, python_type):
                    raise ShotgunError("%s.%s is of type %s, but data %s is not of type %s" % (entity_type, field, type(item), sg_type, python_type))
 def _validate_entity_fields(self, entity_type, fields):
     self._validate_entity_type(entity_type)
     if fields is not None:
         valid_fields = set(self._schema[entity_type].keys())
         for field in fields:
             try:
                 field2, entity_type2, field3 = field.split(".", 2)
                 self._validate_entity_fields(entity_type2, [field3])
             except ValueError:
                 if field not in valid_fields and field not in ("type", "id"):
                     raise ShotgunError("%s is not a valid field for entity %s" % (field, entity_type))
 def _row_matches_filters(self, entity_type, row, filters, filter_operator, retired_only):
             
     if retired_only and not row["__retired"] or not retired_only and row["__retired"]:
         # ignore retired rows unless the retired_only flag is set
         # ignore live rows if the retired_only flag is set
         return False
     elif filter_operator in ("all", None):
         return all(self._row_matches_filter(entity_type, row, filter) for filter in filters)
     elif filter_operator == "any":
         return any(self._row_matches_filter(entity_type, row, filter) for filter in filters)
     else:
         raise ShotgunError("%s is not a valid filter operator" % filter_operator)
 def batch(self, requests):
     results = []
     for request in requests:
         if request["request_type"] == "create":
             results.append(self.create(request["entity_type"], request["data"]))
         elif request["request_type"] == "update":
             # note: Shotgun.update returns a list of a single item
             results.append(self.update(request["entity_type"], request["entity_id"], request["data"])[0])
         elif request["request_type"] == "delete":
             results.append(self.delete(request["entity_type"], request["entity_id"]))
         else:
             raise ShotgunError("Invalid request type %s in request %s" % (request["request_type"], request))
     return results
    def _get_field_from_row(self, entity_type, row, field):
        # split dotted form fields        
        try:
            # is it something like sg_sequence.Sequence.code ?
            field2, entity_type2, field3 = field.split(".", 2)
            
            if field2 in row:
                
                field_value = row[field2]
                
                # all deep links need to be link fields
                if not isinstance(field_value, dict):                    
                    raise ShotgunError("Invalid deep query field %s.%s" % (entity_type, field))
                    
                # make sure that types in the query match type in the linked field
                if entity_type2 != field_value["type"]:
                    raise ShotgunError("Deep query field %s.%s does not match type "
                                       "with data %s" % (entity_type, field, field_value))
                     
                # ok so looks like the value is an entity link
                # e.g. db contains: {"sg_sequence": {"type":"Sequence", "id": 123 } }
                linked_row = self._db[ field_value["type"] ][ field_value["id"] ]
                if field3 in linked_row:
                    return linked_row[field3]
                else:
                    return None

            else:
                # sg returns none for unknown stuff
                return None
        
        except ValueError:
            # this is not a deep-linked field - just something like "code"
            if field in row:
                return row[field]
            else:
                # sg returns none for unknown stuff
                return None
 def _row_matches_filter(self, entity_type, row, filter):
     
     
     try:
         field, operator, rval = filter
     except ValueError:
         raise ShotgunError("Filters must be in the form [lval, operator, rval]")
     lval = self._get_field_from_row(entity_type, row, field)
     
     field_type = self._get_field_type(entity_type, field)
     
     # if we're operating on an entity, we'll need to grab the name from the lval's row
     if field_type == "entity":
         lval_row = self._db[lval["type"]][lval["id"]]
         if "name" in lval_row:
             lval["name"] = lval_row["name"]
         elif "code" in lval_row:
             lval["name"] = lval_row["code"]
     return self._compare(field_type, lval, operator, rval)
 def _validate_entity_exists(self, entity_type, entity_id):
     if entity_id not in self._db[entity_type]:
         raise ShotgunError("No entity of type %s exists with id %s" % (entity_type, entity_id))
    def _compare(self, field_type, lval, operator, rval):
        if field_type == "checkbox":
            if operator == "is":
                return lval == rval
            elif operator == "is_not":
                return lval != rval
        elif field_type in ("float", "number", "date", "date_time"):
            if operator == "is":
                return lval == rval
            elif operator == "is_not":
                return lval != rval
            elif operator == "less_than":
                return lval < rval
            elif operator == "greater_than":
                return lval > rval
            elif operator == "between":
                return lval >= rval[0] and lval <= rval[1]
            elif operator == "not_between":
                return lval < rval[0] or lval > rval[1]
            elif operator == "in":
                return lval in rval
        elif field_type == "list":
            if operator == "is":
                return lval == rval
            elif operator == "is_not":
                return lval != rval
            elif operator == "in":
                return lval in rval
            elif operator == "not_in":
                return lval not in rval
        elif field_type == "entity_type":
            if operator == "is":
                return lval == rval
        elif field_type == "text":
            if operator == "is":
                return lval == rval
            elif operator == "is_not":
                return lval != rval
            elif operator == "in":
                return lval in rval            
            elif operator == "contains":
                return lval in rval
            elif operator == "not_contains":
                return lval not in rval
            elif operator == "starts_with":
                return lval.startswith(rval)
            elif operator == "ends_with":
                return lval.endswith(rval)
        elif field_type == "entity":
            if operator == "is":
                return lval["type"] == rval["type"] and lval["id"] == rval["id"]
            elif operator == "is_not":
                return lval["type"] != rval["type"] or lval["id"] != rval["id"]
            elif operator == "in":
                return all((lval["type"] == sub_rval["type"] and lval["id"] == sub_rval["id"]) for sub_rval in rval)
            elif operator == "type_is":
                return lval["type"] == rval
            elif operator == "type_is_not":
                return lval["type"] != rval
            elif operator == "name_contains":
                return rval in lval["name"]
            elif operator == "name_not_contains":
                return rval not in lval["name"]
            elif operator == "name_starts_with":
                return lval["name"].startswith(rval)
            elif operator == "name_ends_with":
                return lval["name"].endswith(rval)
        elif field_type == "multi_entity":
            if operator == "is":
                return rval["id"] in (sub_lval["id"] for sub_lval in lval)
            elif operator == "is_not":
                return rval["id"] not in (sub_lval["id"] for sub_lval in lval)

        raise ShotgunError("The %s operator is not supported on the %s type" % (operator, field_type))
 def _validate_entity_type(self, entity_type):
     if entity_type not in self._schema:
         raise ShotgunError("%s is not a valid entity" % entity_type)