bobthemighty / punq Goto Github PK
View Code? Open in Web Editor NEWAn IoC container for Python 3.6+
License: MIT License
An IoC container for Python 3.6+
License: MIT License
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!)
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.
It's 2022, and Travis is officially Not Cool. Move to GH Actions, and fix the irritating codecoverage bug.
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?
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 ===============================
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
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.
Is a Typed file available for punq?
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.
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?
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
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)
Currently sphinx
template misses several important features:
Here are some good examples of how we can style it:
Notice this gitlab-corner that is visible on the main page.
Notice this big information about stars and a clear link to github.
Currently there are some tests, but I am not sure how many lines of the project is actually covered.
I will enable coverage for the project and CI #3, but I will also need some help from @bobthemighty.
Please, enable https://coveralls.io/ when you will have some free time.
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!)
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?
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?
When I use string annotations punq
raises NameError
.
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
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
python: 3.6.6
punq: 0.0.4
I guess that it might be the same issue with from __future__ import annotations
.
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?
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.
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:
Am I registering context var wrong or punq doesn't support context vars?
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.
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.
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.
Add a special kwarg to register singletons: container.register(BaseClassName, singleton=Instance())
Possibly, there are other solutions as well.
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.
This line:
https://github.com/bobthemighty/punq/blob/master/setup.py#L18
merges together .rst
and .md
file together, which are two different formats.
This won't work well. I suggest to use the same format, rst
is the standard one for python
ecosystem.
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?
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:
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?
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
.
Line 420 in c26cf2d
And leads to some abandoned place: https://codecov.io/gh/bobthemighty/punq
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.