python-injector / python-injector/injector

Programming to an interface

Open
#149 9 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.5k
Forks
94
PR merge metrics
No merged PRs in 30d

Description

First of all, thanks for the library. It's a very good to see python moving towards this practices.

I have been playing with this library in conjunction with flask-injector (https://pypi.org/project/Flask-Injector - and thanks also for that) for a couple of months now, and I believe there are some limitations, if we want to follow and applies 3 principles from SOLID in large or particularly complex code base. Particularly, if I want to respect Liskov Subistitution , Interface segregation, and dependency inversion.
Some conversation was carried on here: https://github.com/alecthomas/injector/issues/123
I thought about not opening this thread at all, as it appears from there that you are not necessarily going to support that kind of behaviour, but at the same time it's probably an healthy discussion to have as what is asked there it's a very good point, and ultimately will allow me (and others) to understand in which direction this library intends to go.

What do I want to do?
I want to define an interface (using an abstract class, implementing nothing in python via abc) that sets the same contract for all implementations to respect (using type hints and returns in order to be precise about the contract).
After that I want to be able to pass a particular implementation to the injection in the client code via composition, and possibly at construction time, using as type declaration the interface rather than the implementation.

eg.

class NewsFeedRetriever(ABC):

    @abstractmethod
    def get_news(self) -> NewsResults:
        pass
---- 
class NewsResults:

    def __init__(self, data: dict):
        # validate data with some custom logic
        self.__data = data

    def get_data(self) -> dict:
        return self.__data
----
class NewYorkTimesFeedRetriever(NewsFeedRetriever):

    def __init__(self, url: str, user: str, password: str):
        self.__url = url
        self.__user = user
        self.__password = password

    def get_news(self) -> NewsResults:
        # use url user and password to get the news
        data = {"news": "from new_york_times"}
        return NewsResults(data)
----- 
class TheGuardianFeedRetriever(NewsFeedRetriever):

    def __init__(self, url):
        # In this case I don't need user and pass
        self.__url = url

    def get_news(self) -> NewsResults:
        # but I still implement the contract and return NewsResults
        data = {"news": "from the guardian"}
        return NewsResults(data)
----
class WashingtonPostFeedRetriever(NewsFeedRetriever):

    def __init__(self, url):
        # In this case I don't need user and pass
        self.__url = url

    def get_news(self) -> NewsResults:
        # but I still implement the contract and return NewsResults
        data = {"news": "from washington post"}
        return NewsResults(data)
---- 
class NewsFeedAggregator:

    def __init__(self, news_feeder1: NewsFeedRetriever, news_feeder2: NewsFeedRetriever):
        self.__news_feeder1 = news_feeder1
        self.__news_feeder2 = news_feeder2

    def get_news(self) -> [NewsResults]:
        return [self.__news_feeder1.get_news(), self.__news_feeder2.get_news()]

app.py

news_feeder_1 = NewYorkTimesFeedRetriever('https://www.nytimes.com/', 'someuser', 'somepass')
news_feeder_2 = WashingtonPostFeedRetriever('https://www.washingtonpost.com/')

news_feed_aggregator = NewsFeedAggregator(
    news_feeder_1,
    news_feeder_2
)

all_my_news = news_feed_aggregator.get_news()

After some months you get bored with the Washington post and you replace it with the guardian. All you need to do is

news_feeder_2 = TheGuardianFeedRetriever('https://www.theguardian.com/')

If I use bind I can always only bind one implementation!
What is the best way to achieve this with Injector ?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue does not name repository files or tests. Start by reviewing the Injector.bind entry point and the discussion in issue #123 to understand the current single-implementation behavior. Done would require a decided, supported approach for injecting multiple interface implementations, with its expected usage and tests agreed by the maintainers.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.