Skip to content

Commit

Permalink
Merge branch 'master' into allow_typing_hacky
Browse files Browse the repository at this point in the history
# Conflicts:
#	generalimport/fake_module.py
  • Loading branch information
Mandera committed Jun 1, 2023
2 parents d2d3215 + 10c4314 commit d4caa41
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 29 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ Handle all your optional dependencies with a single call!

```mermaid
flowchart LR
3([file]) --> 5([packager])
1([tool]) --> 2([library])
2([library]) --> 4([vector])
2([library]) --> 5([packager])
1([tool]) --> 2([library])
2([library]) --> 3([file])
0([import]) --> 3([file])
3([file]) --> 5([packager])
0([import]) --> 2([library])
click 0 "https://github.com/ManderaGeneral/generalimport"
click 1 "https://github.com/ManderaGeneral/generaltool"
Expand All @@ -59,7 +59,7 @@ style 0 fill:#482

| Package | Ver | Latest Release | Python | Platform | Cover |
|:-----------------------------------------------------------------|:-------------------------------------------------|:----------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------|:--------|
| [generalimport](https://github.com/ManderaGeneral/generalimport) | [0.3.1](https://pypi.org/project/generalimport/) | 2022-10-27 16:21 CEST | [3.8](https://www.python.org/downloads/release/python-380/), [3.9](https://www.python.org/downloads/release/python-390/), [3.10](https://www.python.org/downloads/release/python-3100/), [3.11](https://www.python.org/downloads/release/python-3110/) | Windows, Ubuntu | 96.7 % |
| [generalimport](https://github.com/ManderaGeneral/generalimport) | [0.4.0](https://pypi.org/project/generalimport/) | 2023-05-24 09:26 CEST | [3.8](https://www.python.org/downloads/release/python-380/), [3.9](https://www.python.org/downloads/release/python-390/), [3.10](https://www.python.org/downloads/release/python-3100/), [3.11](https://www.python.org/downloads/release/python-3110/) | Windows, Ubuntu | 96.7 % |
</details>


Expand Down Expand Up @@ -170,15 +170,15 @@ generalimport("your", "optional", "dependencies")
<pre>
<a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/__init__.py#L1'>Module: generalimport</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/fake_module.py#L70'>Class: FakeModule</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/fake_module.py#L94'>Method: error_func</a>
│ └─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/fake_module.py#L98'>Method: error_func_class</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/fake_module.py#L103'>Method: error_func</a>
│ └─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/fake_module.py#L107'>Method: error_func_class</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L8'>Class: GeneralImporter</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L23'>Method: catch</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L47'>Method: create_module</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L50'>Method: exec_module</a>
│ └─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L31'>Method: find_spec</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L24'>Method: catch</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L48'>Method: create_module</a>
│ ├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L51'>Method: exec_module</a>
│ └─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/general_importer.py#L32'>Method: find_spec</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/import_catcher.py#L6'>Class: ImportCatcher</a>
│ └─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/import_catcher.py#L20'>Method: handle</a>
│ └─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/import_catcher.py#L22'>Method: handle</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/exception.py#L30'>Class: MissingDependencyException</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/exception.py#L34'>Function: MissingOptionalDependency</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport_bottom.py#L62'>Function: fake_module_check</a>
Expand All @@ -187,7 +187,7 @@ generalimport("your", "optional", "dependencies")
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport_bottom.py#L7'>Function: get_installed_modules_names</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport_bottom.py#L41'>Function: get_spec</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport_bottom.py#L28'>Function: import_module</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/fake_module.py#L127'>Function: is_imported</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport/fake_module.py#L136'>Function: is_imported</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport_bottom.py#L13'>Function: module_is_installed</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport_bottom.py#L47'>Function: module_is_namespace</a>
├─ <a href='https://github.com/ManderaGeneral/generalimport/blob/master/generalimport_bottom.py#L51'>Function: module_name_is_namespace</a>
Expand All @@ -206,7 +206,7 @@ Issue-creation, discussions and pull requests are most welcome!


<sup>
Generated 2023-05-16 07:41 CEST for commit <a href='https://github.com/ManderaGeneral/generalimport/commit/master'>master</a>.
Generated 2023-05-24 09:26 CEST for commit <a href='https://github.com/ManderaGeneral/generalimport/commit/master'>master</a>.
</sup>
</details>

25 changes: 17 additions & 8 deletions generalimport/fake_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,30 @@ class FakeModule:
__path__ = []
# __args__ = [] # Doesn't seem necessary

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, args, kwargs):
def _error_func(name, trigger, caller, catcher, args, kwargs):
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)

# print(name, trigger, caller, args, kwargs)
Expand All @@ -101,11 +110,11 @@ def _error_func(name, trigger, caller, args, kwargs):
raise MissingDependencyException(msg=msg)

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

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

@staticmethod
def _item_is_exception(item):
Expand All @@ -116,10 +125,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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
name="generalimport",
author='Rickard "Mandera" Abraham',
author_email="[email protected]",
version="0.3.1",
version="0.4.0",
description="Handle all your optional dependencies with a single call!",
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down

0 comments on commit d4caa41

Please sign in to comment.