Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Proxy Object #129

Open
luismsmendonca opened this issue Jul 4, 2023 · 7 comments
Open

Feature Request: Proxy Object #129

luismsmendonca opened this issue Jul 4, 2023 · 7 comments

Comments

@luismsmendonca
Copy link

Add a Proxy object in style of peewee.Proxy in order to have a later initialization of the object. There are some libraries (wrapt or lazy-object-proxy) but they don't cover this specific scenario where initialization occurs after proxy is initialized. If there is interest I can go on and make a pr. Thanks for awesome library.

@Suor
Copy link
Owner

Suor commented Jul 4, 2023

funcy already has LazyObject doesn't it work for you? If not can you please explain the specifics?

@luismsmendonca
Copy link
Author

Hi @Suor , yes I use it a bunch for initialization too. The pattern I'm describing is something along the lines of:

foo_api = Proxy()
foo_api_1 = FooApi()
foo_api.initialize(foo_api_1)
foo_api.bar() # uses foo_api_1

foo_api_2 = FooApi()
foo_api.initialize(foo_api_2)
foo_api.bar() # uses foo_api_2

I find this pattern useful for testing and abstracting connections to db's and stuff.

This cannot be accomplished with the LazyObject since _setup cannot be called for an adhoc object (same as with other libraries mentioned before). This is not much of a "Lazy" pattern but a "deferred" initialization one I believe.

Thanks. Luis

@Suor
Copy link
Owner

Suor commented Jul 4, 2023

And how you want it to look? The same way it's in your code then that could be achieved with something like:

def make_proxy():
    obj = None

    @LazyObject
    def proxy():
        if obj is None:
            raise ValueError("You need to init the proxy before using it")
        return obj

    def init(api):
        nonlocal obj
        obj = api

    return proxy, init

And use it like:

foo_api, init_foo_api = make_proxy()
init_foo_api(FooApi())
foo_api.bar()

It could also be integrated into LazyObject itself somehow, i.e. by allowing ._setup() to accept arguments or smth. Or simply subclassing it, rewriting how initialization is done.

@Suor
Copy link
Owner

Suor commented Jul 4, 2023

The full thing could be written as:

class DeferInitObject:
    def init(self, obj):
        object.__setattr__(self, '__class__', obj.__class__)
        object.__setattr__(self, '__dict__', obj.__dict__)

    def __getattr__(self, name):
        raise ValueError("You need to init the proxy before using it")

    def __setattr__(self, name, value):
        raise ValueError("You need to init the proxy before using it")

Won't work when obj has a class with slots though, same as LazyObject.

@luismsmendonca
Copy link
Author

Hi @Suor,

Thank you very much for your replies.

In the examples you gave the proxy object can only be initialized once and not multiple times like in the example I gave.

Example:

class Foo():
    def bar(self):
        print(id(self))
        
p = DeferInitObject()

p.init(Foo())
p.bar()

p.init(Foo())
p.bar()

on the second init it will raise AttributeError: 'Foo' object has no attribute 'init'

where as with the make_proxy:

p, p_init = make_proxy()

p_init(Foo())
p.bar()

p_init(Foo())
p.bar()

will print same id, i.e.:

$ python foo.py 
140142395670336
140142395670336

effectively not using the second init.

Just as an example:

from peewee import Proxy

p = Proxy()

p.initialize(Foo())
p.bar()

p.initialize(Foo())
p.bar()

gives:

$ python foo.py 
140282243186400
140282243186352

Again, when I'm using peewee I use the Proxy that comes with it, but when no sql db is needed and I don't have peewee installed I find it silly to include a sql db library just to apply this pattern.

Since funcy is always the first in the list :) and this is akin to its sort of utilities, I was wondering if there is an opening to include it.

Thanks,

Luis

@Suor
Copy link
Owner

Suor commented Jul 6, 2023

Ok, I see, I missed that you rewrite the object twice. The name initialize also didn't help. I guess one may get rewiring code out and make it work like:

def rewire(dest, source):
    object.__setattr__(dest, '__class__', source.__class__)
    object.__setattr__(dest, '__dict__', source.__dict__)

foo_api = object()
foo_api_1 = FooApi()
rewire(foo_api, foo_api_1)
foo_api.bar() # uses foo_api_1

foo_api_2 = FooApi()
rewire(foo_api, foo_api_2)
foo_api.bar() # uses foo_api_2

Or you can simply copy this peewee.Proxy code to your project and use it. Or do something in-between.

It might be useful to have it in funcy in some form, not sure which one exactly though.

Also, making it explicit instead of implicit is also an option if you control the using part of your code:

foo_api = Proxy()
foo_api.client = FooApi()
foo_api.client.bar() # uses foo_api_1

foo_api_2 = FooApi()
foo_api.client = foo_api_2
foo_api.client.bar() # uses foo_api_2

This might be easier on the code reader in the future.

@luismsmendonca
Copy link
Author

Hi @Suor,

Never realized how easy it to do this with this rewiring snippet you gave.

Thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants