Repository navigation
Conversation
|
Hrm... I'm not convinced about this. We do get feedback where the A good question for giving us some guidance onto this would be looking at the top-level |
|
Oh, and yes, I'd prefer "really comprehensive runtime checking" over "type hints only". Just seems a much better UX. |
|
@adriangb - How would you like to proceed with this? |
|
I’d personally like to move forward with this but if you still feel like it’s better to keep runtime type checking around feel free to close it. |
|
Another option @tomchristie: we make something like Pydantic or beartype an optional dependency gated by an extra. We can then have a decorator that does nothing at runtime if that extra isn't activated or does runtime type checking if it is. That way if users show up with an issue that seems like it may be related to them passing in the wrong type we can say "please add the |
|
I don't think its worth adding additional dependencies even if they were optional. I would be in favour of just removing the checks altogether and relying on static type checking. |
With regards to type checking, I agree.
Are we referring to specifically just the checks in this pull request, or accepting the issue more generally? |
I was referring to specifically just the checks in this pull request. I am not sure on the broader sense. Most of the code I personally write is internal in an environment where we know we are using static analysis to catch potential type errors. Library code is somewhat different as we can't really know if users are using tools like mypy/pyright so I guess we should be doing some checks at the boundaries where users primarily interact? I am fine with |
I agree with this, I'm in the same boat. I'm a bit torn because for me, personally, the runtime type checking is only hurting performance and making the code harder to iterate on. I don't run into these sorts of issues because type checkers and IDEs prevent them before any code is even run. But you made a great case for keeping the runtime type checking @tomchristie by pointing to frequent issues/discussions where it's helped users. To the point where I'm not even sure this change is a good one. In fact, the most recent one in #2432 is actually a case where there's an argument to be made for more runtime type checking. But ad-hoc testing is always going to have gaps, especially with all of our arguments that accept unions and whatnot. I kind of want to just make |
My suggestion would be closing this and #2404, and that we start with an issue similar to #2436 which digs into...
That'll at least give us a point of reference from which to work, so that whichever resolution we choose to go with, we can make sure we've got a more consistent approach throughout. Make sense? |
|
Just a quick couple of thoughts to add more confusion 😅 Pydantic v2 is around the corner. It will have types in general as a first class citizen, not only models (classes). That might help simplifying using it in some way (if at all). I guess it could be an optional dependency that is only used when installed, but that always means a bit extra complexity in the codebase importing things and if they are not available adding dummy replacements. But maybe it could be done. Another idea to maybe consider (although I'm not even checking the code right now as I'm on my phone), an option is to make some classes dataclasses, and optionally importing Pydantic's dataclasses when available, otherwise standard dataclasses. Anyway, just a couple of thoughts, maybe too random but worth sharing just in case. 🤓🤷 |
|
A thought here: could we feature flag runtime type checking behind an env var? That way if a user reports a problem that seems like it might be caused by their erroneous input we can say "please run your code with # inside some utils.py type file
def maybe_runtime_type_check(func: T) -> T:
if os.environ.get("HTTPX_RUNTIME_TYPE_CHECKS") is not None:
try:
from pydantic import validate_arguments
except ImportError as e:
raise RuntimeError("Runtime type checking requires ...") from e
return validate_arguments(func)
return func
# our main request class
class Request:
@maybe_runtime_type_check
def __init__(self, ...): ...The reason I made it check for the env var every time is so that it can be set dynamically in tests, to debug just one endpoint, etc. with minimal impact on other code execution. |
|
That sounds reasonable to me |
#2404 (comment)
My rationale for removing this: we should either adopt runtime type checking systematically (using beartype, Pydantic's validation decorator or making our inputs (like a
Requestinstance) Pydantic models) or we should remove it all together and rely on static type checking.I think having manual runtime type checking sprinkled around the codebase doesn't scale as well as either of those two solutions.
Amongst those two I think the obvious choice is to rely purely on static type checking and documentation. It's more valuable for users to know upfront that they're giving us the wrong type (e.g. by implementing #2404 which replaces an
Anyfor the value with the types that we are checking for at runtime here) than to do it at runtime.