SOLID Principles and Imports in Python

Viewed 92

I frequently have this problem.

In my projects I have some user input that controls the flow of the code.

So let's say that the user will choose if the Fruit will be banana or apple and the code will do the right job.

I try to apply SOLID Principles in my code.

So I make a Juicable Interface for the fruits that will give me juice.


    # fruits/juicable.py
    
    from abc import ABC, abstractmethod
    
    class Juicable(ABC):
        @abstractmethod
        def squeeze(self):
            pass

Now I make my fruit in a file called banana.py

    # fruits/banana.py

    from fruits.juicable import Juicable

    class Banana(Juicable):
        def squeeze(self):
            print('Banana juice in the glass')

And I make another fruit in a file called apple.py

    # fruits/apple.py

    from fruits.juicable import Juicable

    class Apple(Juicable):
        def squeeze(self):
            print('Apple juice in the glass')

Of course all fruits that make juice will implement the Juicable Interface.

In another file I want to make juice.

    # bar/bartender.py
    
    from fruits.juicable import Juicable    

    def make_juice(fruit: Juicable):
        fruit.squeeze

The problem

Based on user input, I usually import programmatically the correct Python module.

ex. fruits/banana.py if the user chooses banana, I make a Banana() and call make_juice() from the bar/bartender.py

The first problem

is here because if I build the program as executable, there are no files... so it does not work.

To solve the problem I import all Juicables and just call the right one based on user's input.

But this breaks the Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Right?

By adding

    from fruits.banana import banana
    from fruits.apple import apple

To a higher level module, you need to update the module every time you add a new Juicable

Which also breaks the Single Responsibility Principle.

The second problem

appears when I want to list all Juicables to the user to choose.

First I list all juicables with os.listdir because I know they are in the fruits directory. ( And it seems just wrong! )

Then there is the same problem with no1. If I make an executable, there are no files. So I need again to import every one of them from a Higher-level module. And call __subclasses__() from Juicable class to find all Juicables.

The solution

In other languages you just call a similar subclasses() method and you are good to go but I don't know how imports work in other languages.

In Python. You need to import the file and load all Juicables first so later you can call a method to get you all subclasses ( all classes that implement the interface ).

But I want to know what are my subclasses to import them.

Is there a design flaw?

Where do I lose it?

Would it be better if somehow I import * blindly? And how is this done the right way?

0 Answers
Related