def execute_batch(self, command_type, data_type, default_config, batch): """Execute a batch of instructions of the same type.""" # Check the commands for the correct authority for command in batch: if data_type in (DataType.USER, DataType.GROUP): self._assert_authority( "authority", command.body.attributes["authority"] ) self._assert_authority( "query authority", command.body.query["authority"] ) # Get a handler for this action handler = self.handlers.get((command_type, data_type), None) if handler is None: raise UnsupportedOperationError( f"No implementation for {command_type.value} {data_type.value}" ) # Do it return handler.execute( batch, effective_user_id=self.effective_user_id, **default_config )
def _check_upsert_queries(batch, expected_keys): """ Validate the query for each command in `batch`. This method allows you to assert that: * Only the expected fields are present in the query * The value in the query matches the value in the attributes Our current upserting method requires the values we create to conflict with existing records to work. Therefore if we can't try to update with different values than those we create with. The methods are also hard coded to expect certain values which we can mandate here. :param batch: A collection of command objects :param expected_keys: The list of valid keys that are allowed in command queries :raise UnsupportedOperationError: if any of the conditions above are not satisfied """ for command in batch: query = command.body.query # This is technically overkill as the schema should make sure we # can't receive queries we aren't expecting if set(query.keys()) != set(expected_keys): raise UnsupportedOperationError( f"Upserting by query fields '{query.keys()}' is not supported" ) # Checking that the values are the same is a bit more important, as # this happens post schema, and could therefore be wrong for key, expected in query.items(): if command.body.attributes[key] != expected: raise UnsupportedOperationError( "Upserting different values to the query is not supported. " f"Different value found in key '{key}'")
def execute( # pylint: disable=arguments-differ self, batch, on_duplicate="continue", **_): """ Execute GroupMembershipCreateAction. :param on_duplicate: Specify behavior when a record already exists. The default is "continue" """ if on_duplicate != "continue": raise UnsupportedOperationError( "Create modes other than 'continue' have not been implemented") values = [{ "user_id": command.body.member.id, "group_id": command.body.group.id } for command in batch] stmt = insert(GroupMembership).values(values) # This update doesn't change the row, but it does count as it being # 'updated' which means we can get the values in the "RETURNING" # clause and do the select in one go stmt = stmt.on_conflict_do_update( index_elements=["user_id", "group_id"], set_={"user_id": stmt.excluded.user_id}, ) stmt = stmt.returning(GroupMembership.id) try: membership_rows = self._execute_statement(stmt).fetchall() except IntegrityError as err: # https://www.postgresql.org/docs/9.1/errcodes-appendix.html # 23503 = foreign_key_violation if err.orig.pgcode == "23503": raise ConflictingDataError( "Cannot insert group membership as either the user or " f"group specified does not exist: {err.params}") from err raise return [Report(id_) for (id_, ) in membership_rows]
def body(self): """Get the appropriate body object for this command. :return: A different class depending on `DataType` and `data_classes` :raise UnsupportedOperationError: If no type can be found for the given `DataType` """ body = super().body data_type = DataType(body["data"]["type"]) try: # pylint: disable=unsubscriptable-object # It's subscriptable if child classes have set it to a dict class_ = self.data_classes[data_type] except KeyError: raise UnsupportedOperationError("Invalid action on data type") # Don't validate this all the time, we did it on the way in. If we have # mutated it it might not match the schema we except from clients, but # it's still valid return class_(body, validate=False)