GithubHelp home page GithubHelp logo

punq's People

Contributors

bobthemighty avatar dependabot[bot] avatar fourspotproject avatar jbcpollak avatar jlecount avatar kate-pickle avatar sobolevn avatar trendels 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  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

punq's Issues

Error when registering an abstract class

Trying to register an abstract class (abc.ABC) fails.

The problem is in the following statement of the register_concrete_service:

if not type(service) is type:

If you change it by the following:

 if not inspect.isclass(service):

Then it works well.

(After using a few container libraries this is the only one I can happily use without messing up my architecture :-). Thank you!)

pytest fixture for punq?

Is there a good way to set the implementation of something for the duration of a test in pytest?
DI often cites the ability to inject a different implementation for tests, but it is not obvious to me how to do that.

ie I want to register before the test and unregister after.

Drop Travis, fix badges.

It's 2022, and Travis is officially Not Cool. Move to GH Actions, and fix the irritating codecoverage bug.

How To resolve class dependencies registered by key

lets say I have something like this:

class Dependency:
     pass

class ConsumerOfA:
    def __init__(self, dep: Dependency):
        self.dep = dep

container.register('depA', Dependency('a'))
container.register('depB', Dependency('b'))
container.register(ConsumerOfA)

cOA = container.resolve(ConsumerOfA)

How do I ensure that ConsumerOfA gets depA vs depB? Is there a way to add an annotation on init to make sure the right one is injected?

Enabling doctests

I am trying to enable doctests with pytest --doctest-modules, since there are a lot of examples in the doctest format. And continuously testing them will ensure that these examples work and are up-to-date.

There are some problems with it however, which I am trying to resolve at the moment.

» pytest --doctest-modules
================================ test session starts =================================
platform darwin -- Python 3.7.1, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: /Users/sobolev/Documents/github/punq, inifile:
plugins: cov-2.6.1
collected 19 items / 2 errors / 17 selected

======================================= ERRORS =======================================
_________________________ ERROR collecting punq/__init__.py __________________________
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:932: in find
    self._find(tests, obj, name, module, source_lines, globs, {})
.venv/lib/python3.7/site-packages/_pytest/doctest.py:403: in _find
    self, tests, obj, name, module, source_lines, globs, seen
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:995: in _find
    globs, seen)
.venv/lib/python3.7/site-packages/_pytest/doctest.py:403: in _find
    self, tests, obj, name, module, source_lines, globs, seen
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:982: in _find
    test = self._get_test(obj, name, module, globs, source_lines)
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:1066: in _get_test
    filename, lineno)
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:668: in get_doctest
    return DocTest(self.get_examples(string, name), globs,
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:682: in get_examples
    return [x for x in self.parse(string, name)
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:644: in parse
    self._parse_example(m, name, lineno)
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:714: in _parse_example
    lineno + len(source_lines))
../../../.pyenv/versions/3.7.1/lib/python3.7/doctest.py:800: in _check_prefix
    (lineno+i+1, name, line))
E   ValueError: line 26 of the docstring for punq.InvalidForwardReferenceException has inconsistent leading whitespace: "  punq.InvalidForwardReferenceException: name 'Dependency' is not defined"
___________________________ ERROR collecting punq/py_36.py ___________________________
punq/py_36.py:1: in <module>
    from typing import List, _ForwardRef
E   ImportError: cannot import name '_ForwardRef' from 'typing' (/Users/sobolev/.pyenv/versions/3.7.1/lib/python3.7/typing.py)
!!!!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!!!!
============================== 2 error in 0.55 seconds ===============================

Missing import ForwardRef

Breaking change on the import of ForwardRef. New in python 3.7.4

File "/usr/local/lib/python3.6/site-packages/vfab2_library/suts/sut_config.py", line 17, in
import punq
File "/usr/local/lib/python3.6/site-packages/punq/init.py", line 30, in
from ._compat import ensure_forward_ref
File "/usr/local/lib/python3.6/site-packages/punq/_compat.py", line 2, in
from typing import ForwardRef

Introduce ValidateBeforeResolve concept

I have faced a problem with my code that is not related to punq, but can be solved by it in a very interesting way.

I had a service that had a dependency on redis. I used punq to inject redis to it.
The problem was that I had a configuration problem: my redis url was incorrect. And I was not aware of that. My service started as it should, then when my cache was hit for the first time - everything broke apart.

How can this be prevented? By adding a new concept: ValidateBeforeResolve class.

That's what I am talking about written in Ruby: https://github.com/davydovanton/rubyjobs.dev/tree/master/system/boot

In Python it would be something like that:

from punq import ValidateBeforeResolve

class RedisContainer(ValidateBeforeResolve):
     # Is inside base class, has the same semantic as `.register()`:
     # https://github.com/bobthemighty/punq/blob/master/punq/__init__.py#L210
     # def __init__(...): ...
 
     def validate(self) -> 'bool | Exception':
          self.instance.redis.ping()  # raises Exception if fails
          return true 

The thing I don't like about this is that we have to write classes for each validation. We can try to build API around lambda or functions.

Would be happy to hear your feedback.

Improve docs

Hi, @bobthemighty! That's me again 🙂

I am currently using punq in a side-project. It looks awesome!
So, I have decided to write some user-related documentation.
And maybe add some improovments as well, I will open new issues for things I find the most important.

And I know, that this might be sensitive.
Do you have anything that you want to cover?

Also, you can check out our set of projects we are working right now: https://github.com/dry-python
It has its own DI framework which is awesome too.

Best practices for naming classes in different modules meant for the same container

I was looking at punq's source code, and I noticed that, when multiple classes have the same name, the behavior of register() is order-sensitive.

Specifically, if two modules define a helper class or alias with the same name, then the contents of one module are registered, then the other, then the client classes from the first module, then they will inject an instance of the second module's class instead of the first's.

If this is a concern for me, should I be using punq, and, if so, what's the recommended way to avoid collisions without cross-checking every module?

`MissingDependencyError` if class declared in a module with `__main__` part

Hello

# app.py
import aiohttp.web
import punq

from project import routes


class A:
    pass


if __name__ == "__main__":
    container = punq.Container()
    container.register(A, instance=A())

    app = aiohttp.web.Application()
    app["container"] = container
    routes.setup_routes(app)
    aiohttp.web.run_app(app)
# routes.py
import aiohttp.web

from project import view


def setup_routes(app: aiohttp.web.Application):
    app.router.add_route("GET", "/route", view.handler)
# view.py
import aiohttp.web
import punq

from project import app


async def handler(r: aiohttp.web.Request):
    c: punq.Container = r.app["container"]
    c.resolve(app.A)
    return aiohttp.web.Response()

Request to /route results in MissingDependencyError because it exists as __main__.A as I understand

Question: Capture command line arguments to create Singleton for FastApi service

I want to capture the command line arguments to create a Singleton and make it available to all the endpoints in a FastApi service. The (non-working) code below is for illustration only: the command line argument s: str is transformed into the Singleton thisobj and made available to the routes. Is this possible with punq?

ffrom fastapi import FastAPI, Depends, Request

class ThisObj:
    def __init__(self, thing: str) -> None:
        self.thing = thing

# Want to pass CLI arguments to on_startup here
async def on_startup() -> None:
    app.state.thisobj = ThisObj(s)

app = FastAPI(on_startup=[on_startup])

def get_thisobj(request: Request) -> ThisObj:
    return request.app.state.thisobj

@app.get("/")
async def index(thisobj: ThisObj = Depends(get_thisobj)) -> None:
    assert isinstance(thisobj, ThisObj)
    assert thisobj.thing == s

def main(args):
    params: list[str] = args[2].split()
    s: str = params[0]

    uvicorn.run('main:app', host="127.0.0.1", port=8000, reload=True)

if __name__ == "__main__":
    main(sys.argv)

Improve sphinx template

Currently sphinx template misses several important features:

Снимок экрана 2019-05-31 в 17 05 13

  1. It does not have a clear github link from the main page
  2. It does not show the number of stars on the main page (that's very important!)

Here are some good examples of how we can style it:
Снимок экрана 2019-05-31 в 17 07 14
Notice this gitlab-corner that is visible on the main page.

Снимок экрана 2019-05-31 в 17 07 05

Notice this big information about stars and a clear link to github.

README Docs Issue

The README has this example:

If our application's dependencies have their own dependencies, Punq will inject those, too:

class Greeter:
   def greet(self):
      pass


class ConsoleGreeter:
   def __init__(self, config_reader: ConfigReader):
      self.config = config_reader.get_config()

   def greet(self):
      print(self.config['greeting'])


container.register(Greeter)
container.resolve(Greeter).greet()

The way the example is laid out, it doesn't seem like it's doing what the documentation above it would indicate. Is the second to last line supposed to be:

container.register(Greeter, ConsoleGreeter)

Otherwise ConsoleGreeter won't be resolved, is that correct? (Happy to make a PR if this is the case!)

resolve_all method docs

Hello.

I'm using and enjoying your lib in couple of projects.

I've found method resolve_all.

It's not clear, when and why developer will use it. Can you write down some thoughts on it in docstring?

Enable Travis CI

I can definitetly can help with this one.

I will provide a working configuration for python3.6 and onwards,
but I will need a helping hand from @bobthemighty.

Can you please enable Travis CI builds for this repo?

String annotations causes `NameError`

When I use string annotations punq raises NameError.

Example

import attr
import punq

@attr.s(auto_attribs=True)
class Test(object):
    dep: 'Dependency'


@attr.s(auto_attribs=True)
class Dependency(object):
    val = 1


print(Test(Dependency()).dep.val)
#=> 1

container = punq.Container()
container.register(Dependency)
container.register(Test)

print(container.resolve(Test).dep.val)
#=> NameError: name 'Dependency' is not defined

Stacktrace

Traceback (most recent call last):
  File "manage.py", line 19, in <module>
    container.register(Test)
  File "/Users/sobolev/Documents/PyCharmProjects/kira/.venv/lib/python3.6/site-packages/punq/__init__.py", line 122, in register
    self.registrations.register(service, _factory, **kwargs)
  File "/Users/sobolev/Documents/PyCharmProjects/kira/.venv/lib/python3.6/site-packages/punq/__init__.py", line 102, in register
    self.register_concrete_service(service)
  File "/Users/sobolev/Documents/PyCharmProjects/kira/.venv/lib/python3.6/site-packages/punq/__init__.py", line 96, in register_concrete_service
    self._get_needs_for_ctor(service),
  File "/Users/sobolev/Documents/PyCharmProjects/kira/.venv/lib/python3.6/site-packages/punq/__init__.py", line 22, in _get_needs_for_ctor
    sig = typing.get_type_hints(cls.__init__)
  File "/Users/sobolev/.pyenv/versions/3.6.6/lib/python3.6/typing.py", line 1543, in get_type_hints
    value = _eval_type(value, globalns, localns)
  File "/Users/sobolev/.pyenv/versions/3.6.6/lib/python3.6/typing.py", line 350, in _eval_type
    return t._eval_type(globalns, localns)
  File "/Users/sobolev/.pyenv/versions/3.6.6/lib/python3.6/typing.py", line 245, in _eval_type
    eval(self.__forward_code__, globalns, localns),
  File "<string>", line 1, in <module>
NameError: name 'Dependency' is not defined

Versions

python: 3.6.6
punq: 0.0.4

Consideration

I guess that it might be the same issue with from __future__ import annotations.

Priority for kwargs, or support for recursive resolve

Two separate issues but they are related. I have classes that are nodes in a tree and I would like to be able to send in a registered entity as a parameter in kwargs when calling resolve(), like this:

class Example:
    def __init__(self, parent: 'Example', someParam: str):
        ...

parentExample = container.resolve(parent=None, someParam="Hello")
oneExample = container.resolve(parent=parentExample, someParam="world")

But as I understand the code in resolve_impl, punq will try to resolve any "needs" (registered) parameters first which would cause a loop in the __init__ code above, correct?

Is there some way to achieve this other than registering factory classes?

When a service does not depend on something

Dear Bob,

import attr
import punq


@attr.s
class DependsOnSome:
    some = attr.ib()


@attr.s
class NotDependsOnSome:
    pass


container = punq.Container()
container.register(DependsOnSome)
container.register(NotDependsOnSome)

for obj in [DependsOnSome, NotDependsOnSome]:
    x = container.resolve(obj, some="Some")

I just want to make sure that this is an intended behavior of the package.

So as you can see I have two services of which first depends on some while the second one does not.
When I resolve them I do provide the some key for both.

What I get is perfect so first x.some == "Some" and then x does not have some.

It is something which belongs to planned function? 😄
(I haven't seen in the doc.)

Thanks for your help in advance.

ContextVar support

Given code:

from contextvars import ContextVar

import punq

var: ContextVar[str] = ContextVar("var")


class DependsOnContextVar:
    def __init__(self, var: ContextVar[str]):
        self.var = var


if __name__ == '__main__':
    container = punq.Container()
    container.register("var", instance=var)
    container.register(DependsOnContextVar)

    cls = container.resolve(DependsOnContextVar)
    assert cls

Raises following error:

image

Am I registering context var wrong or punq doesn't support context vars?

README example

I have shown this README to several users, some of them complain that the example is not quite clear.
Why? It is not clear for them why we solve configs and environment variables with DI.

So, maybe we can change the example to contain something different? I personally like the ones from the source code.

I can make a PR with several new examples.

Does punq support scopes / instantiation methods?

A lot of DI frameworks support specifying scope of registered objects. For example, if a new instance should be created each time a registered object is resolved, or if only one should be instantiated.

For example, I'd like to be able to do:

container.register(Type, MyImplementation, mode=singleton)

then when I do this:

t1 = container.resolve(Type)
t2 = container.resolve(Type)

t1 and t2 would be the same instance.

This is slightly different than using the .register(Type, instance=my_instance) because in the first example I don't have to create the instance manually.

Callable singletons are registered incorrectly

Bug report

Problem

Let's say I have a class like this:

class RunSomeQuery(object):
    def __init__(self, db):
         self._db = db
    def __call__(self, arg):
         return self._db.query(arg)

And then I register it as a singleton:

import punq

container = punq.Container()
container.register(RunSomeQuery, RunSomeQuery(db=my_db_instance))

The problem is that this line: https://github.com/bobthemighty/punq/blob/master/punq/__init__.py#L122
decides that this class is callable. And registers it as an implementation.

I still expect it to be registered as a singleton instance.

Proposed solution

Add a special kwarg to register singletons: container.register(BaseClassName, singleton=Instance())
Possibly, there are other solutions as well.

Best practices for isolating unrelated dependency instantiation?

Hi, I'm looking forward to using punq, but I'm a bit confused about something. Is there a recommended way to setup conditional registrations of services? For example:

if Config.something_is_true:
    container.register(Service, ServiceImpl1)
else:
    container.register(Service, ServiceImpl2)

as the code and service-construction logic gets more complicated, don't want this all in the main file, and it doesn't make sense for the main file to be aware of all the possible implementations.

I'd like to organize it more sensibly, either with something like:

    # ServiceFactory somehow has access to the container so it can get access to Config
    container.register(Service, provider=ServiceFactory)

or perhaps:

    ServiceFactory.register(container)

I think the second option is the only thing practical right now, but I was wondering if that's the best practice.

Support constructing objects with partial default parameters

Given code like (this is an untested example):

class A:
    pass

class B:
    def __init__(a: A, i: int = 0):
        pass

container.register(A)
container.register(B)
b = container.resolve(B)

I'm getting a stack trace with this error:

punq.MissingDependencyException: Failed to resolve implementation for <class 'int'>

Is it possible to modify punq to use the inspect library to check for defaults, and leave any unsupplied fields empty?

Project status

Hi, @bobthemighty!

I have really enjoyed both your article and the punq itself. Thanks for it!

I am searching for DI/IoC framework that will:

  1. Use type annotations to inject dependencies
  2. Not use any kind of decorators or any other extra syntax
  3. Support wide range of types

This project looks like a perfect fit!
But, I am quite scared that it very immature. And it looks unsupported.

Can you please tell me what is the status of this project?
Maybe you can recommend some other tools that will fulfil my requirements?

support kwonlyargs

When building the implementation, you are getting args but if someone has used *, to make the args keyword only args, then you need to get them from kwonlyargs.

target_args = inspect.getfullargspec(registration.builder).args

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.