From 9295def87ef064b75796ddd2f1f588c7b4c81efa Mon Sep 17 00:00:00 2001 From: zyd Date: Tue, 12 Jul 2022 21:07:26 +0900 Subject: [PATCH] add method hasPath --- README.md | 40 ++++++++++++- ramda/__init__.py | 1 + ramda/hasPath.py | 21 +++++++ ramda/private/_has.py | 6 ++ ramda/private/_helper.py | 3 + test/test_hasPath.py | 124 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 ramda/hasPath.py create mode 100644 test/test_hasPath.py diff --git a/README.md b/README.md index cc507fe..0d8d204 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,43 @@ R.equals(float('nan'), float('nan')) # True - [x] 0.1.2 gte - [ ] has - [ ] hasIn -- [ ] hasPath +- [x] hasPath + +Support both dict and object. + +```python +class Obj: + def __init__(self, v): + self.v = v +obj = Obj(1) + +R.hasPath(['v'], obj) # True +R.hasPath(['v', 'child'], obj) # False + +R.hasPath(['v'], {'v': 1}) # True +R.hasPath(['v', 'child'], {'v': 1}) # False + +# Also support static variable +class Obj: + v = 1 +obj = Obj() +R.hasPath(['v'], obj) # True + +# Also support inherited variable +class Parent: + def __init__(self, a): + self.a = a +class Child(Parent): + def __init__(self, a,b): + super().__init__(a) + self.b = b +child = Child(1, 2) +R.hasPath(['a'], child) # True +R.hasPath(['b'], child) # True +``` + +```` + - [x] 0.1.2 head - [ ] identical - [x] 0.1.2 identity @@ -280,7 +316,7 @@ R.Is(Parent, Parent()) # True R.Is(Parent, Child()) # True R.Is(Child, Child()) # True R.Is(Child, Parent()) # False -``` +```` - [x] 0.1.2 isEmpty diff --git a/ramda/__init__.py b/ramda/__init__.py index 2294073..85d15b8 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -41,6 +41,7 @@ from .groupBy import groupBy from .gt import gt from .gte import gte +from .hasPath import hasPath from .head import head from .identity import identity from .indexOf import indexOf diff --git a/ramda/hasPath.py b/ramda/hasPath.py new file mode 100644 index 0000000..32a0ecf --- /dev/null +++ b/ramda/hasPath.py @@ -0,0 +1,21 @@ +from .isNil import isNil +from .private._curry2 import _curry2 +from .private._has import _has +from .private._helper import getAttribute + + +def inner_hasPath(path, obj): + if len(path) == 0 or isNil(obj): + return False + val = obj + idx = 0 + while idx < len(path): + if not isNil(val) and _has(val, path[idx]): + val = getAttribute(val, path[idx]) + idx += 1 + else: + return False + return True + + +hasPath = _curry2(inner_hasPath) diff --git a/ramda/private/_has.py b/ramda/private/_has.py index 0e48386..ee49cde 100644 --- a/ramda/private/_has.py +++ b/ramda/private/_has.py @@ -1,3 +1,6 @@ +from ._isArrayLike import _isArrayLike + + def _has(obj, key): ''' Since python object can not have None property, we just check for dict. @@ -6,4 +9,7 @@ def _has(obj, key): return isinstance(obj, dict) and key in obj if isinstance(obj, dict): return key in obj or hasattr(obj, key) + if _isArrayLike(obj): + if isinstance(key, int): + return key < len(obj) return hasattr(obj, key) diff --git a/ramda/private/_helper.py b/ramda/private/_helper.py index d640337..ac7f488 100644 --- a/ramda/private/_helper.py +++ b/ramda/private/_helper.py @@ -3,6 +3,7 @@ from ._has import _has from ._isArrayLike import _isArrayLike +from ._isInteger import _isInteger def toNumber(a): @@ -59,6 +60,8 @@ def map(self, fn): """ if isinstance(v, dict) and key in v: return v[key] + if _isArrayLike(v) and _isInteger(key): + return v[key] if _has(v, key): return getattr(v, key, None) if _has(v, 'get'): diff --git a/test/test_hasPath.py b/test/test_hasPath.py new file mode 100644 index 0000000..e38c675 --- /dev/null +++ b/test/test_hasPath.py @@ -0,0 +1,124 @@ + +import unittest + +import ramda as R + +""" +https://github.com/ramda/ramda/blob/master/test/hasPath.js +""" + +dictObj = { + 'objVal': {'b': {'c': 'c'}}, + 'falseVal': False, + 'nullVal': None, + 'arrayVal': ['arr'], +} + + +class TestHasPathForDict(unittest.TestCase): + def test_returns_true_for_existing_path(self): + self.assertEqual(True, R.hasPath(['objVal'], dictObj)) + self.assertEqual(True, R.hasPath(['objVal', 'b'], dictObj)) + self.assertEqual(True, R.hasPath(['objVal', 'b', 'c'], dictObj)) + self.assertEqual(True, R.hasPath(['arrayVal'], dictObj)) + + def test_returns_true_for_existing_path_to_falsy_values(self): + self.assertEqual(True, R.hasPath(['falseVal'], dictObj)) + self.assertEqual(True, R.hasPath(['nullVal'], dictObj)) + + def test_returns_false_for_a_test_for_a_child_to_a_non_object(self): + self.assertEqual(False, R.hasPath(['nullVal', 'child', 'grandChild'], dictObj)) + self.assertEqual(False, R.hasPath(['falseVal', 'child', 'grandChild'], dictObj)) + self.assertEqual(False, R.hasPath(['arrayVal', 0, 'child', 'grandChild'], dictObj)) + + def test_returns_true_for_existing_path_with_indexes(self): + self.assertEqual(True, R.hasPath(['arrayVal', 0], dictObj)) + + def test_returns_false_for_non_existing_path_with_indexes(self): + self.assertEqual(False, R.hasPath(['arrayVal', 1], dictObj)) + + +class Obj: + def __init__(self, objVal, falseVal, nullVal, arrayVal): + self.objVal = objVal + self.falseVal = falseVal + self.nullVal = nullVal + self.arrayVal = arrayVal + + +class ObjVal: + def __init__(self, b): + self.b = b + + +class B: + def __init__(self, c): + self.c = c + + +obj = Obj(ObjVal(B('c')), False, None, ['arr']) + + +class TestHasPathForObject(unittest.TestCase): + def test_returns_true_for_existing_path(self): + self.assertEqual(True, R.hasPath(['objVal'], obj)) + self.assertEqual(True, R.hasPath(['objVal', 'b'], obj)) + self.assertEqual(True, R.hasPath(['objVal', 'b', 'c'], obj)) + self.assertEqual(True, R.hasPath(['arrayVal'], obj)) + + def test_returns_true_for_existing_path_to_falsy_values(self): + self.assertEqual(True, R.hasPath(['falseVal'], obj)) + self.assertEqual(True, R.hasPath(['nullVal'], obj)) + + def test_returns_false_for_a_test_for_a_child_to_a_non_object(self): + self.assertEqual(False, R.hasPath(['nullVal', 'child', 'grandChild'], obj)) + self.assertEqual(False, R.hasPath(['falseVal', 'child', 'grandChild'], obj)) + self.assertEqual(False, R.hasPath(['arrayVal', 0, 'child', 'grandChild'], obj)) + + def test_returns_true_for_existing_path_with_indexes(self): + self.assertEqual(True, R.hasPath(['arrayVal', 0], obj)) + + def test_returns_false_for_non_existing_path_with_indexes(self): + self.assertEqual(False, R.hasPath(['arrayVal', 1], obj)) + + def test_static_variables(self): + class A: + static_a = 'static a' + a = A() + self.assertEqual(True, R.hasPath(['static_a'], a)) + + def test_inherited_variables(self): + class Parent: + def __init__(self, a): + self.a = a + + class Child(Parent): + def __init__(self, a, b): + super().__init__(a) + self.b = b + c = Child('a', 'b') + self.assertEqual(True, R.hasPath(['a'], c)) + self.assertEqual(True, R.hasPath(['b'], c)) + + +class TestHasPathForOthers(unittest.TestCase): + + def test_for_paths_in_arrays(self): + self.assertEqual(True, R.hasPath([0], [1, 2])) + self.assertEqual(False, R.hasPath([2], [1, 2])) + + def test_returns_false_for_non_object(self): + self.assertEqual(False, R.hasPath([], dictObj)) + self.assertEqual(False, R.hasPath([], obj)) + + def test_paths_on_non_objects(self): + self.assertEqual(False, R.hasPath(['a', 'b'], None)) + self.assertEqual(False, R.hasPath(['a', 'b'], True)) + self.assertEqual(False, R.hasPath(['a', 'b'], '')) + + def test_currying(self): + self.assertEqual(True, R.hasPath(['a', 'b'], {'a': {'b': 1}})) + + +if __name__ == '__main__': + unittest.main()