Skip to content

Instantly share code, notes, and snippets.

@matteoferla
Last active January 19, 2022 12:02
Show Gist options
  • Save matteoferla/24d9a319d05773ae219dd678a3aa11be to your computer and use it in GitHub Desktop.
Save matteoferla/24d9a319d05773ae219dd678a3aa11be to your computer and use it in GitHub Desktop.
Yet another decorator wrapping error catching
from typing import Any
from collections import defaultdict
class Safeguard:
"""
A decorator class (not a decorator factory class)
that will catch errors.
.. code-block:: python
@Safeguard
def foo():
pass
foo.default_value = {} # set the value to return on error (default: nan)
foo.turn_off() # disable
foo.turn_on() # enable
foo.errors # Dict[List[Exceptions]] of caught errors
foo.error_to_catch # Exceptions to catch
"""
default_value = float('nan')
errors = defaultdict(list)
error_to_catch = Exception
def __init__(self, func):
import functools
functools.update_wrapper(self, func)
self.func = func
def __call__(self, *args, **kwargs):
"""
This wraps the function call,
not return a function that wraps the function call
"""
try:
return self.func(*args, **kwargs)
except self.error_to_catch as error:
self.errors[error.__class__.__name__].append(error)
return self.default_value
def turn_off(self):
self.error_to_catch = ()
def turn_on(self, error_to_catch = Exception):
self.error_to_catch = error_to_catch
def reset(self):
self.errors = defaultdict(list)
@classmethod
def with_default_value(cls, default_value: Any) -> type: # i.e. the class not an instanace (=self)
"""
Rather hackish as it changes it globally!
"""
cls.default_value = default_value
return cls
@matteoferla
Copy link
Author

matteoferla commented Jan 14, 2022

Hacky loading the latest on a Jupyter notebook by not copypasting a larger code block:

def retrieve_from_gist(gist_sha = '24d9a319d05773ae219dd678a3aa11be', gist_filename = 'safeguard.py', wanted_variable='Safeguard'):
    import requests, importlib
    codeblock = requests.get(f'https://api.github.com/gists/{gist_sha}').json()['files'][gist_filename]['content']
    faux_global = {**globals(),  'Any': importlib.import_module('typing').Any,   'defaultdict': importlib.import_module('collections').defaultdict   }
    exec(codeblock, faux_global)
    return faux_global[wanted_variable]

Safeguard = retrieve_from_gist()

Breaking that down...

API. A Gist url has a username and a hash id in it. Then each revision has a hash id in turn as seen in the url when you press raw. The API does not care for the username and wants only the hash.

exec The exec(response.text) would work fine without the faux_globals if the exec were run in the main namespace, but within a function the imported modules within the excecuted codeblock before the class are not visible to the class as they weren't the global namespace. The importlib.import_module('typing') returns typing, but does not add it to the namespace.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment