def handle_single_key(self, key: str, cf_lock: LockedFieldsJSON) -> None: if isinstance(cf_lock, int) and cf_lock <= 0: raise InvalidFormat(f"The position of key {key} must be >= 0") key_type = get_key_type(key) if key_type in (KEY_TYPE.FQID, KEY_TYPE.FQFIELD) and not isinstance(cf_lock, int): raise InvalidFormat( "CollectionFieldLocks can only be used with collectionfields") if key_type == KEY_TYPE.FQID: self.locked_fqids[key] = cast(int, cf_lock) elif key_type == KEY_TYPE.FQFIELD: self.locked_fqfields[key] = cast(int, cf_lock) elif key_type == KEY_TYPE.COLLECTIONFIELD: if isinstance(cf_lock, int): self.locked_collectionfields[key] = cf_lock else: # build self validating data class try: self.locked_collectionfields[key] = from_dict( CollectionFieldLockWithFilter, cf_lock, ) except (TypeError, MissingValueError, UnionMatchError) as e: raise BadCodingError("Invalid data to initialize class\n" + str(e))
def reserve_next_ids(self, collection: str, amount: int) -> List[int]: if amount <= 0: raise InvalidFormat(f"amount must be >= 1, not {amount}") if len(collection) > COLLECTION_MAX_LEN or not collection: raise InvalidFormat( f"collection length must be between 1 and {COLLECTION_MAX_LEN}" ) query = "select id from id_sequences where collection=%s" arguments = [collection] low_id = self.connection.query_single_value(query, arguments) if low_id is None: low_id = 1 high_id = low_id + amount # high id not included in the result statement = "update id_sequences set id=%s where collection=%s" statement = dedent( f"""\ insert into id_sequences (collection, id) values (%s, %s) on conflict(collection) do update set id=excluded.id;""" ) arguments = [collection, high_id] self.connection.execute(statement, arguments) return list(range(low_id, high_id))
def test_union_of_lists_with_fqid_and_fqfield_fail(): data = MagicMock() with patch("shared.util.self_validating_dataclass.assert_is_fqid" ) as assert_is_fqid, patch( "shared.util.self_validating_dataclass.assert_is_fqfield" ) as assert_is_fqfield: assert_is_fqid.side_effect = InvalidFormat("msg") assert_is_fqfield.side_effect = InvalidFormat("msg") with pytest.raises(InvalidFormat): D([data]) assert_is_fqid.assert_called_with(data) assert_is_fqfield.assert_called_with(data)
def insert_modified_collectionfields_into_db( self, modified_collectionfields, position ): # insert into db, updating all existing fields with position, returning ids value_placeholders = [] arguments: List[Any] = [] for collectionfield in modified_collectionfields: if len(collectionfield) > COLLECTIONFIELD_MAX_LEN: raise InvalidFormat( f"Collection field {collectionfield} is too long " + "(max: {COLLECTIONFIELD_MAX_LEN})" ) arguments.extend( ( collectionfield, position, ) ) value_placeholders.append("(%s, %s)") values = ",".join(value_placeholders) statement = dedent( f"""\ insert into collectionfields (collectionfield, position) values {values} on conflict(collectionfield) do update set position=excluded.position returning id""" ) return self.connection.query_list_of_single_values(statement, arguments)
def build_filter_str(self, filter: Filter, arguments: List[str], table_alias="") -> str: if isinstance(filter, Not): filter_str = self.build_filter_str(filter.not_filter, arguments, table_alias) return f"NOT ({filter_str})" elif isinstance(filter, Or): return " OR ".join( f"({self.build_filter_str(part, arguments, table_alias)})" for part in filter.or_filter) elif isinstance(filter, And): return " AND ".join( f"({self.build_filter_str(part, arguments, table_alias)})" for part in filter.and_filter) elif isinstance(filter, FilterOperator): if table_alias: table_alias += "." if filter.value is None: if filter.operator not in ("=", "!="): raise InvalidFormat( "You can only compare to None with = or !=") operator = filter.operator[::-1].replace("=", "IS").replace( "!", " NOT") condition = f"{table_alias}data->>%s {operator} NULL" arguments += [filter.field] else: condition = f"{table_alias}data->>%s {filter.operator} %s::text" arguments += [filter.field, filter.value] return condition else: raise BadCodingError("Invalid filter type")
def __init__(self, fqid: str, fields: Dict[str, JSON]) -> None: super().__init__(fqid) if len(fields) <= 0: raise InvalidFormat("No fields are given") for field in fields.keys(): assert_is_field(field) assert_no_special_field(field) self.fields = fields
def test_union_of_lists_with_fqid_fail(): fqid: Any = MagicMock(name="fqid") with patch("shared.util.self_validating_dataclass.assert_is_fqid" ) as assert_is_fqid: assert_is_fqid.side_effect = InvalidFormat("msg") with pytest.raises(InvalidFormat): C([fqid]) assert_is_fqid.assert_called_with(fqid)
def reserve_next_ids(self, collection: str, amount: int) -> List[int]: if amount <= 0: raise InvalidFormat(f"amount must be >= 1, not {amount}") if len(collection) > COLLECTION_MAX_LEN or not collection: raise InvalidFormat( f"collection length must be between 1 and {COLLECTION_MAX_LEN}" ) statement = dedent( """\ insert into id_sequences (collection, id) values (%s, %s) on conflict(collection) do update set id=id_sequences.id + excluded.id - 1 returning id;""" ) arguments = [collection, amount + 1] new_max_id = self.connection.query_single_value(statement, arguments) return list(range(new_max_id - amount, new_max_id))
def handle_single_key(self, key: str, position: int) -> None: if position <= 0: raise InvalidFormat(f"The position of key {key} must be >= 0") key_type = get_key_type(key) if key_type == KEY_TYPE.FQID: self.locked_fqids[key] = position elif key_type == KEY_TYPE.FQFIELD: self.locked_fqfields[key] = position elif key_type == KEY_TYPE.COLLECTIONFIELD: self.locked_collectionfields[key] = position
def test_union_of_lists_with_fqid_and_fqfield_success_2(): fqfield = MagicMock() with patch("shared.util.self_validating_dataclass.assert_is_fqid" ) as assert_is_fqid, patch( "shared.util.self_validating_dataclass.assert_is_fqfield" ) as assert_is_fqfield: assert_is_fqid.side_effect = InvalidFormat("msg") D([fqfield]) assert_is_fqid.assert_called_with(fqfield) assert_is_fqfield.assert_called_with(fqfield)
def __init__( self, events: List[BaseRequestEvent], information: JSON, user_id: int, locked_fields: Dict[str, LockedFieldsJSON], ) -> None: self.events = events self.information = information self.user_id = user_id if len(events) <= 0: raise InvalidFormat("No events were given") self.parse_locked_fields(locked_fields)
def calculate_updated_fields(self, model: Model) -> Tuple[Model, List[str]]: all_field_keys = list(self.add.keys()) + list(self.remove.keys()) for field in all_field_keys: db_list = model.get(field, []) if not isinstance(db_list, list): raise InvalidFormat( f"Field {field} on model {self.fqid} is not a list, but of type" + str(type(db_list)) ) for el in db_list: if not isinstance(el, (str, int)): raise InvalidFormat( f"Field {field} on model {self.fqid} contains invalid entry " f"for list update (of type {type(el)}): {el}" ) updated_fields = {} deleted_fields = [] for field, value in self.add.items(): # Iterate over list and remove all entries from value which are already # in the list. If adding multiple entries, this reduces the runtime needed. # When a huge amount of data is added, the normal update should be used # instead. db_list = model.get(field, []) for el in db_list: if el in value: value.remove(el) updated_fields[field] = db_list + value for field, value in self.remove.items(): if field in model: db_list = model.get(field) updated_list = [el for el in db_list if el not in value] updated_fields[field] = updated_list else: deleted_fields.append(field) return updated_fields, deleted_fields
def insert_events( self, events: List[BaseDbEvent], information: JSON, user_id: int ) -> int: if not events: raise BadCodingError() position = self.create_position(information, user_id) for event in events: if len(event.fqid) > FQID_MAX_LEN: raise InvalidFormat( f"fqid {event.fqid} is too long (max: {FQID_MAX_LEN})" ) self.insert_event(event, position) return position
def assert_no_special_field(field: Any) -> None: if is_reserved_field(field): raise InvalidFormat(f"Field {field} is reserved")