diff --git a/python_ls/_ls.py b/python_ls/_ls.py index d5fe516..361eac2 100644 --- a/python_ls/_ls.py +++ b/python_ls/_ls.py @@ -1,5 +1,3 @@ -from collections import Container - try: import pandas as pd except ImportError: @@ -11,7 +9,7 @@ BAD = object() -def ls(obj, attr=None, depth=None, dunder=False, under=True): +def ls(obj, attr=None, depth=None, dunder=False, under=True, unsafe=False): """ Run a recursive find for a named attribute :param obj: Root object to search @@ -25,7 +23,7 @@ def ls(obj, attr=None, depth=None, dunder=False, under=True): depth = 1 for attr, value in iter_ls(obj, attr=attr, depth=depth, - dunder=dunder, under=under): + dunder=dunder, under=under, unsafe=unsafe): size = '' if has_pandas and isinstance(value, pd.DataFrame): size = '{0}x{1}'.format(*value.shape) @@ -42,7 +40,7 @@ def xdir(obj, attr=None, depth=None, dunder=False, under=True): dunder=dunder, under=under))) -def iter_ls(obj, attr=None, depth=1, dunder=False, under=True, +def iter_ls(obj, attr=None, depth=1, dunder=False, under=True, unsafe=False, visited=None, current_depth=1, path=''): visited = visited or set() @@ -96,8 +94,11 @@ def attr_filter_callback(a): if isinstance(obj, dict) or (has_pandas and isinstance(obj, pd.DataFrame)): val = obj[a] else: - val = getattr(obj, a) - except Exception: + if unsafe or not isinstance(getattr(type(obj), a, None), property): + val = getattr(obj, a) + else: + val = BAD + except Exception as exc: val = BAD if include(a): @@ -109,6 +110,6 @@ def attr_filter_callback(a): if val is not BAD and not a.startswith('__'): for sub_a, sub_val in iter_ls(val, attr=attr, depth=depth, dunder=dunder, - under=under, visited=visited, + under=under, unsafe=unsafe, visited=visited, current_depth=current_depth + 1, path=new_path): yield sub_a, sub_val diff --git a/tests/test_ls.py b/tests/test_ls.py index 1a71368..58a9765 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -1,11 +1,18 @@ -from python_ls import iter_ls import pytest +from python_ls import iter_ls + class Object(object): pass +class Universe: + @property + def answer(self): + return 41 + 1 + + @pytest.fixture def test_obj(): o = Object() @@ -51,3 +58,12 @@ def test_depth_is_None(test_obj): actual = [x[0] for x in iter_ls(test_obj, 'something', depth=None)] assert actual == expected + +def test_ls_unsafe(): + actual = list(iter_ls(Universe(), unsafe=True)) + assert ('answer', 42) in actual + + +def test_ls_safe(): + actual = list(iter_ls(Universe())) + assert ('answer', 42) not in actual