Parameterizing Python Mixins - Generic Mixins

49 Views Asked by At

An API I'm developing a Python client for has various endpoints, each supporting different methods. As an example, let's call these endpoints invoice, user and address. The API also doesn't really delete items when calling the DELETE verb (e.g. DELETE /invoice/123), instead they're soft deleted and placed in some "recycle bin".

In addition to these endpoints, the API exposes a generic endpoint called "wastebasket", offering the following endpoints

  • GET /wastebasket/<endpoint>/ - returns all soft deleted items of type <endpoint>, where <endpoint> can be any of the aforementioned endpoints
  • DELETE /wastebasket/<endpoint>/pk - fully deletes the item with primary key pk.

For each endpoint, I have a class which is defining the methods supported for this endpoint. For example:

from ..models.invoice import Invoice, InvoiceCreate
from ..core.protocol import IsEVClientProtocol

class InvoiceMixin:

    endpoint_name = "invoice"

    def get_invoices(
        self: IsEVClientProtocol, query: str = None, limit: int = 100
    ) -> list[Invoice]:
        # Fetches all invoices. Implementation omitted
        pass

    def create_invoice(self: IsEVClientProtocol, invoice: InvoiceCreate) -> Invoice:
        # Creates an invoice. Implementation omitted
        pass

There's more methods to the invoice endpoint and similar classes for the other endpoints, but you get the idea. I'm looking for a generic way to add the get_deleted_<endpoint> and purge_<endpoint> methods to those classes, without writing them in each class separately.

Thinking about Mixins specifically, is it possible to parameterise a Mixin in such a way that the methods can be added to this class (note that the name must be unique across endpoints, e.g. the method name should contain the endpoint name), and proper type hinting is desirable.

One option I've been looking into is a generator pattern for Mixins, similar to this:

from ...core.protocol import IsEVClientProtocol


def recycle_bin_mixin(endpoint_name: str, model_class):
    def get_deleted(self: IsEVClientProtocol) -> list[model_class]:
        # Implementation omitted
        pass

    class RecycleBinMixin:
        pass

    setattr(RecycleBinMixin, f"get_deleted_{endpoint_name}s", get_deleted)

    return RecycleBinMixin

Then use it like this:

class InvoiceMixin(recycle_bin_mixin("invoice", Invoice)):
    ...

In general this works, it does have the drawback that the methods added this way are not type hinted in any ways and IDEs won't offer auto completion. I also think that this is not the most efficient way of doing it.

How to do this in a better way?

0

There are 0 best solutions below