I am trying to use the functions __import__(...) and getattr(...) to make an object of a class that is located inside a module. That module itself is included inside a package.
The class is named TestScript. It is located in a file named TestScript.py. The file is inside a folder named TestScripts. So from my understanding of python, the file TestScript.py is interpreted as module and the folder TestScripts is interpreted as package.
So, I tried it like this:
moduleName = 'TestScripts.TestScript'
className = 'TestScript'
module = __import__(moduleName)
targetClass = getattr(module, className)
instance = targetClass()
But an AttributeError occurs with the message "module 'TestScripts' has no attribute 'TestScript'".
I expected instance to be an object of the TestScript class after the execution of this code snippet. What do I miss?
For the full explanation see __import__
To make your code do what you want:
moduleName = 'Testscripts.TestScript'
className = 'TestClass'
module = __import__(moduleName) #could use fromlist here as #Iguananaut suggests
targetModule = getattr(module, "TestScript")
targetClass = getattr(targetModule, className)
instance = targetClass()
As a note - python docs strongly discourage direct use of __import__
I've a class in python that contains a static method. I want to mock.patch it in order to see if it was called. When trying to do it I get an error:
AttributeError: path.to.A does not have the attribute 'foo'
My setup can be simplified to:
class A:
#staticMethod
def foo():
bla bla
Now the test code that fails with error:
def test():
with mock.patch.object("A", "foo") as mock_helper:
mock_helper.return_value = ""
A.some_other_static_function_that_could_call_foo()
assert mock_helper.call_count == 1
You can always use patch as a decorator, my preferred way of patching things:
from mock import patch
#patch('absolute.path.to.class.A.foo')
def test(mock_foo):
mock_foo.return_value = ''
# ... continue with test here
EDIT: Your error seems to hint that you have a problem elsewhere in your code. Possibly some signal or trigger that requires this method that is failing?
I was getting that same error message when trying to patch a method using the #patch decorator.
Here is the full error I got.
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/tornado/testing.py", line 136, in __call__
result = self.orig_method(*args, **kwargs)
File "/usr/local/lib/python3.6/unittest/mock.py", line 1171, in patched
arg = patching.__enter__()
File "/usr/local/lib/python3.6/unittest/mock.py", line 1243, in __enter__
original, local = self.get_original()
File "/usr/local/lib/python3.6/unittest/mock.py", line 1217, in get_original
"%s does not have the attribute %r" % (target, name)
AttributeError: <module 'py-repo.models.Device' from
'/usr/share/projects/py-repo/models/Device.py'> does not have the attribute 'get_device_from_db'
What I ended up doing to fix this was changing the patch decorator I used
from
#patch('py-repo.models.Device.get_device_from_db')
to #patch.object(DeviceModel, 'get_device_from_db')
I really wish I could explain further why that was the issue but I'm still pretty new to Python myself. The patch documentation was especially helpful in figuring out what was available to work with. Important: I should note that get_device_from_db uses the #staticmethod decorator which may be changing things. Hope it helps though.
What worked for me:
#patch.object(RedisXComBackend, '_handle_conn')
def test_xcoms(self, mock_method: MagicMock):
mock_method.return_value = fakeredis.FakeStrictRedis()
'_handle_conn' (static function) looks like this:
#staticmethod
def _handle_conn():
redis_hook = RedisHook()
conn: Redis = redis_hook.get_conn()
I'm writing a piece of software over on github. It's basically a tray icon with some extra features. I want to provide a working piece of code without actually having to make the user install what are essentially dependencies for optional features and I don't actually want to import things I'm not going to use so I thought code like this would be "good solution":
---- IN LOADING FUNCTION ----
features = []
for path in sys.path:
if os.path.exists(os.path.join(path, 'pynotify')):
features.append('pynotify')
if os.path.exists(os.path.join(path, 'gnomekeyring.so')):
features.append('gnome-keyring')
#user dialog to ask for stuff
#notifications available, do you want them enabled?
dlg = ConfigDialog(features)
if not dlg.get_notifications():
features.remove('pynotify')
service_start(features ...)
---- SOMEWHERE ELSE ------
def service_start(features, other_config):
if 'pynotify' in features:
import pynotify
#use pynotify...
There are some issues however. If a user formats his machine and installs the newest version of his OS and redeploys this application, features suddenly disappear without warning. The solution is to present this on the configuration window:
if 'pynotify' in features:
#gtk checkbox
else:
#gtk label reading "Get pynotify and enjoy notification pop ups!"
But if this is say, a mac, how do I know I'm not sending the user on a wild goose chase looking for a dependency they can never fill?
The second problem is the:
if os.path.exists(os.path.join(path, 'gnomekeyring.so')):
issue. Can I be sure that the file is always called gnomekeyring.so across all the linux distros?
How do other people test these features? The problem with the basic
try:
import pynotify
except:
pynotify = disabled
is that the code is global, these might be littered around and even if the user doesn't want pynotify....it's loaded anyway.
So what do people think is the best way to solve this problem?
The try: method does not need to be global — it can be used in any scope and so modules can be "lazy-loaded" at runtime. For example:
def foo():
try:
import external_module
except ImportError:
external_module = None
if external_module:
external_module.some_whizzy_feature()
else:
print("You could be using a whizzy feature right now, if you had external_module.")
When your script is run, no attempt will be made to load external_module. The first time foo() is called, external_module is (if available) loaded and inserted into the function's local scope. Subsequent calls to foo() reinsert external_module into its scope without needing to reload the module.
In general, it's best to let Python handle import logic — it's been doing it for a while. :-)
You might want to have a look at the imp module, which basically does what you do manually above. So you can first look for a module with find_module() and then load it via load_module() or by simply importing it (after checking the config).
And btw, if using except: I always would add the specific exception to it (here ImportError) to not accidently catch unrelated errors.
Not sure if this is good practice, but I created a function that does the optional import (using importlib) and error handling:
def _optional_import(module: str, name: str = None, package: str = None):
import importlib
try:
module = importlib.import_module(module)
return module if name is None else getattr(module, name)
except ImportError as e:
if package is None:
package = module
msg = f"install the '{package}' package to make use of this feature"
raise ValueError(msg) from e
If an optional module is not available, the user will at least get the idea what to do. E.g.
# code ...
if file.endswith('.json'):
from json import load
elif file.endswith('.yaml'):
# equivalent to 'from yaml import safe_load as load'
load = _optional_import('yaml', 'safe_load', package='pyyaml')
# code using load ...
The main disadvantage with this approach is that your imports have to be done in-line and are not all on the top of your file. Therefore, it might be considered better practice to use a slight adaptation of this function (assuming that you are importing a function or the like):
def _optional_import_(module: str, name: str = None, package: str = None):
import importlib
try:
module = importlib.import_module(module)
return module if name is None else getattr(module, name)
except ImportError as e:
if package is None:
package = module
msg = f"install the '{package}' package to make use of this feature"
import_error = e
def _failed_import(*args):
raise ValueError(msg) from import_error
return _failed_import
Now, you can make the imports with the rest of your imports and the error will only be raised when the function that failed to import is actually used. E.g.
from utils import _optional_import_ # let's assume we import the function
from json import load as json_load
yaml_load = _optional_import_('yaml', 'safe_load', package='pyyaml')
# unimportant code ...
with open('test.txt', 'r') as fp:
result = yaml_load(fp) # will raise a value error if import was not successful
PS: sorry for the late answer!
Another option is to use #contextmanager and with. In this situation, you do not know beforehand which dependencies are needed:
from contextlib import contextmanager
#contextmanager
def optional_dependencies(error: str = "ignore"):
assert error in {"raise", "warn", "ignore"}
try:
yield None
except ImportError as e:
if error == "raise":
raise e
if error == "warn":
msg = f'Missing optional dependency "{e.name}". Use pip or conda to install.'
print(f'Warning: {msg}')
Usage:
with optional_dependencies("warn"):
import module_which_does_not_exist_1
import module_which_does_not_exist_2
z = 1
print(z)
Output:
Warning: Missing optional dependency "module_which_does_not_exist_1". Use pip or conda to install.
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In [43], line 5
3 import module_which_does_not_exist_2
4 z = 1
----> 5 print(z)
NameError: name 'z' is not defined
Here, you should define all your imports immediately after with. The first module which is not installed will throw ImportError, which is caught by optional_dependencies. Depending on how you want to handle this error, it will either ignore it, print a warning, or raise it again.
The entire code will only run if all the modules are installed.
Here's a production-grade solution, using importlib and Pandas's import_optional_dependency as suggested by #dre-hh
from typing import *
import importlib, types
def module_exists(
*names: Union[List[str], str],
error: str = "ignore",
warn_every_time: bool = False,
__INSTALLED_OPTIONAL_MODULES: Dict[str, bool] = {}
) -> Optional[Union[Tuple[types.ModuleType, ...], types.ModuleType]]:
"""
Try to import optional dependencies.
Ref: https://stackoverflow.com/a/73838546/4900327
Parameters
----------
names: str or list of strings.
The module name(s) to import.
error: str {'raise', 'warn', 'ignore'}
What to do when a dependency is not found.
* raise : Raise an ImportError.
* warn: print a warning.
* ignore: If any module is not installed, return None, otherwise,
return the module(s).
warn_every_time: bool
Whether to warn every time an import is tried. Only applies when error="warn".
Setting this to True will result in multiple warnings if you try to
import the same library multiple times.
Returns
-------
maybe_module : Optional[ModuleType, Tuple[ModuleType...]]
The imported module(s), if all are found.
None is returned if any module is not found and `error!="raise"`.
"""
assert error in {"raise", "warn", "ignore"}
if isinstance(names, (list, tuple, set)):
names: List[str] = list(names)
else:
assert isinstance(names, str)
names: List[str] = [names]
modules = []
for name in names:
try:
module = importlib.import_module(name)
modules.append(module)
__INSTALLED_OPTIONAL_MODULES[name] = True
except ImportError:
modules.append(None)
def error_msg(missing: Union[str, List[str]]):
if not isinstance(missing, (list, tuple)):
missing = [missing]
missing_str: str = ' '.join([f'"{name}"' for name in missing])
dep_str = 'dependencies'
if len(missing) == 1:
dep_str = 'dependency'
msg = f'Missing optional {dep_str} {missing_str}. Use pip or conda to install.'
return msg
missing_modules: List[str] = [name for name, module in zip(names, modules) if module is None]
if len(missing_modules) > 0:
if error == "raise":
raise ImportError(error_msg(missing_modules))
if error == "warn":
for name in missing_modules:
## Ensures warning is printed only once
if warn_every_time is True or name not in __INSTALLED_OPTIONAL_MODULES:
print(f'Warning: {error_msg(name)}')
__INSTALLED_OPTIONAL_MODULES[name] = False
return None
if len(modules) == 1:
return modules[0]
return tuple(modules)
Usage: ignore errors (error="ignore", default behavior)
Suppose we want to run certain code only if the required libraries exists:
if module_exists("pydantic", "sklearn"):
from pydantic import BaseModel
from sklearn.metrics import accuracy_score
class AccuracyCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(accuracy_score(y_true, y_pred), self.num_decimals)
print("Defined AccuracyCalculator in global context")
If either dependencies pydantic or skelarn do not exist, then the class AccuracyCalculator will not be defined and the print statement will not run.
Usage: raise ImportError (error="raise")
Alternatively, you can raise a error if any module does not exist:
if module_exists("pydantic", "sklearn", error="raise"):
from pydantic import BaseModel
from sklearn.metrics import accuracy_score
class AccuracyCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(accuracy_score(y_true, y_pred), self.num_decimals)
print("Defined AccuracyCalculator in global context")
Output:
line 60, in module_exists(error, __INSTALLED_OPTIONAL_MODULES, *names)
58 if len(missing_modules) > 0:
59 if error == "raise":
---> 60 raise ImportError(error_msg(missing_modules))
61 if error == "warn":
62 for name in missing_modules:
ImportError: Missing optional dependencies "pydantic" "sklearn". Use pip or conda to install.
Usage: print a warning (error="warn")
Alternatively, you can print a warning if the module does not exist.
if module_exists("pydantic", "sklearn", error="warn"):
from pydantic import BaseModel
from sklearn.metrics import accuracy_score
class AccuracyCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(accuracy_score(y_true, y_pred), self.num_decimals)
print("Defined AccuracyCalculator in global context")
if module_exists("pydantic", "sklearn", error="warn"):
from pydantic import BaseModel
from sklearn.metrics import roc_auc_score
class RocAucCalculator(BaseModel):
num_decimals: int = 5
def calculate(self, y_pred: List, y_true: List) -> float:
return round(roc_auc_score(y_true, y_pred), self.num_decimals)
print("Defined RocAucCalculator in global context")
Output:
Warning: Missing optional dependency "pydantic". Use pip or conda to install.
Warning: Missing optional dependency "sklearn". Use pip or conda to install.
Here, we ensure that only one warning is printed for each missing module, otherwise you would get a warning each time you try to import.
This is very useful for Python libraries where you might try to import the same optional dependencies many times, and only want to see one Warning.
You can pass warn_every_time=True to always print the warning when you try to import.
I'm really excited to share this new technique I came up with to handle optional dependencies!
The concept is to produce the error when the uninstalled package is used not imported.
Just add a single call before your imports. You don't need to change any code at all. No more using try: when importing. No more using conditional skip decorators when writing tests.
Main components
An importer to return a fake module for missing imports
A fake module that raises an exception when it's used
A custom Exception that will skip tests automatically if raised within one
Minimal Code Example
import sys
import importlib
from unittest.case import SkipTest
from _pytest.outcomes import Skipped
class MissingOptionalDependency(SkipTest, Skipped):
def __init__(self, msg=None):
self.msg = msg
def __repr__(self):
return f"MissingOptionalDependency: {self.msg}" if self.msg else f"MissingOptionalDependency"
class GeneralImporter:
def __init__(self, *names):
self.names = names
sys.meta_path.insert(0, self)
def find_spec(self, fullname, path=None, target=None):
if fullname in self.names:
return importlib.util.spec_from_loader(fullname, self)
def create_module(self, spec):
return FakeModule(name=spec.name)
def exec_module(self, module):
pass
class FakeModule:
def __init__(self, name):
self.name = name
def __call__(self, *args, **kwargs):
raise MissingOptionalDependency(f"Optional dependency '{self.name}' was used but it isn't installed.")
GeneralImporter("notinstalled")
import notinstalled # No error
print(notinstalled) # <__main__.FakeModule object at 0x0000014B7F6D9E80>
notinstalled() # MissingOptionalDependency: Optional dependency 'notinstalled' was used but it isn't installed.
Package
The technique above has some shortcomings that my package fixes.
It's open-source, lightweight, and has no dependencies!
Some key differences to the example above:
Covers more than 100 dunder methods (All tested)
Covers 15 common dunder attribute lookups
Entry function is generalimport which returns an ImportCatcher
ImportCatcher holds names, scope, and caught names
It can be enabled and disabled
The scope prevents external packages from being affected
Wildcard support to allow any package to be imported
Puts the importer first in sys.meta_path
Lets it catch namespace imports (Usually occurs with uninstalled packages)
Generalimport on GitHub
pip install generalimport
Minimal example
from generalimport import generalimport
generalimport("notinstalled")
from notinstalled import missing_func # No error
missing_func() # Error occurs here
The readme on GitHub goes more in-depth
One way to handle the problem of different dependencies for different features is to implement the optional features as plugins. That way the user has control over which features are activated in the app but isn't responsible for tracking down the dependencies herself. That task then gets handled at the time of each plugin's installation.
I am trying to run a python project. Some part of the code calls a serializer with the following code:
try:
fo = open(data_file, "rb")
except IOError:
print "Couldn't open data file: %s" % data_file
return
try:
myobject = pickle.load(fo)
except:
fo.close()
print "Unexpected error:", sys.exc_info()[0]
raise
fo.close()
return myobject
When this part of the code is run, I get an error on
myobject = pickle.load(fo)
The error is:
myobject = pickle.load(fo)
File "/cs/local/lib/pkg/epd-7.3.1/lib/python2.7/pickle.py", line 1378, in load
return Unpickler(file).load()
File "/cs/local/lib/pkg/epd-7.3.1/lib/python2.7/pickle.py", line 858, in load
dispatch[key](self)
File "/cs/local/lib/pkg/epd-7.3.1/lib/python2.7/pickle.py", line 1090, in load_global
klass = self.find_class(module, name)
File "/cs/local/lib/pkg/epd-7.3.1/lib/python2.7/pickle.py", line 1124, in find_class
__import__(module)
ImportError: No module named label
I have looked at : Import Error using cPickle in Python
but I cant use any of the solutions because:
"You can open the file binarily and replace options with the module you replaced the old module options with." => I dont know which binary file the solution is refering to. I dont seem to have any binary file in my package.
In my package, I dont have a module named label to import it.
I'm very lost and I would appreciate any help, any suggestions.
When pickle serializes an object, it serializes modules by reference. So if you have a function or some other python object that has a call stack, it might refer to the module label, which cannot be found. If you have a serialized class, class instance, function, or especially a closure… you might have a import label in the source code used to build that object. a pickled object is a set of instructions for python for how to turn binary bits of information into a python object. if some of the bits are missing, such as a module… (pickle again stores this by reference), then your unpickle will fail.
You could either try to install the label module, or you could ask the party who serialized the object to serialize it with a serializer that serializes the module itself instead of doing so by reference. I think you can do this with the dill serializer.
If the person who serialized the object had label in their globals, and there was a closure being serialized, pickle includes everything in globals… so it might not even be relevant, but you'd need it do unserialize the object. You could also ask for a re-pickle by a serializer that is more cautious about including globals, like dill or cloudpickle.
That's basically what Import Error using cPickle in Python is saying in a less general way.
My scrpt has the following line:
libro_dia = xlrd.open_workbook(file_contents = libro_dia)
When libro_dia is not valid, it raises the following error:
XLRDError: Unsupported format, or corrupt file: Expected BOF record; found '<!DOCTYP'
I whant to handle this error, so I write:
try:
libro_dia = xlrd.open_workbook(file_contents = libro_dia)
except XLRDError:
no_termina = False
But it raises the following error:
NameError: name 'XLRDError' is not defined
What's going on?
You don't have XLRDError imported. I'm not familiar with xlrd, but something like:
from xlrd import XLRDError
might work. Alternatively, qualify your Error when handling it:
try:
libro_dia = xlrd.open_workbook(file_contents = libro_dia)
except xlrd.XLRDError: #<-- Qualified error here
no_termina = False
The above is assuming you have the following import:
import xlrd
In response to your comment:
There are several ways to use imports in python. If you import by using import xlrd, then you will have to qualify every object in that module as xlrd.SomeObject. An alternative way is by using the form from xlrd import *, which would allow you to reference the XLRD error without its' module namespace. This is lazy and a bad idea though, as it can lead to namespace clashes. If you would like to reference the error without qualifying it, the correct way to do it would be from xlrd import XLRDError, which would allow you to say except XLRDError. Read more about Python Modules
XLRDError is a custom exception and must be imported to your namespace just like any other object.
Edit: As Burhan Khalid has noted, you could just modify the except block to except xlrd.XLRDError if you already have import xlrd in your module.
Try using
xlrd.XLRDError as e and e.message should contain the error string
Sample:
try:
workbook = xlrd.open_workbook(sys.argv[1])
except xlrd.XLRDError as e:
print e.message
sys.exit(-1)