dependencies icon indicating copy to clipboard operation
dependencies copied to clipboard

workaround to avoid checking types on dependencies?

Open s22chan opened this issue 3 years ago • 13 comments

I need to use some virtual proxies for some complex classes and https://github.com/ionelmc/python-lazy-object-proxy provides a no effort pluggable solution.

Unfortunately, https://github.com/proofit404/dependencies/blob/697f97c925419ad034efe7b416611f51fc80f97d/src/_dependencies/objects/classes.py#L36-L38 triggers the instantiation.

I was wondering if there's a way to get around this, or will I have to make custom proxies for each class?

Great library by the way, really appreciate how much effort you're putting into supporting it.

s22chan avatar Mar 02 '22 16:03 s22chan

Good evening,

Could you provide full code example which cause en error with library you mention?

I would try to figure out how to fix it.

Best regards, Artem.

proofit404 avatar Mar 02 '22 18:03 proofit404

from dependencies import Injector
from lazy_object_proxy import Proxy

class Outer:
   def __init__(self, inner):
       self.inner = inner

class Inner:
   def __init__(self):
     print("inner")

class Root(Injector):
    outer = Outer
    inner = Inner

Ideally, Root.outer would assign the inner proxy to outer, which will defer any resource allocation until the outer.inner attribute is actually used; but calling isinstance(inner, _IsScope) calls https://github.com/ionelmc/python-lazy-object-proxy/blob/03003b012feef472b4bb54b971a8f4782a41f93f/src/lazy_object_proxy/slots.py#L128 which instantiates at https://github.com/ionelmc/python-lazy-object-proxy/blob/03003b012feef472b4bb54b971a8f4782a41f93f/src/lazy_object_proxy/slots.py#L106.

I understand that dependencies can't be expected to support all these little hacks, so unless there's an easy solution, I'll look into other libraries/custom code to make virtual proxies. Thanks again!

s22chan avatar Mar 02 '22 19:03 s22chan

Good morning,

I did some initial testing for lazy_object_proxy library. Generally, I'm interested in support of such libraries.

What I have at that time:

>>> from dependencies import Injector
>>> from lazy_object_proxy import Proxy
>>> class Inner:
...     def __init__(self, x):
...         print('x', x)
...
>>> class Container(Injector):
...     a = Proxy
...     factory = Inner
...     x = 1
...
Traceback (most recent call last):
  ...
_dependencies.exceptions.DependencyError: Proxy.__init__ have arbitrary argument list and keyword arguments

Due to implementation on C layer.

This could be solved in the future with shield object #487

proofit404 avatar Mar 03 '22 09:03 proofit404

Speaking about early instantiation, I could confirm it :cry:

>>> from lazy_object_proxy import Proxy
>>> from dependencies import Injector
>>> class Inner:
...     def __init__(self, x):
...         print('x', x)
...
>>> class Container(Injector):
...     a = Proxy
...     factory = Inner
...     x = 1
...
>>> Container.a
x 1
<Proxy at 0x7fbdfb7a5840 with factory <__main__.Inner object at 0x7fbdfb7ced10>>

In that case I see two approaches to workaround this problem.

First of all, you could try to wrap Injector itself into lazy proxy.

>>> class Container(Injector):
...     inner = Inner
...     x = 1
...
>>> Proxy(lambda: Container.inner)
<Proxy at 0x7fbdfb4b7f40 with factory <function <lambda> at 0x7fbdfb67e950>>
>>> str(Proxy(lambda: Container.inner))
x 1
'<__main__.Inner object at 0x7fbdfb7ced10>'

Or better rewrite your classes to avoid resource allocation inside constructor.

class Inner:
    def __init__(self, socket):
        ...
    @classmethod
    def connect(cls, host, port):
        socket = make_connection(host, port)
        return cls(socket)

I would recommend this talk https://www.youtube.com/watch?v=FThx_Jk24Rc

What do you think?

proofit404 avatar Mar 03 '22 09:03 proofit404

sorry I realize I didn't complete the example:

class Root(Injector):
    outer = Outer
    @value
    def inner():
        return Proxy(Inner)

is how I would use it, so I would've liked the wiring of the proxy to be defined by the Injector. Not sure how I would use a Proxied injector in this case to supply the argument to Outer

Yeah I totally agree that the constructors should be lightweight, but I'm refactoring a rather large code-base and would like to be doing this baby steps at a time. Thanks for the video talk.

s22chan avatar Mar 03 '22 19:03 s22chan

Indeed, Proxy(Injector()) could be used only to wrap main injector. This solution does not work with nested Injectors.

I just tried to reproduce example you provide:

from dependencies import Injector, value
from lazy_object_proxy import Proxy


class Inner:
    def __init__(self, x):
        print("inner")
        self.x = x

    def do(self):
        return self.x


class Outer:
    def __init__(self, inner):
        print("outer")
        self.inner = inner

    def do(self):
        return self.inner.do()


class Container(Injector):
    outer = Outer

    @value
    def inner(x):
        return Proxy(lambda: Inner(x))

    x = 1
>>> o = Container.outer
outer

>>> o.do()
inner
1

Looks like it kinda works :thinking:

proofit404 avatar Mar 03 '22 21:03 proofit404

odd I'm not sure why I get a different result running your sample. I'll debug it later. Thanks again for the great support:

In [2]: o = Container.outer
inner
outer

s22chan avatar Mar 03 '22 22:03 s22chan

Quick suggestion: try both ipython and python -i consoles. It could introduce additional magic.

proofit404 avatar Mar 03 '22 23:03 proofit404

I've tried both, it still outputs inner. I've also tried python 3.7..3.9. The only time I don't see this is if I rollback dependencies to 6.0.1 (from 7.1.7)

I'll update it if I can see what's going on later (I'm also using lazy-objects-proxy==1.7.1)

s22chan avatar Mar 03 '22 23:03 s22chan

That's because I'm an idiot.

My system has python 3.10 by default, most recent dependencies release specified 3.9 as supported, so pip decided to install 3.0.0 version as most compatible.

proofit404 avatar Mar 04 '22 00:03 proofit404

Sorry it takes me too long to get back to this issue.

I could confirm Proxy object does eager initialization in the example I post previously.

>>> from dependencies import Injector, value
>>> from lazy_object_proxy import Proxy
>>> class Inner:
...     def __init__(self, x):
...         print("inner")
...         self.x = x
...     def do(self):
...         return self.x
...
>>> class Outer:
...     def __init__(self, inner):
...         print("outer")
...         self.inner = inner
...     def do(self):
...         return self.inner.do()
...
>>> class Container(Injector):
...     outer = Outer
...     @value
...     def inner(x):
...         return Proxy(lambda: Inner(x))
...     x = 1
...
>>> o = Container.outer
inner
outer
>>> o.do()
1
>>>
dependencies==7.1.7
lazy-object-proxy==1.7.1

proofit404 avatar May 13 '22 09:05 proofit404

I found the way to workaround isinstance check you mention it the very first message.

from dependencies import Injector, value
from lazy_object_proxy import Proxy as _Proxy


class Inner:
    def __init__(self, x):
        print("inner")
        self.x = x

    def do(self):
        return self.x


class Outer:
    def __init__(self, inner):
        print("outer")
        self.inner = inner

    def do(self):
        return self.inner.do()


class Proxy(_Proxy):
    @property
    def __class__(self):
        return Proxy


class Container(Injector):
    outer = Outer

    @value
    def inner(x):
        return Proxy(lambda: Inner(x))

    x = 1
>>> from t import Container
>>> o = Container.outer
outer
>>> o.do()
inner
1
>>>

I'll include this example into documentation later today, since it's not obvious how to solve it.

proofit404 avatar May 13 '22 09:05 proofit404

Thanks Artem, that's amazing!

s22chan avatar May 13 '22 14:05 s22chan