Esempio n. 1
0
    def add_binding(self, out_descriptor, dep_entity_names, use_tuples_for_output=True):
        self._clear_active_flow()

        out_dnode = dnode_from_descriptor(out_descriptor)
        out_entities = list(
            map(self._entities_by_name.get, out_dnode.all_entity_names())
        )
        dep_entities = list(map(self._entities_by_name.get, dep_entity_names))

        should_persists = set(entity.should_persist for entity in out_entities)
        (should_persist,) = should_persists

        binding = ModelBinding(
            out_dnode=out_dnode,
            out_entities=out_entities,
            dep_entities=dep_entities,
            use_tuples_for_output=use_tuples_for_output,
        )
        for out_entity in out_entities:
            assert out_entity.binding is None
            out_entity.binding = binding
            for dep_entity_name in dep_entity_names:
                dep_entity = self._entities_by_name[dep_entity_name]
                dep_entity.dependent_entities.append(out_entity)

        self._add_binding_to_flow(binding)
Esempio n. 2
0
def test_entity_type_checks():
    @attr.s
    class TypeExample:
        descriptor = attr.ib()
        call_is_type = attr.ib()
        call_assume_type = attr.ib()

    DN = DescriptorNode
    examples = [
        TypeExample("x", DN.is_entity, DN.assume_entity),
        TypeExample("x, y", DN.is_tuple, DN.assume_tuple),
        TypeExample("<x>", DN.is_draft, DN.assume_draft),
    ]

    for example in examples:
        dnode = dnode_from_descriptor(example.descriptor)
        assert example.call_is_type(dnode)
        assert example.call_assume_type(dnode) is dnode

        for other_example in examples:
            if other_example is example:
                continue
            assert not other_example.call_is_type(dnode)
            with pytest.raises(TypeError):
                other_example.call_assume_type(dnode)
Esempio n. 3
0
    def query_and_check_entity(self, entity_name):
        flow = self._builder.build()

        flow_exception = None
        try:
            flow_value = flow.get(entity_name)
        except CodeVersioningError as e:
            flow_exception = e

        model_exception = None
        try:
            model_value = self._compute_model_value(entity_name)
        except CodeVersioningError as e:
            model_exception = e

        assert (flow_exception is None) == (model_exception is None)

        if model_exception is None:
            assert flow_value == model_value

        else:
            assert flow_exception.__class__ == model_exception.__class__

            # Now we know that a CodeVersioningError has been thrown, we'll try to get
            # the flow back into a good state by fixing the versioning error. We can
            # pick any of the entities mentioned in the error, since they're all set
            # by the same binding. We'll keep recursively retrying until all the errors
            # are fixed and the query succeeds. At that point both the real flow and the
            # model flow should have computed and persisted the same set of descriptors,
            # so we'll be back in a known-good state.
            # (That's why we do the fixing inside this function instead of leaving it
            # to the caller: it's a little awkward, but it lets us guarantee that both
            # flows will be in sync by the time we return.)

            # It would be nice if we could also assert that both exceptions have the
            # same `bad_descriptor` field, but if there are multiple bad descriptors,
            # which one we get first is not defined. In theory we could, after fixing
            # everything, check that the sets of bad descriptors matched, but I'm not
            # sure it's worth the extra bookkeeping.
            bad_entity_name = dnode_from_descriptor(
                model_exception.bad_descriptor).all_entity_names()[0]
            self.update_binding(
                entity_name=bad_entity_name,
                change_func_version=False,
                update_version_annotation=True,
            )

            self.query_and_check_entity(entity_name)

        assert set(self._descriptors_computed_by_flow) == set(
            self._descriptors_computed_by_model)
        self._clear_called_descriptors()
Esempio n. 4
0
    def query_and_check_entity(self, entity_name, expect_exact_call_count_matches=None):
        if expect_exact_call_count_matches is None:
            expect_exact_call_count_matches = self._expect_exact_call_count_matches

        self._build_active_flow_if_missing()

        flow_exception = None
        try:
            flow_value = self._active_flow.get(entity_name)
        except CodeVersioningError as e:
            flow_exception = e

        context = ModelExecutionContext(
            parallel_execution_enabled=self._parallel_execution_enabled,
            query_caching_enabled=self._query_caching_enabled,
            versioning_mode=self._versioning_mode,
            memoized_flow_values_by_entity_name=self._active_model_memoized_values_by_entity_name,
            memoized_query_values_by_entity_name={},
            computed_descriptors=self._descriptors_computed_by_model,
        )
        model_exception = None
        try:
            model_value = self._compute_model_value(entity_name, context)
        except CodeVersioningError as e:
            model_exception = e

        assert (flow_exception is None) == (model_exception is None)

        if model_exception is None:
            assert flow_value == model_value

            if expect_exact_call_count_matches:
                assert sorted(self._descriptors_computed_by_flow) == sorted(
                    self._descriptors_computed_by_model
                )
            else:
                assert set(self._descriptors_computed_by_flow) == set(
                    self._descriptors_computed_by_model
                )
            self._clear_called_descriptors()

        else:
            assert flow_exception.__class__ == model_exception.__class__

            # Now we know that a CodeVersioningError has been thrown, we'll try to get
            # the flow back into a good state by fixing the versioning error. We can
            # pick any of the entities mentioned in the error, since they're all set
            # by the same binding. We'll keep recursively retrying until all the errors
            # are fixed and the query succeeds. At that point both the real flow and the
            # model flow should have computed and persisted the same set of descriptors,
            # so we'll be back in a known-good state.
            # (That's why we do the fixing inside this function instead of leaving it
            # to the caller: it's a little awkward, but it lets us guarantee that both
            # flows will be in sync by the time we return.)

            # It would be nice if we could also assert that both exceptions have the
            # same `bad_descriptor` field, but if there are multiple bad descriptors,
            # which one we get first is not defined. In theory we could, after fixing
            # everything, check that the sets of bad descriptors matched, but I'm not
            # sure it's worth the extra bookkeeping.
            bad_entity_name = dnode_from_descriptor(
                model_exception.bad_descriptor
            ).all_entity_names()[0]
            self.update_binding(
                entity_name=bad_entity_name,
                change_func_version=False,
                update_version_annotation=True,
            )

            self.query_and_check_entity(
                entity_name,
                # Since our query was interrupted, some extra non-persisted entities
                # may have been computed, so we can't expect the final counts to line
                # up exactly.
                expect_exact_call_count_matches=False,
            )