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

Should HTTPError be a base class for RequestError and HTTPStatusError? #1108

Closed
iwoloschin opened this issue Jul 31, 2020 · 8 comments · Fixed by #1125
Closed

Should HTTPError be a base class for RequestError and HTTPStatusError? #1108

iwoloschin opened this issue Jul 31, 2020 · 8 comments · Fixed by #1125

Comments

@iwoloschin
Copy link

I'm going through some of my projects preparing them for the 0.14 release and it occurred to me that the following line may be confusing and trip folks up.

HTTPError = RequestError

This will break something like the following since the HTTPStatusError is now no longer caught, it'd just raise straight up and crash the program.

try:
    r = httpx.get('https://httpbin.org/status/400')
    r.raise_for_status()
except httpx.HTTPError as error:
    print(error)

Instead, can we change the line to:

HTTPError = (HTTPStatusError, RequestError)

That way at least something close to (or maybe exactly like?) the original functionality is maintained, at least until it is formally deprecated?

@StephenBrown2
Copy link
Contributor

Would need to include all the top-level exceptions from #1095, so:

HTTPError = (HTTPStatusError, RequestError, NotRedirectResponse, CookieConflict, StreamError)

@tomchristie
Copy link
Member

That's a really interesting idea, thanks!

An alternative might be to keep it as an exception class, as a base class of RequestError and HTTPStatusError?

@StephenBrown2
Copy link
Contributor

An alternative might be to keep it as an exception class, as a base class of RequestError and HTTPStatusError?

I'd support this option.

@iwoloschin
Copy link
Author

Actually, yeah, an exception class is probably better, you could probably do something to print out a deprecation warning that way?

@tomchristie tomchristie changed the title Temporarily Expand HTTPError Coverage Should HTTPError be a base class for RequestError and HTTPStatusError? Aug 1, 2020
@tomchristie
Copy link
Member

I guess theres a question here, re: if we should actually prefer to have that as a base class permanently, or if we'd only be doing this to capture both cases during a gentle deprecation.

Incidentally I'm not sure there is any way we'd be able to raise a deprecation warning to users importing and using an exception class in an except statement.

@florimondmanca
Copy link
Member

florimondmanca commented Aug 1, 2020

Incidentally I'm not sure there is any way we'd be able to raise a deprecation warning to users importing and using an exception class in an except statement.

Actually I think there's a way: Module __getattr__. :-) This use case of managing deprecation warnings on module attributes is actually the flagship behind the PEP, it seems. This feature is 3.7+ only, but can we afford a hard-break on 3.6 maybe…?

# httpx/__init__.py

def __getattr__(name: str):  # type: ignore  # Called if `httpx.<name>` was not found
    if name == "HTTPError":
        from ._utils import warn_deprecated

       # (Not sure about this error message, considering we might want to point at "it could be any of the top-level exceptions, pick one explicitly".)
        warn_deprecated("HTTPError is deprecated, please use one of RequestError or HTTPStatusError instead")
        from ._exceptions import HTTPError

        return HTTPError

    raise AttributeError(f"module {__name__} has no attribute {name}")

Result:

$ python -Wo -c "import httpx; print(httpx.HTTPError)"
/Users/florimond/Developer/python-projects/httpx/httpx/__init__.py:45: DeprecationWarning: HTTPError is deprecated
  warn_deprecated("HTTPError is deprecated")
<class 'httpx._exceptions.RequestError'>

@florimondmanca
Copy link
Member

florimondmanca commented Aug 1, 2020

if we should actually prefer to have that as a base class permanently, or if we'd only be doing this to capture both cases during a gentle deprecation.

I think we should only keep HTTPError as deprecation scaffolding. To me, the key idea of the work on the exception hierarchy was to more clearly separate between "errors that can occur when making a request" and "the developer made a mistake". If we keep HTTPError, users won't be nudged into making that separation clear in their code (they'd repeat what they've been used to do with Requests?), and we'd stay in the previous Requests-inherited state of "some developer mistakes may and will go un-noticed".

@iwoloschin
Copy link
Author

iwoloschin commented Aug 1, 2020

My only goal here was to highlight a relatively innocent change that would wind up being a horribly breaking change. To be fair, pre-1.0 breaking changes are completely fair game, but it's nice to at least alert users to the breaking changes!

That being said, I do like the concept of "one base exception per library", but in httpx's case I think that having a few base exceptions is reasonable. For instance, some of the code I was updating when I realized this might be a problem looks a lot better than the original code with only HTTPError. Also, I've just started using Pylance and it is much happier with the split exceptions since it is now completely unambiguous whether a .response is present or not.

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 a pull request may close this issue.

4 participants