8000 fix(timer): BlynkTimer Broken on Low Memory Ports by BradenM · Pull Request #6 · blynkkk/lib-python · GitHub
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on May 7, 2025. It is now read-only.

fix(timer): BlynkTimer Broken on Low Memory Ports #6

Merged
merged 4 commits into from
Jun 20, 2019

Conversation

BradenM
Copy link
Contributor
@BradenM BradenM commented Jun 20, 2019

Fixes #5

Notes

On most low memory ports (such as the esp8266) the config variable MICROPY_PY_FUNCTION_ATTRS is disabled, effectively meaning func.__name__ doesn't exist. (See micropython/micropython#614 for more details)

Because Timer relies on func.__name__ to name its timers, its effectively broken on any port without MICROPY_PY_FUNCTION_ATTRS enabled.

Changes

This PR fixes this issue by doing the following:

  • Checks if func.__name__ exists

  • If yes, continues as normal

  • If not:

    • Check if obj.func exists and reattempt with that (original fallback)
    • If not, then simply return "timer"
  • Also adds a note in TIMERS.md referring to this change.

This essentially means that on ports without MICROPY_PY_FUNCTION_ATTRS, timers are named X_timer, with X as the original number of timer.

BradenM added 2 commits June 19, 2019 23:08
On low memory ports MICROPY_PY_FUNCTION_ATTRS is disabled, leading BlynkTimer to raise an AttributeError when trying to get a name for a new timer. This fixes it by naming each timer 'X_timer' (where X is the # of timer) if the __name__ attribute does not exist.

Fixes blynkkk#5
Copy link
Collaborator
@antohaUa antohaUa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your findings . All looks good. I have only one small proposal for code modification

@antohaUa
Copy link
Collaborator
antohaUa commented Jun 20, 2019

getattr should capture AttributeError if default value was provided.
https://docs.python.org/3.3/library/functions.html#getattr
But i understood what do you mean. Correct if we have recursion and no obj.func then we will fail.
Cause we call it without getattr -> " self._get_func_name(obj.func)"
Thus we can try to change order of verification to avoid such issue:

def _get_func_name(self, obj):
    if getattr(obj, 'func', None) is None:
        return 'timer'
    elif getattr(obj, '__name__', None) is None:
        return self._get_func_name(obj.func)
    return obj.__name__

@BradenM
Copy link
Contributor Author
BradenM commented Jun 20, 2019

Okay, I pushed a cleaner version that should work for all cases.
But still,

def _get_func_name(self, obj):
    if getattr(obj, 'func', None) is None:
        return 'timer'
    elif getattr(obj, 'name', None) is None:
        return self._get_func_name(obj.func)
    return obj.name

would also be problematic. function never has a func attribute, so anytime you called that function with a function object (even if obj.__name__ exists) you would receive timer as the return value. And yes I was referring to the AttributeError that would be raised when return self._get_func_name(obj.func) was called, cause getattr will only catch the AttributeError that it raises itself.

Also getattr(object, 'attr', None) is None is the same as hasattr(object, 'attr'), since None is falsy.

To be honest, I just don't understand why obj.func needs to be checked for anyways. I very well could be overlooking it, but I don't see where any object could be passed to _get_func_name that could have a func attribute.

@antohaUa
Copy link
Collaborator

I ok with hasattr - maybe previously I just get used to getattrs )).
About obj.func - Here is some investigation is needed. For now lets just extend code with additional logic
I think for now without additional investigation it will be more "safety" )

def _get_func_name(self, obj):
    if hasattr(obj, 'func') is False:
        return 'timer'
    elif hasattr(obj, '__name__') is False:
        return self._get_func_name(obj.func)
    return obj.__name__

@BradenM
Copy link
Contributor Author
BradenM commented Jun 20, 2019

Well haha, see my co 8000 mment

This will still always return 'timer' for any function object, cause a function object in micropython can only possibly have __class__ or __name__ has base attributes

@BradenM
Copy link
Contributor Author
BradenM commented Jun 20, 2019

With:

def _get_func_name(self, obj):
    if hasattr(obj, 'func'):
       return self._get_func_name(obj.func)
    return getattr(obj, '__name__', 'timer')

The flow can go three ways:

1.) obj does indeed have a func attribute

  • self._get_func_name(obj.func) is called

2.) obj does not have a func attribute, but does have a __name__ attribute

  • getattr(obj, '__name__', 'timer') will return the functions name

3.) obj does not have a func attribute, nor does it have a __name__ attribute

  • getattr(obj, '__name__', 'timer') will return 'timer' cause obj does not have a __name__ attribute

Is there another usage case that I am missing?

@BradenM
Copy link
Contributor Author
BradenM commented Jun 20, 2019

Okay, final comment for the night haha.

main.py

def _get_func_name(obj):
    if hasattr(obj, 'func'):
       print(obj.func)
       return _get_func_name(obj.func)
    return getattr(obj, '__name__', 'timer')

def test():
    pass

class FakeFuncAttr:
    func = test
    def __init__():
        pass

def test_func_name():
    print("GET FUNC NAME")
    x = test
    print(_get_func_name(x))


def test_with_func_attr():
    print("TEST WITH FUNC ATTR (forced for test)")
    x = FakeFuncAttr
    # setattr(x, 'func', y) - doesnt work on micropython :(
    print(_get_func_name(x))

Results on ESP8266 (no function attrs enabled):

MicroPython v1.11 on 2019-06-03; ESP module with ESP8266
Type "help()" for more information.
>>> 
>>> import main
>>> main.test_func_name()
GET FUNC NAME
timer
>>> main.test_with_func_attr()
TEST WITH FUNC ATTR (forced for test)
<function test at 0x3ffefff0>
timer
>>> 

Results on ESP32 (function attrs enabled):

MicroPython v1.11-47-g1a51fc9dd on 2019-06-18; ESP32 module with ESP32Type "help()" for more information.
>>> 
>>> import main
>>> main.test_
test_func_name  test_with_func_attr
>>> main.test_func_name()
GET FUNC NAME
test
>>> main.test_with_func_attr()
TEST WITH FUNC ATTR (forced for test)
<function test at 0x3ffc3b00>
test
>>> 

Is this not what we are trying to achieve? Hope we come to a conclusion here haha, but I am gonna catch some sleep. Its been fun haha.

@antohaUa
Copy link
Collaborator

Finally I have remebered purpose of obj.func ))
It is used when we have constructions like

@timer.register(interval=2)
@ti
8000
mer.register(interval=1)
def test():
    print('Hello!')

Initially I on quick view I was thinking that this is something internal )

So here you are right and you solution will cover expected cases.
Let's just add comments for quick understanding what goes on for future cases:

def _get_func_name(self, obj):
       # double decorator case
       if hasattr(obj, 'func'):
           return self._get_func_name(obj.func)

       # default 'timer' value to support micropython low memory ports
       return getattr(obj, '__name__', 'timer')

@BradenM
Copy link
Contributor Author
BradenM commented Jun 20, 2019

Haha! Wow, thats it, I never thought about that possibility. But yeah your definitely right about adding some comments. Went ahead and updated it.

@antohaUa antohaUa merged commit 37df411 into blynkkk:master Jun 20, 2019
@antohaUa
Copy link
Collaborator

Thx!

@BradenM BradenM deleted the fix/low-mem-timer branch June 20, 2019 19:54
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BlynkTimer Broken on Low Memory Ports
2 participants
0