[Fixed] Strangeness with Sklupt behaving differently to python

Strange bug with trying to cache server calls (same as this post, but different type of error).
When testing the following code with

m = Menu(name="test")
print(m)
print(m.name)

I get the output from anvil

name: test
klass

but when run directly on python I get (as expected)

name: test
test

I can only assume this is a bug in Sklupt to do with the getter function in Data as it prints the entire object fine.

from time import time


def merge_two_dicts(x, y):
  x.update(y)
  return x


def partial(fun, *part_args, **part_kwargs):
  pk = part_kwargs
  pa = part_args
  def wrapper(*extra_args, **extra_kwargs):
    args = list(pa)
    args.extend(extra_args)
    kwargs = merge_two_dicts(pk, extra_kwargs)
    return fun(*args, **kwargs)
  return wrapper

class Data:
  def getter(self, name):
    if time() - self._last_update > self.__class__.update_time:
      self._update()
      self._last_update = time()
    return getattr(self, "_" + name)

  def setter(self, value, name):
    self.set_multiple(**{name: value})

  def set_multiple(self, **values):
    if set(self.__class__.attributes) == values.keys():
      self._last_update = time()
    for k in values:
      setattr(self, "_" + k, values[k])

  def _update(self):
    raise NotImplementedError("Must override _update")

  def __init__(self, **kwargs):
    for a in self.__class__.attributes:
      setattr(self, "_" + a, None)
    self._last_update = 0
    self.set_multiple(**kwargs)

  def __str__(self):
    output = ""
    for a in self.__class__.attributes:
      value = getattr(self, "_" + a)
      output += ", " + a + ": " + str(value)
    return output[2:]
  

class Menu(Data):
  attributes = [ "name"]
  update_time = 30

  def _update(self):
    pass

  def __init__(self, **kwargs):
    Data.__init__(self, **kwargs)

for a in Menu.attributes:
  setattr(Menu, a, property(partial(Menu.getter, name=a), partial(Menu.setter, name=a)))

This is a bug in Skulpt - the name property is not properly escaped, and so conflicts with bits of the underlying Javascript.

Moved to bug reports.

In the meantime, I suspect you can probably accomplish what you want a little more simply, using __getattr__ and __setattr__ and a dictionary to store your values…

1 Like

Are you suggesting something like this?
This is giving me the same error but is simpler

from time import time


def merge_two_dicts(x, y):
  x.update(y)
  return x


def partial(fun, *part_args, **part_kwargs):
  pk = part_kwargs
  pa = part_args
  def wrapper(*extra_args, **extra_kwargs):
    args = list(pa)
    args.extend(extra_args)
    kwargs = merge_two_dicts(pk, extra_kwargs)
    return fun(*args, **kwargs)
  return wrapper


class Data:

  def __getattr__(self, name):
    if time() - self._last_update > self.__class__.update_time:
      self._update()
      self._last_update = time()
    return self._data.get(name, None)

  def __setattr__(self, key, value):
    if key not in self.__class__.attributes:
      self.__dict__[key] = value
    self.set_multiple(**{key: value})

  def set_multiple(self, **new_values):
    if set(self.__class__.attributes) == new_values.keys():
      self._last_update = time()
    for k in new_values:
       self._data[k]= new_values[k]

  def _update(self):
    raise NotImplementedError("Must override _update")

  def __init__(self, **kwargs):
    self.__dict__['_data'] = {}
    self._last_update = 0
    self.set_multiple(**kwargs)

  def __str__(self):
    output = ""
    for a in self.__class__.attributes:
      value = self.__getattr__(a)
      output += ", " + a + ": " + str(value)
    return output[2:]

class Menu(Data):
  attributes = ["name"]
  update_time = 30

  def _update(self):
    # should be done as part of the meal
    pass

  def __init__(self, **kwargs):
    Data.__init__(self, **kwargs)

Yeah, that’s a bit easier on the eyes :wink:

Workaround for now: The bug with the name attribute happens in object.__getattribute__, which triggers before __getattr__. So actually, if you override __getattribute__ rather than __getattr__, you should dodge this bug.

1 Like

I’ve added

  def __getattribute__(self, key):
    if key  == 'name':
      return self._data[key]
    v = object.__getattribute__(self, key)
    if hasattr(v, '__get__'):
       return v.__get__(None, self)
    return v

This does fix it
Thanks for the help

I don’t think that hasattr() is necessary - object.__getattribute__ already does the descriptor lookup for you. But yes, that’s a tidy workaround.

1 Like