From 995eef036270b2ffd1e1a41c26166f7beeed967e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl>
Date: Tue, 5 Mar 2024 08:49:42 +0100
Subject: [PATCH] added __len__ to Optional

---
 CHANGELOG.md                        |  2 +-
 satella/__init__.py                 |  2 +-
 satella/coding/optionals.py         | 41 ++++++++++-------------------
 tests/test_coding/test_optionals.py | 30 +++++++++++++++++++++
 4 files changed, 46 insertions(+), 29 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4201c01..1d8fe903 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,4 +4,4 @@
 * satella.coding.Context are considered experimental
 * added a way to register an object to be cleaned up via MemoryPressureManager
 * fixed get_own_cpu_usage() to work on Windows
-
+* added __len__ to Optional
diff --git a/satella/__init__.py b/satella/__init__.py
index d7226f3a..1eb0d8fc 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.24.2a4'
+__version__ = '2.24.2a5'
diff --git a/satella/coding/optionals.py b/satella/coding/optionals.py
index 00be5883..92f17433 100644
--- a/satella/coding/optionals.py
+++ b/satella/coding/optionals.py
@@ -15,10 +15,7 @@ def iterate_if_nnone(iterable: tp.Optional[tp.Iterable]) -> tp.Iterable:
     :param iterable: iterable to iterate over
     :return: an empty generator if iterable is none, else an iterator over iterable
     """
-    if iterable is not None:
-        return iter(iterable)
-    else:
-        return iter(tuple())
+    return iter(iterable) if iterable is not None else iter(tuple())
 
 
 def call_if_nnone(clbl: tp.Optional[tp.Callable[..., V]], *args, **kwargs) -> tp.Optional[V]:
@@ -77,41 +74,34 @@ class Optional(Proxy[T]):
         me = getattr(self, '_Proxy__obj')
         if me is None:
             return False
-        return me == other
+        return other == me
 
     def __getattr__(self, item):
-        if getattr(self, '_Proxy__obj') is None:
-            return EMPTY_OPTIONAL
-        else:
-            return super().__getattr__(item)
+        return EMPTY_OPTIONAL if getattr(self, '_Proxy__obj') is None else super().__getattr__(item)
 
     def __call__(self, *args, **kwargs):
-        if getattr(self, '_Proxy__obj') is None:
-            return EMPTY_OPTIONAL
-        else:
-            return super().__call__(*args, **kwargs)
+        return EMPTY_OPTIONAL if getattr(self, '_Proxy__obj') is None else super().__call__(*args, **kwargs)
 
     def __bool__(self):
-        if getattr(self, '_Proxy__obj') is None:
-            return False
-        else:
-            return super().__bool__()
+        return False if getattr(self, '_Proxy__obj') is None else super().__bool__()
 
     def __getitem__(self, item):
-        if getattr(self, '_Proxy__obj') is None:
-            return EMPTY_OPTIONAL
-        else:
-            return super().__getitem__(item)
+        return EMPTY_OPTIONAL if getattr(self, '_Proxy__obj') is None else super().__getattr__(item)
 
     def __setitem__(self, key, value) -> None:
         if getattr(self, '_Proxy__obj') is None:
             return
-        return super().__setitem__(key, value)
+        super().__setitem__(key, value)
 
     def __delitem__(self, key) -> None:
         if getattr(self, '_Proxy__obj') is None:
             return
-        return super().__delitem__(key)
+        super().__delitem__(key)
+
+    def __len__(self) -> int:
+        obj = getattr(self, '_Proxy__obj')
+        return 0 if obj is None else len(obj)
+
 
 
 EMPTY_OPTIONAL = Optional(None)
@@ -125,7 +115,4 @@ def extract_optional(v: tp.Union[T, Optional[T]]) -> T:
     :param v: value to extract the value from
     :return: resulting value
     """
-    if isinstance(v, Optional):
-        return getattr(v, '_Proxy__obj')
-    else:
-        return v
+    return getattr(v, '_Proxy__obj') if isinstance(v, Optional) else v
diff --git a/tests/test_coding/test_optionals.py b/tests/test_coding/test_optionals.py
index 80a164bb..d94def9c 100644
--- a/tests/test_coding/test_optionals.py
+++ b/tests/test_coding/test_optionals.py
@@ -20,6 +20,35 @@ class TestOptionals(unittest.TestCase):
         self.assertFalse(c)
         self.assertTrue(b)
 
+    def test_optional_lambda(self):
+        Optional(None())
+        Optional(lambda: 5)()
+
+    def test_object(self):
+        class Obj:
+            def __init__(self):
+                self.a = 5
+
+        obj = Obj()
+        opt = Optional(obj)
+        Optional(None).a
+        self.assertEqual(opt.a, 5)
+        opt.a = 6
+        self.assertEqual(obj.a, 6)
+        self.assertEqual(opt.a, 6)
+        del opt.a
+        self.assertRaises(AttributeError, obj.a)
+        self.assertRaises(AttributeError, opt.a)
+
+    def test_list(self):
+        a = [1, 2, 3]
+        opt = Optional(a)
+        self.assertEqual(opt[0], 1)
+        del opt[0]
+        self.assertEqual(len(opt), 2)
+        opt[0] = 4
+        self.assertEqual(opt[0], 4)
+
     def test_optional_eq(self):
         class Opt:
             a = 5
@@ -30,6 +59,7 @@ class TestOptionals(unittest.TestCase):
         self.assertIn(2, Optional(a).b)
         self.assertNotEqual(Optional(None).a, 5)
         self.assertNotIn(2, Optional(None).b)
+        self.assertEqual(Optional(5), Optional(5))
 
     def test_optional(self):
         self.assertIsNone(call_if_nnone(None))
-- 
GitLab