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

Add specific gRPC client errors #53

Merged
merged 9 commits into from
May 29, 2024

Conversation

llucax
Copy link
Contributor

@llucax llucax commented May 27, 2024

This makes error handling more pythonic, as one can now just catch the exception type one is interested in, without having to do a second-level matching using the status.

It also helps avoiding to expose the grpclib classes to the user.

@github-actions github-actions bot added part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests part:client Affects the client code labels May 27, 2024
@llucax
Copy link
Contributor Author

llucax commented May 27, 2024

This is marked as a draft because it is based on #49 and it needs tests, but it is pretty much ready for a review, and I would appreciate early feedback, as I will probably move this to client-base once it is proved to work. @frequenz-floss/python-sdk-team

@llucax llucax force-pushed the more-exceptions branch 2 times, most recently from f6dc279 to 03af118 Compare May 27, 2024 13:03
@llucax llucax linked an issue May 27, 2024 that may be closed by this pull request
@llucax llucax self-assigned this May 27, 2024
@llucax llucax added the type:enhancement New feature or enhancement visitble to users label May 27, 2024
@matthias-wende-frequenz matthias-wende-frequenz changed the title Add a specific gRPC client errors Add specific gRPC client errors May 27, 2024
llucax added 2 commits May 28, 2024 12:07
Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
This makes error handling more pythonic, as one can now just catch the
exception type one is interested in, without having to do a second-level
matching using the status.

It also helps avoiding to expose the grpclib classes to the user.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
@llucax llucax force-pushed the more-exceptions branch from e8ff474 to a3d16b3 Compare May 28, 2024 10:07
@llucax llucax marked this pull request as ready for review May 28, 2024 10:09
@llucax llucax requested review from a team as code owners May 28, 2024 10:09
@llucax llucax enabled auto-merge May 28, 2024 10:10
@llucax
Copy link
Contributor Author

llucax commented May 28, 2024

Enabling auto-merge.

@@ -41,14 +60,31 @@
"ComponentMetricId",
"ComponentType",
"Connection",
"DataLoss",
"EVChargerCableState",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, but I usually like to organize stuff with comments, when there are so many things inside __all__, for example:

__all__ = [
    # 
    # Generic types
    # 
    "Component",
    "ComponentCategory",
    # 
    # Streaming types
    # 
    "BatteryData",
    # 
    # Exceptions
    # 
    "DataLoss",
]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't that info comes from the imports already? For me this is all very verbose and duplicated, I really would like to move to use from m import * in __init__.py and that's it. It works as expected with mypy (and official in Python, is part of the typing PEP, PEP484), but sadly mkdocstrings doesn't support it yet, so we still need to use __all__ if we want symbols to appear in the docs 😢

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list of symbols in __all__ is much more readable from the jumbled import statements where the symbols are not aligned and are grouped by the file they come from and not the category. Please don't remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this is a separate conversation, so let's not have it in this PR, I prefer to focus on this PR here, so I will just focus on having comments in __all__ or not.

For me __all__ is just noise, another way to do pylint: disable=unused-import or whatever is called. I think the key is to match files to categories, this is basically why we split symbols in different files, so for me looking at the import already gives me all the information I need. IMHO adding the comments to __all__ is adding even more duplication, now I not only have to repeat the y of from x import y, I also have to repeat the x (and if we are not repeating it, we need to re-organize which symbol goes to which file IMHO, not fixing it with comments in __all__).

That said, if it really adds clarity for you, I can add it, I guess we should try to make stuff as readable as possible for as much people as possible 🤷 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a separate conversation, so let's not have it in this PR, I prefer to focus on this PR here

I didn't start it 😈

For me all is just noise

and you didn't really stop either. 😠

if it really adds clarity for you, I can add it

I like doing it, but I mentioned optional anyway, so it is up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing while I think about this (I was looking into it to see if I would use a different categorization or the same as in files). If these comments are really intended to the users, I think we should add that classification in the module documentation instead, if we do that, we have some duplication, we still need to more or less list all the symbols twice, but I do see much more added value there, if this classification is shown in the docs.

In any case, this this is pre-existing code and not something added by this PR, I would rather do this as a separate PR, when improving the docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and you didn't really stop either. 😠

Haha, I meant just about removing __all__ completely.

if it really adds clarity for you, I can add it

I know see the value in the grouping as user documentation, but I don't want to get into writing the docs now that the project will change a lot when the upgrade to v0.17.0 happens.

If you think the current mapping of files/categories makes sense, I could add comments to match the files, that's not a problem as it is very low effort. Just let me know and I will add it, is just a few tab-tab with copilot.

If not, I will need to spend some time thinking which categories are appropriate, and if I do that, I will want to also map those categories to files as it should be. This will be something much more time consuming and again kind of pointless just before the upgrade to v0.17.0.


Returns:
An instance of
[GrpcStatusError][frequenz.client.microgrid.GrpcStatusError] if
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this can be UnrecognizedGrpcError?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. Do you think it would be useful to catch a UnrecognizedGrpcError? Otherwise I'm not sure what would be the advantage of giving it a special class. I can add it anyway, I'm not against it, mostly curious if you already had a use case in mind.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, didn't realize GrpcStatusError is a superclass for all the other errors. Why is it not just called GrpcError, btw?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add it anyway, I'm not against it, mostly curious if you already had a use case in mind.

No, it makes sense the way you've done it, thought the GrpcStatusError was a separate thing just for the unrecognized case. But also found the Status part of the error's name weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, actually because GrpcStatusError is a superclass for all other errors it makes sense to have this separately, otherwise there is no way to tell if the status we received is unrecognized, if you catch GrpcStatusError, then you'll catch all of the others.

I wondered if it shouldn't be just mapped to UnknownError, but I think it shouldn't because so far every know gRPC error status is mapped to a single class, and if we do so UnknownError could have a UNKNOWN status or any other random int that we don't know about as status, which is more confusing, so I guess a dedicated error for an unrecognized status still makes sense.

src/frequenz/client/microgrid/_client.py Outdated Show resolved Hide resolved
@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

Added a new exception UnrecognizedGrpcStatus and added the subclass at the end of the Raises: docs in the client, I will squash once it is pre-approved.

@llucax llucax requested a review from shsms May 29, 2024 09:04
@shsms
Copy link
Contributor

shsms commented May 29, 2024

and added the subclass at the end

I think it should be a subclass of ....

did you also see my question above about "Why not call it GrpcError?" The error is not with the status, but with grpc.

@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

I think it should be a subclass of ....

OK, I can do that.

did you also see my question above about "Why not call it GrpcError?" The error is not with the status, but with grpc.

Why isn't the error with the status? The error is we don't recognize the status, right? I don't see how this is not an unrecognized status, then we could call it UnrecognizedGprcStatus or UnrecognizedGprcStatusError, but I'm trying to be consistent with other specific errors not having the Error suffix unless the class name makes sense only with it.

@shsms
Copy link
Contributor

shsms commented May 29, 2024

No, I meant for the superclass GrpcStatusError, GrpcError fits better. There is context above, I guess you missed it. UnrecognizedGprcStatus sounds fine.

@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

No, I meant for the superclass GrpcStatusError, GrpcError fits better. There is context above, I guess you missed it. UnrecognizedGprcStatus sounds fine.

OK, I just wanted to emphasize that this error is basically mapping status codes to exceptions, I'm not sure if we could eventually want to raise other types of gRPC errors that are not tied to a status, like if you can't connect to the gRPC service at all, but maybe that is not strictly a gRPC error. I'm open to rename it to GrpcError if it makes more sense to you.

@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

Updated with a subclass of... and a fix for the commit adding UnrecognizedGrpcStatus.

@shsms
Copy link
Contributor

shsms commented May 29, 2024

like if you can't connect to the gRPC service at all, but maybe that is not strictly a gRPC error

For that we have ClientError right? But likely we'll receive a 'TimedOut' or 'Unavailable' error from the grpc library.

I'm open to rename it to GrpcError if it makes more sense to you.

I think we should, to make it clear that a grpc error happened, and not something that happened while dealing with a status. When a service method fails without providing a status or has a panic or something, we'll see the status 'Unknown'. So there will always be a status. grpclib seems to call it grpclib.GRPCError too.

@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

Pushed the rename. This should be ready for a final LGTM and then I can squash the fixups.

when the api call exceeded the timeout.
ClientError: If the are any errors communicating with the Microgrid API,
most likely a subclass of
[GrpcStatusError][frequenz.client.microgrid.GrpcStatusError].
Copy link
Contributor

@shsms shsms May 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to change too :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right. Now I hope all references are updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, updated

@llucax llucax requested a review from shsms May 29, 2024 10:38
@llucax llucax force-pushed the more-exceptions branch 2 times, most recently from 06a0754 to 48d826b Compare May 29, 2024 10:42
llucax added 4 commits May 29, 2024 12:43
Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
This flag can simplify deciding if an operation returning an error can
be blindly retried. Some errors might need some intervention to change
the system's state, but another actor might do that, so a bling retry
might still succeed.

Retrying is still largely missing, but it will be solved separately,
see: frequenz-floss#52

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
@llucax llucax force-pushed the more-exceptions branch from 48d826b to 684f353 Compare May 29, 2024 10:43
@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

I just squashed and pushed because I guess you already did the review of the fixup commits.

llucax added 3 commits May 29, 2024 12:47
When we receive a gRPC status code that we don't recognize, we raise a
`GrpcStatusError` instead of the base class for all gRPC status errors.

This will allow users to more easily differentiate between known and
unknown status codes.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Also improve the docs for the class slightly to be more clear about
which errors should be more protocol-independent and safer to catch.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
@llucax llucax force-pushed the more-exceptions branch from 684f353 to 642c62b Compare May 29, 2024 10:47
@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

Sorry, one last update to improve the release notes to adjust to the current changes.

Copy link
Contributor

@shsms shsms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome! We can take this further by defining more domain specific error statuses - either using the grpc status mechanism, don't know if that's possible, or through the description. For example,

except FailedPrecondition as e:
    if e.desc() == "OVER_SOC":
        # allow discharge, but block charge

but that would need to be coordinated with the server side.

@llucax llucax added this pull request to the merge queue May 29, 2024
Merged via the queue into frequenz-floss:v0.x.x with commit 01a741e May 29, 2024
14 checks passed
@llucax llucax deleted the more-exceptions branch May 29, 2024 11:11
@llucax
Copy link
Contributor Author

llucax commented May 29, 2024

Yeah, we can define more meaningful errors in the error description I guess. Or maybe the "details", never looked what is that for and if it can be easily set by the server. I would do it more structurally, and have more exceptions in the hierarchy so we don't need to resort again to inspecting the exception to have a look at another level of type of error, so more like:

except BatteryAlreadyFull:
    ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
part:client Affects the client code part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests type:enhancement New feature or enhancement visitble to users
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a specific client errors
2 participants