Repository: pyinstaller/pyinstaller

Issue:
is_module_satisfies() returns True for packages that are not installed
`is_module_satisfies()` should return `False` if the package being provided is not installed.
Instead I get:

    >>> from PyInstaller.utils.hooks import is_module_satisfies
    >>> is_module_satisfies("xyz")
    Traceback (most recent call last):
    File "<string>", line 2, in <module>
    ImportError: No module named xyz
    True
    >>>
is_module_satisfies() returns True for packages that are not installed
`is_module_satisfies()` should return `False` if the package being provided is not installed.
Instead I get:

    >>> from PyInstaller.utils.hooks import is_module_satisfies
    >>> is_module_satisfies("xyz")
    Traceback (most recent call last):
    File "<string>", line 2, in <module>
    ImportError: No module named xyz
    True
    >>>


Discussion:
This is cause by [this line](https://github.com/pyinstaller/pyinstaller/blob/v3.3.1/PyInstaller/utils/hooks/__init__.py#L492) setting version to an empty string if the module does not exist. That makes the test just below evaluate to true.

Needs test-case :-)
Hi @htgoebel can I get assigned to this issue? I think I fixed it on my branch. If you could tell me how I could start a PR it would be awesome sir. 
Feel free to provide a pull-request for this. We are not using "Assignment" in the project. (And github technically does not all allow to set you as "Assignee".) I'm looking forward to you contribution :-)


See [Creating pull requests](http://pyinstaller.readthedocs.io/en/v3.3.1/development/pull-requests.html?highlight=pull) and [commit messages](http://pyinstaller.readthedocs.io/en/stable/development/commit-messages.html?highlight=commit) for guidelines.
Thank you sir. There is also one thing I wanted to get clarification on. The exec_statement function creates a new shell and tries to import the package. This throws a ModuleNotFoundException if the package is not found, but the function returns the correct value anyways. Is that ok or what should the correct behaviour be?
I am attaching the code for that part here.
```python
def get_module_attribute(module_name, attr_name):
attr_value_if_undefined = '!)ABadCafe@(D15ea5e#*DeadBeef$&Fee1Dead%^'
    attr_value = exec_statement("""
        import %s as m
        print(getattr(m, %r, %r))
    """ % (module_name, attr_name, attr_value_if_undefined))
    if attr_value == attr_value_if_undefined:
        raise AttributeError(
            'Module %r has no attribute %r' % (module_name, attr_name))
    else:
        return attr_value
```
Some comments from the module were removed before pasting here. These are lines from 260-298 of the highlighted function.
```
>>> from PyInstaller.utils.hooks import is_module_satisfies
>>> a=is_module_satisfies('abd')
Traceback (most recent call last):
  File "<string>", line 2, in <module>
ModuleNotFoundError: No module named 'abd'
>>> a
False
```
This is the output after I made the change for the empty string check. 
This is cause by [this line](https://github.com/pyinstaller/pyinstaller/blob/v3.3.1/PyInstaller/utils/hooks/__init__.py#L492) setting version to an empty string if the module does not exist. That makes the test just below evaluate to true.

Needs test-case :-)
Hi @htgoebel can I get assigned to this issue? I think I fixed it on my branch. If you could tell me how I could start a PR it would be awesome sir. 
Feel free to provide a pull-request for this. We are not using "Assignment" in the project. (And github technically does not all allow to set you as "Assignee".) I'm looking forward to you contribution :-)


See [Creating pull requests](http://pyinstaller.readthedocs.io/en/v3.3.1/development/pull-requests.html?highlight=pull) and [commit messages](http://pyinstaller.readthedocs.io/en/stable/development/commit-messages.html?highlight=commit) for guidelines.
Thank you sir. There is also one thing I wanted to get clarification on. The exec_statement function creates a new shell and tries to import the package. This throws a ModuleNotFoundException if the package is not found, but the function returns the correct value anyways. Is that ok or what should the correct behaviour be?
I am attaching the code for that part here.
```python
def get_module_attribute(module_name, attr_name):
attr_value_if_undefined = '!)ABadCafe@(D15ea5e#*DeadBeef$&Fee1Dead%^'
    attr_value = exec_statement("""
        import %s as m
        print(getattr(m, %r, %r))
    """ % (module_name, attr_name, attr_value_if_undefined))
    if attr_value == attr_value_if_undefined:
        raise AttributeError(
            'Module %r has no attribute %r' % (module_name, attr_name))
    else:
        return attr_value
```
Some comments from the module were removed before pasting here. These are lines from 260-298 of the highlighted function.
```
>>> from PyInstaller.utils.hooks import is_module_satisfies
>>> a=is_module_satisfies('abd')
Traceback (most recent call last):
  File "<string>", line 2, in <module>
ModuleNotFoundError: No module named 'abd'
>>> a
False
```
This is the output after I made the change for the empty string check. 

Resolving patch:
diff --git a/PyInstaller/utils/hooks/__init__.py b/PyInstaller/utils/hooks/__init__.py
--- a/PyInstaller/utils/hooks/__init__.py
+++ b/PyInstaller/utils/hooks/__init__.py
@@ -490,8 +490,12 @@ class method (e.g., `idontevenknow<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1`) is
         module_name = requirements_parsed.project_name
         version = get_module_attribute(module_name, version_attr)
 
-    # Compare this version against the version parsed from these requirements.
-    return version in requirements_parsed
+    if not version:
+        # Module does not exist in the system.
+        return False
+    else:
+        # Compare this version against the one parsed from the requirements.
+        return version in requirements_parsed
 
 
 def is_package(module_name):


Test patch:
diff --git a/tests/unit/test_hookutils.py b/tests/unit/test_hookutils.py
--- a/tests/unit/test_hookutils.py
+++ b/tests/unit/test_hookutils.py
@@ -15,7 +15,8 @@
 
 from PyInstaller.utils.hooks import collect_data_files, collect_submodules, \
     get_module_file_attribute, remove_prefix, remove_suffix, \
-    remove_file_extension, is_module_or_submodule
+    remove_file_extension, is_module_or_submodule, \
+    is_module_satisfies
 from PyInstaller.compat import exec_python, ALL_SUFFIXES
 
 
@@ -188,6 +189,10 @@ def test_is_module_or_submodule():
     assert not is_module_or_submodule('foo', 'foo.bar')
 
 
+def test_is_module_satisfies_package_not_installed():
+    assert is_module_satisfies('pytest')
+    assert not is_module_satisfies('magnumopus-no-package-test-case')
+
 
 _DATA_BASEPATH = join(TEST_MOD_PATH, TEST_MOD)
 _DATA_PARAMS = [