@chepner's answer is correct, though I didn't understand why or how it worked because I never understood the concept of mixins. I couldn't map it to my naive notion of multiple inheritance. So I did some testing with trial and error and now have more confidence that I have a better conceptual (though perhaps technically inaccurate) understanding of how they work. I had previously been conceptually dissuaded by the notion of "multiple inheritance" to imply multiple independent parent classes/objects. However (to use a geeky analogy) I now see it more like the parent being "Tuvix" from Star Trek Voyager. The parents (Tuvok and Neelix) are not independent individuals. There's only 1 parent: Tuvix, who is a merging of the 2 parents.
And from my testing, I have come to understand that the order of the superclasses establishes whose characteristics dominate. Precedence goes from left to right. Anything you "override" in the left-side class is what gets set/called when a derived class calls/gets it.
I don't need to reiterate @chepner's answer, but I will provide an example to demonstrate why it works... Take these classes as an example:
class mybaseclass():
"""a.k.a. TestCase"""
classvar = "base"
def member_override_test(self):
print(f"member_override_test in mybaseclass, classvar: [{self.classvar}]")
@classmethod
def classmethod_override_test(cls):
print(f"classmethod_override_test in mybaseclass, classvar: [{cls.classvar}]")
def run_member_super_test(self):
print(f"classvar: {self.classvar}")
print("Calling member_override_test from mybaseclass:")
self.member_override_test()
print("Calling classmethod_override_test from mybaseclass:")
self.classmethod_override_test()
print("Calling run_test from mybaseclass:")
self.run_test()
class mymixinclass():
"""a.k.a. TestSkeleton"""
classvar = "mixin"
def member_override_test(self):
print(f"member_override_test in mymixinclass, classvar: [{self.classvar}]")
@classmethod
def classmethod_override_test(cls):
print(f"classmethod_override_test in mymixinclass, classvar: [{cls.classvar}]")
class mypseudoderivedclass(mymixinclass, mybaseclass):
"""a.k.a. TracebaseTestCase"""
def member_override_test(self):
print(f"member_override_test in mypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.member_override_test")
super().member_override_test()
def classmethod_override_test(self):
print(f"classmethod_override_test in mypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.classmethod_override_test")
super().classmethod_override_test()
def run_test(self):
print(f"classvar: {self.classvar}")
print("Calling member_override_test from mypseudoderivedclass object:")
self.member_override_test()
print("Calling classmethod_override_test from mypseudoderivedclass object:")
self.classmethod_override_test()
class reversemypseudoderivedclass(mybaseclass, mymixinclass):
"""a.k.a. TracebaseTestCase - reversing the order of the mixins"""
def member_override_test(self):
print(f"member_override_test in reversemypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.member_override_test")
super().member_override_test()
def classmethod_override_test(self):
print(f"classmethod_override_test in reversemypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.classmethod_override_test")
super().classmethod_override_test()
def run_test(self):
print(f"classvar: {self.classvar}")
print("Calling member_override_test from mypseudoderivedclass object:")
self.member_override_test()
print("Calling classmethod_override_test from mypseudoderivedclass object:")
self.classmethod_override_test()
And here's what you see when you play with those classes in the python shell:
In [1]: from DataRepo.tests.tracebase_test_case import mypseudoderivedclass, reversemypseudoderivedclass
...: mpdc = mypseudoderivedclass()
In [2]: mpdc.run_test()
...:
classvar: mixin
Calling member_override_test from mypseudoderivedclass object:
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
In [3]: mpdc.member_override_test()
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
In [4]: mpdc.classmethod_override_test()
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
In [5]: mpdc.run_member_super_test()
classvar: mixin
Calling member_override_test from mybaseclass:
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
Calling classmethod_override_test from mybaseclass:
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
Calling run_test from mybaseclass:
classvar: mixin
Calling member_override_test from mypseudoderivedclass object:
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
In [6]: rmpdc = reversemypseudoderivedclass()
...: rmpdc.run_test()
classvar: base
Calling member_override_test from mypseudoderivedclass object:
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
In [7]: rmpdc.member_override_test()
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
In [8]: rmpdc.classmethod_override_test()
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
In [9]: rmpdc.run_member_super_test()
classvar: base
Calling member_override_test from mybaseclass:
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
Calling classmethod_override_test from mybaseclass:
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
Calling run_test from mybaseclass:
classvar: base
Calling member_override_test from mypseudoderivedclass object:
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
Note that the class variable classvar
's value and the method handling the (super) calls depend on the order of the mixins in the class's multiple inheritances.
Factory Alternative
Before I understood the mixins, I tried another solution that solves the sample problem in a different way. You can indeed parameterize the base class, which I learned from another answer, by creating a factory function, then vivify the derived classes by making factory calls.
Note, in this answer, I realized I could avoid overriding setUpClass
by setting the "class start time" in the member data:
import time
from django.test import TestCase, TransactionTestCase
LONG_TEST_THRESH_SECS = 20
LONG_TEST_ALERT_STR = f" [ALERT > {LONG_TEST_THRESH_SECS}]"
def test_case_class_factory(base_class):
class TracebaseTestCaseTemplate(base_class):
maxDiff = None
databases = "__all__"
classStartTime = time.time()
def setUp(self):
self.testStartTime = time.time()
def tearDown(self):
reportRunTime(self.id(), self.testStartTime)
@classmethod
def setUpTestData(cls):
super().setUpTestData()
reportRunTime(f"{cls.__name__}.setUpTestData", cls.classStartTime)
class Meta:
abstract = True
return TracebaseTestCaseTemplate
def reportRunTime(id, startTime):
t = time.time() - startTime
heads_up = "" # String to include for tests that run too long
if t > LONG_TEST_THRESH_SECS:
heads_up = LONG_TEST_ALERT_STR
print("TEST TIME%s: %s: %.3f" % (heads_up, id, t))
# Classes created by the factory with different base classes:
TracebaseTestCase = test_case_class_factory(TestCase)
TracebaseTransactionTestCase = test_case_class_factory(TransactionTestCase)
Both solutions work, and while I like the preserved inheritance of the factory method, once you understand mixins, I feel like @chepner's code is easier to read, so I will select his answer.