diff --git a/README.md b/README.md index 08a0869..17711c9 100644 --- a/README.md +++ b/README.md @@ -233,13 +233,19 @@ R.equals(float('nan'), float('nan')) # True - [ ] groupWith - [x] 0.1.2 gt - [x] 0.1.2 gte -- [ ] has +- [x] has + +Similar to `hasPath`. + - [ ] hasIn - [x] hasPath Support both dict and object. ```python + +R.hasPath(['a', 'b'], {'a': {'b': 42}}) # True + class Obj: def __init__(self, v): self.v = v @@ -251,11 +257,11 @@ R.hasPath(['v', 'child'], obj) # False R.hasPath(['v'], {'v': 1}) # True R.hasPath(['v', 'child'], {'v': 1}) # False -# Also support static variable +# Does not include static variable class Obj: v = 1 obj = Obj() -R.hasPath(['v'], obj) # True +R.hasPath(['v'], obj) # False # Also support inherited variable class Parent: @@ -270,8 +276,6 @@ R.hasPath(['a'], child) # True R.hasPath(['b'], child) # True ``` -```` - - [x] 0.1.2 head - [ ] identical - [x] 0.1.2 identity @@ -337,6 +341,8 @@ this is for checking if the given value is None or not. - [x] 0.1.4 juxt - [x] 0.1.2 keys +For object, `keys` does not return object's methods. + ```python # When using R.keys(obj) and obj is a class instance, we use obj.__dict__ as keys. class A: @@ -376,6 +382,8 @@ R.keys({'a': 1, 'b': 2}) # ['a', 'b'] - [x] 0.2.0 keysIn +For object, `keysIn` does not return object's methods. + Different from `keys`, `keysIn` will return all attributes of the object, including super class attributes and class static variables. ```python diff --git a/ramda/__init__.py b/ramda/__init__.py index 3aa680f..a9f5f02 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -42,6 +42,7 @@ from .groupBy import groupBy from .gt import gt from .gte import gte +from .has import has from .hasPath import hasPath from .head import head from .identity import identity diff --git a/ramda/has.py b/ramda/has.py new file mode 100644 index 0000000..19e617d --- /dev/null +++ b/ramda/has.py @@ -0,0 +1,4 @@ +from .hasPath import hasPath +from .private._curry2 import _curry2 + +has = _curry2(lambda prop, obj: hasPath([prop], obj)) diff --git a/ramda/hasPath.py b/ramda/hasPath.py index 32a0ecf..06fef3e 100644 --- a/ramda/hasPath.py +++ b/ramda/hasPath.py @@ -1,6 +1,6 @@ from .isNil import isNil from .private._curry2 import _curry2 -from .private._has import _has +from .private._hasOwn import _hasOwn from .private._helper import getAttribute @@ -10,7 +10,7 @@ def inner_hasPath(path, obj): val = obj idx = 0 while idx < len(path): - if not isNil(val) and _has(val, path[idx]): + if not isNil(val) and _hasOwn(val, path[idx]): val = getAttribute(val, path[idx]) idx += 1 else: diff --git a/ramda/private/_hasOwn.py b/ramda/private/_hasOwn.py new file mode 100644 index 0000000..75df068 --- /dev/null +++ b/ramda/private/_hasOwn.py @@ -0,0 +1,14 @@ +from ._isArrayLike import _isArrayLike + + +def _hasOwn(obj, prop): + if prop is None: + return isinstance(obj, dict) and prop in obj + if isinstance(obj, dict): + return prop in obj + if _isArrayLike(obj): + if isinstance(prop, int): + return prop < len(obj) + if hasattr(obj, '__dict__'): + return prop in obj.__dict__ + return False diff --git a/test/private/test__has.py b/test/private/test__has.py new file mode 100644 index 0000000..fd094fc --- /dev/null +++ b/test/private/test__has.py @@ -0,0 +1,55 @@ +import unittest + +from ramda.private._has import _has + + +class Test_has(unittest.TestCase): + def test_array(self): + self.assertEqual(False, _has([], 0)) + self.assertEqual(True, _has([1, 2, 3], 0)) + self.assertEqual(True, _has([1, 2, 3], 1)) + self.assertEqual(True, _has([1, 2, 3], 2)) + self.assertEqual(False, _has([1, 2, 3], 3)) + + def test_dict(self): + self.assertEqual(False, _has({}, 'a')) + self.assertEqual(True, _has({'a': False, 'b': {'c': 1}}, 'a')) + self.assertEqual(False, _has({'a': False, 'b': {'c': 1}}, 'c')) # do not support nested dict + self.assertEqual(True, _has({'a': False, 'b': {'c': 1}}, 'get')) # do not check method + + def test_object(self): + class Parent: + c = 'parent static' + + def __init__(self): + self.d = 'parent instance' + + def foo(self): + return 'parent method' + + class Obj(Parent): + a = 'static' + + def __init__(self, b): + super().__init__() + self.b = b + + def bar(self): + return 'obj method' + + obj = Obj('b') + + self.assertEqual(True, _has(obj, 'a')) + self.assertEqual(True, _has(obj, 'b')) + self.assertEqual(True, _has(obj, 'c')) + self.assertEqual(True, _has(obj, 'd')) + self.assertEqual(True, _has(obj, 'foo')) + self.assertEqual(True, _has(obj, 'bar')) + + def test_if_key_is_none(self): + self.assertEqual(True, _has({None: False}, None)) + self.assertEqual(False, _has({'None': True}, None)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/private/test__hasOwn.py b/test/private/test__hasOwn.py new file mode 100644 index 0000000..321f34a --- /dev/null +++ b/test/private/test__hasOwn.py @@ -0,0 +1,55 @@ +import unittest + +from ramda.private._hasOwn import _hasOwn + + +class Test_HasOwn(unittest.TestCase): + def test_array(self): + self.assertEqual(False, _hasOwn([], 0)) + self.assertEqual(True, _hasOwn([1, 2, 3], 0)) + self.assertEqual(True, _hasOwn([1, 2, 3], 1)) + self.assertEqual(True, _hasOwn([1, 2, 3], 2)) + self.assertEqual(False, _hasOwn([1, 2, 3], 3)) + + def test_dict(self): + self.assertEqual(False, _hasOwn({}, 'a')) + self.assertEqual(True, _hasOwn({'a': False, 'b': {'c': 1}}, 'a')) + self.assertEqual(False, _hasOwn({'a': False, 'b': {'c': 1}}, 'c')) # do not support nested dict + self.assertEqual(False, _hasOwn({'a': False, 'b': {'c': 1}}, 'get')) # do not check method + + def test_object(self): + class Parent: + c = 'parent static' + + def __init__(self): + self.d = 'parent instance' + + def foo(self): + return 'parent method' + + class Obj(Parent): + a = 'static' + + def __init__(self, b): + super().__init__() + self.b = b + + def bar(self): + return 'obj method' + + obj = Obj('b') + + self.assertEqual(False, _hasOwn(obj, 'a')) + self.assertEqual(True, _hasOwn(obj, 'b')) + self.assertEqual(False, _hasOwn(obj, 'c')) + self.assertEqual(True, _hasOwn(obj, 'd')) + self.assertEqual(False, _hasOwn(obj, 'foo')) + self.assertEqual(False, _hasOwn(obj, 'bar')) + + def test_if_key_is_none(self): + self.assertEqual(True, _hasOwn({None: False}, None)) + self.assertEqual(False, _hasOwn({'None': True}, None)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_has.py b/test/test_has.py new file mode 100644 index 0000000..dba32d1 --- /dev/null +++ b/test/test_has.py @@ -0,0 +1,67 @@ + +import unittest + +import ramda as R + +""" +https://github.com/ramda/ramda/blob/master/test/has.js +""" + +dictFred = {'name': 'Fred', 'age': 23} +dictAnon = {'age': 99} + + +class Fred: + def __init__(self): + self.name = 'Fred' + self.age = 23 + + +class Anon: + def __init__(self): + self.age = 99 + + +fred = Fred() +anon = Anon() + + +class TestHasForDict(unittest.TestCase): + def test_returns_true_if_the_specified_property_is_present(self): + self.assertEqual(True, R.has('name', dictFred)) + + def test_returns_false_if_the_specified_property_is_absent(self): + self.assertEqual(False, R.has('name', dictAnon)) + + +class TestHasForObject(unittest.TestCase): + def test_returns_true_if_the_specified_property_is_present(self): + self.assertEqual(True, R.has('name', fred)) + + def test_returns_false_if_the_specified_property_is_absent(self): + self.assertEqual(False, R.has('name', anon)) + + +class TestHasForOthers(unittest.TestCase): + def test_does_not_check_static(self): + class Person: + static_var = 'static' + def __init__(self): + self.name = 'bob' + bob = Person() + + self.assertEqual(True, R.has('name', bob)) + self.assertEqual(False, R.has('static_var', bob)) + + def test_returns_false_for_non_objects(self): + self.assertEqual(False, R.has('a', None)) + self.assertEqual(False, R.has('a', True)) + self.assertEqual(False, R.has('a', False)) + self.assertEqual(False, R.has('a', '')) + + def test_currying(self): + self.assertEqual(True, R.has('a')({'a': {'b': 1}})) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_hasPath.py b/test/test_hasPath.py index e38c675..717fd4e 100644 --- a/test/test_hasPath.py +++ b/test/test_hasPath.py @@ -85,7 +85,7 @@ def test_static_variables(self): class A: static_a = 'static a' a = A() - self.assertEqual(True, R.hasPath(['static_a'], a)) + self.assertEqual(False, R.hasPath(['static_a'], a)) def test_inherited_variables(self): class Parent: diff --git a/test/test_keys.py b/test/test_keys.py index c320aca..4b4345b 100644 --- a/test/test_keys.py +++ b/test/test_keys.py @@ -45,6 +45,11 @@ def test_works_with_primitives(self): self.assertEqual([], R.keys([])) self.assertEqual([], R.keys({})) + def test_works_with_methods_properties(self): + self.assertEqual(['foo'], R.keys({'foo': lambda x: x})) + + # keys does not work with methods in object + if __name__ == '__main__': unittest.main()