Skip to content

Commit

Permalink
feature : exists=...
Browse files Browse the repository at this point in the history
  • Loading branch information
bwallrich committed Feb 9, 2024
1 parent 59216d4 commit 2c7127f
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 9 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ available options for all types ares :
| ```onChange=func``` | None | similar to ```onchange``` |
| ```set=func``` | None | a read only value, calculated from other .See [set or compute function](#set-or-compute) |
| ```compute=func``` | None | similar to ```set``` |
| ```exists=func``` | None | a function to say if the object "exists", depending on values from other attributs. See [exists](#exists) for details |

See [functions](#functions) for mor details and examples how to use them.

Expand Down Expand Up @@ -317,6 +318,42 @@ a.b = 2 # -> output "The value of b as changed from 0 to 2"
a.b = 3-1 # -> nothing displayed
```

### exists

A function wich must return ```True|False``` to say if this key exists.

```python
# example
from stricto import Dict, Int, String

def check_if_female(value, o):
"""
return true if Female
"""
if o.gender == "Male":
return False
return True

cat=Dict({
"name" : String(),
"gender" : String( default = 'Male', in='Male', 'Female' ]),
"female_infos" : Dict(
{
"number_of_litter" : Int(default=0, required=True)
# ... some other attributes

}, exists=check_if_female )
})

a.set({ "name" : "Felix", "gender" : "Male" }
a.female_infos # -> None
a.female_infos.number_of_litter = 2 # -> Raise an Error

a.gender = "Female"
a.female_infos.number_of_litter = 2 # -> Ok
a.female_infos # -> { "number_of_litter" : 2 }
```

## Tests

```bash
Expand Down
27 changes: 23 additions & 4 deletions stricto/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,16 @@ def __copy__(self):
result.__dict__["parent"] = self.__dict__["parent"]
result.__dict__["attribute_name"] = self.__dict__["attribute_name"]
result.__dict__["_on_change"] = self.__dict__["_on_change"]
result.__dict__["_exists"] = self.__dict__["_exists"]
result._locked = True
return result

def __repr__(self):
a = {}
for key in self._keys:
v = getattr(self, key)
if v.exists() is False:
continue
a[key] = getattr(self, key)
return a.__repr__()

Expand All @@ -131,6 +135,9 @@ def __ne__(self, other):
def get_value(self):
a = {}
for key in self._keys:
key_object = getattr(self, key)
if key_object.exists() is False:
continue
a[key] = getattr(self, key).get_value()
return a

Expand All @@ -141,7 +148,9 @@ def get(self, key: str, default=None):
if key not in self._keys:
return default
v = self.__dict__[key]
return default if v is None else v
if v.exists() is False:
return default
return v

def set_value_without_checks(self, value):
for key in self._keys:
Expand All @@ -161,7 +170,10 @@ def auto_set(self):

GenericType.auto_set(self)
for key in self._keys:
getattr(self, key).auto_set()
key_object = getattr(self, key)
if key_object.exists() is False:
continue
key_object.auto_set()

self.__dict__["currently_doing_autoset"] = False

Expand All @@ -173,8 +185,11 @@ def check(self, value):
# check reccursively subtypes
if isinstance(value, dict):
for key in self._keys:
key_object = self.__dict__[key]
if key_object.exists() is False:
continue
sub_value = value.get(key)
self.__dict__[key].check(sub_value)
key_object.check(sub_value)

# check if a non-described value
for key in value:
Expand All @@ -188,8 +203,12 @@ def check(self, value):

if isinstance(value, Dict):
for key in self._keys:
key_object = self.__dict__[key]
if key_object.exists() is False:
continue

sub_value = value.get(key).get_value()
self.__dict__[key].check(sub_value)
key_object.check(sub_value)
return

def check_type(self, value):
Expand Down
21 changes: 19 additions & 2 deletions stricto/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,19 @@ def __init__(self, **kwargs):
self._constraints = constraint if isinstance(constraint, list) else [constraint]



self._default = kwargs.pop("default", None)
self.check(self._default)
self._value = self._default
self._old_value = self._value

# transformation of the value before setting
self._transform = kwargs.pop("transform", None)
# the value is computed
self._auto_set = kwargs.pop("set", kwargs.pop("compute", None))
# on change trigger
self._on_change = kwargs.pop("onChange", kwargs.pop("onchange", None))
# this object exist
self._exists = kwargs.pop("exists", kwargs.pop("existsIf", True))


def set_hierachy_attributs(self, root, parent, name):
Expand All @@ -60,6 +64,13 @@ def am_i_root(self):
return True
return False

def exists(self):
"""
Return True if the object Exist, othewise False.
exist can be a function to make this field dependant from the value of another
"""
return self.get_args_or_execute_them(self._exists, self._value)

def path_name(self):
"""
return a string with the name of the object
Expand Down Expand Up @@ -244,6 +255,9 @@ def set(self, value):
"""
Fill with a value or raise an Error if not valid
"""

if self.exists() is False:
raise Error(ErrorType.NOTALIST, "locked", self.path_name())

if self._auto_set is not None:
if self.root.currently_doing_autoset is False:
Expand All @@ -263,6 +277,9 @@ def set_value_without_checks(self, value):
return True if some changement, otherwise False
"""

if self.exists() is False:
raise Error(ErrorType.NOTALIST, "locked", self.path_name())

if self._auto_set is not None:
if self.root.currently_doing_autoset is False:
raise Error(ErrorType.READONLY, "Cannot modify value", self.path_name())
Expand Down Expand Up @@ -380,5 +397,5 @@ def get_args_or_execute_them(self, arg, value):
min = computeMin -> return computeMin( value )
"""
if callable(arg):
return arg(self, value, self.root)
return arg(value, self.root)
return arg
44 changes: 43 additions & 1 deletion tests/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import unittest
import json

from stricto import String, Int, Dict, List, Error
from stricto import String, Int, Dict, List, Bool, Error


class TestDict(unittest.TestCase):
Expand Down Expand Up @@ -257,3 +257,45 @@ def test_auto_set_loop(self):
self.assertEqual(a.c, 2)
self.assertEqual(a.d, 3)
self.assertEqual(a.e, 4)


def test_not_exist(self):
"""
test not exist
"""
def check_exists(value, o): # pylint: disable=unused-argument
"""
return if exists or not
"""
return o.must_exists.get_value()

a = Dict(
{
"must_exists" : Bool( default=False),
"a": Int(),
"b": Int(default=1),
"c": Int(default=2),
"d": Int(default=3, required=True, exists=check_exists),
"e": Int(default=4),
}
)
a.set({"a": 2})
with self.assertRaises(KeyError) as e:
a.d=a.get_value()['d']
self.assertEqual(e.exception.args[0], "d")
self.assertEqual(a.get('d'), None)
self.assertEqual(repr (a), "{'must_exists': False, 'a': 2, 'b': 1, 'c': 2, 'e': 4}")

with self.assertRaises(Error) as e:
a.set({"d": 2})
self.assertEqual(e.exception.message, "locked")
with self.assertRaises(Error) as e:
a.d=2
self.assertEqual(e.exception.message, "locked")

a.must_exists = True
self.assertEqual(a.get('d'), 3)
self.assertEqual(a.get_value()['d'], 3)
self.assertEqual(repr (a), "{'must_exists': True, 'a': 2, 'b': 1, 'c': 2, 'd': 3, 'e': 4}")
a.set({"d": 2})
self.assertEqual(a.d, 2)
2 changes: 1 addition & 1 deletion tests/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class TestList(unittest.TestCase): # pylint: disable=too-many-public-methods
"""
Test on Strings
Test on Lists
"""

def test_error_type(self):
Expand Down
10 changes: 9 additions & 1 deletion tests/test_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ def test_compare(self):
b = String()
b.set("bar")
self.assertNotEqual(a, b)
self.assertEqual(a, "foo")
self.assertEqual(a == "foo", True)
self.assertEqual(a != "foo", False)
for c in [ b, "bar"]:
self.assertEqual(a < c, False)
self.assertEqual(a <= c, False)
self.assertEqual(a > c, True)
self.assertEqual(a >= c, True)

# check for non reference
b.set(a)
Expand Down Expand Up @@ -122,7 +130,7 @@ def test_regexp(self):
a.set("AtoZ")

# function return a regexp
a = String(regexp=lambda self, value, root: r".*Z$")
a = String(regexp=lambda value, root: r".*Z$")
with self.assertRaises(Error) as e:
a.set("Foo")
self.assertEqual(e.exception.message, "Dont match regexp")
Expand Down

0 comments on commit 2c7127f

Please sign in to comment.