Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow abstract class in factory methods #13905

Closed
anis-campos opened this issue Oct 15, 2022 · 3 comments
Closed

Allow abstract class in factory methods #13905

anis-campos opened this issue Oct 15, 2022 · 3 comments
Labels

Comments

@anis-campos
Copy link

anis-campos commented Oct 15, 2022

Feature

Let's begin by defining a repository pattern that defines a simple CRUD behaviour, allowing to persist a given resource

class IRepository(Generic[T], ABC):
    @abstractmethod
    def create(self, item: T): -> T ...
    @abstractmethod
    def read(self, id:str): -> T ...
    @abstractmethod
    def update(self, item:T)->T: ...
    @abstractmethod
    def delete(self, id:str)->None: ...

class IPersonRepo(IRepository[Person]):
    """Allows CRUD over persons plus some complex persistence use cases"""

class IBookRepo(IRepository[Book]):
    """Allows CRUD over books plus some complex persistence use cases"""

Then, let's say that the app uses different implementations of those interfaces depending on the environment. So to keep it clean and steady, we provide a simple factory that provides the appropriate instance given the context.

TRepo=TypeVar("TRepo", bound=IRepository)

def get_repo(cls: Type[TRepo])-> TRepo: ...

Then, once the app setup is done, in the use case we can ask for the repository and start using it. But alas we are stuck here

book_repo = get_repo(IBookRepo) 

>>> error: Only concrete class can be given where "Type[IBookRepo]" is expected  [misc]

Pitch

I could find this error associated in #5374 with a clear answer from @ilevkivskyi stating that this shall be forbidden.

I assume that the issue here is the fact that it makes it very unsafe to instantiate a given type if it is allowed to pass abstract classes, I have no objections here.

But it would be nice to still allow this kind of factory pattern that decouples interfaces and implementations.

I'm thinking that we could provide a allow_abstract inside TypeVar, as in TRepo=TypeVar("TRepo", bound=IRepository, allow_abstract=True), so get_repo can freely receive abstract types and return a concrete implementation ?

@anis-campos
Copy link
Author

Note: There is alternatives

One is to no have type inference:

def get_repo(cls: Any)-> Any: ...

book_repo: IBookRepo = get_repo(IBookRepo) 

It works, but at the expenses of not type checking, not really satisfying.

The other is to ignore the error at every call of the get_repo, but it is a toll that we are not ready to pay.

The only viable solution is to simply explicitly resolve every interface via a dedicated function, like so

def get_book_repo() -> IBookRepo: ...
def get_person_repo() -> IPersonRepo: ...

This seems to do the trick and might be enough, even though it lacks in "finesse".

@ilevkivskyi
Copy link
Member

A possible way can be to have something like AbstractType[T] in mypy_extensions which you can't instantiate, but where you can pass abstract classes. I am however not sure this is really worth it now that we have a dedicate error code for this error. You can just do --disable-error-code=type-abstract

@anis-campos
Copy link
Author

anis-campos commented Oct 17, 2022

I tough about AbstractType as well but it seems a tiny bit overkill as this will only serve for one purpose: factories/service locators.

I did not realise this was under a special error code. This could be enough, granted that we are sure enough that we will not introduce bugs in other use case protected by this check.

Thanks for the reply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants