示例#1
0
    def _parse_question(self, question):
        """Parse a question in the Google Cloud Pub/Sub or Google Cloud Run format.

        :param dict|Message question:
        :return (dict, str, bool, packaging.version.Version|None):
        """
        try:
            # Parse question directly from Pub/Sub or Dataflow.
            data = json.loads(question.data.decode())

            # Acknowledge it if it's directly from Pub/Sub
            if hasattr(question, "ack"):
                question.ack()

        except Exception:
            # Parse question from Google Cloud Run.
            data = json.loads(base64.b64decode(question["data"]).decode("utf-8").strip())

        logger.info("%r received a question.", self)

        question_uuid = get_nested_attribute(question, "attributes.question_uuid")
        forward_logs = bool(int(get_nested_attribute(question, "attributes.forward_logs")))

        try:
            parent_sdk_version = get_nested_attribute(question, "attributes.octue_sdk_version")
        except AttributeError:
            parent_sdk_version = None

        if parent_sdk_version:
            parent_sdk_version = packaging.version.parse(parent_sdk_version)

        return data, question_uuid, forward_logs, parent_sdk_version
示例#2
0
    def satisfies(self, raise_error_if_filter_is_invalid=True, **kwargs):
        """Check that the instance satisfies the given filter for the given filter value. The filter should be provided
        as a single keyword argument such as `name__first__equals="Joe"`

        :param bool raise_error_if_filter_is_invalid:
        :param {str: any} kwargs: a single keyword argument whose key is the name of the filter and whose value is the value to filter for
        :raise ValueError: if more than one keyword argument is received
        :raise AttributeError: if the instance doesn't have the attribute specified in the filter name (only if `raise_error_if_filter_is_invalid` is `True`)
        :raise octue.exceptions.InvalidInputException: if the filter name is invalid or there are no filters for the type of attribute specified in the filter name
        :return mixed:
        """
        if len(kwargs) != 1:
            raise ValueError(f"The satisfies method only takes one keyword argument; received {kwargs!r}.")

        filter_name, filter_value = list(kwargs.items())[0]

        try:
            attribute_name, filter_action = self._split_filter_name(filter_name)

            try:
                attribute = get_nested_attribute(self, attribute_name)

            except AttributeError as error:
                if raise_error_if_filter_is_invalid:
                    raise error

                return False

            filter_ = self._get_filter(attribute, filter_action)
            return filter_(attribute, filter_value)

        except exceptions.InvalidInputException as error:
            return self._try_equals_filter_shortcut(filter_name, filter_value, error)
示例#3
0
    def order_by(self, attribute_name, check_start_value=None, check_constant_increment=None, reverse=False):
        """Order the `Filterable`s in the container by an attribute with the given name, returning them as a new
        `FilterList` regardless of the type of filter container begun with (`FilterSet`s and `FilterDict`s are
        inherently orderless).

        :param str attribute_name: name of attribute (optionally nested) to order by e.g. "a", "a.b", "a.b.c"
        :param any check_start_value: if provided, check that the first item in the ordered container has the given start value for the attribute ordered by
        :param int|float|None check_constant_increment: if given, check that the ordered-by attribute of each of the items in the ordered container increases by the given value when progressing along the sequence
        :param bool reverse: if True, reverse the ordering
        :raise octue.exceptions.InvalidInputException: if an attribute with the given name doesn't exist on any of the container's members
        :return FilterList:
        """
        attribute_name = ".".join(attribute_name.split("__"))

        try:
            results = FilterList(
                sorted(self, key=lambda item: get_nested_attribute(item, attribute_name), reverse=reverse)
            )

        except AttributeError:
            raise exceptions.InvalidInputException(
                f"An attribute named {attribute_name!r} does not exist on one or more members of {self!r}."
            )

        if check_start_value is not None:
            if get_nested_attribute(results[0], attribute_name) != check_start_value:
                raise exceptions.BrokenSequenceException(
                    f"The attribute {attribute_name!r} of the first item of {results!r} does equal the given start "
                    f"value {check_start_value!r}."
                )

        if check_constant_increment is not None:
            required_increment = check_constant_increment

            for i in range(len(results) - 1):
                actual_increment = get_nested_attribute(results[i + 1], attribute_name) - get_nested_attribute(
                    results[i], attribute_name
                )

                if actual_increment != required_increment:
                    raise exceptions.BrokenSequenceException(
                        f"The attributes {attribute_name!r} of the items of {results!r} do not increase by a constant "
                        f"increment of {required_increment}."
                    )

        return results
示例#4
0
    def order_by(self, attribute_name, reverse=False):
        """Order the instance by the given attribute_name, returning the instance's elements as a new FilterList.

        :param str attribute_name: name of attribute (optionally nested) to order by e.g. "a", "a.b", "a.b.c"
        :param bool reverse: if True, reverse the ordering
        :raise octue.exceptions.InvalidInputException: if an attribute with the given name doesn't exist on any of the FilterDict's values
        :return FilterList:
        """
        try:
            return FilterList(
                sorted(self.items(), key=lambda item: get_nested_attribute(item[1], attribute_name), reverse=reverse)
            )

        except AttributeError:
            raise exceptions.InvalidInputException(
                f"An attribute named {attribute_name!r} does not exist on one or more members of {self!r}."
            )
示例#5
0
def answer_question(question, project_name):
    """Answer a question sent to an app deployed in Google Cloud.

    :param dict|tuple|apache_beam.io.gcp.pubsub.PubsubMessage question:
    :param str project_name:
    :return None:
    """
    service_configuration, app_configuration = load_service_and_app_configuration(
        DEFAULT_SERVICE_CONFIGURATION_PATH)
    service_id = os.environ.get(
        "SERVICE_ID") or service_configuration.service_id

    service = Service(
        service_id=service_id,
        backend=GCPPubSubBackend(project_name=project_name),
    )

    question_uuid = get_nested_attribute(question, "attributes.question_uuid")
    answer_topic = service.instantiate_answer_topic(question_uuid)

    try:
        service.name = service_configuration.name

        runner = Runner(
            app_src=service_configuration.app_source_path,
            twine=service_configuration.twine_path,
            configuration_values=app_configuration.configuration_values,
            configuration_manifest=app_configuration.configuration_manifest,
            children=app_configuration.children,
            output_location=app_configuration.output_location,
            project_name=project_name,
            service_id=service_id,
        )

        service.run_function = functools.partial(runner.run)

        service.answer(question, answer_topic=answer_topic)
        logger.info(
            "Analysis successfully run and response sent for question %r.",
            question_uuid)

    # Forward any errors in the deployment configuration (errors in the analysis are already forwarded by the service).
    except BaseException as error:  # noqa
        service.send_exception(topic=answer_topic)
        logger.exception(error)
示例#6
0
    def _try_equals_filter_shortcut(self, filter_name, filter_value, error):
        """Try to use the equals filter shortcut e.g. `a=7` instead of `a__equals=7` or `a__b=7` instead of
        `a__b__equals=7`. Raise the error if this is not applicable (e.g. the filter name is just wrong).

        :param str filter_name:
        :param mixed filter_value:
        :param Exception error:  the error to raise if the equals filter shortcut is not applicable
        :raise Exception: if the equals filter shortcut is not applicable
        :return mixed:
        """
        possible_attribute_name = ".".join(filter_name.split("__"))

        if has_nested_attribute(self, possible_attribute_name):
            attribute = get_nested_attribute(self, possible_attribute_name)
            filter_ = self._get_filter(attribute, "equals")
            return filter_(attribute, filter_value)

        raise error
示例#7
0
 def test_get_nested_dictionary_attribute(self):
     """Test that nested attributes ending in a dictionary key can be accessed."""
     inner_mock = Mock(b={"hello": "world"})
     outer_mock = Mock(a=inner_mock)
     self.assertEqual(get_nested_attribute(instance=outer_mock, nested_attribute_name="a.b.hello"), "world")
示例#8
0
 def test_get_nested_attribute(self):
     """Test that nested attributes can be accessed."""
     inner_mock = Mock(b=3)
     outer_mock = Mock(a=inner_mock)
     self.assertEqual(get_nested_attribute(instance=outer_mock, nested_attribute_name="a.b"), 3)