Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-fakeredis for 
openSUSE:Factory checked in at 2023-09-28 00:24:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-fakeredis (Old)
 and      /work/SRC/openSUSE:Factory/.python-fakeredis.new.23327 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-fakeredis"

Thu Sep 28 00:24:57 2023 rev:21 rq:1113668 version:2.19.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-fakeredis/python-fakeredis.changes        
2023-09-12 21:02:40.643734758 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-fakeredis.new.23327/python-fakeredis.changes 
    2023-09-28 00:45:11.471503670 +0200
@@ -1,0 +2,7 @@
+Tue Sep 26 13:23:51 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 2.19.0:
+  * Implement Bloom filters commands #239
+  * Fix error on blocking XREADGROUP #237
+
+-------------------------------------------------------------------

Old:
----
  fakeredis-2.18.1-gh.tar.gz

New:
----
  fakeredis-2.19.0-gh.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-fakeredis.spec ++++++
--- /var/tmp/diff_new_pack.7VGmuY/_old  2023-09-28 00:45:12.623545344 +0200
+++ /var/tmp/diff_new_pack.7VGmuY/_new  2023-09-28 00:45:12.623545344 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-fakeredis
-Version:        2.18.1
+Version:        2.19.0
 Release:        0
 Summary:        Fake implementation of redis API for testing purposes
 License:        BSD-3-Clause AND MIT

++++++ fakeredis-2.18.1-gh.tar.gz -> fakeredis-2.19.0-gh.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/.github/workflows/test.yml 
new/fakeredis-py-2.19.0/.github/workflows/test.yml
--- old/fakeredis-py-2.18.1/.github/workflows/test.yml  2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/.github/workflows/test.yml  2023-09-25 
23:33:16.000000000 +0200
@@ -43,7 +43,7 @@
     name: >
       py:${{ matrix.python-version }},${{ matrix.redis-image }},
       redis-py:${{ matrix.redis-py }},cov:${{ matrix.coverage }},
-      lupa:${{ matrix.lupa }}, json:${{matrix.json}}
+      lupa:${{ matrix.lupa }}, json:${{matrix.extra}}
     needs:
       - "lint"
     runs-on: ubuntu-latest
@@ -64,7 +64,7 @@
             redis-image: "redis/redis-stack-server:7.2.0-v0"
             redis-py: "5.0.0"
             lupa: true
-            json: true
+            extra: true
             coverage: true
             hypothesis: true
 
@@ -99,11 +99,11 @@
       - name: Install lupa
         if: ${{ matrix.lupa }}
         run: |
-          poetry run pip install fakeredis[lua]
+          poetry run pip install "fakeredis[lua]"
       - name: Install json
-        if: ${{ matrix.json }}
+        if: ${{ matrix.extra }}
         run: |
-          poetry run pip install fakeredis[json]
+          poetry run pip install "fakeredis[json,bf]"
       - name: Get version
         id: getVersion
         shell: bash
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/docs/about/changelog.md 
new/fakeredis-py-2.19.0/docs/about/changelog.md
--- old/fakeredis-py-2.18.1/docs/about/changelog.md     2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/docs/about/changelog.md     2023-09-25 
23:33:16.000000000 +0200
@@ -5,6 +5,16 @@
 
 ## Next release
 
+## v2.19.0
+
+### 🚀 Features
+
+- Implement Bloom filters commands #239
+
+### 🐛 Bug Fixes
+
+- Fix error on blocking XREADGROUP #237
+
 ## v2.18.1
 
 ### 🐛 Bug Fixes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fakeredis-py-2.18.1/docs/redis-commands/RedisBloom.md 
new/fakeredis-py-2.19.0/docs/redis-commands/RedisBloom.md
--- old/fakeredis-py-2.18.1/docs/redis-commands/RedisBloom.md   2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/docs/redis-commands/RedisBloom.md   2023-09-25 
23:33:16.000000000 +0200
@@ -1,34 +1,38 @@
 # Probabilistic commands
 
-Module currently not implemented in fakeredis.
+## `bf` commands (5/10 implemented)
 
+### [BF.ADD](https://redis.io/commands/bf.add/)
 
-### Unsupported bf commands 
-> To implement support for a command, see 
[here](../../guides/implement-command/) 
+Adds an item to a Bloom Filter
 
-#### [BF.RESERVE](https://redis.io/commands/bf.reserve/) <small>(not 
implemented)</small>
+### [BF.MADD](https://redis.io/commands/bf.madd/)
 
-Creates a new Bloom Filter
+Adds one or more items to a Bloom Filter. A filter will be created if it does 
not exist
 
-#### [BF.ADD](https://redis.io/commands/bf.add/) <small>(not 
implemented)</small>
+### [BF.EXISTS](https://redis.io/commands/bf.exists/)
 
-Adds an item to a Bloom Filter
+Checks whether an item exists in a Bloom Filter
 
-#### [BF.MADD](https://redis.io/commands/bf.madd/) <small>(not 
implemented)</small>
+### [BF.MEXISTS](https://redis.io/commands/bf.mexists/)
 
-Adds one or more items to a Bloom Filter. A filter will be created if it does 
not exist
+Checks whether one or more items exist in a Bloom Filter
 
-#### [BF.INSERT](https://redis.io/commands/bf.insert/) <small>(not 
implemented)</small>
+### [BF.CARD](https://redis.io/commands/bf.card/)
 
-Adds one or more items to a Bloom Filter. A filter will be created if it does 
not exist
+Returns the cardinality of a Bloom filter
 
-#### [BF.EXISTS](https://redis.io/commands/bf.exists/) <small>(not 
implemented)</small>
 
-Checks whether an item exists in a Bloom Filter
+### Unsupported bf commands 
+> To implement support for a command, see 
[here](../../guides/implement-command/) 
 
-#### [BF.MEXISTS](https://redis.io/commands/bf.mexists/) <small>(not 
implemented)</small>
+#### [BF.RESERVE](https://redis.io/commands/bf.reserve/) <small>(not 
implemented)</small>
 
-Checks whether one or more items exist in a Bloom Filter
+Creates a new Bloom Filter
+
+#### [BF.INSERT](https://redis.io/commands/bf.insert/) <small>(not 
implemented)</small>
+
+Adds one or more items to a Bloom Filter. A filter will be created if it does 
not exist
 
 #### [BF.SCANDUMP](https://redis.io/commands/bf.scandump/) <small>(not 
implemented)</small>
 
@@ -42,10 +46,6 @@
 
 Returns information about a Bloom Filter
 
-#### [BF.CARD](https://redis.io/commands/bf.card/) <small>(not 
implemented)</small>
-
-Returns the cardinality of a Bloom filter
-
 
 
 ### Unsupported cf commands 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/docs/requirements.txt 
new/fakeredis-py-2.19.0/docs/requirements.txt
--- old/fakeredis-py-2.18.1/docs/requirements.txt       2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/docs/requirements.txt       2023-09-25 
23:33:16.000000000 +0200
@@ -1,2 +1,2 @@
-mkdocs==1.5.2
-mkdocs-material==9.2.8
+mkdocs==1.5.3
+mkdocs-material==9.4.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/fakeredis/_basefakesocket.py 
new/fakeredis-py-2.19.0/fakeredis/_basefakesocket.py
--- old/fakeredis-py-2.18.1/fakeredis/_basefakesocket.py        2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/fakeredis/_basefakesocket.py        2023-09-25 
23:33:16.000000000 +0200
@@ -208,10 +208,10 @@
         else:
             return result
 
-    def _blocking(self, timeout, func):
+    def _blocking(self, timeout: Union[float, int], func: Callable):
         """Run a function until it succeeds or timeout is reached.
 
-        The timeout must be an integer, and 0 means infinite. The function
+        The timeout is in seconds, and 0 means infinite. The function
         is called with a boolean to indicate whether this is the first call.
         If it returns None, it is considered to have "failed" and is retried
         each time the condition variable is notified, until the timeout is
@@ -222,16 +222,11 @@
         ret = func(True)
         if ret is not None or self._in_transaction:
             return ret
-        if timeout:
-            deadline = time.time() + timeout
-        else:
-            deadline = None
+        deadline = time.time() + timeout if timeout else None
         while True:
             timeout = deadline - time.time() if deadline is not None else None
             if timeout is not None and timeout <= 0:
                 return None
-            # Python <3.2 doesn't return a status from wait. On Python 3.2+
-            # we bail out early on False.
             if self._db.condition.wait(timeout=timeout) is False:
                 return None  # Timeout expired
             ret = func(False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/fakeredis/_fakesocket.py 
new/fakeredis-py-2.19.0/fakeredis/_fakesocket.py
--- old/fakeredis-py-2.18.1/fakeredis/_fakesocket.py    2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/fakeredis/_fakesocket.py    2023-09-25 
23:33:16.000000000 +0200
@@ -1,4 +1,4 @@
-from fakeredis.stack import JSONCommandsMixin
+from fakeredis.stack import JSONCommandsMixin, BFCommandsMixin
 from ._basefakesocket import BaseFakeSocket
 from .commands_mixins.bitmap_mixin import BitmapCommandsMixin
 from .commands_mixins.connection_mixin import ConnectionCommandsMixin
@@ -33,6 +33,7 @@
     StreamsCommandsMixin,
     JSONCommandsMixin,
     GeoCommandsMixin,
+    BFCommandsMixin,
 ):
     def __init__(self, server, db):
         super(FakeSocket, self).__init__(server, db)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/fakeredis/_msgs.py 
new/fakeredis-py-2.19.0/fakeredis/_msgs.py
--- old/fakeredis-py-2.18.1/fakeredis/_msgs.py  2023-09-08 21:12:42.000000000 
+0200
+++ new/fakeredis-py-2.19.0/fakeredis/_msgs.py  2023-09-25 23:33:16.000000000 
+0200
@@ -93,3 +93,7 @@
     "or use negative to start from the end of the list"
 )
 NUMKEYS_GREATER_THAN_ZERO_MSG = "numkeys should be greater than 0"
+FILTER_FULL_MSG = ""
+NONSCALING_FILTERS_CANNOT_EXPAND_MSG = "Nonscaling filters cannot expand"
+ITEM_EXISTS_MSG = "item exists"
+NOT_FOUND_MSG = "not found"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fakeredis-py-2.18.1/fakeredis/commands_mixins/streams_mixin.py 
new/fakeredis-py-2.19.0/fakeredis/commands_mixins/streams_mixin.py
--- old/fakeredis-py-2.18.1/fakeredis/commands_mixins/streams_mixin.py  
2023-09-08 21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/fakeredis/commands_mixins/streams_mixin.py  
2023-09-25 23:33:16.000000000 +0200
@@ -107,7 +107,7 @@
         for item, start_id in stream_start_id_list:
             stream_results = self._xrange(item.value, start_id, max_inf, 
False, count)
             if first_pass and (count is None or len(stream_results) < count):
-                raise SimpleError(msgs.WRONGTYPE_MSG)
+                return None
             if len(stream_results) > 0:
                 res.append([item.key, stream_results])
         return res
@@ -124,7 +124,7 @@
         for group, stream_name, start_id in group_params:
             stream_results = group.group_read(consumer_name, start_id, count, 
noack)
             if first_pass and (count is None or len(stream_results) < count):
-                raise SimpleError(msgs.WRONGTYPE_MSG)
+                return None
             if len(stream_results) > 0 or start_id != b">":
                 res.append([stream_name, stream_results])
         return res
@@ -173,7 +173,7 @@
             return self._xread(stream_start_id_list, count, False)
         else:
             return self._blocking(
-                timeout, functools.partial(self._xread, stream_start_id_list, 
count)
+                timeout / 1000.0, functools.partial(self._xread, 
stream_start_id_list, count)
             )
 
     @command(name="XREADGROUP", fixed=(bytes, bytes, bytes), repeat=(bytes,))
@@ -183,11 +183,9 @@
         (count, timeout, noack), left_args = extract_args(
             args, ("+count", "+block", "noack"), error_on_unexpected=False
         )
-        if (
-                len(left_args) < 3
+        if (len(left_args) < 3
                 or not casematch(left_args[0], b"STREAMS")
-                or len(left_args) % 2 != 1
-        ):
+                or len(left_args) % 2 != 1):
             raise SimpleError(msgs.SYNTAX_ERROR_MSG)
         left_args = left_args[1:]
         num_streams = int(len(left_args) / 2)
@@ -218,7 +216,7 @@
             return self._xreadgroup(consumer_name, group_params, count, noack, 
False)
         else:
             return self._blocking(
-                timeout,
+                timeout / 1000.0,
                 functools.partial(
                     self._xreadgroup, consumer_name, group_params, count, noack
                 ),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/fakeredis/stack/__init__.py 
new/fakeredis-py-2.19.0/fakeredis/stack/__init__.py
--- old/fakeredis-py-2.18.1/fakeredis/stack/__init__.py 2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/fakeredis/stack/__init__.py 2023-09-25 
23:33:16.000000000 +0200
@@ -6,5 +6,18 @@
     if e.name == "fakeredis.stack._json_mixin":
         raise e
 
-    class JSONCommandsMixin:  # type: ignore
+
+    class JSONCommandsMixin:  # noqa: E303
+        pass
+
+try:
+    import pybloom_live  # noqa: F401
+
+    from ._bf_mixin import BFCommandsMixin  # noqa: F401
+except ImportError as e:
+    if e.name == "fakeredis.stack._bf_mixin":
+        raise e
+
+
+    class BFCommandsMixin:  # noqa: E303
         pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/fakeredis/stack/_bf_mixin.py 
new/fakeredis-py-2.19.0/fakeredis/stack/_bf_mixin.py
--- old/fakeredis-py-2.18.1/fakeredis/stack/_bf_mixin.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/fakeredis-py-2.19.0/fakeredis/stack/_bf_mixin.py        2023-09-25 
23:33:16.000000000 +0200
@@ -0,0 +1,202 @@
+"""Command mixin for emulating `redis-py`'s BF functionality."""
+import io
+
+import pybloom_live
+
+from fakeredis import _msgs as msgs
+from fakeredis._command_args_parsing import extract_args
+from fakeredis._commands import command, Key, CommandItem, Float, Int
+from fakeredis._helpers import SimpleError, OK, casematch
+
+
+class ScalableBloomFilter(pybloom_live.ScalableBloomFilter):
+    NO_GROWTH = 0
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.filters.append(
+            pybloom_live.BloomFilter(
+                capacity=self.initial_capacity,
+                error_rate=self.error_rate * self.ratio))
+
+    def add(self, key):
+        if key in self:
+            return True
+        if self.scale == self.NO_GROWTH and self.filters and 
self.filters[-1].count >= self.filters[-1].capacity:
+            raise SimpleError(msgs.FILTER_FULL_MSG)
+        return super(ScalableBloomFilter, self).add(key)
+
+
+class BFCommandsMixin:
+
+    @staticmethod
+    def _bf_add(key: CommandItem, item: bytes) -> int:
+        res = key.value.add(item)
+        key.updated()
+        return 0 if res else 1
+
+    @staticmethod
+    def _bf_exist(key: CommandItem, item: bytes) -> int:
+        return 1 if (item in key.value) else 0
+
+    @command(
+        name="BF.ADD",
+        fixed=(Key(ScalableBloomFilter), bytes),
+        repeat=(),
+    )
+    def bf_add(self, key, value: bytes):
+        return BFCommandsMixin._bf_add(key, value)
+
+    @command(
+        name="BF.MADD",
+        fixed=(Key(ScalableBloomFilter), bytes),
+        repeat=(bytes,),
+    )
+    def bf_madd(self, key, *values):
+        res = list()
+        for value in values:
+            res.append(BFCommandsMixin._bf_add(key, value))
+        return res
+
+    @command(
+        name="BF.CARD",
+        fixed=(Key(ScalableBloomFilter),),
+        repeat=(),
+    )
+    def bf_card(self, key):
+        return len(key.value)
+
+    @command(
+        name="BF.EXISTS",
+        fixed=(Key(ScalableBloomFilter), bytes),
+        repeat=(),
+    )
+    def bf_exist(self, key, value: bytes):
+        return BFCommandsMixin._bf_exist(key, value)
+
+    @command(
+        name="BF.MEXISTS",
+        fixed=(Key(ScalableBloomFilter), bytes),
+        repeat=(bytes,),
+    )
+    def bf_mexists(self, key, *values: bytes):
+        res = list()
+        for value in values:
+            res.append(BFCommandsMixin._bf_exist(key, value))
+        return res
+
+    @command(
+        name="BF.RESERVE",
+        fixed=(Key(), Float, Int,),
+        repeat=(bytes,),
+        flags=msgs.FLAG_LEAVE_EMPTY_VAL,
+    )
+    def bf_reserve(self, key: CommandItem, error_rate, capacity, *args: bytes):
+        if key.value is not None:
+            raise SimpleError(msgs.ITEM_EXISTS_MSG)
+        (expansion, non_scaling), _ = extract_args(args, ("+expansion", 
"nonscaling"))
+        if expansion is not None and non_scaling:
+            raise SimpleError(msgs.NONSCALING_FILTERS_CANNOT_EXPAND_MSG)
+        if expansion is None:
+            expansion = 2
+        scale = ScalableBloomFilter.NO_GROWTH if non_scaling else expansion
+        key.update(ScalableBloomFilter(capacity, error_rate, scale))
+        return OK
+
+    @command(
+        name="BF.INSERT",
+        fixed=(Key(),),
+        repeat=(bytes,),
+    )
+    def bf_insert(self, key: CommandItem, *args: bytes):
+        (capacity, error_rate, expansion, non_scaling, no_create), left_args = 
extract_args(
+            args, ("+capacity", ".error", "+expansion", "nonscaling", 
"nocreate"),
+            error_on_unexpected=False, left_from_first_unexpected=True)
+        # if no_create and (capacity is not None or error_rate is not None):
+        #     raise SimpleError("...")
+        if len(left_args) < 2 or not casematch(left_args[0], b'items'):
+            raise SimpleError("...")
+        items = left_args[1:]
+
+        error_rate = error_rate or 0.001
+        capacity = capacity or 100
+        if key.value is None and no_create:
+            raise SimpleError(msgs.NOT_FOUND_MSG)
+        if expansion is not None and non_scaling:
+            raise SimpleError(msgs.NONSCALING_FILTERS_CANNOT_EXPAND_MSG)
+        if expansion is None:
+            expansion = 2
+        scale = ScalableBloomFilter.NO_GROWTH if non_scaling else expansion
+        if key.value is None:
+            key.value = ScalableBloomFilter(capacity, error_rate, scale)
+        res = list()
+        for item in items:
+            res.append(self._bf_add(key, item))
+        key.updated()
+        return res
+
+    @command(
+        name="BF.INFO",
+        fixed=(Key(),),
+        repeat=(bytes,),
+    )
+    def bf_info(self, key: CommandItem, *args: bytes):
+        if key.value is None or type(key.value) is not ScalableBloomFilter:
+            raise SimpleError('...')
+        if len(args) > 1:
+            raise SimpleError(msgs.SYNTAX_ERROR_MSG)
+        if len(args) == 0:
+            return [
+                b'Capacity', key.value.capacity,
+                b'Size', key.value.capacity,
+                b'Number of filters', len(key.value.filters),
+                b'Number of items inserted', key.value.count,
+                b'Expansion rate', key.value.scale if key.value.scale > 0 else 
None,
+            ]
+        if casematch(args[0], b'CAPACITY'):
+            return key.value.capacity
+        elif casematch(args[0], b'SIZE'):
+            return key.value.capacity
+        elif casematch(args[0], b'FILTERS'):
+            return len(key.value.filters)
+        elif casematch(args[0], b'ITEMS'):
+            return key.value.count
+        elif casematch(args[0], b'EXPANSION'):
+            return key.value.scale if key.value.scale > 0 else None
+        else:
+            raise SimpleError(msgs.SYNTAX_ERROR_MSG)
+
+    @command(
+        name="BF.SCANDUMP",
+        fixed=(Key(), Int,),
+        repeat=(),
+        flags=msgs.FLAG_LEAVE_EMPTY_VAL,
+    )
+    def bf_scandump(self, key: CommandItem, iterator: int):
+        if key.value is None:
+            raise SimpleError(msgs.NOT_FOUND_MSG)
+        f = io.BytesIO()
+
+        if iterator == 0:
+            key.value.tofile(f)
+            f.seek(0)
+            s = f.read()
+            f.close()
+            return [1, s]
+        else:
+            return [0, None]
+
+    @command(
+        name="BF.LOADCHUNK",
+        fixed=(Key(), Int, bytes),
+        repeat=(),
+        flags=msgs.FLAG_LEAVE_EMPTY_VAL,
+    )
+    def bf_loadchunk(self, key: CommandItem, iterator: int, data: bytes):
+        if key.value is not None and type(key.value) is not 
ScalableBloomFilter:
+            raise SimpleError(msgs.NOT_FOUND_MSG)
+        f = io.BytesIO(data)
+        key.value = ScalableBloomFilter.fromfile(f)
+        f.close()
+        key.updated()
+        return OK
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/poetry.lock 
new/fakeredis-py-2.19.0/poetry.lock
--- old/fakeredis-py-2.18.1/poetry.lock 2023-09-08 21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/poetry.lock 2023-09-25 23:33:16.000000000 +0200
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.6.1 and should not be 
changed by hand.
+# This file is automatically @generated by Poetry 1.5.1 and should not be 
changed by hand.
 
 [[package]]
 name = "async-timeout"
@@ -36,6 +36,117 @@
 tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", 
"pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
 
 [[package]]
+name = "bitarray"
+version = "2.8.1"
+description = "efficient arrays of booleans -- C extension"
+optional = true
+python-versions = "*"
+files = [
+    {file = "bitarray-2.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = 
"sha256:6be965028785413a6163dd55a639b898b22f67f9b6ed554081c23e94a602031e"},
+    {file = "bitarray-2.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = 
"sha256:29e19cb80a69f6d1a64097bfbe1766c418e1a785d901b583ef0328ea10a30399"},
+    {file = "bitarray-2.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = 
"sha256:a0f6d705860f59721d7282496a4d29b5fd78690e1c1473503832c983e762b01b"},
+    {file = 
"bitarray-2.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:6df04efdba4e1bf9d93a1735e42005f8fcf812caf40c03934d9322412d563499"},
+    {file = 
"bitarray-2.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:18530ed3ddd71e9ff95440afce531efc3df7a3e0657f1c201c2c3cb41dd65869"},
+    {file = 
"bitarray-2.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash 
= "sha256:e4cd81ffd2d58ef68c22c825aff89f4a47bd721e2ada0a3a96793169f370ae21"},
+    {file = 
"bitarray-2.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:8367768ab797105eb97dfbd4577fcde281618de4d8d3b16ad62c477bb065f347"},
+    {file = 
"bitarray-2.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:848af80518d0ed2aee782018588c7c88805f51b01271935df5b256c8d81c726e"},
+    {file = "bitarray-2.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = 
"sha256:c54b0af16be45de534af9d77e8a180126cd059f72db8b6550f62dda233868942"},
+    {file = "bitarray-2.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:f30cdce22af3dc7c73e70af391bfd87c4574cc40c74d651919e20efc26e014b5"},
+    {file = "bitarray-2.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = 
"sha256:bc03bb358ae3917247d257207c79162e666d407ac473718d1b95316dac94162b"},
+    {file = "bitarray-2.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = 
"sha256:cf38871ed4cd89df9db7c70f729b948fa3e2848a07c69f78e4ddfbe4f23db63c"},
+    {file = "bitarray-2.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:4a637bcd199c1366c65b98f18884f0d0b87403f04676b21e4635831660d722a7"},
+    {file = "bitarray-2.8.1-cp310-cp310-win32.whl", hash = 
"sha256:904719fb7304d4115228b63c178f0cc725ad3b73e285c4b328e45a99a8e3fad6"},
+    {file = "bitarray-2.8.1-cp310-cp310-win_amd64.whl", hash = 
"sha256:1e859c664500d57526fe07140889a3b58dca54ff3b16ac6dc6d534a65c933084"},
+    {file = "bitarray-2.8.1-cp311-cp311-macosx_10_9_universal2.whl", hash = 
"sha256:2d3f28a80f2e6bb96e9360a4baf3fbacb696b5aba06a14c18a15488d4b6f398f"},
+    {file = "bitarray-2.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = 
"sha256:4677477a406f2a9e064920463f69172b865e4d69117e1f2160064d3f5912b0bd"},
+    {file = "bitarray-2.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = 
"sha256:9061c0a50216f24c97fb2325de84200e5ad5555f25c854ddcb3ceb6f12136055"},
+    {file = 
"bitarray-2.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:843af12991161b358b6379a8dc5f6636798f3dacdae182d30995b6a2df3b263e"},
+    {file = 
"bitarray-2.8.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:9336300fd0acf07ede92e424930176dc4b43ef1b298489e93ba9a1695e8ea752"},
+    {file = 
"bitarray-2.8.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash 
= "sha256:f0af01e1f61fe627f63648c0c6f52de8eac56710a2ef1dbce4851d867084cc7e"},
+    {file = 
"bitarray-2.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:2ab81c74a1805fe74330859b38e70d7525cdd80953461b59c06660046afaffcf"},
+    {file = 
"bitarray-2.8.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:b2015a9dd718393e814ff7b9e80c58190eb1cef7980f86a97a33e8440e158ce2"},
+    {file = "bitarray-2.8.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = 
"sha256:5b0493ab66c6b8e17e9fde74c646b39ee09c236cf28a787cb8cbd3a83c05bff7"},
+    {file = "bitarray-2.8.1-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:81e83ed7e0b1c09c5a33b97712da89e7a21fd3e5598eff3975c39540f5619792"},
+    {file = "bitarray-2.8.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = 
"sha256:741c3a2c0997c8f8878edfc65a4a8f7aa72eede337c9bc0b7bd8a45cf6e70dbc"},
+    {file = "bitarray-2.8.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = 
"sha256:57aeab27120a8a50917845bb81b0976e33d4759f2156b01359e2b43d445f5127"},
+    {file = "bitarray-2.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:17c32ba584e8fb9322419390e0e248769ed7d59de3ffa7432562a4c0ec4f1f82"},
+    {file = "bitarray-2.8.1-cp311-cp311-win32.whl", hash = 
"sha256:b67733a240a96f09b7597af97ac4d60c59140cfcfd180f11a7221863b82f023a"},
+    {file = "bitarray-2.8.1-cp311-cp311-win_amd64.whl", hash = 
"sha256:7b29d4bf3d3da1847f2be9e30105bf51caaf5922e94dc827653e250ed33f4e8a"},
+    {file = "bitarray-2.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = 
"sha256:5f6175c1cf07dadad3213d60075704cf2e2f1232975cfd4ac8328c24a05e8f78"},
+    {file = 
"bitarray-2.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:0cc066c7290151600b8872865708d2d00fb785c5db8a0df20d70d518e02f172b"},
+    {file = 
"bitarray-2.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:4ce2ef9291a193a0e0cd5e23970bf3b682cc8b95220561d05b775b8d616d665f"},
+    {file = 
"bitarray-2.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash 
= "sha256:c5582dd7d906e6f9ec1704f99d56d812f7d395d28c02262bc8b50834d51250c3"},
+    {file = 
"bitarray-2.8.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:2aa2267eb6d2b88ef7d139e79a6daaa84cd54d241b9797478f10dcb95a9cd620"},
+    {file = 
"bitarray-2.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:a04d4851e83730f03c4a6aac568c7d8b42f78f0f9cc8231d6db66192b030ce1e"},
+    {file = "bitarray-2.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = 
"sha256:f7d2ec2174d503cbb092f8353527842633c530b4e03b9922411640ac9c018a19"},
+    {file = "bitarray-2.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = 
"sha256:b65a04b2e029b0694b52d60786732afd15b1ec6517de61a36afbb7808a2ffac1"},
+    {file = "bitarray-2.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = 
"sha256:55020d6fb9b72bd3606969f5431386c592ed3666133bd475af945aa0fa9e84ec"},
+    {file = "bitarray-2.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = 
"sha256:797de3465f5f6c6be9a412b4e99eb6e8cdb86b83b6756655c4d83a65d0b9a376"},
+    {file = "bitarray-2.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = 
"sha256:f9a66745682e175e143a180524a63e692acb2b8c86941073f6dd4ee906e69608"},
+    {file = "bitarray-2.8.1-cp36-cp36m-win32.whl", hash = 
"sha256:443726af4bd60515e4e41ea36c5dbadb29a59bc799bcbf431011d1c6fd4363e3"},
+    {file = "bitarray-2.8.1-cp36-cp36m-win_amd64.whl", hash = 
"sha256:2b0f754a5791635b8239abdcc0258378111b8ee7a8eb3e2bbc24bcc48a0f0b08"},
+    {file = "bitarray-2.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = 
"sha256:d175e16419a52d54c0ac44c93309ba76dc2cfd33ee9d20624f1a5eb86b8e162e"},
+    {file = 
"bitarray-2.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:f3128234bde3629ab301a501950587e847d30031a9cbf04d95f35cbf44469a9e"},
+    {file = 
"bitarray-2.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:75104c3076676708c1ac2484ebf5c26464fb3850312de33a5b5bf61bfa7dbec5"},
+    {file = 
"bitarray-2.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash 
= "sha256:82bfb6ab9b1b5451a5483c9a2ae2a8f83799d7503b384b54f6ab56ea74abb305"},
+    {file = 
"bitarray-2.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:2dc064a63445366f6b26eaf77230d326b9463e903ba59d6ff5efde0c5ec1ea0e"},
+    {file = 
"bitarray-2.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:cbe54685cf6b17b3e15faf6c4b76773bc1c484bc447020737d2550a9dde5f6e6"},
+    {file = "bitarray-2.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = 
"sha256:9fed8aba8d1b09cf641b50f1e6dd079c31677106ea4b63ec29f4c49adfabd63f"},
+    {file = "bitarray-2.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = 
"sha256:7c17dd8fb146c2c680bf1cb28b358f9e52a14076e44141c5442148863ee95d7d"},
+    {file = "bitarray-2.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = 
"sha256:c9efcee311d9ba0c619743060585af9a9b81496e97b945843d5e954c67722a75"},
+    {file = "bitarray-2.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = 
"sha256:dc7acffee09822b334d1b46cd384e969804abdf18f892c82c05c2328066cd2ae"},
+    {file = "bitarray-2.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = 
"sha256:ea71e0a50060f96ad0821e0ac785e91e44807f8b69555970979d81934961d5bd"},
+    {file = "bitarray-2.8.1-cp37-cp37m-win32.whl", hash = 
"sha256:69ab51d551d50e4d6ca35abc95c9d04b33ad28418019bb5481ab09bdbc0df15c"},
+    {file = "bitarray-2.8.1-cp37-cp37m-win_amd64.whl", hash = 
"sha256:3024ab4c4906c3681408ca17c35833237d18813ebb9f24ae9f9e3157a4a66939"},
+    {file = "bitarray-2.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = 
"sha256:46fdd27c8fa4186d8b290bf74a28cbd91b94127b1b6a35c265a002e394fa9324"},
+    {file = "bitarray-2.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = 
"sha256:d32ccd2c0d906eae103ef84015f0545a395052b0b6eb0e02e9023ca0132557f6"},
+    {file = "bitarray-2.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = 
"sha256:9186cf8135ca170cd907d8c4df408a87747570d192d89ec4ff23805611c702a0"},
+    {file = 
"bitarray-2.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:b8d6e5ff385fea25caf26fd58b43f087deb763dcaddd18d3df2895235cf1b484"},
+    {file = 
"bitarray-2.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:9d6a9c72354327c7aa9890ff87904cbe86830cb1fb58c39750a0afac8df5e051"},
+    {file = 
"bitarray-2.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = 
"sha256:d2f13b7d0694ce2024c82fc595e6ccc3918e7f069747c3de41b1ce72a9a1e346"},
+    {file = 
"bitarray-2.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:2d38ceca90ed538706e3f111513073590f723f90659a7af0b992b29776a6e816"},
+    {file = 
"bitarray-2.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:2b977c39e3734e73540a2e3a71501c2c6261c70c6ce59d427bb7c4ecf6331c7e"},
+    {file = "bitarray-2.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = 
"sha256:214c05a7642040f6174e29f3e099549d3c40ac44616405081bf230dcafb38767"},
+    {file = "bitarray-2.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:ad440c17ef2ff42e94286186b5bcf82bf87c4026f91822675239102ebe1f7035"},
+    {file = "bitarray-2.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = 
"sha256:28dee92edd0d21655e56e1870c22468d0dabe557df18aa69f6d06b1543614180"},
+    {file = "bitarray-2.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = 
"sha256:df9d8a9a46c46950f306394705512553c552b633f8bf3c11359c4204289f11e3"},
+    {file = "bitarray-2.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:1a0d27aad02d8abcb1d3b7d85f463877c4937e71adf9b6adb9367f2cdad91a52"},
+    {file = "bitarray-2.8.1-cp38-cp38-win32.whl", hash = 
"sha256:6033303431a7c85a535b3f1b0ec28abc2ebc2167c263f244993b56ccb87cae6b"},
+    {file = "bitarray-2.8.1-cp38-cp38-win_amd64.whl", hash = 
"sha256:9b65d487451e0e287565c8436cf4da45260f958f911299f6122a20d7ec76525c"},
+    {file = "bitarray-2.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = 
"sha256:9aad7b4670f090734b272c072c9db375c63bd503512be9a9393e657dcacfc7e2"},
+    {file = "bitarray-2.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = 
"sha256:bf80804014e3736515b84044c2be0e70080616b4ceddd4e38d85f3167aeb8165"},
+    {file = "bitarray-2.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = 
"sha256:e7f7231ef349e8f4955d9b39561f4683a418a73443cfce797a4eddbee1ba9664"},
+    {file = 
"bitarray-2.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:67e8fb18df51e649adbc81359e1db0f202d72708fba61b06f5ac8db47c08d107"},
+    {file = 
"bitarray-2.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:9d5df3d6358425c9dfb6bdbd4f576563ec4173d24693a9042d05aadcb23c0b98"},
+    {file = 
"bitarray-2.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = 
"sha256:6ea51ba4204d086d5b76e84c31d2acbb355ed1b075ded54eb9b7070b0b95415d"},
+    {file = 
"bitarray-2.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:1414582b3b7516d2282433f0914dd9846389b051b2aea592ae7cc165806c24ac"},
+    {file = 
"bitarray-2.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:5934e3a623a1d485e1dcfc1990246e3c32c6fc6e7f0fd894750800d35fdb5794"},
+    {file = "bitarray-2.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = 
"sha256:aa08a9b03888c768b9b2383949a942804d50d8164683b39fe62f0bfbfd9b4204"},
+    {file = "bitarray-2.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:00ff372dfaced7dd6cc2dffd052fafc118053cf81a442992b9a23367479d77d7"},
+    {file = "bitarray-2.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = 
"sha256:dd76bbf5a4b2ab84b8ffa229f5648e80038ba76bf8d7acc5de9dd06031b38117"},
+    {file = "bitarray-2.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = 
"sha256:e88a706f92ad1e0e1e66f6811d10b6155d5f18f0de9356ee899a7966a4e41992"},
+    {file = "bitarray-2.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:b2560475c5a1ff96fcab01fae7cf6b9a6da590f02659556b7fccc7991e401884"},
+    {file = "bitarray-2.8.1-cp39-cp39-win32.whl", hash = 
"sha256:74cd1725d08325b6669e6e9a5d09cec29e7c41f7d58e082286af5387414d046d"},
+    {file = "bitarray-2.8.1-cp39-cp39-win_amd64.whl", hash = 
"sha256:e48c45ea7944225bcee026c457a70eaea61db3659d9603f07fc8a643ab7e633b"},
+    {file = "bitarray-2.8.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = 
"sha256:c2426dc7a0d92d8254def20ab7a231626397ce5b6fb3d4f44be74cc1370a60c3"},
+    {file = 
"bitarray-2.8.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:d34790a919f165b6f537935280ef5224957d9ce8ab11d339f5e6d0319a683ccc"},
+    {file = 
"bitarray-2.8.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:6c26a923080bc211cab8f5a5e242e3657b32951fec8980db0616e9239aade482"},
+    {file = 
"bitarray-2.8.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:0de1bc5f971aba46de88a4eb0dbb5779e30bbd7514f4dcbff743c209e0c02667"},
+    {file = "bitarray-2.8.1-pp37-pypy37_pp73-win_amd64.whl", hash = 
"sha256:3bb5f2954dd897b0bac13b5449e5c977534595b688120c8af054657a08b01f46"},
+    {file = "bitarray-2.8.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = 
"sha256:62ac31059a3c510ef64ed93d930581b262fd4592e6d95ede79fca91e8d3d3ef6"},
+    {file = 
"bitarray-2.8.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:ae32ac7217e83646b9f64d7090bf7b737afaa569665621f110a05d9738ca841a"},
+    {file = 
"bitarray-2.8.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:3994f7dc48d21af40c0d69fca57d8040b02953f4c7c3652c2341d8947e9cbedf"},
+    {file = 
"bitarray-2.8.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:8c361201e1c3ee6d6b2266f8b7a645389880bccab1b29e22e7a6b7b6e7831ad5"},
+    {file = "bitarray-2.8.1-pp38-pypy38_pp73-win_amd64.whl", hash = 
"sha256:861850d6a58e7b6a7096d0b0efed9c6d993a6ab8b9d01e781df1f4d80cc00efa"},
+    {file = "bitarray-2.8.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = 
"sha256:ee772c20dcb56b03d666a4e4383d0b5b942b0ccc27815e42fe0737b34cba2082"},
+    {file = 
"bitarray-2.8.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:63fa75e87ad8c57d5722cc87902ca148ef8bbbba12b5c5b3c3730a1bc9ac2886"},
+    {file = 
"bitarray-2.8.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:3b999fb66980f885961d197d97d7ff5a13b7ab524ccf45ccb4704f4b82ce02e3"},
+    {file = 
"bitarray-2.8.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:3243e4b8279ff2fe4c6e7869f0e6930c17799ee9f8d07317f68d44a66b46281e"},
+    {file = "bitarray-2.8.1-pp39-pypy39_pp73-win_amd64.whl", hash = 
"sha256:542358b178b025dcc95e7fb83389e9954f701c41d312cbb66bdd763cbe5414b5"},
+    {file = "bitarray-2.8.1.tar.gz", hash = 
"sha256:e68ceef35a88625d16169550768fcc8d3894913e363c24ecbf6b8c07eb02c8f3"},
+]
+
+[[package]]
 name = "bleach"
 version = "6.0.0"
 description = "An easy safelist-based HTML-sanitizing tool."
@@ -334,34 +445,34 @@
 
 [[package]]
 name = "cryptography"
-version = "41.0.3"
+version = "41.0.4"
 description = "cryptography is a package which provides cryptographic recipes 
and primitives to Python developers."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash 
= "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"},
-    {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = 
"sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"},
-    {file = 
"cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"},
-    {file = 
"cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"},
-    {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = 
"sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"},
-    {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = 
"sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"},
-    {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = 
"sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"},
-    {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = 
"sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"},
-    {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = 
"sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"},
-    {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = 
"sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"},
-    {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", 
hash = 
"sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"},
-    {file = 
"cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = 
"sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"},
-    {file = 
"cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = 
"sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"},
-    {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = 
"sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"},
-    {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", 
hash = 
"sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"},
-    {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", 
hash = 
"sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"},
-    {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", 
hash = 
"sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"},
-    {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = 
"sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"},
-    {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", 
hash = 
"sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"},
-    {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", 
hash = 
"sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"},
-    {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", 
hash = 
"sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"},
-    {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = 
"sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"},
-    {file = "cryptography-41.0.3.tar.gz", hash = 
"sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"},
+    {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash 
= "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"},
+    {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = 
"sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"},
+    {file = 
"cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"},
+    {file = 
"cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"},
+    {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = 
"sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"},
+    {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = 
"sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"},
+    {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = 
"sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"},
+    {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = 
"sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"},
+    {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = 
"sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"},
+    {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = 
"sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"},
+    {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", 
hash = 
"sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"},
+    {file = 
"cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = 
"sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"},
+    {file = 
"cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = 
"sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"},
+    {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = 
"sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"},
+    {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", 
hash = 
"sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"},
+    {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", 
hash = 
"sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"},
+    {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", 
hash = 
"sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"},
+    {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = 
"sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"},
+    {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", 
hash = 
"sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"},
+    {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", 
hash = 
"sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"},
+    {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", 
hash = 
"sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"},
+    {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = 
"sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"},
+    {file = "cryptography-41.0.4.tar.gz", hash = 
"sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"},
 ]
 
 [package.dependencies]
@@ -378,17 +489,6 @@
 test-randomorder = ["pytest-randomly"]
 
 [[package]]
-name = "decorator"
-version = "5.1.1"
-description = "Decorators for Humans"
-optional = true
-python-versions = ">=3.5"
-files = [
-    {file = "decorator-5.1.1-py3-none-any.whl", hash = 
"sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
-    {file = "decorator-5.1.1.tar.gz", hash = 
"sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
-]
-
-[[package]]
 name = "deprecated"
 version = "1.2.14"
 description = "Python @deprecated decorator to deprecate old python classes, 
functions or methods."
@@ -637,20 +737,17 @@
 
 [[package]]
 name = "jsonpath-ng"
-version = "1.5.3"
+version = "1.6.0"
 description = "A final implementation of JSONPath for Python that aims to be 
standard compliant, including arithmetic and binary comparison operators and 
providing clear AST for metaprogramming."
 optional = true
 python-versions = "*"
 files = [
-    {file = "jsonpath-ng-1.5.3.tar.gz", hash = 
"sha256:a273b182a82c1256daab86a313b937059261b5c5f8c4fa3fc38b882b344dd567"},
-    {file = "jsonpath_ng-1.5.3-py2-none-any.whl", hash = 
"sha256:f75b95dbecb8a0f3b86fd2ead21c2b022c3f5770957492b9b6196ecccfeb10aa"},
-    {file = "jsonpath_ng-1.5.3-py3-none-any.whl", hash = 
"sha256:292a93569d74029ba75ac2dc3d3630fc0e17b2df26119a165fa1d498ca47bf65"},
+    {file = "jsonpath-ng-1.6.0.tar.gz", hash = 
"sha256:5483f8e9d74c39c9abfab554c070ae783c1c8cbadf5df60d561bc705ac68a07e"},
+    {file = "jsonpath_ng-1.6.0-py3-none-any.whl", hash = 
"sha256:6fd04833412c4b3d9299edf369542f5e67095ca84efa17cbb7f06a34958adc9f"},
 ]
 
 [package.dependencies]
-decorator = "*"
 ply = "*"
-six = "*"
 
 [[package]]
 name = "keyring"
@@ -955,6 +1052,20 @@
 ]
 
 [[package]]
+name = "pybloom-live"
+version = "4.0.0"
+description = "Bloom filter: A Probabilistic data structure"
+optional = true
+python-versions = "*"
+files = [
+    {file = "pybloom_live-4.0.0.tar.gz", hash = 
"sha256:99545c5d3b05bd388b5491e36b823b706830a686ba18b4c19063d08de5321110"},
+]
+
+[package.dependencies]
+bitarray = ">=0.3.4"
+xxhash = ">=3.0.0"
+
+[[package]]
 name = "pycodestyle"
 version = "2.11.0"
 description = "Python style guide checker"
@@ -1162,6 +1273,20 @@
 dev = ["pre-commit", "pytest-asyncio", "tox"]
 
 [[package]]
+name = "python-dotenv"
+version = "1.0.0"
+description = "Read key-value pairs from a .env file and set them as 
environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "python-dotenv-1.0.0.tar.gz", hash = 
"sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
+    {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = 
"sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
 name = "pywin32"
 version = "306"
 description = "Python for Window Extensions"
@@ -1285,13 +1410,13 @@
 
 [[package]]
 name = "rich"
-version = "13.5.2"
+version = "13.5.3"
 description = "Render rich text, tables, progress bars, syntax highlighting, 
markdown and more to the terminal"
 optional = false
 python-versions = ">=3.7.0"
 files = [
-    {file = "rich-13.5.2-py3-none-any.whl", hash = 
"sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"},
-    {file = "rich-13.5.2.tar.gz", hash = 
"sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"},
+    {file = "rich-13.5.3-py3-none-any.whl", hash = 
"sha256:9257b468badc3d347e146a4faa268ff229039d4c2d176ab0cffb4c4fbc73d5d9"},
+    {file = "rich-13.5.3.tar.gz", hash = 
"sha256:87b43e0543149efa1253f485cd845bb7ee54df16c9617b8a893650ab84b4acb6"},
 ]
 
 [package.dependencies]
@@ -1483,13 +1608,13 @@
 
 [[package]]
 name = "types-redis"
-version = "4.6.0.5"
+version = "4.6.0.7"
 description = "Typing stubs for redis"
 optional = false
 python-versions = "*"
 files = [
-    {file = "types-redis-4.6.0.5.tar.gz", hash = 
"sha256:5f179d10bd3ca995a8134aafcddfc3e12d52b208437c4529ef27e68acb301f38"},
-    {file = "types_redis-4.6.0.5-py3-none-any.whl", hash = 
"sha256:4f662060247a2363c7a8f0b7e52915d68960870ff16a749a891eabcf87ed0be4"},
+    {file = "types-redis-4.6.0.7.tar.gz", hash = 
"sha256:28c4153ddb5c9d4f10def44a2454673c361d2d5fc3cd867cf3bb1520f3f59a38"},
+    {file = "types_redis-4.6.0.7-py3-none-any.whl", hash = 
"sha256:05b1bf92879b25df20433fa1af07784a0d7928c616dc2ebf9087618db77ccbd0"},
 ]
 
 [package.dependencies]
@@ -1509,13 +1634,13 @@
 
 [[package]]
 name = "urllib3"
-version = "2.0.4"
+version = "2.0.5"
 description = "HTTP library with thread-safe connection pooling, file post, 
and more."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "urllib3-2.0.4-py3-none-any.whl", hash = 
"sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"},
-    {file = "urllib3-2.0.4.tar.gz", hash = 
"sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"},
+    {file = "urllib3-2.0.5-py3-none-any.whl", hash = 
"sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e"},
+    {file = "urllib3-2.0.5.tar.gz", hash = 
"sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594"},
 ]
 
 [package.extras]
@@ -1657,6 +1782,100 @@
 ]
 
 [[package]]
+name = "xxhash"
+version = "3.3.0"
+description = "Python binding for xxHash"
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "xxhash-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = 
"sha256:70ef7288d1cb1ad16e02d101ea43bb0e392d985d60b9b0035aee80663530960d"},
+    {file = "xxhash-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = 
"sha256:44ff8c673cab50be46784e0aec62aa6f0ca9ea765e2b0690e8945d0cd950dcaf"},
+    {file = 
"xxhash-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:dfebc90273ae2beb813d8118a2bfffb5a5a81ac054fbfd061ea18fd0a81db0ac"},
+    {file = 
"xxhash-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:9084e68bedbd665c7e9241a7b597c28f4775edeb3941bf608ecb38732a5f8fb5"},
+    {file = 
"xxhash-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = 
"sha256:d72493a14a3e89564b1a6c7400b9b40621e8f4692410706ef27c66aeadc7b431"},
+    {file = 
"xxhash-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:98779cbe9068dd7734cc3210693894d5cc9b156920e9c336f10fb99f46bebbd8"},
+    {file = 
"xxhash-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:499f8a12767dd28b98ab6b7c7da7d294564e4c9024a2aaa5d0b0b98a8bef2f92"},
+    {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = 
"sha256:4dabda7f42c548f98d8e07e390bda2953fc58302c0e07ded7b3fe0637e7ecd2f"},
+    {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:c416409646c793c46370f0f1859253302ee70aeda5278c2a0ca41462f8ec1244"},
+    {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = 
"sha256:b8bd31aaad8a80a7302730676cec26bea3ef1fd9835875aa47fea073aca9fe05"},
+    {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = 
"sha256:3af8e3bcd630f905efbdfe7a51b51fc1ca3c9dca8b155f841925f3ad41685d41"},
+    {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:d86b79c707fc7025d967af71db652429a06a8179175e45bd2e9f17b8af6f5949"},
+    {file = "xxhash-3.3.0-cp310-cp310-win32.whl", hash = 
"sha256:98fe771f36ee9d3a1f5741424a956a2ba9651d9508a9f64a024b57f2cf796414"},
+    {file = "xxhash-3.3.0-cp310-cp310-win_amd64.whl", hash = 
"sha256:0a65131f7f731ecf7e3dd27f09d877aff3000a79a446caaa2c0d8d0ec0bc7186"},
+    {file = "xxhash-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = 
"sha256:a9761e425e79d23797fa0bec2d781dbadb9fe5dcc2bf69030855f5e393c3bec8"},
+    {file = "xxhash-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = 
"sha256:d28c7ef1deb3c3ac5f5290176ca3d501daa97c2e1f7443bf5d8b61ac651794b2"},
+    {file = 
"xxhash-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:701b7cefffc25de1b7ddfae6505da70a3b3a11e312c2e2b33b09e180bbceb43d"},
+    {file = 
"xxhash-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:b1644f8b8e19a242c3047a089541067248a651038cabb9fcab3c13eb1dfcd757"},
+    {file = 
"xxhash-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = 
"sha256:20e7d0e3488cc0f0dbe360731b7fe32e1f2df46bf2de2db3317d301efb93084c"},
+    {file = 
"xxhash-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:156c52eca2b20f9839723bef0b929a290f6c2f1c98ccb24e82f58f96f3c16007"},
+    {file = 
"xxhash-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:2d6ce4d3828d79044ed08994e196c20f69c18133ed8a4286afe3e98989adeeac"},
+    {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = 
"sha256:b85b63757ade2439c8d7d71842c40d42c0ab3b69279ed02afbd3b1635f7d2b4b"},
+    {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:b2b9051e40b7b649a9a2a38fb223ca6a593d332012df885746b81968948f9435"},
+    {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = 
"sha256:81b7ce050f26fc1daaaa0d24e320815306736d14608e1ba31920e693a7ca9afb"},
+    {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = 
"sha256:7442500fcce71669953ca959682dcd47452bc3f9c95c8d88315874aeabec9f82"},
+    {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:36a05bf59a515cfb07f3f83373c527fff2ecaa77eaf30c968c788aea582070a1"},
+    {file = "xxhash-3.3.0-cp311-cp311-win32.whl", hash = 
"sha256:da16f9cd62c6fde74683be1b28c28ef865e706da13e3bee4ba836fcc520de0cc"},
+    {file = "xxhash-3.3.0-cp311-cp311-win_amd64.whl", hash = 
"sha256:40fd49ef6964b1c90c0bea63cd184f6d0b36e59144a080e8b3ac2c4c06bf6bf2"},
+    {file = "xxhash-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = 
"sha256:672c60cce1f8026ae32c651f877aa64f342876083a36a4b1ff91bc876aaf0e34"},
+    {file = 
"xxhash-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:6bb6c83d7a65dd3065566c77425ba72df96982174e8ef613d809052d68ae77ab"},
+    {file = 
"xxhash-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:a4170f3016b621e3200ebfcc18de6f50eb8e8fc1303e16324b1f5625afd51b57"},
+    {file = 
"xxhash-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = 
"sha256:bfb9c45d502ab38c0f4edf98a678694ae0f345613ef4900ade98c71f64db4d78"},
+    {file = 
"xxhash-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:48af026a2b1569666da42a478248a1f03f4e2350a34eb661afe3cb45429ca1d7"},
+    {file = 
"xxhash-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:fe627de8fe8ddfa8b6477bda4ae5d5843ad1a0c83601dcff72247039465cc901"},
+    {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = 
"sha256:427fc60a188e345534f35b0aa76f7640c5ddf0354f1c9ad826a2bc086282982d"},
+    {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = 
"sha256:d80acb20c7f268fe3150ac0be6a6b798062af56a1795eef855b26c9eae11a99c"},
+    {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = 
"sha256:e71100818943422d1fbbe460e7be7fc4f2d2ba9371b2a745eb09e29ef0493f4a"},
+    {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = 
"sha256:e3b9bb5fdbe284c7b61c5d82c76688e52bbaf48ab1e53de98c072cc696fa331f"},
+    {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = 
"sha256:1e25f6c8c46cf1ed8237f610abb231093a748c97d6c2c092789a7cad7e7ef290"},
+    {file = "xxhash-3.3.0-cp37-cp37m-win32.whl", hash = 
"sha256:928208dfecc563be59ae91868d1658e78809cb1e6a0bd74960a96c915db6390c"},
+    {file = "xxhash-3.3.0-cp37-cp37m-win_amd64.whl", hash = 
"sha256:bd1b4531a66da6dde1974662c1fd6fb1a2f27e40542e3df5e5e5dbab8ea4aee7"},
+    {file = "xxhash-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = 
"sha256:deebb296df92e082b6d0171a7d6227b503e2897cea4f8bdd3d708094974d4cf6"},
+    {file = "xxhash-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = 
"sha256:cd96e9cb0e2baa294e6d572207d9731c3bb8e2511f1ff70f2bf17266b4488bd9"},
+    {file = 
"xxhash-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash 
= "sha256:3756b44bf247e422a2e47a38f25d03cf4a5ed539fdc2be3c60043e872e6ff13d"},
+    {file = 
"xxhash-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash 
= "sha256:69550c3c053b8f135ceac97b85dc1b2bc54b7613a966f550f32b43bed81c788a"},
+    {file = 
"xxhash-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = 
"sha256:9fc8736fc3e0c5aad435520873b9d2e27ddcc5a830b07e00e9c4d3a61ded9675"},
+    {file = 
"xxhash-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = 
"sha256:80ead7774392efbd95f9f701155048f9ca26cf55133db6f5bb5a0ec69376bda5"},
+    {file = 
"xxhash-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:b8737c9b3fd944d856faafa92c95f6198649ad57987935b6d965d086938be917"},
+    {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = 
"sha256:2c8e078d0b9f85212801c41bd9eec8122003929686b0ee33360ffbfdf1a189ab"},
+    {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:f399269d20ef1dd910331f9ad49e8510c3ba2aa657b623293b536038f266a5c5"},
+    {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = 
"sha256:f3661decef5f9ff7ab50edbef463bf7dc717621b56755dbae5458a946a033b10"},
+    {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = 
"sha256:5ec374d0f1e7d43ef48a4ff643600833d7a325ecc6933b4d6ad9282f55751cf7"},
+    {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:39a947ff02d9a85673f5ce1f6f34059e24c714a797440485bd81b2c3cb69a7ff"},
+    {file = "xxhash-3.3.0-cp38-cp38-win32.whl", hash = 
"sha256:4a4f0645a0ec03b229fb04f2e66bdbcb1ffd341a70d6c86c3ee015ffdcd70fad"},
+    {file = "xxhash-3.3.0-cp38-cp38-win_amd64.whl", hash = 
"sha256:8af5a687c0fb4357c230eec8a57ca07d3172faa3cb69beb0cbad40672ae6fa4b"},
+    {file = "xxhash-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = 
"sha256:e5bfafda019ecc6202af6f3cf08220fa66af9612ba16ef831033ae3ac7bd1f89"},
+    {file = "xxhash-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = 
"sha256:3d113b433bc817adf845689a051363777835577858263ec4325d1934fcb7e394"},
+    {file = 
"xxhash-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash 
= "sha256:56aacf4bf65f575c0392be958aceff719d850950bb6af7d804b32d4bc293159c"},
+    {file = 
"xxhash-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash 
= "sha256:0f5d3e4e0937dad05585e9bd772bbdf0ca40cd8b2f54789d7a1f3091b608118c"},
+    {file = 
"xxhash-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = 
"sha256:23605d7fc67bc7daa0d263b3a26de3375cfcc0b51ab7de5026625415c05b6fed"},
+    {file = 
"xxhash-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = 
"sha256:fe525be0392d493558a2b10d764bcaae9850cc262b417176a8b001f16e085fc6"},
+    {file = 
"xxhash-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:b234d08786884f5c8d55dfebb839cfbd846d812e3a052c39ca7e8ce7055fed68"},
+    {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = 
"sha256:b031395b4b9c3085d9ea1ce89896ab01a65fc63172b2bfda5dd318fefe5e2f93"},
+    {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:5afe44da46b48c75169e622a532dca3fe585343c0577cfd7c18ecd3f1200305d"},
+    {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = 
"sha256:c59f233f38b6a49d5e4ddf16be910a5bbf36a2989b6b2c8591853fb9f5a5e691"},
+    {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = 
"sha256:ed016e278c5c4633270903c7cf3b9dfb0bd293b7335e43fe695cb95541da53c9"},
+    {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:7a8bd6612fb35487e9ab329bb37b3df44f58baf752010dde9282593edbfed7e7"},
+    {file = "xxhash-3.3.0-cp39-cp39-win32.whl", hash = 
"sha256:015a0498bde85364abc53fcc713af962dd4555391929736d9c0ff2c555436a03"},
+    {file = "xxhash-3.3.0-cp39-cp39-win_amd64.whl", hash = 
"sha256:06a484097af32caf1cfffadd60c3ca140c9e52b40a551fb1f6f0fdfd6f7f8977"},
+    {file = "xxhash-3.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = 
"sha256:6c3809740124bbc777d29e3ae53de24f4c13fd5e62878086a8feadf0dcb654a5"},
+    {file = 
"xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:ae092f0daaeece2acdd6ec46e2ab307d8d6f22b01ecca14dc6078844dbd88339"},
+    {file = 
"xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:3498e72ff2610b049b97bb81d1ea6e7bfa5b7a45efb3f255d77ec2fa2bc91653"},
+    {file = 
"xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:b0004dded9d86f129961326e980420187640fb7ba65a184009429861c1d09df7"},
+    {file = "xxhash-3.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = 
"sha256:41c8bfd27191928bae6fd2b66872965532267785094a03c0ee5f358d9dba51c2"},
+    {file = "xxhash-3.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = 
"sha256:71db8498e329cef3588b0617f762a3fe31d899872e76a68ce2840e35a1318a5b"},
+    {file = 
"xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:4d1d24d71b6209bc0124286932c4f0660c1103cb996fe34cb374bc12ac251940"},
+    {file = 
"xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:61004587a09b5b385e43d95ffe3a76c9d934dfd79ea38272d5c20ddfba8eab8f"},
+    {file = 
"xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:3f0c92e3fa826425c73acafb31e022a719c85423847a9433d3a9e61e4ac97543"},
+    {file = "xxhash-3.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = 
"sha256:367e03f1484ce471c94e731b98f5e4a05b43e7188b16692998e1cc89fd1159a5"},
+    {file = "xxhash-3.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = 
"sha256:ed04c47dfaab98fcda0b748af9ee6fe8c888a0a0fbd13720e0f0221671e387e1"},
+    {file = 
"xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:7cbfde62516435ca198220aff048a8793383cb7047c7b88714a061968bca786d"},
+    {file = 
"xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:73682225faa973ee56743f0fcd36bfcbfec503be258e0e420fb34313f52f1e7b"},
+    {file = 
"xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:d49efdce2086c2c506af20ed18a1115b40af7aad6d4ee27cb31d7c810585a3f2"},
+    {file = "xxhash-3.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = 
"sha256:546a0bb8e5a657cadf0da290b30ccd561cb89c256a5421ab8d5eb12eaf087349"},
+    {file = "xxhash-3.3.0.tar.gz", hash = 
"sha256:c3f9e322b1ebeebd44e3d9d2d9b124e0c550c1ef41bd552afdcdd719516ee41a"},
+]
+
+[[package]]
 name = "zipp"
 version = "3.15.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
@@ -1672,10 +1891,11 @@
 testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", 
"more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs 
(>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy 
(>=0.9.1)"]
 
 [extras]
+bf = ["pybloom-live"]
 json = ["jsonpath-ng"]
 lua = ["lupa"]
 
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.7"
-content-hash = 
"96748427698f26d782b4e78d62177e0ab4f1189dd79b7fa96812f44e11ac1351"
+content-hash = 
"0d54c148749572a47a2b39425eac1535490dcee7512a97696b2124fda83b5a9c"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/pyproject.toml 
new/fakeredis-py-2.19.0/pyproject.toml
--- old/fakeredis-py-2.18.1/pyproject.toml      2023-09-08 21:12:42.000000000 
+0200
+++ new/fakeredis-py-2.19.0/pyproject.toml      2023-09-25 23:33:16.000000000 
+0200
@@ -8,7 +8,7 @@
 packages = [
     { include = "fakeredis" },
 ]
-version = "2.18.1"
+version = "2.19.0"
 description = "Python implementation of redis API, can be used for testing 
purposes."
 readme = "README.md"
 keywords = ["redis", "RedisJson", "tests", "redis-stack"]
@@ -46,11 +46,13 @@
 redis = ">=4"
 sortedcontainers = "^2"
 lupa = { version = ">=1.14,<3.0", optional = true }
-jsonpath-ng = { version = "^1.5", optional = true }
+jsonpath-ng = { version = "^1.6", optional = true }
+pybloom-live = { version = "^4.0", optional = true }
 
 [tool.poetry.extras]
 lua = ["lupa"]
 json = ["jsonpath-ng"]
+bf = ["pybloom-live"]
 
 [tool.poetry.dev-dependencies]
 coverage = "^7"
@@ -62,6 +64,7 @@
 pytest = "^7.4"
 pytest-asyncio = "^0.21"
 pytest-cov = "^4.0"
+python-dotenv = { version = "^1", python = ">=3.8.1" }
 pytest-mock = "^3.10"
 types-redis = "^4.6"
 twine = "^4.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/scripts/create_issues.py 
new/fakeredis-py-2.19.0/scripts/create_issues.py
--- old/fakeredis-py-2.18.1/scripts/create_issues.py    2023-09-08 
21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/scripts/create_issues.py    2023-09-25 
23:33:16.000000000 +0200
@@ -17,7 +17,7 @@
 IGNORE_GROUPS = {
     'suggestion', 'tdigest', 'scripting', 'cf', 'topk',
     'hyperloglog', 'graph', 'timeseries', 'connection',
-    'server', 'generic', 'cms', 'bf', 'cluster',
+    'server', 'generic', 'cms', 'cluster',
     'search', 'bitmap',
 }
 IGNORE_COMMANDS = {
@@ -49,9 +49,7 @@
 
 
 def get_unimplemented_and_implemented_commands() -> tuple[dict[str, 
list[str]], dict[str, list[str]]]:
-    """Returns 2 dictionaries, one of unimplemented commands and another of 
implemented commands
-
-    """
+    """Returns 2 dictionaries, one of unimplemented commands and another of 
implemented commands"""
     commands = download_redis_commands()
     implemented_commands_set = implemented_commands()
     implemented_dict, unimplemented_dict = commands_groups(commands, 
implemented_commands_set)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fakeredis-py-2.18.1/test/test_mixins/test_streams_commands.py 
new/fakeredis-py-2.19.0/test/test_mixins/test_streams_commands.py
--- old/fakeredis-py-2.18.1/test/test_mixins/test_streams_commands.py   
2023-09-08 21:12:42.000000000 +0200
+++ new/fakeredis-py-2.19.0/test/test_mixins/test_streams_commands.py   
2023-09-25 23:33:16.000000000 +0200
@@ -455,6 +455,7 @@
     # r.xreadgroup(group, consumer, streams={stream: ">"})
     # r.xtrim(stream, 0)
     # assert r.xreadgroup(group, consumer, streams={stream: "0"}) == expected
+    r.xreadgroup(group, consumer, streams={stream: '>'}, count=10, block=500)
 
 
 def test_xinfo_stream(r: redis.Redis):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fakeredis-py-2.18.1/test/test_scan.py 
new/fakeredis-py-2.19.0/test/test_scan.py
--- old/fakeredis-py-2.18.1/test/test_scan.py   2023-09-08 21:12:42.000000000 
+0200
+++ new/fakeredis-py-2.19.0/test/test_scan.py   2023-09-25 23:33:16.000000000 
+0200
@@ -195,6 +195,6 @@
 
 def test_scan_stream(r: redis.Redis):
     r.xadd("mystream", {"test": "value"})
-    assert r.type("mystream") == b"stream"
+    assert r.type("mystream") == b"stream"  # noqa: E721
     for s in r.scan_iter(_type="STRING"):
         print(s)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fakeredis-py-2.18.1/test/test_stack/test_bloom_redis_py.py 
new/fakeredis-py-2.19.0/test/test_stack/test_bloom_redis_py.py
--- old/fakeredis-py-2.18.1/test/test_stack/test_bloom_redis_py.py      
1970-01-01 01:00:00.000000000 +0100
+++ new/fakeredis-py-2.19.0/test/test_stack/test_bloom_redis_py.py      
2023-09-25 23:33:16.000000000 +0200
@@ -0,0 +1,151 @@
+import pytest
+import redis.commands.bf
+from redis.commands.bf import BFInfo
+
+json_tests = pytest.importorskip("pybloom_live")
+
+
+def get_protocol_version(r):
+    if isinstance(r, redis.Redis) or isinstance(r, redis.asyncio.Redis):
+        return r.connection_pool.connection_kwargs.get("protocol")
+    elif isinstance(r, redis.cluster.AbstractRedisCluster):
+        return r.nodes_manager.connection_kwargs.get("protocol")
+
+
+def assert_resp_response(r, response, resp2_expected, resp3_expected):
+    protocol = get_protocol_version(r)
+    if protocol in [2, "2", None]:
+        assert response == resp2_expected
+    else:
+        assert response == resp3_expected
+
+
+def intlist(obj):
+    return [int(v) for v in obj]
+
+
+@pytest.mark.xfail
+def test_create(r: redis.Redis):
+    """Test CREATE/RESERVE calls"""
+    assert r.bf().create("bloom", 0.01, 1000)
+    assert r.bf().create("bloom_e", 0.01, 1000, expansion=1)
+    assert r.bf().create("bloom_ns", 0.01, 1000, noScale=True)
+    assert r.cf().create("cuckoo", 1000)
+    assert r.cf().create("cuckoo_e", 1000, expansion=1)
+    assert r.cf().create("cuckoo_bs", 1000, bucket_size=4)
+    assert r.cf().create("cuckoo_mi", 1000, max_iterations=10)
+    assert r.cms().initbydim("cmsDim", 100, 5)
+    assert r.cms().initbyprob("cmsProb", 0.01, 0.01)
+    assert r.topk().reserve("topk", 5, 100, 5, 0.9)
+
+
+def test_bf_reserve(r: redis.Redis):
+    """Testing BF.RESERVE"""
+    assert r.bf().reserve("bloom", 0.01, 1000)
+    assert r.bf().reserve("bloom_e", 0.01, 1000, expansion=1)
+    assert r.bf().reserve("bloom_ns", 0.01, 1000, noScale=True)
+
+
+def test_bf_add(r: redis.Redis):
+    assert r.bf().create("bloom", 0.01, 1000)
+    assert 1 == r.bf().add("bloom", "foo")
+    assert 0 == r.bf().add("bloom", "foo")
+    assert [0] == intlist(r.bf().madd("bloom", "foo"))
+    assert [0, 1] == r.bf().madd("bloom", "foo", "bar")
+    assert [0, 0, 1] == r.bf().madd("bloom", "foo", "bar", "baz")
+    assert 1 == r.bf().exists("bloom", "foo")
+    assert 0 == r.bf().exists("bloom", "noexist")
+    assert [1, 0] == intlist(r.bf().mexists("bloom", "foo", "noexist"))
+
+
+def test_bf_insert(r: redis.Redis):
+    assert r.bf().create("bloom", 0.01, 1000)
+    assert [1] == intlist(r.bf().insert("bloom", ["foo"]))
+    assert [0, 1] == intlist(r.bf().insert("bloom", ["foo", "bar"]))
+    assert [1] == intlist(r.bf().insert("captest", ["foo"], capacity=10))
+    assert [1] == intlist(r.bf().insert("errtest", ["foo"], error=0.01))
+    assert 1 == r.bf().exists("bloom", "foo")
+    assert 0 == r.bf().exists("bloom", "noexist")
+    assert [1, 0] == intlist(r.bf().mexists("bloom", "foo", "noexist"))
+    info = r.bf().info("bloom")
+    assert_resp_response(
+        r,
+        2,
+        info.get("insertedNum"),
+        info.get("Number of items inserted"),
+    )
+    assert_resp_response(
+        r,
+        1000,
+        info.get("capacity"),
+        info.get("Capacity"),
+    )
+    assert_resp_response(
+        r,
+        1,
+        info.get("filterNum"),
+        info.get("Number of filters"),
+    )
+
+
+def test_bf_scandump_and_loadchunk(r: redis.Redis):
+    r.bf().create("myBloom", "0.0001", "1000")
+
+    # Test is probabilistic and might fail. It is OK to change variables if
+    # certain to not break anything
+
+    res = 0
+    for x in range(1000):
+        r.bf().add("myBloom", x)
+        assert r.bf().exists("myBloom", x)
+        rv = r.bf().exists("myBloom", f"nonexist_{x}")
+        res += rv == x
+    assert res < 5
+
+    cmds = list()
+    first = 0
+    while first is not None:
+        cur = r.bf().scandump("myBloom", first)
+        if cur[0] == 0:
+            first = None
+        else:
+            first = cur[0]
+            cmds.append(cur)
+
+    # Remove the filter
+    r.bf().client.delete("myBloom")
+
+    # Now, load all the commands:
+    for cmd in cmds:
+        r.bf().loadchunk("myBloom1", *cmd)
+
+    for x in range(1000):
+        assert r.bf().exists("myBloom1", x), f'{x} not in filter'
+
+
+def test_bf_info(r: redis.Redis):
+    # Store a filter
+    r.bf().create("nonscaling", "0.0001", "1000", noScale=True)
+    info: BFInfo = r.bf().info("nonscaling")
+    assert info.expansionRate is None
+
+    expansion = 4
+    r.bf().create("expanding", "0.0001", "1000", expansion=expansion)
+    info = r.bf().info("expanding")
+    assert info.expansionRate == 4
+    assert info.capacity == 1000
+    assert info.insertedNum == 0
+
+
+def test_bf_card(r: redis.Redis):
+    # return 0 if the key does not exist
+    assert r.bf().card("not_exist") == 0
+
+    # Store a filter
+    assert r.bf().add("bf1", "item_foo") == 1
+    assert r.bf().card("bf1") == 1
+
+    # Error when key is of a type other than Bloom filter.
+    with pytest.raises(redis.ResponseError):
+        r.set("setKey", "value")
+        r.bf().card("setKey")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fakeredis-py-2.18.1/test/test_stack/test_bloomfilter.py 
new/fakeredis-py-2.19.0/test/test_stack/test_bloomfilter.py
--- old/fakeredis-py-2.18.1/test/test_stack/test_bloomfilter.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/fakeredis-py-2.19.0/test/test_stack/test_bloomfilter.py 2023-09-25 
23:33:16.000000000 +0200
@@ -0,0 +1,80 @@
+import pytest
+import redis
+
+from fakeredis import _msgs as msgs
+
+json_tests = pytest.importorskip("pybloom_live")
+
+
+def test_bf_add(r: redis.Redis):
+    assert r.bf().add('key', 'value') == 1
+    assert r.bf().add('key', 'value') == 0
+
+    r.set('key1', 'value')
+    with pytest.raises(redis.exceptions.ResponseError):
+        r.bf().add('key1', 'v')
+
+
+def test_bf_madd(r: redis.Redis):
+    assert r.bf().madd('key', 'v1', 'v2', 'v2') == [1, 1, 0]
+    assert r.bf().madd('key', 'v1', 'v2', 'v4') == [0, 0, 1]
+
+    r.set('key1', 'value')
+    with pytest.raises(redis.exceptions.ResponseError):
+        r.bf().add('key1', 'v')
+
+
+def test_bf_card(r: redis.Redis):
+    assert r.bf().madd('key', 'v1', 'v2', 'v3') == [1, 1, 1]
+    assert r.bf().card('key') == 3
+    assert r.bf().card('key-new') == 0
+
+    r.set('key1', 'value')
+    with pytest.raises(redis.exceptions.ResponseError):
+        r.bf().card('key1')
+
+
+def test_bf_exists(r: redis.Redis):
+    assert r.bf().madd('key', 'v1', 'v2', 'v3') == [1, 1, 1]
+    assert r.bf().exists('key', 'v1') == 1
+    assert r.bf().exists('key', 'v5') == 0
+    assert r.bf().exists('key-new', 'v5') == 0
+
+    r.set('key1', 'value')
+    with pytest.raises(redis.exceptions.ResponseError):
+        r.bf().add('key1', 'v')
+
+
+def test_bf_mexists(r: redis.Redis):
+    assert r.bf().madd('key', 'v1', 'v2', 'v3') == [1, 1, 1]
+    assert r.bf().mexists('key', 'v1') == [1, ]
+    assert r.bf().mexists('key', 'v1', 'v5') == [1, 0]
+    assert r.bf().mexists('key-new', 'v5') == [0, ]
+
+    r.set('key1', 'value')
+    with pytest.raises(redis.exceptions.ResponseError):
+        r.bf().add('key1', 'v')
+
+
+def test_bf_reserve(r: redis.Redis):
+    assert r.bf().reserve("bloom", 0.01, 1000)
+    assert r.bf().reserve("bloom_ns", 0.01, 1000, noScale=True)
+    with pytest.raises(redis.exceptions.ResponseError, 
match=msgs.NONSCALING_FILTERS_CANNOT_EXPAND_MSG):
+        assert r.bf().reserve("bloom_e", 0.01, 1000, expansion=1, noScale=True)
+    with pytest.raises(redis.exceptions.ResponseError, 
match=msgs.ITEM_EXISTS_MSG):
+        assert r.bf().reserve("bloom", 0.01, 1000)
+
+
+def test_bf_insert(r: redis.Redis):
+    assert r.bf().create("bloom", 0.01, 1000)
+    assert r.bf().insert("bloom", ["foo"]) == [1]
+    assert r.bf().insert("bloom", ["foo", "bar"]) == [0, 1]
+    assert r.bf().insert("captest", ["foo"], capacity=10) == [1]
+    assert r.bf().insert("errtest", ["foo"], error=0.01) == [1]
+    assert r.bf().exists("bloom", "foo") == 1
+    assert r.bf().exists("bloom", "noexist") == 0
+    assert r.bf().mexists("bloom", "foo", "noexist") == [1, 0]
+    with pytest.raises(redis.exceptions.ResponseError, 
match=msgs.NOT_FOUND_MSG):
+        r.bf().insert("nocreate", [1, 2, 3], noCreate=True)
+    # with pytest.raises(redis.exceptions.ResponseError, 
match=msgs.NONSCALING_FILTERS_CANNOT_EXPAND_MSG):
+    #     r.bf().insert("nocreate", [1, 2, 3], expansion=2, noScale=True)

Reply via email to