Skip to content

Instantly share code, notes, and snippets.

@obestwalter
Last active February 20, 2022 17:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save obestwalter/e82068b95a9576024d0f9504587e24d6 to your computer and use it in GitHub Desktop.
Save obestwalter/e82068b95a9576024d0f9504587e24d6 to your computer and use it in GitHub Desktop.
When inspecting self in this way in a property it endlessly recursess
import traceback
class SomeClass:
a_class_attr = "don't find me"
def an_instance_method(self):
...
def _a_private_method(self):
...
def make_name2callable(obj):
n2c = {n: getattr(obj, n) for n in dir(obj)}
return {k: v for k, v in n2c.items() if not k.startswith("_") and callable(v)}
class ThisWorks(SomeClass):
def name2callable(self):
return make_name2callable(self)
class ThisDoesnt(SomeClass):
@property
def name2callable(self):
return make_name2callable(self)
print(ThisWorks().name2callable())
try:
print(ThisDoesnt().name2callable)
except RecursionError:
print("why does this cause a recursion?")
traceback.print_exc()
@obestwalter
Copy link
Author

For completeness here is a little test/demo that uses this:

import os
import sys

import pytest


class _XOLI_AUTO_ALIAS:
    """On Instantiation: inject all public methods into aliases"""

    def __init__(self):
        """create map from alias to callable.

        Transform names from pythony snake_case to bashy slug-case
        """
        from xonsh.built_ins import XSH

        XSH.aliases.update(
            {
                k.replace("_", "-"): v
                for k, v in {n: getattr(self, n) for n in dir(self)}.items()
                if not k.startswith("_") and callable(v)
            }
        )


# end of production code
#########################################################################################


class FakeXonshBuiltIns(type(os)):
    DEFAULT_ALIASES = {"default": os.getcwd}

    def __init__(self):
        self.XSH = self._XSH()

    class _XSH:
        def __init__(self):
            self.aliases = FakeXonshBuiltIns.DEFAULT_ALIASES.copy()


@pytest.fixture(autouse=True)
def provide_global_xonsh_aliases():
    sys.modules["xonsh.built_ins"] = FakeXonshBuiltIns()
    from xonsh.built_ins import XSH

    assert isinstance(XSH, FakeXonshBuiltIns._XSH)
    assert XSH.aliases == FakeXonshBuiltIns.DEFAULT_ALIASES
    assert XSH.aliases is not FakeXonshBuiltIns.DEFAULT_ALIASES


def test_auto_alias():
    class Oliasses(_XOLI_AUTO_ALIAS):
        a_public_class_atribute = "dont find me!"

        def an_instance_method(self):
            ...

        def another_instance_method(self):
            ...

        @classmethod
        def a_class_method(cls):
            ...

        @staticmethod
        def a_static_method():
            ...

        def _a_private_method(self):
            ...  # don't find me!

    class Oliherited(Oliasses):
        def a_method_on_inheritor(self):
            ...

    from xonsh.built_ins import XSH

    # ensure defaults are as expected
    assert XSH.aliases == FakeXonshBuiltIns.DEFAULT_ALIASES

    # trigger alias generation
    Oliherited()

    # ensure existing aliases are not clobbered
    assert XSH.aliases.pop("default") == os.getcwd

    # ensure new aliases are what is expected
    assert len(XSH.aliases) == 5

    # ensure all method names are in there and transformed properly
    for name in [
        "an_instance_method",
        "another_instance_method",
        "a_class_method",
        "a_static_method",
        "a_method_on_inheritor",
    ]:
        assert name not in XSH.aliases
        obj = XSH.aliases[name.replace("_", "-")]
        assert obj.__name__ == name
        assert callable(obj)

    # ensure_no_privates_and_no_non_callables
    private_attrs = ["a_public_class_atribute", "_a_private_method"]
    for name in private_attrs:
        assert name not in XSH.aliases
        assert name.replace("_", "-") not in XSH.aliases

    # general check that nothing weird ended up in aliases
    for k, v in XSH.aliases.items():
        assert not k.startswith("_")
        assert callable(v)

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