Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing a thin object proxy by implementing the __get__ descriptor

I'm trying to create a think proxy using the __get__ descriptor over a certain type of data so that it can be used conveniently by client code (best shown with an example):

class Value(object):
    def __init__(self, val):
        self.val = val
    def __get__(self, instance, owner):
        return self.val

class Container(object):
    x = Value(1)
    y = [ Value(2), Value(3) ]

c = Container()
assert c.x == 1
assert c.y[0] == 2 # fails.

Now, I mostly see why it fails, so I'm wondering if theres a better way to achieve my goal which is wrap up the data so that when the client accesses it they simply have to access the Value object directly (in reality, the __get__ method would do something with self.val before returning it)

Details

The actual case I'm trying to tackle is utility code to assist in writing PageObjects & PageElements for selenium test cases. What I want to provide is a clean and simple way for test authors to write PageObjects of the form:

class MyLogin(PageObject):
    username_text_box = PageElement(By.ID, "username")
    password_text_box = PageElement(By.ID, "password")
    submit_button = PageElement(By.CLASS_NAME, "login-button")

driver = webdriver.Firefox()
driver.get("http://mypage.com")
login_page = MyLogin(driver)
login_page.username_text_box.send_keys("username01")
login_page.password_text_box.send_keys("XXXX")
login_page.submit_button.click()

I'm trying to encapsulate the business of fetching the actual WebElement inside the PageElement class and I want to allow the client to have access to the WebElement once they access the PageElement Object. Being able to have lists, dictionaries assists in making the PageObject instance clearer and to group related PageElements.

like image 215
Ali-Akber Saifee Avatar asked May 13 '26 08:05

Ali-Akber Saifee


2 Answers

You need to add an additional proxy if you want to proxy the array accessor as well. Here's an example using property() for x since it's just an attribute, and an array proxy accessor for y.

self._x and self._y are the internal, private accessors for the true Value's

import functools

class array_proxy(object):
    def __init__(self, accessor):
        self.accessor = accessor

    def __getitem__(self, key):
        return self.accessor(key)

class Value(object):
    def __init__(self, val):
        self.val = val

class Container(object):
    def __init__(self):
        self.__x = Value(1)
        self.__y = [Value(2), Value(3)]
        self.y = array_proxy(functools.partial(Container.get_y_element, self))

    @property
    def x(self):
        val = self.__x.val
        # do stuff with val
        return val

    def get_y_element(self, key):
        val = self.__y[key].val
        # do stuff with val
        return val


c = Container()
assert c.x == 1
assert c.y[0] == 2

If you can force the other people to use get_y_element directly then it's a bit simpler to proxy it, and it's more explicit to the user about what is happening when they access it.

like image 105
jdeuce Avatar answered May 14 '26 21:05

jdeuce


Python_noob's answer is good.

It can be made simpler and less generic - for easier understanding.

The matter is the descriptor protocol just "works" with class attributes. In the example snippet you put, the class attribute is the list y, not the Value objects inside it.

If you just implement can create a sequence class that replaces __getitem__ with a call to the item's __get__ you should get the behavior you want:

class Value(object):
    def __init__(self, val):
        self.val = val
    def __get__(self, instance, owner):
        return self.val

class ValueList(list):
    def __getitem__(self, item):
        return list.__getitem__(self,item).__get__(self.instance, self.instance.__class__)

class Container(object):
    def __init__(self, *args, **kw):
        self.y = ValueList(self.__class__.y)
        self.y.instance = self
        # ...

    x = Value(1)
    y = [ Value(2), Value(3) ]

c = Container()
assert c.x == 1
assert c.y[0] == 2 # works.

Note that the sequence class has no way to authomatically know to which instance it belongs - so this has to be explicitly set at instantiation. (Your Value in the example would suffer from the same fate at its __init__ method: it does not know its Container instance )

like image 45
jsbueno Avatar answered May 14 '26 21:05

jsbueno