Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions ramda/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions ramda/hasPath.py
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions ramda/private/_has.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
3 changes: 3 additions & 0 deletions ramda/private/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from ._has import _has
from ._isArrayLike import _isArrayLike
from ._isInteger import _isInteger


def toNumber(a):
Expand Down Expand Up @@ -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'):
Expand Down
124 changes: 124 additions & 0 deletions test/test_hasPath.py
Original file line number Diff line number Diff line change
@@ -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()