Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support more complex types by using generics #93

Open
matts1 opened this issue Feb 20, 2024 · 1 comment
Open

Support more complex types by using generics #93

matts1 opened this issue Feb 20, 2024 · 1 comment

Comments

@matts1
Copy link

matts1 commented Feb 20, 2024

It's currently very hard to work with custom providers. One example of somewhere seriously lacking is if I want to do assertions on the following type:

FooInfo = provider(fields = {"name": "str")
BarInfo = provider(fields = {
    "foos": "(depset[Foo]) the foos",
})

I would like to be able to write something like:

env.expect.that_provider(BarInfo).foos()
  .contains_at_least_predicates(lambda foo: foo.name().equals("foo"))

I propose that we allow generics by turning generic types into functions. For example:

FooInfoSubject = subjects.struct(name = subjects.string)
BarInfoSubject = subjects.struct(
    foos = subjects.depset(FooInfoSubject)
)

This would make the above predicates far more easily implemented (although the caveat is that it would require a change in matchers so that you could simply use them as a boolean predicate - at the moment, not matching simply fails the test).

We could also use this to easily solve #63 via the generic type subjects.optional (eg. subjects.optional(subjects.string)).

@matts1
Copy link
Author

matts1 commented Feb 21, 2024

FWIW, I've mostly solved this by just doing the following:

# I strongly dislike how struct is defined, IMO the attrs should be curried
def struct(**attrs):
    return lambda value, *, meta: subjects.struct(value, meta=meta, attrs=attrs)

def optional(factory):
    def new_factory(value, *, meta):
        # Optimisations could be done here to just pull all the methods from the parent and add the method is_none()
        def some():
            if value == None:
                meta.add_failure("Wanted a value, but got None", None)
            return factory(value, meta = meta)

        def is_none():
            if value != None:
                meta.add_failure("Wanted None, but got a value", value)

        return struct(some = some, is_none = is_none)

generic_subjects = struct(struct = struct, optional = optional)

You can then just write:

FooInfo = provider(fields = {"a": "(Optional[str])")
FooInfoSubject = generic_subjects.struct(a = generic_subjects.optional(subjects.str))

foo_some = FooInfo(a = "abc")
foo_none = FooInfo(a = None)

env.expect.that_value(foo_some, factory = FooInfoSubject).a().some().contains("abc")
env.expect.that_value(foo_none, factory = FooInfoSubject).a().is_none()

@matts1 matts1 mentioned this issue Feb 21, 2024
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant