Skip to content

Commit

Permalink
Merge pull request #38 from ManderaGeneral/custom_message_kwarg
Browse files Browse the repository at this point in the history
Custom messages and ImportCatcher relay to FakeModule
  • Loading branch information
Mandera committed May 24, 2023
2 parents 8506b94 + 418bf7c commit d854259
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 16 deletions.
25 changes: 17 additions & 8 deletions generalimport/fake_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,39 @@ class FakeModule:
Unhandled use-cases: https://github.com/ManderaGeneral/generalimport/issues?q=is%3Aissue+is%3Aopen+label%3Aunhandled """
__path__ = []

def __init__(self, spec, trigger: Optional[str] = None):
def __init__(self, spec, trigger: Optional[str] = None, catcher=None):
self.name = spec.name
self.trigger = trigger
self.catcher = catcher

self.__name__ = spec.name
self.__loader__ = spec.loader
self.__spec__ = spec
self.__fake_module__ = True # Should not be needed, but let's keep it for safety?

@staticmethod
def _error_func(name, trigger, caller):
def _error_func(name, trigger, caller, catcher):
required_by = f" (required by '{trigger}')" if trigger else ""
name_part = f"{name}{required_by} " if name else ""
msg = f"Optional dependency {name_part}was used but it isn't installed."
msg = f"{msg} Triggered by '{caller}'."

msg_list = [
f"Optional dependency {name_part}was used but it isn't installed.",
f"Triggered by '{caller}'.",
]
if catcher and catcher.message:
msg_list.append(catcher.message)

msg = " ".join(msg_list)

logger.debug(msg=msg)
raise MissingDependencyException(msg=msg)

def error_func(self, _caller: str, *args, **kwargs):
self._error_func(name=self.name, trigger=self.trigger, caller=_caller)
self._error_func(name=self.name, trigger=self.trigger, caller=_caller, catcher=self.catcher)

@classmethod
def error_func_class(cls, _caller: str, *args, **kwargs):
cls._error_func(name=None, trigger=None, caller=_caller)
cls._error_func(name=None, trigger=None, caller=_caller, catcher=None)

@staticmethod
def _item_is_exception(item):
Expand All @@ -107,10 +116,10 @@ def _item_is_dunder(item):
return item in NON_CALLABLE_DUNDERS

def __getattr__(self, item):
fakemodule = FakeModule(spec=self.__spec__, trigger=item)
fakemodule = FakeModule(spec=self.__spec__, trigger=item, catcher=self.catcher)
if self._item_is_exception(item=item) or self._item_is_dunder(item=item):
fakemodule.error_func(item)
return FakeModule(spec=self.__spec__, trigger=item)
return fakemodule


# Sets all the callable dunders of FakeModule to 'error_func()' by preserving the name of the dunder that triggered it.
Expand Down
9 changes: 5 additions & 4 deletions generalimport/general_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class GeneralImporter:

def __init__(self):
self.catchers = []
self.latest_catcher = None

self._singleton()
self._skip_fullname = None
Expand Down Expand Up @@ -45,7 +46,7 @@ def find_spec(self, fullname, path=None, target=None):
return self._handle_relay(fullname=fullname, spec=spec)

def create_module(self, spec):
return FakeModule(spec=spec)
return FakeModule(spec=spec, catcher=self.latest_catcher)

def exec_module(self, module):
pass
Expand Down Expand Up @@ -77,11 +78,11 @@ def _handle_ignore(self, fullname, reason):
return None

def _handle_handle(self, fullname, reason):
catcher = self.catch(fullname=fullname)
if not catcher:
self.latest_catcher = self.catch(fullname=fullname)
if not self.latest_catcher:
return self._handle_ignore(fullname=fullname, reason="Unhandled")

getLogger(__name__).info(f"{catcher} is handling '{fullname}' - {reason}")
getLogger(__name__).info(f"{self.latest_catcher} is handling '{fullname}' - {reason}")

sys.modules.pop(fullname, None) # Remove possible namespace

Expand Down
4 changes: 3 additions & 1 deletion generalimport/import_catcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
class ImportCatcher:
WILDCARD = "*"

def __init__(self, *names):
def __init__(self, *names, message=None):
self.names = set(names)
self.message = message

self.added_names = set()
self.added_fullnames = set()
self.enabled = True
Expand Down
41 changes: 41 additions & 0 deletions generalimport/test/test_generalimport.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,47 @@ def test_logging_message(self):
else:
self.fail("Error not raised")

def test_message(self):
generalimport("nonexisting", message="FooBar")
import nonexisting

try:
nonexisting()
except MissingDependencyException as e:
self.assertIn("FooBar", str(e))
else:
self.fail("Error not raised")

def test_catcher_relay_single(self):
catcher = generalimport("nonexisting")
import nonexisting

self.assertIs(nonexisting.catcher, catcher)

def test_catcher_relay_two(self):
catcher = generalimport("foo", "bar")
import foo
import bar

self.assertIs(foo.catcher, catcher)
self.assertIs(bar.catcher, catcher)

def test_catcher_relay_double(self):
catcher = generalimport("foo")
from foo import bar

self.assertIs(bar.catcher, catcher)

def test_catcher_relay_double_and_two(self):
foobar_catcher = generalimport("foo")
hiii_catcher = generalimport("hiii")

from foo import bar
import hiii

self.assertIs(bar.catcher, foobar_catcher)
self.assertIs(hiii.catcher, hiii_catcher)




Expand Down
4 changes: 2 additions & 2 deletions generalimport/top.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ def get_importer():
""" Return existing or new GeneralImporter instance. """
return GeneralImporter.singleton_instance or GeneralImporter()

def generalimport(*names):
def generalimport(*names, message=None):
""" Adds names to a new ImportCatcher instance.
Creates GeneralImporter instance if it doesn't exist. """
# print(get_previous_frame_filename())
_assert_no_dots(names=names)
catcher = ImportCatcher(*names)
catcher = ImportCatcher(*names, message=message)
get_importer().catchers.append(catcher)
return catcher

Expand Down
2 changes: 1 addition & 1 deletion metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"enabled": true,
"private": false,
"name": "generalimport",
"version": "0.3.1",
"version": "0.4.0",
"description": "Handle all your optional dependencies with a single call!",
"topics": [],
"manifest": [],
Expand Down

0 comments on commit d854259

Please sign in to comment.