From e09ce40dcaf224b21aee709d38683f75acd8dc1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl>
Date: Fri, 2 Apr 2021 20:28:00 +0200
Subject: [PATCH] 2.15.4

---
 CHANGELOG.md                               |  1 +
 docs/coding/structures.rst                 |  6 ++
 satella/__init__.py                        |  2 +-
 satella/coding/structures/__init__.py      |  2 +
 satella/coding/structures/push_iterable.py | 66 ++++++++++++++++++++++
 tests/test_coding/test_structures.py       | 20 ++++++-
 6 files changed, 95 insertions(+), 2 deletions(-)
 create mode 100644 satella/coding/structures/push_iterable.py

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07ff7c9d..3af8fc87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,3 +3,4 @@
 **bugfix release**
 
 * fixed import problem
+* added 
diff --git a/docs/coding/structures.rst b/docs/coding/structures.rst
index e7486d0e..79b679ff 100644
--- a/docs/coding/structures.rst
+++ b/docs/coding/structures.rst
@@ -6,6 +6,12 @@ Structures
     :members:
 
 
+PushIterable
+------------
+
+.. autoclass:: satella.coding.structures.PushIterable
+    :members:
+
 SyncableDroppable
 -----------------
 
diff --git a/satella/__init__.py b/satella/__init__.py
index 4895c882..e824526d 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.15.3'
+__version__ = '2.15.4'
diff --git a/satella/coding/structures/__init__.py b/satella/coding/structures/__init__.py
index c1d21f02..e0efee2b 100644
--- a/satella/coding/structures/__init__.py
+++ b/satella/coding/structures/__init__.py
@@ -18,8 +18,10 @@ from .typednamedtuple import typednamedtuple
 from .lru import LRU
 from .syncable_droppable import DBStorage, SyncableDroppable
 from .tuples import Vector
+from .push_iterable import PushIterable
 
 __all__ = [
+    'PushIterable',
     'Vector',
     'DBStorage', 'SyncableDroppable',
     'LRU',
diff --git a/satella/coding/structures/push_iterable.py b/satella/coding/structures/push_iterable.py
new file mode 100644
index 00000000..4daad6ef
--- /dev/null
+++ b/satella/coding/structures/push_iterable.py
@@ -0,0 +1,66 @@
+import typing as tp
+from collections import deque
+
+from satella.coding.typing import T
+
+
+class PushIterable(tp.Generic[T]):
+    """
+    An iterable that you can add elements to it's head, and they will be popped
+    before this iterable is consumed when a pop is called.
+
+    Example:
+
+    .. code-block:: python
+
+        a = PushIterable([1, 2, 3])
+        assert a.pop() == 1
+        a.push(0)
+        assert a.pop() == 0
+        assert a.pop() == 2
+        a.push(0)
+        a.push(1)
+        assert a.pop() == 1
+        assert a.pop() == 0
+        a.push_left(0)
+        a.push_left(1)
+        assert a.pop() == 0
+        assert a.pop() == 1
+        assert a.pop() == 3
+        assertRaises(StopIteration, a.pop)
+    """
+    def __init__(self, iterable: tp.Iterable[T]):
+        self.iterable = iter(iterable)
+        self.collection = deque()
+
+    def pop(self) -> T:
+        """
+        Return next element from the stack
+        :return: a next element
+        """
+        if not self.collection:
+            return next(self.iterable)
+        else:
+            return self.collection.pop()
+
+    def __iter__(self):
+        return self
+
+    def __next__(self) -> T:
+        return self.pop()
+
+    def push(self, item: T) -> None:
+        """
+        Push an item so that next pop will retrieve this item
+
+        :param item: item to push
+        """
+        self.collection.append(item)
+
+    def push_left(self, item):
+        """
+        Push an item so that last pop from internal buffer will retrieve this item
+
+        :param item: item to push
+        """
+        self.collection.appendleft(item)
diff --git a/tests/test_coding/test_structures.py b/tests/test_coding/test_structures.py
index 8b544849..9d7a7605 100644
--- a/tests/test_coding/test_structures.py
+++ b/tests/test_coding/test_structures.py
@@ -14,11 +14,29 @@ from satella.coding.structures import TimeBasedHeap, Heap, typednamedtuple, \
     DirtyDict, KeyAwareDefaultDict, Proxy, ReprableMixin, TimeBasedSetHeap, ExpiringEntryDict, SelfCleaningDefaultDict, \
     CacheDict, StrEqHashableMixin, ComparableIntEnum, HashableIntEnum, ComparableAndHashableBy, \
     ComparableAndHashableByInt, SparseMatrix, ExclusiveWritebackCache, Subqueue, \
-    CountingDict, ComparableEnum, LRU, LRUCacheDict, Vector, DefaultDict
+    CountingDict, ComparableEnum, LRU, LRUCacheDict, Vector, DefaultDict, PushIterable
 
 
 class TestMisc(unittest.TestCase):
 
+    def test_push_iterable(self):
+
+        a = PushIterable([1, 2, 3])
+        self.assertEqual(a.pop(), 1)
+        a.push(0)
+        self.assertEqual(a.pop(), 0)
+        self.assertEqual(a.pop(), 2)
+        a.push(0)
+        a.push(1)
+        self.assertEqual(a.pop(), 1)
+        self.assertEqual(a.pop(), 0)
+        a.push_left(0)
+        a.push_left(1)
+        self.assertEqual(a.pop(), 0)
+        self.assertEqual(a.pop(), 1)
+        self.assertEqual(a.pop(), 3)
+        self.assertRaises(StopIteration, a.pop)
+
     def test_default_dict(self):
         a = DefaultDict(lambda: '')
         a[2]
-- 
GitLab