-
Notifications
You must be signed in to change notification settings - Fork 48
Define generic napalm metaclass to catch exceptions #259
base: develop
Are you sure you want to change the base?
Conversation
And raise a napalm exception instead. As discussed under napalm-automation/napalm-junos#161
ae1e4cf
to
160176a
Compare
napalm_base/base.py
Outdated
from napalm_base import validate | ||
|
||
log = logging.getLogger(__file__) | ||
|
||
ERROR_MAP = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a class attribute and it should be empty for the base driver. Then each driver can implement it's own ERROR_MAP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, makes sense. Just that I'm not sure yet what's the best approach... this might be a chicken and egg type thing (napalm-base depends on the driver, which depends on napalm-base). I will investigate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
napalm-base depends on the driver
But it doesn't. It's just a class method that uses a class attribute. Your subclass just happens to override the class attribute. The parent class has no idea about it :)
napalm_base/base.py
Outdated
err_class = ERROR_MAP[err_full_name] | ||
log.info('Raising {} instead'.format(err_class.__name__)) | ||
raise err_class(str(error)) | ||
# Raise everything else, using the original class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if the else
statement here would do something like this:
https://github.com/napalm-automation/napalm-base/blob/develop/napalm_base/base.py#L87
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potentially raising an UncaughtException
or something like that. Something we can distinguish easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks great, just proposed a few minor changes.
@dbarrosop @ktbyers pushed the changes as suggested. The current approach requires the driver to define a map like: class JunOSDriver(NetworkDriver):
"""JunOSDriver class - inherits NetworkDriver from napalm_base."""
ERROR_MAP = {
'jnpr.junos.exception.ConnectClosedError': napalm_base.exceptions.ConnectionClosedException,
'jnpr.junos.exception.ConnectAuthError': napalm_base.exceptions.ConnectAuthError,
'jnpr.junos.exception.ConnectTimeoutError': napalm_base.exceptions.ConnectTimeoutError,
'jnpr.junos.exception.LockError': napalm_base.exceptions.LockError,
'jnpr.junos.exception.UnlockError': napalm_base.exceptions.UnlockError,
'jnpr.junos.exception.CommitError': napalm_base.exceptions.CommitError
} We can have either have the mapping between the full path, either 1:1 class, either both (although if we have both the code might become even less obvious). |
I would import the classes directly to avoid issues that are only triggered at runtime because someone misspelled a path or class name. |
I still don't like using the metaclass. I think a decorator would be a more logical construct to use. The thing I don't like is that it is hidden in the code. When someone looks at the code in napalm-ios, napalm-eos, et cetera, they will have no clue that this is going on. Also the pattern we are applying falls pretty logically into the decorator pattern. Something like @normalize_exceptions The negative being we are going to explicitly have to decorate functions in each driver, but even with this I think the decorator pattern is a more logical solution than the metaclass. We could still have normalize_exceptions code in napalm-base and have a map similar to what was proposed earlier. Having this pattern makes it clear that we are normalizing exceptions and where this normalization applies to. |
I'd fine with an explicit decorator. FYI: this is exactly what this metaclass does - decorates all the methods (with |
Yes, understood...it is a trade-off of slightly more work versus hidden-behind-the-scenes. |
I'm fine with any of the approaches. So @ktbyers @dbarrosop please let me know which one would be preferable (if you see any advantages/disadvantages). |
Sounds good... |
I'd rather use the metaclass to avoid "forgetting" about it. Just make sure that the metaclass and the wrapper function are well documented and it should be fine. The traceback should show the wrapper function if something goes bad so not super concerned about the whole stuff failing without being super explicit about it. |
I won't put up any more objections on this decorator vs metaclass so don't hold it up because of me... |
@mirceaulinic when you have the time link a PR using this. Would be great to have them hand in hand to see how it's used :) |
Looks like https://pythonhosted.org/six/#six.reraise is needed for full compatibility between py2 and py3 for re-raising exceptions. Standalone code in https://stackoverflow.com/questions/34463087/raise-exception-in-python-2-x-and-3-x |
Hi @bewing - thanks for suggestion. I recall I had some changes not pushed yet, hope I didn't stash them. Let me revisit this somewhere next week. |
And raise a napalm exception instead.
As discussed under napalm-automation/napalm-junos#161
Log trace: