Sphinx-doc :automodule: with Mock imports - python

I'm attempting to use sphinx-doc :automodule: in conjunction with Mock-ed out modules as per this answer. Specifically I'm using Mock for PyQt5 module imports which are not available on ReadTheDocs.
Strangely, I'm finding that any class that inherits from a Mock-ed module's class is not included in the resulting documentation. It appears as though sphinx-doc can't see them for some reason.
My slightly-custom Mock is as follows:
from mock import Mock as MagicMock
class Mock(MagicMock):
__all__ = ['QApplication','pyqtSignal','pyqtSlot','QObject','QAbstractItemModel','QModelIndex','QTabWidget',
'QWebPage','QTableView','QWebView','QAbstractTableModel','Qt','QWidget','QPushButton','QDoubleSpinBox',
'QListWidget','QDialog','QSize','QTableWidget','QMainWindow','QTreeWidget',
'QAbstractItemDelegate','QColor','QGraphicsItemGroup','QGraphicsItem','QGraphicsPathItem',
'QGraphicsTextItem','QGraphicsRectItem','QGraphicsScene','QGraphicsView',]
def __init__(self, *args, **kwargs):
super(Mock, self).__init__()
#classmethod
def __getattr__(cls, name):
if name in ('__file__', '__path__'):
return os.devnull
else:
return Mock
#classmethod
def __setattr__(*args, **kwargs):
pass
def __setitem__(self, *args, **kwargs):
return
def __getitem__(self, *args, **kwargs):
return Mock
The __all__ is required to allow from x import * style imports for the PyQt5 classes.
I can confirm that changing the superclass to object results in the classes being correctly documented, as does remove the Mock (generating locally). Forcing the documentation by using :autoclass: results in a single line saying that the class inherits from Mock.

I resolved this in the end by not using Mock for Qt objects. In my application there is a qt.py wrapper file that handles differences between PyQt4 and PyQt5 and allows them to be subsequently imported for use (while ignoring the Qt namespace rearrangement).
In this file I wrapped the actual import code in a test for ReadTheDocs and then if detected returned a series of dummy classes inheriting directly from object. Additions were required where objects have attributes, but this is only used once in the code base. It'll need to be kept up to date, but it solves the problem.
# ReadTheDocs
ON_RTD = os.environ.get('READTHEDOCS', None) == 'True'
if not ON_RTD:
#... do the normal import here ...
else:
class QMockObject(object):
def __init__(self, *args, **kwargs):
super(QMockObject, self).__init__()
def __call__(self, *args, **kwargs):
return None
class QApplication(QMockObject):
pass
class pyqtSignal(QMockObject):
pass
class pyqtSlot(QMockObject):
pass
class QObject(QMockObject):
pass
class QAbstractItemModel(QMockObject):
pass
class QModelIndex(QMockObject):
pass
class QTabWidget(QMockObject):
pass
class QWebPage(QMockObject):
pass
class QTableView(QMockObject):
pass
class QWebView(QMockObject):
pass
class QAbstractTableModel(QMockObject):
pass
class Qt(QMockObject):
DisplayRole = None
class QWidget(QMockObject):
pass
class QPushButton(QMockObject):
pass
class QDoubleSpinBox(QMockObject):
pass
class QListWidget(QMockObject):
pass
class QDialog(QMockObject):
pass
class QSize(QMockObject):
pass
class QTableWidget(QMockObject):
pass
class QMainWindow(QMockObject):
pass
class QTreeWidget(QMockObject):
pass
class QAbstractItemDelegate(QMockObject):
pass
class QColor(QMockObject):
pass
class QGraphicsItemGroup(QMockObject):
pass
class QGraphicsItem(QMockObject):
pass
class QGraphicsPathItem(QMockObject):
pass
class QGraphicsTextItem(QMockObject):
pass
class QGraphicsRectItem(QMockObject):
pass
class QGraphicsScene(QMockObject):
pass
class QGraphicsView(QMockObject):
pass
app = None

Related

Django - call Manager with multiple inherited classes

So I have this class that helps me override the update method of a queryset:
class QuerySetUpdateOverriden(QuerySet, object):
def update(self, *args, **kwargs):
super().update(*args, **kwargs)
if hasattr(self, 'method_from_object'):
self.method_from_object()
return
and here's my class where I use it:
class MyObject:
objects = QuerySetUpdateOverriden.as_manager()
def method_from_object(self):
print("called")
the print statement is never reached.
And I get why - the objects field doesn't inherit MyObject too.
So, the question is - how can I make it inherit MyObject so method_from_object will be called?
Thanks.
You test if self has a method called 'method_from_object', but your QuerySetUpdateOverriden has no method call like this. And MyObject does not inherit from QuerySetUpdateOverriden.
This code would be work i think:
class MyObjectManager(QuerySetUpdateOverriden.as_manager()):
def method_from_object(self):
print("called")
class MyObject(models.Model):
objects = QuerySetUpdateOverriden.as_manager()

How to typehint that an object of a class is also adhering to a Protocol in Python?

I have a set of classes, Lets call them Foo and Bar, where both inherit from a base class Father that is defined outside of the current scope (not by me). I have definied a protocol class DummyProtocol that has a function do_something.
class DummyProtocol(Protocol):
def do_something(self):
...
class Foo(Father):
def do_something(self):
pass
class Bar(Father):
def do_something(self):
pass
I have a function create_instance.
def create_dummy_and_father_instance(cls, *args, **kwargs):
return cls(*args, **kwargs)
I want to typehint it in a way, that cls is typehinted to accept a class that is of type Father that also implements the DummyProtocol.
So I changed the function to this to indicate that cls is a type that inherit from both Father and DummyProtocol
def create_dummy_and_father_instance(
cls: Type[tuple[Father, DummyProtocol]], *args, **kwargs
):
return cls(*args, **kwargs)
But I get this error in mypy:
Cannot instantiate type "Type[Tuple[Father, DummyProtocol]]"
I came across the same issue and found this discussion on proposed Intersection types which seem to be exactly what is needed (e.g. see this comment).
Unfortunately this feature is not yet supported by the Python typing system, but there's a PEP in the making.
You can define a second Father class which inherits from Father and Protocol (see also mypy: how to verify a type has multiple super classes):
class DummyProtocol(Protocol):
def do_something(self):
...
class Father:
pass
class Father2(Father, DummyProtocol):
pass
class Foo(Father2):
def do_something(self):
pass
class Bar(Father2):
def do_something(self):
pass
class FooNot(Father):
pass
def create_dummy_and_father_instance(
cls: Type[Father2]
):
return cls()
create_dummy_and_father_instance(Foo)
create_dummy_and_father_instance(Bar)
create_dummy_and_father_instance(FooNot) # mypy error ok

How to mock a #property method with "self" as parameter?

I'm trying to mock a property on only a specific instance of a class. To simplify the example, I have a Thing which gets initialised with a name, and based on this name the class will load a configuration file in a pre-defined location /conf/{name}_conf.yaml.
When testing, a couple of instances of Thing are created and I just want to override the configuration for one of these.
I commented below the initial code that I had to change to make it work:
class Thing():
def __init__(self, name):
self.name = name
# I wasn't able to mock this:
# self.configuration_name = f'/configuration/{self.name}_configuration.yaml'
# #property <- nor this
def configuration_filename(self):
return f'/configuration/{self.name}_configuration.yaml'
And in my tests, the mock should be able to take as parameter a different configuration file (specific to the test), but only be applied to the instance of Thing named test_thing.
I got it working with the above implementation like this:
configuration_filename_orig = Thing.configuration_filename
def my_patched_configuration_filename(self, configuration_filename, *args, **kwargs):
if self.slug == 'cmstest':
return configuration_filename
else:
return configuration_filename_orig(self, *args, **kwargs)
Then I can "inject" a custom test configuration file for each test class like this:
from functools import partial
from test.utils import my_patched_configuration_filename
...
#patch.object(Thing, 'configuration_filename', autospec=True, side_effect=partial(my_patched_configuration_filename, configuration_filename='testdata/test_1.yaml'))
class ConfigurationTests(TestCase):
def test_1(self, mocked_conf):
# test something
def test_2(self, mocked_conf):
# test something else
#patch.object(Thing, 'configuration_filename', autospec=True, side_effect=partial(my_patched_configuration_filename, configuration_filename='testdata/test_2.yaml'))
class ConfigurationTestsAdvanced(TestCase):
def test_1(self, mocked_conf):
# test something
def test_2(self, mocked_conf):
# test something else
Now... this works but I wonder if there's a way to do something similar but with a real property on the Thing class (either with the #property decorator or with the property initialised in the the __init__ method).
I've spent a couple of hours trying different things... but the main issue seems that using return_value doesn't pass the self argument to the mock, so I can't use it.
Any idea ?
ok there might be a better way but I got this working as follow:
I can use the #property decorator on my class, that's what I want to mock:
class Thing():
def __init__(self, name):
self.name = name
#property
def configuration_filename(self):
return f'/configuration/{self.name}_configuration.yaml'
I create a new mock class, based on Mock:
# first copy original
configuration_filename_orig = Thing.configuration_filename.__get__
class ConfigurationPropertyMock(Mock):
# and here, add the `self` to the args
def __get__(self, obj, obj_type=None, *args, **kwargs):
return self(obj, obj_type, *args, **kwargs)
def patched_filename(self, *args, **kwargs):
configuration_filename = kwargs.pop('configuration_filename')
if self.slug == 'cmstest' and configuration_filename:
return configuration_filename
else:
return configuration_filename_orig(self, *args, **kwargs)
And I patch the test class where I can pass a custom configuration_filename:
from unittest.mock import patch
from tests.utils import ConfigurationPropertyMock, patched_filename
...
#patch('somewhere.Thing.configuration_filename',
new_callable=ConfigurationPropertyMock,
side_effect=partial(patched_filename, configuration_filename='test_conf.yaml'))
)
class YAMLApiConfigurationTests(TestCase):
def test_api_configuration_document(self, mocked_conf):
# test here, the test configuration is loaded
pass
VoilĂ  :)

Mock class instances without calling `__init__` and mock their respective attributes

I have a class MyClass with a complex __init__ function.
This class had a method my_method(self) which I would like to test.
my_method only needs attribute my_attribute from the class instance.
Is there a way I can mock class instances without calling __init__ and by setting the attributes of each class instance instead?
What I have:
# my_class.py
from utils import do_something
class MyClass(object):
def __init__(self, *args, **kwargs):
# complicated function which I would like to bypass when initiating a mocked instance class
pass
def my_method(self):
return do_something(self.my_attribute)
What I tried
#mock.patch("my_class.MyClass")
def test_my_method(class_mock, attribute):
instance = class_mock.return_value
instance.my_attribute = attribute
example_instance = my_class.MyClass()
out_my_method = example_instance.my_method()
# then perform some assertions on `out_my_method`
however this still makes usage of __init__ which I hope we can by-pass or mock.
As I mentioned in the comments, one way to test a single method without having to create an instance is:
MyClass.my_method(any_object_with_my_attribute)
The problem with this, as with both options in quamrana's answer, is that we have now expanded the scope of any future change just because of the tests. If a change to my_method requires access to an additional attribute, we now have to change both the implementation and something else (the SuperClass, the MockMyClass, or in this case any_object_with_my_attribute_and_another_one).
Let's have a more concrete example:
import json
class MyClass:
def __init__(self, filename):
with open(filename) as f:
data = json.load(f)
self.foo = data.foo
self.bar = data.bar
self.baz = data.baz
def my_method(self):
return self.foo ** 2
Here any test that requires an instance of MyClass. is painful because of the file access in __init__. A more testable implementation would split apart the detail of how the data is accessed and the initialisation of a valid instance:
class MyClass:
def __init__(self, foo, bar, baz):
self.foo = foo
self.bar = bar
self.baz = baz
def my_method(self):
return self.foo ** 2
#classmethod
def from_json(cls, filename):
with open(filename) as f:
data = json.load(f)
return cls(data.foo, data.bar, data.baz)
You have to refactor MyClass("path/to/file") to MyClass.from_json("path/to/file"), but wherever you already have the data (e.g. in your tests) you can use e.g. MyClass(1, 2, 3) to create the instance without requiring a file (you only need to consider the file in the tests of from_json itself). This makes it clearer what the instance actually needs, and allows the introduction of other ways to construct an instance without changing the interface.
There are at least two options I can see:
Extract a super class:
class SuperClass:
def __init__(self, attribute):
self.my_attribute = attribute
def my_method(self):
return do_something(self.my_attribute)
class MyClass(SuperClass):
def __init__(self, *args, **kwargs):
super().__init__(attribute) # I don't know where attribute comes from
# complicated function which I would like to bypass when initiating a mocked instance class
Your tests can instantiate SuperClass and call my_method().
Inherit from MyClass as is and make your own simple __init__():
class MockMyClass(MyClass):
def __init__(self, attribute):
self.my_attribute = attribute
Now your test code can instantiate MockMyClass with the required attribute and call my_method()
For instance, you could write the test as follows
def test_my_method(attribute):
class MockMyClass(MyClass):
def __init__(self, attribute):
self.my_attribute = attribute
out_my_method = MockMyClass(attribute).my_method()
# perform assertions on out_my_method

Test abstract class calls parent method in Python

I'm currently refactoring features and I ended up with this abstractions
I have this classes
class AbstractClassA(SomeOtherAbstractClass):
#abstractmethod
def some_abstract_method(self):
pass
def my_method(self)):
service.some_method
class AbstractClassB(AbstractClassA):
#abstractmethod
def another_abstract_method(self):
pass
def some_abstract_method(self):
some_implementation
def my_method(self):
super().my_method()
do_any_other_stuff
And I need to test if the AbstractClassB.my_method calls super().my_method().
I've tried to test this by creating some ImplementationClass that inherits from AbstractClassB and then mocking the AbstractClassA.my_method and checking if it was called but it didn't work...
class AbstractClassBImplementation(AbstractClassB):
def some_abstract_method(self):
calls_service()
class TestAbstractClassB(TestCase):
#patch('module.submodule.AbstractClassA.my_method')
def test_class_b_calls_class_a_my_method(self, my_method_mock):
instance = AbstractClassBImplementation()
instance.my_method()
self.assertTrue(my_method_mock.called)
Someone know how to test this?

Categories

Resources