Giter Club home page Giter Club logo

haps's People

Contributors

deepsourcebot avatar ekiro avatar wasinski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

haps's Issues

Support for multiple profiles in egg

Lets assume such a situation

from abc import ABC, abstractmethod
from haps import base, egg, Container as IoC, PROFILES
from haps.config import Configuration

@base
class IMailService(ABC):
    @abstractmethod
    def send_mail(self, text: str, to: str, **kwargs):
        pass

@egg
class MailService(IMailService):
    def send_mail(self, text: str, to: str, **kwargs):
        # Send mail here
        pass

@egg(profile='mail_disabled')
class DummyMailService(IMailService):
    def send_mail(self, text: str, to: str, **kwargs):
        pass


profiles = ['mail_disabled']

Configuration().set(PROFILES, profiles)
IoC.autodiscover(['app'])

Case

When it comes to testing I need to configure test profiles like this:

profiles = ['mail_disabled']

Configuration().set(PROFILES, profiles)
IoC.autodiscover(['app'])

Assume I have more than one service like MailService with Dummy implementation, then the configuration will look like this:

profiles = ['mail_disabled', 'serviceX_disabled', 'serviceY_disabled', ...]

Configuration().set(PROFILES, profiles)
IoC.autodiscover(['app'])

It's a lot of profiles and we need to update base tests configuration every time we add a new service...

Solution

We can make it a lot easier with defining multiple profiles on eggs like

...

@egg(profiles=['mail_disabled', 'test'])
class DummyMailService(IMailService):
    def send_mail(self, text: str, to: str, **kwargs):
        pass

Then our testing configuration will be simply:

profiles = ['test']

Configuration().set(PROFILES, profiles)
IoC.autodiscover(['app'])

Defining injectable where egg is same as base

I often run into the case where I don't actually want to define an interface for my implementation, but still make use of the autodiscovery and DI for instances of that class.
The way I usually go about doing this with haps is to define both the base and egg decorators:

@base
@egg
@scope(SINGLETON_SCOPE)
class MySimpleService:
    pass

A quick search on my current project yields 12 occurences of this combination of decorators and I'm wondering if it might make sense to introduce a simplified decorator for marking a class as "injectable" (i.e. acting as both the base and egg).
I am aware that one of the purposes of dependency injection is to decouple an interface from its implementation (e.g. for testability), but especially for private projects with fewer test cases I'd still like to make use of the control inversion and let a library figure out the autowiring of my application.

I couldn't come up with a very nice API for this concept, so I'll just throw "@injectable(scope=SINGLETON_SCOPE)" and "@base_egg" in the room.

Add `@inject.without` and `@inject.only`

Now it's possible to omit some function parameters during injection, but that requires not annotating them. Every annotated parameter has to be registered as a dependency.

@inject
def f(s1: IService, s2: IService2, s3):
    pass

f(some_service)

Inject should have 2 optional forms, without and only

@inject.without('s3')
def f(s1: IService, s2: IService2, s3: IService3):
    pass

@inject.only('s1', 's2')
def f2(s1: IService, s2: IService2, s3: IService3):
    pass

Possibility to use multiple bases

Suppose the following case:

  • I'm getting a ClientFoo from an external library "Ext". ClientFoo is very complex and it is difficult to do composition over inheritance, and I don't want to extract an interface either as the third-party package might change in the future, causing unnecessary maintenance effort.
  • I want to write my own shared library "Shared" and @inject ClientFoo from "Ext" in some type initializers, so I annotate it with @base and @egg.
  • In the actual application "App" (using both "Ext" and "Shared"), I want to amend ClientFoo with some custom functionality, so I create a subclass ClientBar. It would be valid to inject ClientFoo into types of "Shared", but not necessarily into all the types of "App" because of the extra features required. In "Shared", I would like to rely only on ClientFoo so that its types become reusable but still easy to instantiate, whereas I want to receive concrete ClientBars in "App".

To solve this, my first thought was to annotate ClientBar with both @base and @egg aswell, but that violates the "single base" principle.
Is it possible to overcome this issue with the constraints given above? Alternatively, is there a simple way to monkeypatch haps to allow using multiple bases in particular cases?

Allow resolving configuration values from arbitrary sources

In order to make use of the awesome python-decouple library in conjunction with haps, I wrote the following monkeypatch:

from typing import Any

import decouple
from haps.config import Configuration
from haps.exceptions import UnknownConfigVariable


def decouple_resolver(self: Configuration, var_name: str) -> Any:
    """
    Monkeypatched resolver for using python-decouple together with haps.
    Gives priority to explicitly-declared resolvers in the application, then falls back to retrieving the value
    from decouple.
    """
    if var_name in self.resolvers:
        return self.resolvers[var_name]()
    env_var = decouple.config(var_name)
    if env_var:
        return env_var
    raise UnknownConfigVariable(f"No resolver registered for {var_name}")

# Override haps default resolver
Configuration._resolve_var = decouple_resolver

It occured to me that it would be nice to be able to provide custom variable resolvers for arbitrary keys (not just specified ones) to haps which it will use in a configurable lookup order (aka priority). The advantage of using decouple is that it already searches in a bunch of settings repositories (.env, settings.ini, env vars) and so simply adding it as a fallback is enough, but people might also want to add their own settings sources (some YAML or JSON files or whatever).

Handle multiple configurations

Pytest

When running pytest the configure_ioc() fixture is called first and then while loading application create_application() is called and that call produces haps.exceptions.ConfigurationError: Value for haps.profiles already set.

/api/resources/api.py

from haps import Container as IoC, PROFILES
from haps.config import Configuration

def create_application() -> API:
    profiles = ['profile1', 'profile2']
    Configuration().set(PROFILES, profiles)
    IoC.autodiscover(['app1', 'app2'])

/tests/conftest.py

import pytest
from haps import Container as IoC, PROFILES

@pytest.fixture(scope="session", autouse=True)
def configure_ioc():
    # Initialize haps IoC Container
    profiles = ['test']

    Configuration().set(PROFILES, profiles)
    IoC.autodiscover(['app1', 'app2'])

Alembic

The same situation is with Alembic migrations env.py file, which is called through alembic command and imports the app there.
Error:
haps.exceptions.ConfigurationError: Value for haps.profiles already set

/alembic/env.py

from haps import Container as IoC, PROFILES
from haps.config import Configuration
from alembic import context

cfg = Configuration()
cfg.set(haps.PROFILES, ())
IoC.autodiscover(['app1', 'app2'])
context.config.set_main_option('sqlalchemy.url', cfg.get_var('db_url'))

...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.