If the other class goes out of normal collaborative inheritance for its instances creation process, and even for picking the classes which to instantiate, then your best approach there will certainly not be inheritance.
You will be better creating an association with one instance of the object in that other hierarchy, and keep yourself a clean and uniform interface acording to your needs.
That is:
class MyCustomBase:
def __init__(self, [enough parameters to instantiate the 3rdy party thing you need]):
self.api = third_party_factory(parameters)
...
...
def stantard_comunicate(self, data):
if isinstance(self, api, TypeOne):
data = transform_data_for_type_one(data)
self.api.comunicate_one(data)
elif isinstance(self, api, TypeTwo)
data = transform_data_for_type_two(data)
self.api.comunicate_two(data)
...
If the 3rdy party lib has lots of methods and attributes, most of which you
don't need to customize, you don't need to write then down one by one, either -
you can customize attribute access on your wrapper class to defer to the wrapped
attribute/method directly with a simple __getattr__ method. Inside your class above add:
def __getattr__(self, attr):
return getattr(self.api, attr)
Without any inheritance.
If you need inheritance...
Sometimes you will need your proxy object to "be" of the other kind, such as when passing your own instances to methods or classes provided by the other library.
You can then force dynamic inheritance without meddling with the other side's factory method by dynamically creating a class with a much simpler factory method on your side:
def myfactory(...):
instance = thirdy_party_factory(...)
class MyClass(type(instance)):
def __init__(self, ...):
super().__init__(...)
self.my_init_code()
def my_init_code(self):
# whatver you need to initialize the attributes
# you use go here
...
# other methods you may want to customize can make use
# of "super()" normally
# Convert "other_instance" to be of type "my_instance":
instance.__class__ = MyClass
return instance
(If many such instances will be created in a long-lived process, then you should cache the dynamically created classes - The simpler way is just defer the class factory to an even simpler factory method that takes the parent class as parameter, and use Python's built-in "lru_cache" for that):
from functools import lru_cache
@lru_cache()
der inner_factory(Base):
class MyClass(type(instance)):
def __init__(self, ...):
super().__init__(...)
self.my_init_code()
def my_init_code(self):
# whatver you need to initialize the attributes
# you use go here
...
# other methods you may want to customize can make use
# of "super()" normally
return MyClass
def myfactory(...):
instance = thirdy_party_factory(...)
MyClass = inner_factory(type(instance))
# Convert "other_instance" to be of type "my_instance":
instance.__class__ = MyClass
return instance
Monkey Patching
The examples above are both on "your side only" of the story - but depending on how you are using things and on the nature of the other library, it may be easier to "monkey patch" it - that is: to replace, in the namespace of the 3rdy party factory method, the base classes it intends to use by your derived classes -so that your derieved classes will be used by the library's factory.
Monkey patching in Python is just a matter of assigning objects - but the standard library have a unittest.mock.patch callable that offers an utility that not only performs the patching, but takes care of the clean-up, restoring the original values, after the patched use is over.
If your library's factory do not use any Base classes itself, and builds all of the class inside the factory body, this approach can't be used. (and keep in mind that as this is not a proper "collaborative" practice, that library's authors are free to change this part of the design in any release).
But essentially:
from unittest.mock import patch
from thirdparty.bases import ThirdyBase
from thirdyparty.factories import factory
class MyStandardBase(ThirdyBase):
def implement_standard_api(self, ...):
...
def mycode():
# Bellow, the last item on the dotted path is the name
# of the base class _inside_ the module that defines the
# factory function.
with patch("thirdyparty.factories.ThirdyBase", MyStandardBase):
myinstance = factory(...)