Skip to content

Add type hints#55

Merged
henri-hulski merged 1 commit into
morepath:masterfrom
seantis:feat/typing
Jun 5, 2026
Merged

Add type hints#55
henri-hulski merged 1 commit into
morepath:masterfrom
seantis:feat/typing

Conversation

@Daverball
Copy link
Copy Markdown
Contributor

These type hints are based on my existing stubs, although I decided to change one of the sharp corners, when it comes to defining actions. Currently dectate expects you to only include the parameters you actually will be passed based on your config and app_class_arg values, mostly as a matter of style. This violates LSP, but is technically still safe within the confines of how these methods are invoked.

The result is that type checkers will emit incompatible override errors for pretty much every action implementation in morepath and in dectate's tests as well as any custom actions defined by third parties. I begrudgingly put up with those errors for my own code base, but I don't think it's acceptable for a wider audience, so I decided to erase the type of those methods and replace them with Any. That does bring some drawbacks, but since the only guaranteed safe way to invoke these methods cannot really be expressed by the type system anyway, I think it's fine.

There's one more sharp corner when it comes to directive classmethods. Currently there's a notion of being able to call it with only part of the required arguments and then later fill them in via the DirectiveAbbreviation context manager, the incomplete directive is meant to be discarded, without ever being used as a decorator.

Here I decided to go the other way, since erasing the signature of every directive classmethod, seems like a really bad idea and there's currently no type modifier to create a partial signature from a complete signature. So if you still want to do this, you will need to either ignore the type error on the incomplete directive or pass in some default value for the arguments you intend to replace in the context manager.

I also thought about adding a partial attribute to the method returned by directive, so the new recommended way to do this looks like this:

with App.action.partial(foo="a") as action:
    @action(bar="b")
    def baz():
        pass

So using the erased signature is a conscious decision. That seems like a better trade-off to me. We can still support the old way of doing it, you just may get a type error for a missing required argument when you do it that way.

If you agree that's a good idea, then I can go add that and update the docs where appropriate.

/cc @henri-hulski

@henri-hulski
Copy link
Copy Markdown
Member

I think this is a good idea.
After we can see in practice if we need some adjustments.
So let's start with your concept.

@Daverball
Copy link
Copy Markdown
Contributor Author

@henri-hulski I implemented my idea. I had to add a custom descriptor and bound method class to make it work, but it should behave identically to a native classmethod and bound method.

@henri-hulski
Copy link
Copy Markdown
Member

Cool! Looks good to me.

@henri-hulski henri-hulski merged commit 052d178 into morepath:master Jun 5, 2026
7 checks passed
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

Successfully merging this pull request may close these issues.

2 participants