예제 #1
0
    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
        )
예제 #2
0
    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}'")
예제 #3
0
    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]
예제 #4
0
    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)