about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/future/types
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/future/types')
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/__init__.py257
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newbytes.py460
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newdict.py76
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newint.py386
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newlist.py95
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newmemoryview.py29
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newobject.py117
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newopen.py32
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newrange.py170
-rw-r--r--.venv/lib/python3.12/site-packages/future/types/newstr.py426
10 files changed, 2048 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/future/types/__init__.py b/.venv/lib/python3.12/site-packages/future/types/__init__.py
new file mode 100644
index 00000000..06250770
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/__init__.py
@@ -0,0 +1,257 @@
+"""
+This module contains backports the data types that were significantly changed
+in the transition from Python 2 to Python 3.
+
+- an implementation of Python 3's bytes object (pure Python subclass of
+  Python 2's builtin 8-bit str type)
+- an implementation of Python 3's str object (pure Python subclass of
+  Python 2's builtin unicode type)
+- a backport of the range iterator from Py3 with slicing support
+
+It is used as follows::
+
+    from __future__ import division, absolute_import, print_function
+    from builtins import bytes, dict, int, range, str
+
+to bring in the new semantics for these functions from Python 3. And
+then, for example::
+
+    b = bytes(b'ABCD')
+    assert list(b) == [65, 66, 67, 68]
+    assert repr(b) == "b'ABCD'"
+    assert [65, 66] in b
+
+    # These raise TypeErrors:
+    # b + u'EFGH'
+    # b.split(u'B')
+    # bytes(b',').join([u'Fred', u'Bill'])
+
+
+    s = str(u'ABCD')
+
+    # These raise TypeErrors:
+    # s.join([b'Fred', b'Bill'])
+    # s.startswith(b'A')
+    # b'B' in s
+    # s.find(b'A')
+    # s.replace(u'A', b'a')
+
+    # This raises an AttributeError:
+    # s.decode('utf-8')
+
+    assert repr(s) == 'ABCD'      # consistent repr with Py3 (no u prefix)
+
+
+    for i in range(10**11)[:10]:
+        pass
+
+and::
+
+    class VerboseList(list):
+        def append(self, item):
+            print('Adding an item')
+            super().append(item)        # new simpler super() function
+
+For more information:
+---------------------
+
+- future.types.newbytes
+- future.types.newdict
+- future.types.newint
+- future.types.newobject
+- future.types.newrange
+- future.types.newstr
+
+
+Notes
+=====
+
+range()
+-------
+``range`` is a custom class that backports the slicing behaviour from
+Python 3 (based on the ``xrange`` module by Dan Crosta). See the
+``newrange`` module docstring for more details.
+
+
+super()
+-------
+``super()`` is based on Ryan Kelly's ``magicsuper`` module. See the
+``newsuper`` module docstring for more details.
+
+
+round()
+-------
+Python 3 modifies the behaviour of ``round()`` to use "Banker's Rounding".
+See http://stackoverflow.com/a/10825998. See the ``newround`` module
+docstring for more details.
+
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import functools
+from numbers import Integral
+
+from future import utils
+
+
+# Some utility functions to enforce strict type-separation of unicode str and
+# bytes:
+def disallow_types(argnums, disallowed_types):
+    """
+    A decorator that raises a TypeError if any of the given numbered
+    arguments is of the corresponding given type (e.g. bytes or unicode
+    string).
+
+    For example:
+
+        @disallow_types([0, 1], [unicode, bytes])
+        def f(a, b):
+            pass
+
+    raises a TypeError when f is called if a unicode object is passed as
+    `a` or a bytes object is passed as `b`.
+
+    This also skips over keyword arguments, so
+
+        @disallow_types([0, 1], [unicode, bytes])
+        def g(a, b=None):
+            pass
+
+    doesn't raise an exception if g is called with only one argument a,
+    e.g.:
+
+        g(b'Byte string')
+
+    Example use:
+
+    >>> class newbytes(object):
+    ...     @disallow_types([1], [unicode])
+    ...     def __add__(self, other):
+    ...          pass
+
+    >>> newbytes('1234') + u'1234'      #doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+      ...
+    TypeError: can't concat 'bytes' to (unicode) str
+    """
+
+    def decorator(function):
+
+        @functools.wraps(function)
+        def wrapper(*args, **kwargs):
+            # These imports are just for this decorator, and are defined here
+            # to prevent circular imports:
+            from .newbytes import newbytes
+            from .newint import newint
+            from .newstr import newstr
+
+            errmsg = "argument can't be {0}"
+            for (argnum, mytype) in zip(argnums, disallowed_types):
+                # Handle the case where the type is passed as a string like 'newbytes'.
+                if isinstance(mytype, str) or isinstance(mytype, bytes):
+                    mytype = locals()[mytype]
+
+                # Only restrict kw args only if they are passed:
+                if len(args) <= argnum:
+                    break
+
+                # Here we use type() rather than isinstance() because
+                # __instancecheck__ is being overridden. E.g.
+                # isinstance(b'abc', newbytes) is True on Py2.
+                if type(args[argnum]) == mytype:
+                    raise TypeError(errmsg.format(mytype))
+
+            return function(*args, **kwargs)
+        return wrapper
+    return decorator
+
+
+def no(mytype, argnums=(1,)):
+    """
+    A shortcut for the disallow_types decorator that disallows only one type
+    (in any position in argnums).
+
+    Example use:
+
+    >>> class newstr(object):
+    ...     @no('bytes')
+    ...     def __add__(self, other):
+    ...          pass
+
+    >>> newstr(u'1234') + b'1234'     #doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+      ...
+    TypeError: argument can't be bytes
+
+    The object can also be passed directly, but passing the string helps
+    to prevent circular import problems.
+    """
+    if isinstance(argnums, Integral):
+        argnums = (argnums,)
+    disallowed_types = [mytype] * len(argnums)
+    return disallow_types(argnums, disallowed_types)
+
+
+def issubset(list1, list2):
+    """
+    Examples:
+
+    >>> issubset([], [65, 66, 67])
+    True
+    >>> issubset([65], [65, 66, 67])
+    True
+    >>> issubset([65, 66], [65, 66, 67])
+    True
+    >>> issubset([65, 67], [65, 66, 67])
+    False
+    """
+    n = len(list1)
+    for startpos in range(len(list2) - n + 1):
+        if list2[startpos:startpos+n] == list1:
+            return True
+    return False
+
+
+if utils.PY3:
+    import builtins
+    bytes = builtins.bytes
+    dict = builtins.dict
+    int = builtins.int
+    list = builtins.list
+    object = builtins.object
+    range = builtins.range
+    str = builtins.str
+
+    # The identity mapping
+    newtypes = {bytes: bytes,
+                dict: dict,
+                int: int,
+                list: list,
+                object: object,
+                range: range,
+                str: str}
+
+    __all__ = ['newtypes']
+
+else:
+
+    from .newbytes import newbytes
+    from .newdict import newdict
+    from .newint import newint
+    from .newlist import newlist
+    from .newrange import newrange
+    from .newobject import newobject
+    from .newstr import newstr
+
+    newtypes = {bytes: newbytes,
+                dict: newdict,
+                int: newint,
+                long: newint,
+                list: newlist,
+                object: newobject,
+                range: newrange,
+                str: newbytes,
+                unicode: newstr}
+
+    __all__ = ['newbytes', 'newdict', 'newint', 'newlist', 'newrange', 'newstr', 'newtypes']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newbytes.py b/.venv/lib/python3.12/site-packages/future/types/newbytes.py
new file mode 100644
index 00000000..c9d584a7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newbytes.py
@@ -0,0 +1,460 @@
+"""
+Pure-Python implementation of a Python 3-like bytes object for Python 2.
+
+Why do this? Without it, the Python 2 bytes object is a very, very
+different beast to the Python 3 bytes object.
+"""
+
+from numbers import Integral
+import string
+import copy
+
+from future.utils import istext, isbytes, PY2, PY3, with_metaclass
+from future.types import no, issubset
+from future.types.newobject import newobject
+
+if PY2:
+    from collections import Iterable
+else:
+    from collections.abc import Iterable
+
+
+_builtin_bytes = bytes
+
+if PY3:
+    # We'll probably never use newstr on Py3 anyway...
+    unicode = str
+
+
+class BaseNewBytes(type):
+    def __instancecheck__(cls, instance):
+        if cls == newbytes:
+            return isinstance(instance, _builtin_bytes)
+        else:
+            return issubclass(instance.__class__, cls)
+
+
+def _newchr(x):
+    if isinstance(x, str):  # this happens on pypy
+        return x.encode('ascii')
+    else:
+        return chr(x)
+
+
+class newbytes(with_metaclass(BaseNewBytes, _builtin_bytes)):
+    """
+    A backport of the Python 3 bytes object to Py2
+    """
+    def __new__(cls, *args, **kwargs):
+        """
+        From the Py3 bytes docstring:
+
+        bytes(iterable_of_ints) -> bytes
+        bytes(string, encoding[, errors]) -> bytes
+        bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
+        bytes(int) -> bytes object of size given by the parameter initialized with null bytes
+        bytes() -> empty bytes object
+
+        Construct an immutable array of bytes from:
+          - an iterable yielding integers in range(256)
+          - a text string encoded using the specified encoding
+          - any object implementing the buffer API.
+          - an integer
+        """
+
+        encoding = None
+        errors = None
+
+        if len(args) == 0:
+            return super(newbytes, cls).__new__(cls)
+        elif len(args) >= 2:
+            args = list(args)
+            if len(args) == 3:
+                errors = args.pop()
+            encoding=args.pop()
+        # Was: elif isinstance(args[0], newbytes):
+        # We use type() instead of the above because we're redefining
+        # this to be True for all unicode string subclasses. Warning:
+        # This may render newstr un-subclassable.
+        if type(args[0]) == newbytes:
+            # Special-case: for consistency with Py3.3, we return the same object
+            # (with the same id) if a newbytes object is passed into the
+            # newbytes constructor.
+            return args[0]
+        elif isinstance(args[0], _builtin_bytes):
+            value = args[0]
+        elif isinstance(args[0], unicode):
+            try:
+                if 'encoding' in kwargs:
+                    assert encoding is None
+                    encoding = kwargs['encoding']
+                if 'errors' in kwargs:
+                    assert errors is None
+                    errors = kwargs['errors']
+            except AssertionError:
+                raise TypeError('Argument given by name and position')
+            if encoding is None:
+                raise TypeError('unicode string argument without an encoding')
+            ###
+            # Was:   value = args[0].encode(**kwargs)
+            # Python 2.6 string encode() method doesn't take kwargs:
+            # Use this instead:
+            newargs = [encoding]
+            if errors is not None:
+                newargs.append(errors)
+            value = args[0].encode(*newargs)
+            ###
+        elif hasattr(args[0], '__bytes__'):
+            value = args[0].__bytes__()
+        elif isinstance(args[0], Iterable):
+            if len(args[0]) == 0:
+                # This could be an empty list or tuple. Return b'' as on Py3.
+                value = b''
+            else:
+                # Was: elif len(args[0])>0 and isinstance(args[0][0], Integral):
+                #      # It's a list of integers
+                # But then we can't index into e.g. frozensets. Try to proceed
+                # anyway.
+                try:
+                    value = bytearray([_newchr(x) for x in args[0]])
+                except:
+                    raise ValueError('bytes must be in range(0, 256)')
+        elif isinstance(args[0], Integral):
+            if args[0] < 0:
+                raise ValueError('negative count')
+            value = b'\x00' * args[0]
+        else:
+            value = args[0]
+        if type(value) == newbytes:
+            # Above we use type(...) rather than isinstance(...) because the
+            # newbytes metaclass overrides __instancecheck__.
+            # oldbytes(value) gives the wrong thing on Py2: the same
+            # result as str(value) on Py3, e.g. "b'abc'". (Issue #193).
+            # So we handle this case separately:
+            return copy.copy(value)
+        else:
+            return super(newbytes, cls).__new__(cls, value)
+
+    def __repr__(self):
+        return 'b' + super(newbytes, self).__repr__()
+
+    def __str__(self):
+        return 'b' + "'{0}'".format(super(newbytes, self).__str__())
+
+    def __getitem__(self, y):
+        value = super(newbytes, self).__getitem__(y)
+        if isinstance(y, Integral):
+            return ord(value)
+        else:
+            return newbytes(value)
+
+    def __getslice__(self, *args):
+        return self.__getitem__(slice(*args))
+
+    def __contains__(self, key):
+        if isinstance(key, int):
+            newbyteskey = newbytes([key])
+        # Don't use isinstance() here because we only want to catch
+        # newbytes, not Python 2 str:
+        elif type(key) == newbytes:
+            newbyteskey = key
+        else:
+            newbyteskey = newbytes(key)
+        return issubset(list(newbyteskey), list(self))
+
+    @no(unicode)
+    def __add__(self, other):
+        return newbytes(super(newbytes, self).__add__(other))
+
+    @no(unicode)
+    def __radd__(self, left):
+        return newbytes(left) + self
+
+    @no(unicode)
+    def __mul__(self, other):
+        return newbytes(super(newbytes, self).__mul__(other))
+
+    @no(unicode)
+    def __rmul__(self, other):
+        return newbytes(super(newbytes, self).__rmul__(other))
+
+    def __mod__(self, vals):
+        if isinstance(vals, newbytes):
+            vals = _builtin_bytes.__str__(vals)
+
+        elif isinstance(vals, tuple):
+            newvals = []
+            for v in vals:
+                if isinstance(v, newbytes):
+                    v = _builtin_bytes.__str__(v)
+                newvals.append(v)
+            vals = tuple(newvals)
+
+        elif (hasattr(vals.__class__, '__getitem__') and
+                hasattr(vals.__class__, 'iteritems')):
+            for k, v in vals.iteritems():
+                if isinstance(v, newbytes):
+                    vals[k] = _builtin_bytes.__str__(v)
+
+        return _builtin_bytes.__mod__(self, vals)
+
+    def __imod__(self, other):
+        return self.__mod__(other)
+
+    def join(self, iterable_of_bytes):
+        errmsg = 'sequence item {0}: expected bytes, {1} found'
+        if isbytes(iterable_of_bytes) or istext(iterable_of_bytes):
+            raise TypeError(errmsg.format(0, type(iterable_of_bytes)))
+        for i, item in enumerate(iterable_of_bytes):
+            if istext(item):
+                raise TypeError(errmsg.format(i, type(item)))
+        return newbytes(super(newbytes, self).join(iterable_of_bytes))
+
+    @classmethod
+    def fromhex(cls, string):
+        # Only on Py2:
+        return cls(string.replace(' ', '').decode('hex'))
+
+    @no(unicode)
+    def find(self, sub, *args):
+        return super(newbytes, self).find(sub, *args)
+
+    @no(unicode)
+    def rfind(self, sub, *args):
+        return super(newbytes, self).rfind(sub, *args)
+
+    @no(unicode, (1, 2))
+    def replace(self, old, new, *args):
+        return newbytes(super(newbytes, self).replace(old, new, *args))
+
+    def encode(self, *args):
+        raise AttributeError("encode method has been disabled in newbytes")
+
+    def decode(self, encoding='utf-8', errors='strict'):
+        """
+        Returns a newstr (i.e. unicode subclass)
+
+        Decode B using the codec registered for encoding. Default encoding
+        is 'utf-8'. errors may be given to set a different error
+        handling scheme.  Default is 'strict' meaning that encoding errors raise
+        a UnicodeDecodeError.  Other possible values are 'ignore' and 'replace'
+        as well as any other name registered with codecs.register_error that is
+        able to handle UnicodeDecodeErrors.
+        """
+        # Py2 str.encode() takes encoding and errors as optional parameter,
+        # not keyword arguments as in Python 3 str.
+
+        from future.types.newstr import newstr
+
+        if errors == 'surrogateescape':
+            from future.utils.surrogateescape import register_surrogateescape
+            register_surrogateescape()
+
+        return newstr(super(newbytes, self).decode(encoding, errors))
+
+        # This is currently broken:
+        # # We implement surrogateescape error handling here in addition rather
+        # # than relying on the custom error handler from
+        # # future.utils.surrogateescape to be registered globally, even though
+        # # that is fine in the case of decoding. (But not encoding: see the
+        # # comments in newstr.encode()``.)
+        #
+        # if errors == 'surrogateescape':
+        #     # Decode char by char
+        #     mybytes = []
+        #     for code in self:
+        #         # Code is an int
+        #         if 0x80 <= code <= 0xFF:
+        #             b = 0xDC00 + code
+        #         elif code <= 0x7F:
+        #             b = _unichr(c).decode(encoding=encoding)
+        #         else:
+        #             # # It may be a bad byte
+        #             # FIXME: What to do in this case? See the Py3 docs / tests.
+        #             # # Try swallowing it.
+        #             # continue
+        #             # print("RAISE!")
+        #             raise NotASurrogateError
+        #         mybytes.append(b)
+        #     return newbytes(mybytes)
+        # return newbytes(super(newstr, self).decode(encoding, errors))
+
+    @no(unicode)
+    def startswith(self, prefix, *args):
+        return super(newbytes, self).startswith(prefix, *args)
+
+    @no(unicode)
+    def endswith(self, prefix, *args):
+        return super(newbytes, self).endswith(prefix, *args)
+
+    @no(unicode)
+    def split(self, sep=None, maxsplit=-1):
+        # Py2 str.split() takes maxsplit as an optional parameter, not as a
+        # keyword argument as in Python 3 bytes.
+        parts = super(newbytes, self).split(sep, maxsplit)
+        return [newbytes(part) for part in parts]
+
+    def splitlines(self, keepends=False):
+        """
+        B.splitlines([keepends]) -> list of lines
+
+        Return a list of the lines in B, breaking at line boundaries.
+        Line breaks are not included in the resulting list unless keepends
+        is given and true.
+        """
+        # Py2 str.splitlines() takes keepends as an optional parameter,
+        # not as a keyword argument as in Python 3 bytes.
+        parts = super(newbytes, self).splitlines(keepends)
+        return [newbytes(part) for part in parts]
+
+    @no(unicode)
+    def rsplit(self, sep=None, maxsplit=-1):
+        # Py2 str.rsplit() takes maxsplit as an optional parameter, not as a
+        # keyword argument as in Python 3 bytes.
+        parts = super(newbytes, self).rsplit(sep, maxsplit)
+        return [newbytes(part) for part in parts]
+
+    @no(unicode)
+    def partition(self, sep):
+        parts = super(newbytes, self).partition(sep)
+        return tuple(newbytes(part) for part in parts)
+
+    @no(unicode)
+    def rpartition(self, sep):
+        parts = super(newbytes, self).rpartition(sep)
+        return tuple(newbytes(part) for part in parts)
+
+    @no(unicode, (1,))
+    def rindex(self, sub, *args):
+        '''
+        S.rindex(sub [,start [,end]]) -> int
+
+        Like S.rfind() but raise ValueError when the substring is not found.
+        '''
+        pos = self.rfind(sub, *args)
+        if pos == -1:
+            raise ValueError('substring not found')
+
+    @no(unicode)
+    def index(self, sub, *args):
+        '''
+        Returns index of sub in bytes.
+        Raises ValueError if byte is not in bytes and TypeError if can't
+        be converted bytes or its length is not 1.
+        '''
+        if isinstance(sub, int):
+            if len(args) == 0:
+                start, end = 0, len(self)
+            elif len(args) == 1:
+                start = args[0]
+            elif len(args) == 2:
+                start, end = args
+            else:
+                raise TypeError('takes at most 3 arguments')
+            return list(self)[start:end].index(sub)
+        if not isinstance(sub, bytes):
+            try:
+                sub = self.__class__(sub)
+            except (TypeError, ValueError):
+                raise TypeError("can't convert sub to bytes")
+        try:
+            return super(newbytes, self).index(sub, *args)
+        except ValueError:
+            raise ValueError('substring not found')
+
+    def __eq__(self, other):
+        if isinstance(other, (_builtin_bytes, bytearray)):
+            return super(newbytes, self).__eq__(other)
+        else:
+            return False
+
+    def __ne__(self, other):
+        if isinstance(other, _builtin_bytes):
+            return super(newbytes, self).__ne__(other)
+        else:
+            return True
+
+    unorderable_err = 'unorderable types: bytes() and {0}'
+
+    def __lt__(self, other):
+        if isinstance(other, _builtin_bytes):
+            return super(newbytes, self).__lt__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __le__(self, other):
+        if isinstance(other, _builtin_bytes):
+            return super(newbytes, self).__le__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __gt__(self, other):
+        if isinstance(other, _builtin_bytes):
+            return super(newbytes, self).__gt__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __ge__(self, other):
+        if isinstance(other, _builtin_bytes):
+            return super(newbytes, self).__ge__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __native__(self):
+        # We can't just feed a newbytes object into str(), because
+        # newbytes.__str__() returns e.g. "b'blah'", consistent with Py3 bytes.
+        return super(newbytes, self).__str__()
+
+    def __getattribute__(self, name):
+        """
+        A trick to cause the ``hasattr`` builtin-fn to return False for
+        the 'encode' method on Py2.
+        """
+        if name in ['encode', u'encode']:
+            raise AttributeError("encode method has been disabled in newbytes")
+        return super(newbytes, self).__getattribute__(name)
+
+    @no(unicode)
+    def rstrip(self, bytes_to_strip=None):
+        """
+        Strip trailing bytes contained in the argument.
+        If the argument is omitted, strip trailing ASCII whitespace.
+        """
+        return newbytes(super(newbytes, self).rstrip(bytes_to_strip))
+
+    @no(unicode)
+    def strip(self, bytes_to_strip=None):
+        """
+        Strip leading and trailing bytes contained in the argument.
+        If the argument is omitted, strip trailing ASCII whitespace.
+        """
+        return newbytes(super(newbytes, self).strip(bytes_to_strip))
+
+    def lower(self):
+        """
+        b.lower() -> copy of b
+
+        Return a copy of b with all ASCII characters converted to lowercase.
+        """
+        return newbytes(super(newbytes, self).lower())
+
+    @no(unicode)
+    def upper(self):
+        """
+        b.upper() -> copy of b
+
+        Return a copy of b with all ASCII characters converted to uppercase.
+        """
+        return newbytes(super(newbytes, self).upper())
+
+    @classmethod
+    @no(unicode)
+    def maketrans(cls, frm, to):
+        """
+        B.maketrans(frm, to) -> translation table
+
+        Return a translation table (a bytes object of length 256) suitable
+        for use in the bytes or bytearray translate method where each byte
+        in frm is mapped to the byte at the same position in to.
+        The bytes objects frm and to must be of the same length.
+        """
+        return newbytes(string.maketrans(frm, to))
+
+
+__all__ = ['newbytes']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newdict.py b/.venv/lib/python3.12/site-packages/future/types/newdict.py
new file mode 100644
index 00000000..d90316cb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newdict.py
@@ -0,0 +1,76 @@
+"""
+A dict subclass for Python 2 that behaves like Python 3's dict
+
+Example use:
+
+>>> from builtins import dict
+>>> d1 = dict()    # instead of {} for an empty dict
+>>> d2 = dict(key1='value1', key2='value2')
+
+The keys, values and items methods now return iterators on Python 2.x
+(with set-like behaviour on Python 2.7).
+
+>>> for d in (d1, d2):
+...     assert not isinstance(d.keys(), list)
+...     assert not isinstance(d.values(), list)
+...     assert not isinstance(d.items(), list)
+"""
+
+import sys
+
+from future.utils import with_metaclass
+from future.types.newobject import newobject
+
+
+_builtin_dict = dict
+ver = sys.version_info
+
+
+class BaseNewDict(type):
+    def __instancecheck__(cls, instance):
+        if cls == newdict:
+            return isinstance(instance, _builtin_dict)
+        else:
+            return issubclass(instance.__class__, cls)
+
+
+class newdict(with_metaclass(BaseNewDict, _builtin_dict)):
+    """
+    A backport of the Python 3 dict object to Py2
+    """
+
+    if ver >= (3,):
+        # Inherit items, keys and values from `dict` in 3.x
+        pass
+    elif ver >= (2, 7):
+        items = dict.viewitems
+        keys = dict.viewkeys
+        values = dict.viewvalues
+    else:
+        items = dict.iteritems
+        keys = dict.iterkeys
+        values = dict.itervalues
+
+    def __new__(cls, *args, **kwargs):
+        """
+        dict() -> new empty dictionary
+        dict(mapping) -> new dictionary initialized from a mapping object's
+            (key, value) pairs
+        dict(iterable) -> new dictionary initialized as if via:
+            d = {}
+            for k, v in iterable:
+                d[k] = v
+        dict(**kwargs) -> new dictionary initialized with the name=value pairs
+            in the keyword argument list.  For example:  dict(one=1, two=2)
+        """
+
+        return super(newdict, cls).__new__(cls, *args)
+
+    def __native__(self):
+        """
+        Hook for the future.utils.native() function
+        """
+        return dict(self)
+
+
+__all__ = ['newdict']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newint.py b/.venv/lib/python3.12/site-packages/future/types/newint.py
new file mode 100644
index 00000000..ebc5715e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newint.py
@@ -0,0 +1,386 @@
+"""
+Backport of Python 3's int, based on Py2's long.
+
+They are very similar. The most notable difference is:
+
+- representation: trailing L in Python 2 removed in Python 3
+"""
+from __future__ import division
+
+import struct
+
+from future.types.newbytes import newbytes
+from future.types.newobject import newobject
+from future.utils import PY3, isint, istext, isbytes, with_metaclass, native
+
+
+if PY3:
+    long = int
+    from collections.abc import Iterable
+else:
+    from collections import Iterable
+
+
+class BaseNewInt(type):
+    def __instancecheck__(cls, instance):
+        if cls == newint:
+            # Special case for Py2 short or long int
+            return isinstance(instance, (int, long))
+        else:
+            return issubclass(instance.__class__, cls)
+
+
+class newint(with_metaclass(BaseNewInt, long)):
+    """
+    A backport of the Python 3 int object to Py2
+    """
+    def __new__(cls, x=0, base=10):
+        """
+        From the Py3 int docstring:
+
+        |  int(x=0) -> integer
+        |  int(x, base=10) -> integer
+        |
+        |  Convert a number or string to an integer, or return 0 if no
+        |  arguments are given.  If x is a number, return x.__int__().  For
+        |  floating point numbers, this truncates towards zero.
+        |
+        |  If x is not a number or if base is given, then x must be a string,
+        |  bytes, or bytearray instance representing an integer literal in the
+        |  given base.  The literal can be preceded by '+' or '-' and be
+        |  surrounded by whitespace.  The base defaults to 10.  Valid bases are
+        |  0 and 2-36. Base 0 means to interpret the base from the string as an
+        |  integer literal.
+        |  >>> int('0b100', base=0)
+        |  4
+
+        """
+        try:
+            val = x.__int__()
+        except AttributeError:
+            val = x
+        else:
+            if not isint(val):
+                raise TypeError('__int__ returned non-int ({0})'.format(
+                    type(val)))
+
+        if base != 10:
+            # Explicit base
+            if not (istext(val) or isbytes(val) or isinstance(val, bytearray)):
+                raise TypeError(
+                    "int() can't convert non-string with explicit base")
+            try:
+                return super(newint, cls).__new__(cls, val, base)
+            except TypeError:
+                return super(newint, cls).__new__(cls, newbytes(val), base)
+        # After here, base is 10
+        try:
+            return super(newint, cls).__new__(cls, val)
+        except TypeError:
+            # Py2 long doesn't handle bytearray input with an explicit base, so
+            # handle this here.
+            # Py3: int(bytearray(b'10'), 2) == 2
+            # Py2: int(bytearray(b'10'), 2) == 2 raises TypeError
+            # Py2: long(bytearray(b'10'), 2) == 2 raises TypeError
+            try:
+                return super(newint, cls).__new__(cls, newbytes(val))
+            except:
+                raise TypeError("newint argument must be a string or a number,"
+                                "not '{0}'".format(type(val)))
+
+    def __repr__(self):
+        """
+        Without the L suffix
+        """
+        value = super(newint, self).__repr__()
+        assert value[-1] == 'L'
+        return value[:-1]
+
+    def __add__(self, other):
+        value = super(newint, self).__add__(other)
+        if value is NotImplemented:
+            return long(self) + other
+        return newint(value)
+
+    def __radd__(self, other):
+        value = super(newint, self).__radd__(other)
+        if value is NotImplemented:
+            return other + long(self)
+        return newint(value)
+
+    def __sub__(self, other):
+        value = super(newint, self).__sub__(other)
+        if value is NotImplemented:
+            return long(self) - other
+        return newint(value)
+
+    def __rsub__(self, other):
+        value = super(newint, self).__rsub__(other)
+        if value is NotImplemented:
+            return other - long(self)
+        return newint(value)
+
+    def __mul__(self, other):
+        value = super(newint, self).__mul__(other)
+        if isint(value):
+            return newint(value)
+        elif value is NotImplemented:
+            return long(self) * other
+        return value
+
+    def __rmul__(self, other):
+        value = super(newint, self).__rmul__(other)
+        if isint(value):
+            return newint(value)
+        elif value is NotImplemented:
+            return other * long(self)
+        return value
+
+    def __div__(self, other):
+        # We override this rather than e.g. relying on object.__div__ or
+        # long.__div__ because we want to wrap the value in a newint()
+        # call if other is another int
+        value = long(self) / other
+        if isinstance(other, (int, long)):
+            return newint(value)
+        else:
+            return value
+
+    def __rdiv__(self, other):
+        value = other / long(self)
+        if isinstance(other, (int, long)):
+            return newint(value)
+        else:
+            return value
+
+    def __idiv__(self, other):
+        # long has no __idiv__ method. Use __itruediv__ and cast back to
+        # newint:
+        value = self.__itruediv__(other)
+        if isinstance(other, (int, long)):
+            return newint(value)
+        else:
+            return value
+
+    def __truediv__(self, other):
+        value = super(newint, self).__truediv__(other)
+        if value is NotImplemented:
+            value = long(self) / other
+        return value
+
+    def __rtruediv__(self, other):
+        return super(newint, self).__rtruediv__(other)
+
+    def __itruediv__(self, other):
+        # long has no __itruediv__ method
+        mylong = long(self)
+        mylong /= other
+        return mylong
+
+    def __floordiv__(self, other):
+        return newint(super(newint, self).__floordiv__(other))
+
+    def __rfloordiv__(self, other):
+        return newint(super(newint, self).__rfloordiv__(other))
+
+    def __ifloordiv__(self, other):
+        # long has no __ifloordiv__ method
+        mylong = long(self)
+        mylong //= other
+        return newint(mylong)
+
+    def __mod__(self, other):
+        value = super(newint, self).__mod__(other)
+        if value is NotImplemented:
+            return long(self) % other
+        return newint(value)
+
+    def __rmod__(self, other):
+        value = super(newint, self).__rmod__(other)
+        if value is NotImplemented:
+            return other % long(self)
+        return newint(value)
+
+    def __divmod__(self, other):
+        value = super(newint, self).__divmod__(other)
+        if value is NotImplemented:
+            mylong = long(self)
+            return (mylong // other, mylong % other)
+        return (newint(value[0]), newint(value[1]))
+
+    def __rdivmod__(self, other):
+        value = super(newint, self).__rdivmod__(other)
+        if value is NotImplemented:
+            mylong = long(self)
+            return (other // mylong, other % mylong)
+        return (newint(value[0]), newint(value[1]))
+
+    def __pow__(self, other):
+        value = super(newint, self).__pow__(other)
+        if value is NotImplemented:
+            return long(self) ** other
+        return newint(value)
+
+    def __rpow__(self, other):
+        value = super(newint, self).__rpow__(other)
+        if isint(value):
+            return newint(value)
+        elif value is NotImplemented:
+            return other ** long(self)
+        return value
+
+    def __lshift__(self, other):
+        if not isint(other):
+            raise TypeError(
+                "unsupported operand type(s) for <<: '%s' and '%s'" %
+                (type(self).__name__, type(other).__name__))
+        return newint(super(newint, self).__lshift__(other))
+
+    def __rshift__(self, other):
+        if not isint(other):
+            raise TypeError(
+                "unsupported operand type(s) for >>: '%s' and '%s'" %
+                (type(self).__name__, type(other).__name__))
+        return newint(super(newint, self).__rshift__(other))
+
+    def __and__(self, other):
+        if not isint(other):
+            raise TypeError(
+                "unsupported operand type(s) for &: '%s' and '%s'" %
+                (type(self).__name__, type(other).__name__))
+        return newint(super(newint, self).__and__(other))
+
+    def __or__(self, other):
+        if not isint(other):
+            raise TypeError(
+                "unsupported operand type(s) for |: '%s' and '%s'" %
+                (type(self).__name__, type(other).__name__))
+        return newint(super(newint, self).__or__(other))
+
+    def __xor__(self, other):
+        if not isint(other):
+            raise TypeError(
+                "unsupported operand type(s) for ^: '%s' and '%s'" %
+                (type(self).__name__, type(other).__name__))
+        return newint(super(newint, self).__xor__(other))
+
+    def __neg__(self):
+        return newint(super(newint, self).__neg__())
+
+    def __pos__(self):
+        return newint(super(newint, self).__pos__())
+
+    def __abs__(self):
+        return newint(super(newint, self).__abs__())
+
+    def __invert__(self):
+        return newint(super(newint, self).__invert__())
+
+    def __int__(self):
+        return self
+
+    def __nonzero__(self):
+        return self.__bool__()
+
+    def __bool__(self):
+        """
+        So subclasses can override this, Py3-style
+        """
+        if PY3:
+            return super(newint, self).__bool__()
+
+        return super(newint, self).__nonzero__()
+
+    def __native__(self):
+        return long(self)
+
+    def to_bytes(self, length, byteorder='big', signed=False):
+        """
+        Return an array of bytes representing an integer.
+
+        The integer is represented using length bytes.  An OverflowError is
+        raised if the integer is not representable with the given number of
+        bytes.
+
+        The byteorder argument determines the byte order used to represent the
+        integer.  If byteorder is 'big', the most significant byte is at the
+        beginning of the byte array.  If byteorder is 'little', the most
+        significant byte is at the end of the byte array.  To request the native
+        byte order of the host system, use `sys.byteorder' as the byte order value.
+
+        The signed keyword-only argument determines whether two's complement is
+        used to represent the integer.  If signed is False and a negative integer
+        is given, an OverflowError is raised.
+        """
+        if length < 0:
+            raise ValueError("length argument must be non-negative")
+        if length == 0 and self == 0:
+            return newbytes()
+        if signed and self < 0:
+            bits = length * 8
+            num = (2**bits) + self
+            if num <= 0:
+                raise OverflowError("int too small to convert")
+        else:
+            if self < 0:
+                raise OverflowError("can't convert negative int to unsigned")
+            num = self
+        if byteorder not in ('little', 'big'):
+            raise ValueError("byteorder must be either 'little' or 'big'")
+        h = b'%x' % num
+        s = newbytes((b'0'*(len(h) % 2) + h).zfill(length*2).decode('hex'))
+        if signed:
+            high_set = s[0] & 0x80
+            if self > 0 and high_set:
+                raise OverflowError("int too big to convert")
+            if self < 0 and not high_set:
+                raise OverflowError("int too small to convert")
+        if len(s) > length:
+            raise OverflowError("int too big to convert")
+        return s if byteorder == 'big' else s[::-1]
+
+    @classmethod
+    def from_bytes(cls, mybytes, byteorder='big', signed=False):
+        """
+        Return the integer represented by the given array of bytes.
+
+        The mybytes argument must either support the buffer protocol or be an
+        iterable object producing bytes.  Bytes and bytearray are examples of
+        built-in objects that support the buffer protocol.
+
+        The byteorder argument determines the byte order used to represent the
+        integer.  If byteorder is 'big', the most significant byte is at the
+        beginning of the byte array.  If byteorder is 'little', the most
+        significant byte is at the end of the byte array.  To request the native
+        byte order of the host system, use `sys.byteorder' as the byte order value.
+
+        The signed keyword-only argument indicates whether two's complement is
+        used to represent the integer.
+        """
+        if byteorder not in ('little', 'big'):
+            raise ValueError("byteorder must be either 'little' or 'big'")
+        if isinstance(mybytes, unicode):
+            raise TypeError("cannot convert unicode objects to bytes")
+        # mybytes can also be passed as a sequence of integers on Py3.
+        # Test for this:
+        elif isinstance(mybytes, Iterable):
+            mybytes = newbytes(mybytes)
+        b = mybytes if byteorder == 'big' else mybytes[::-1]
+        if len(b) == 0:
+            b = b'\x00'
+        # The encode() method has been disabled by newbytes, but Py2's
+        # str has it:
+        num = int(native(b).encode('hex'), 16)
+        if signed and (b[0] & 0x80):
+            num = num - (2 ** (len(b)*8))
+        return cls(num)
+
+
+# def _twos_comp(val, bits):
+#     """compute the 2's compliment of int value val"""
+#     if( (val&(1<<(bits-1))) != 0 ):
+#         val = val - (1<<bits)
+#     return val
+
+
+__all__ = ['newint']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newlist.py b/.venv/lib/python3.12/site-packages/future/types/newlist.py
new file mode 100644
index 00000000..74d8f6ce
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newlist.py
@@ -0,0 +1,95 @@
+"""
+A list subclass for Python 2 that behaves like Python 3's list.
+
+The primary difference is that lists have a .copy() method in Py3.
+
+Example use:
+
+>>> from builtins import list
+>>> l1 = list()    # instead of {} for an empty list
+>>> l1.append('hello')
+>>> l2 = l1.copy()
+
+"""
+
+import sys
+import copy
+
+from future.utils import with_metaclass
+from future.types.newobject import newobject
+
+
+_builtin_list = list
+ver = sys.version_info[:2]
+
+
+class BaseNewList(type):
+    def __instancecheck__(cls, instance):
+        if cls == newlist:
+            return isinstance(instance, _builtin_list)
+        else:
+            return issubclass(instance.__class__, cls)
+
+
+class newlist(with_metaclass(BaseNewList, _builtin_list)):
+    """
+    A backport of the Python 3 list object to Py2
+    """
+    def copy(self):
+        """
+        L.copy() -> list -- a shallow copy of L
+        """
+        return copy.copy(self)
+
+    def clear(self):
+        """L.clear() -> None -- remove all items from L"""
+        for i in range(len(self)):
+            self.pop()
+
+    def __new__(cls, *args, **kwargs):
+        """
+        list() -> new empty list
+        list(iterable) -> new list initialized from iterable's items
+        """
+
+        if len(args) == 0:
+            return super(newlist, cls).__new__(cls)
+        elif type(args[0]) == newlist:
+            value = args[0]
+        else:
+            value = args[0]
+        return super(newlist, cls).__new__(cls, value)
+
+    def __add__(self, value):
+        return newlist(super(newlist, self).__add__(value))
+
+    def __radd__(self, left):
+        " left + self "
+        try:
+            return newlist(left) + self
+        except:
+            return NotImplemented
+
+    def __getitem__(self, y):
+        """
+        x.__getitem__(y) <==> x[y]
+
+        Warning: a bug in Python 2.x prevents indexing via a slice from
+        returning a newlist object.
+        """
+        if isinstance(y, slice):
+            return newlist(super(newlist, self).__getitem__(y))
+        else:
+            return super(newlist, self).__getitem__(y)
+
+    def __native__(self):
+        """
+        Hook for the future.utils.native() function
+        """
+        return list(self)
+
+    def __nonzero__(self):
+        return len(self) > 0
+
+
+__all__ = ['newlist']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newmemoryview.py b/.venv/lib/python3.12/site-packages/future/types/newmemoryview.py
new file mode 100644
index 00000000..09f804dc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newmemoryview.py
@@ -0,0 +1,29 @@
+"""
+A pretty lame implementation of a memoryview object for Python 2.6.
+"""
+from numbers import Integral
+import string
+
+from future.utils import istext, isbytes, PY2, with_metaclass
+from future.types import no, issubset
+
+if PY2:
+    from collections import Iterable
+else:
+    from collections.abc import Iterable
+
+# class BaseNewBytes(type):
+#     def __instancecheck__(cls, instance):
+#         return isinstance(instance, _builtin_bytes)
+
+
+class newmemoryview(object):   # with_metaclass(BaseNewBytes, _builtin_bytes)):
+    """
+    A pretty lame backport of the Python 2.7 and Python 3.x
+    memoryviewview object to Py2.6.
+    """
+    def __init__(self, obj):
+        return obj
+
+
+__all__ = ['newmemoryview']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newobject.py b/.venv/lib/python3.12/site-packages/future/types/newobject.py
new file mode 100644
index 00000000..31b84fc1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newobject.py
@@ -0,0 +1,117 @@
+"""
+An object subclass for Python 2 that gives new-style classes written in the
+style of Python 3 (with ``__next__`` and unicode-returning ``__str__`` methods)
+the appropriate Python 2-style ``next`` and ``__unicode__`` methods for compatible.
+
+Example use::
+
+    from builtins import object
+
+    my_unicode_str = u'Unicode string: \u5b54\u5b50'
+
+    class A(object):
+        def __str__(self):
+            return my_unicode_str
+
+    a = A()
+    print(str(a))
+
+    # On Python 2, these relations hold:
+    assert unicode(a) == my_unicode_string
+    assert str(a) == my_unicode_string.encode('utf-8')
+
+
+Another example::
+
+    from builtins import object
+
+    class Upper(object):
+        def __init__(self, iterable):
+            self._iter = iter(iterable)
+        def __next__(self):                 # note the Py3 interface
+            return next(self._iter).upper()
+        def __iter__(self):
+            return self
+
+    assert list(Upper('hello')) == list('HELLO')
+
+"""
+
+
+class newobject(object):
+    """
+    A magical object class that provides Python 2 compatibility methods::
+        next
+        __unicode__
+        __nonzero__
+
+    Subclasses of this class can merely define the Python 3 methods (__next__,
+    __str__, and __bool__).
+    """
+    def next(self):
+        if hasattr(self, '__next__'):
+            return type(self).__next__(self)
+        raise TypeError('newobject is not an iterator')
+
+    def __unicode__(self):
+        # All subclasses of the builtin object should have __str__ defined.
+        # Note that old-style classes do not have __str__ defined.
+        if hasattr(self, '__str__'):
+            s = type(self).__str__(self)
+        else:
+            s = str(self)
+        if isinstance(s, unicode):
+            return s
+        else:
+            return s.decode('utf-8')
+
+    def __nonzero__(self):
+        if hasattr(self, '__bool__'):
+            return type(self).__bool__(self)
+        if hasattr(self, '__len__'):
+            return type(self).__len__(self)
+        # object has no __nonzero__ method
+        return True
+
+    # Are these ever needed?
+    # def __div__(self):
+    #     return self.__truediv__()
+
+    # def __idiv__(self, other):
+    #     return self.__itruediv__(other)
+
+    def __long__(self):
+        if not hasattr(self, '__int__'):
+            return NotImplemented
+        return self.__int__()  # not type(self).__int__(self)
+
+    # def __new__(cls, *args, **kwargs):
+    #     """
+    #     dict() -> new empty dictionary
+    #     dict(mapping) -> new dictionary initialized from a mapping object's
+    #         (key, value) pairs
+    #     dict(iterable) -> new dictionary initialized as if via:
+    #         d = {}
+    #         for k, v in iterable:
+    #             d[k] = v
+    #     dict(**kwargs) -> new dictionary initialized with the name=value pairs
+    #         in the keyword argument list.  For example:  dict(one=1, two=2)
+    #     """
+
+    #     if len(args) == 0:
+    #         return super(newdict, cls).__new__(cls)
+    #     elif type(args[0]) == newdict:
+    #         return args[0]
+    #     else:
+    #         value = args[0]
+    #     return super(newdict, cls).__new__(cls, value)
+
+    def __native__(self):
+        """
+        Hook for the future.utils.native() function
+        """
+        return object(self)
+
+    __slots__ = []
+
+__all__ = ['newobject']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newopen.py b/.venv/lib/python3.12/site-packages/future/types/newopen.py
new file mode 100644
index 00000000..b75d45af
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newopen.py
@@ -0,0 +1,32 @@
+"""
+A substitute for the Python 3 open() function.
+
+Note that io.open() is more complete but maybe slower. Even so, the
+completeness may be a better default. TODO: compare these
+"""
+
+_builtin_open = open
+
+class newopen(object):
+    """Wrapper providing key part of Python 3 open() interface.
+
+    From IPython's py3compat.py module. License: BSD.
+    """
+    def __init__(self, fname, mode="r", encoding="utf-8"):
+        self.f = _builtin_open(fname, mode)
+        self.enc = encoding
+
+    def write(self, s):
+        return self.f.write(s.encode(self.enc))
+
+    def read(self, size=-1):
+        return self.f.read(size).decode(self.enc)
+
+    def close(self):
+        return self.f.close()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, etype, value, traceback):
+        self.f.close()
diff --git a/.venv/lib/python3.12/site-packages/future/types/newrange.py b/.venv/lib/python3.12/site-packages/future/types/newrange.py
new file mode 100644
index 00000000..dc5eb802
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newrange.py
@@ -0,0 +1,170 @@
+"""
+Nearly identical to xrange.py, by Dan Crosta, from
+
+    https://github.com/dcrosta/xrange.git
+
+This is included here in the ``future`` package rather than pointed to as
+a dependency because there is no package for ``xrange`` on PyPI. It is
+also tweaked to appear like a regular Python 3 ``range`` object rather
+than a Python 2 xrange.
+
+From Dan Crosta's README:
+
+    "A pure-Python implementation of Python 2.7's xrange built-in, with
+    some features backported from the Python 3.x range built-in (which
+    replaced xrange) in that version."
+
+    Read more at
+        https://late.am/post/2012/06/18/what-the-heck-is-an-xrange
+"""
+from __future__ import absolute_import
+
+from future.utils import PY2
+
+if PY2:
+    from collections import Sequence, Iterator
+else:
+    from collections.abc import Sequence, Iterator
+from itertools import islice
+
+from future.backports.misc import count   # with step parameter on Py2.6
+# For backward compatibility with python-future versions < 0.14.4:
+_count = count
+
+
+class newrange(Sequence):
+    """
+    Pure-Python backport of Python 3's range object.  See `the CPython
+    documentation for details:
+    <http://docs.python.org/py3k/library/functions.html#range>`_
+    """
+
+    def __init__(self, *args):
+        if len(args) == 1:
+            start, stop, step = 0, args[0], 1
+        elif len(args) == 2:
+            start, stop, step = args[0], args[1], 1
+        elif len(args) == 3:
+            start, stop, step = args
+        else:
+            raise TypeError('range() requires 1-3 int arguments')
+
+        try:
+            start, stop, step = int(start), int(stop), int(step)
+        except ValueError:
+            raise TypeError('an integer is required')
+
+        if step == 0:
+            raise ValueError('range() arg 3 must not be zero')
+        elif step < 0:
+            stop = min(stop, start)
+        else:
+            stop = max(stop, start)
+
+        self._start = start
+        self._stop = stop
+        self._step = step
+        self._len = (stop - start) // step + bool((stop - start) % step)
+
+    @property
+    def start(self):
+        return self._start
+
+    @property
+    def stop(self):
+        return self._stop
+
+    @property
+    def step(self):
+        return self._step
+
+    def __repr__(self):
+        if self._step == 1:
+            return 'range(%d, %d)' % (self._start, self._stop)
+        return 'range(%d, %d, %d)' % (self._start, self._stop, self._step)
+
+    def __eq__(self, other):
+        return (isinstance(other, newrange) and
+                (self._len == 0 == other._len or
+                 (self._start, self._step, self._len) ==
+                 (other._start, other._step, other._len)))
+
+    def __len__(self):
+        return self._len
+
+    def index(self, value):
+        """Return the 0-based position of integer `value` in
+        the sequence this range represents."""
+        try:
+            diff = value - self._start
+        except TypeError:
+            raise ValueError('%r is not in range' % value)
+        quotient, remainder = divmod(diff, self._step)
+        if remainder == 0 and 0 <= quotient < self._len:
+            return abs(quotient)
+        raise ValueError('%r is not in range' % value)
+
+    def count(self, value):
+        """Return the number of occurrences of integer `value`
+        in the sequence this range represents."""
+        # a value can occur exactly zero or one times
+        return int(value in self)
+
+    def __contains__(self, value):
+        """Return ``True`` if the integer `value` occurs in
+        the sequence this range represents."""
+        try:
+            self.index(value)
+            return True
+        except ValueError:
+            return False
+
+    def __reversed__(self):
+        return iter(self[::-1])
+
+    def __getitem__(self, index):
+        """Return the element at position ``index`` in the sequence
+        this range represents, or raise :class:`IndexError` if the
+        position is out of range."""
+        if isinstance(index, slice):
+            return self.__getitem_slice(index)
+        if index < 0:
+            # negative indexes access from the end
+            index = self._len + index
+        if index < 0 or index >= self._len:
+            raise IndexError('range object index out of range')
+        return self._start + index * self._step
+
+    def __getitem_slice(self, slce):
+        """Return a range which represents the requested slce
+        of the sequence represented by this range.
+        """
+        scaled_indices = (self._step * n for n in slce.indices(self._len))
+        start_offset, stop_offset, new_step = scaled_indices
+        return newrange(self._start + start_offset,
+                        self._start + stop_offset,
+                        new_step)
+
+    def __iter__(self):
+        """Return an iterator which enumerates the elements of the
+        sequence this range represents."""
+        return range_iterator(self)
+
+
+class range_iterator(Iterator):
+    """An iterator for a :class:`range`.
+    """
+    def __init__(self, range_):
+        self._stepper = islice(count(range_.start, range_.step), len(range_))
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        return next(self._stepper)
+
+    def next(self):
+        return next(self._stepper)
+
+
+__all__ = ['newrange']
diff --git a/.venv/lib/python3.12/site-packages/future/types/newstr.py b/.venv/lib/python3.12/site-packages/future/types/newstr.py
new file mode 100644
index 00000000..8ca191f9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/future/types/newstr.py
@@ -0,0 +1,426 @@
+"""
+This module redefines ``str`` on Python 2.x to be a subclass of the Py2
+``unicode`` type that behaves like the Python 3.x ``str``.
+
+The main differences between ``newstr`` and Python 2.x's ``unicode`` type are
+the stricter type-checking and absence of a `u''` prefix in the representation.
+
+It is designed to be used together with the ``unicode_literals`` import
+as follows:
+
+    >>> from __future__ import unicode_literals
+    >>> from builtins import str, isinstance
+
+On Python 3.x and normally on Python 2.x, these expressions hold
+
+    >>> str('blah') is 'blah'
+    True
+    >>> isinstance('blah', str)
+    True
+
+However, on Python 2.x, with this import:
+
+    >>> from __future__ import unicode_literals
+
+the same expressions are False:
+
+    >>> str('blah') is 'blah'
+    False
+    >>> isinstance('blah', str)
+    False
+
+This module is designed to be imported together with ``unicode_literals`` on
+Python 2 to bring the meaning of ``str`` back into alignment with unprefixed
+string literals (i.e. ``unicode`` subclasses).
+
+Note that ``str()`` (and ``print()``) would then normally call the
+``__unicode__`` method on objects in Python 2. To define string
+representations of your objects portably across Py3 and Py2, use the
+:func:`python_2_unicode_compatible` decorator in  :mod:`future.utils`.
+
+"""
+
+from numbers import Number
+
+from future.utils import PY3, istext, with_metaclass, isnewbytes
+from future.types import no, issubset
+from future.types.newobject import newobject
+
+
+if PY3:
+    # We'll probably never use newstr on Py3 anyway...
+    unicode = str
+    from collections.abc import Iterable
+else:
+    from collections import Iterable
+
+
+class BaseNewStr(type):
+    def __instancecheck__(cls, instance):
+        if cls == newstr:
+            return isinstance(instance, unicode)
+        else:
+            return issubclass(instance.__class__, cls)
+
+
+class newstr(with_metaclass(BaseNewStr, unicode)):
+    """
+    A backport of the Python 3 str object to Py2
+    """
+    no_convert_msg = "Can't convert '{0}' object to str implicitly"
+
+    def __new__(cls, *args, **kwargs):
+        """
+        From the Py3 str docstring:
+
+          str(object='') -> str
+          str(bytes_or_buffer[, encoding[, errors]]) -> str
+
+          Create a new string object from the given object. If encoding or
+          errors is specified, then the object must expose a data buffer
+          that will be decoded using the given encoding and error handler.
+          Otherwise, returns the result of object.__str__() (if defined)
+          or repr(object).
+          encoding defaults to sys.getdefaultencoding().
+          errors defaults to 'strict'.
+
+        """
+        if len(args) == 0:
+            return super(newstr, cls).__new__(cls)
+        # Special case: If someone requests str(str(u'abc')), return the same
+        # object (same id) for consistency with Py3.3. This is not true for
+        # other objects like list or dict.
+        elif type(args[0]) == newstr and cls == newstr:
+            return args[0]
+        elif isinstance(args[0], unicode):
+            value = args[0]
+        elif isinstance(args[0], bytes):   # i.e. Py2 bytes or newbytes
+            if 'encoding' in kwargs or len(args) > 1:
+                value = args[0].decode(*args[1:], **kwargs)
+            else:
+                value = args[0].__str__()
+        else:
+            value = args[0]
+        return super(newstr, cls).__new__(cls, value)
+
+    def __repr__(self):
+        """
+        Without the u prefix
+        """
+
+        value = super(newstr, self).__repr__()
+        # assert value[0] == u'u'
+        return value[1:]
+
+    def __getitem__(self, y):
+        """
+        Warning: Python <= 2.7.6 has a bug that causes this method never to be called
+        when y is a slice object. Therefore the type of newstr()[:2] is wrong
+        (unicode instead of newstr).
+        """
+        return newstr(super(newstr, self).__getitem__(y))
+
+    def __contains__(self, key):
+        errmsg = "'in <string>' requires string as left operand, not {0}"
+        # Don't use isinstance() here because we only want to catch
+        # newstr, not Python 2 unicode:
+        if type(key) == newstr:
+            newkey = key
+        elif isinstance(key, unicode) or isinstance(key, bytes) and not isnewbytes(key):
+            newkey = newstr(key)
+        else:
+            raise TypeError(errmsg.format(type(key)))
+        return issubset(list(newkey), list(self))
+
+    @no('newbytes')
+    def __add__(self, other):
+        return newstr(super(newstr, self).__add__(other))
+
+    @no('newbytes')
+    def __radd__(self, left):
+        " left + self "
+        try:
+            return newstr(left) + self
+        except:
+            return NotImplemented
+
+    def __mul__(self, other):
+        return newstr(super(newstr, self).__mul__(other))
+
+    def __rmul__(self, other):
+        return newstr(super(newstr, self).__rmul__(other))
+
+    def join(self, iterable):
+        errmsg = 'sequence item {0}: expected unicode string, found bytes'
+        for i, item in enumerate(iterable):
+            # Here we use type() rather than isinstance() because
+            # __instancecheck__ is being overridden. E.g.
+            # isinstance(b'abc', newbytes) is True on Py2.
+            if isnewbytes(item):
+                raise TypeError(errmsg.format(i))
+        # Support use as a staticmethod: str.join('-', ['a', 'b'])
+        if type(self) == newstr:
+            return newstr(super(newstr, self).join(iterable))
+        else:
+            return newstr(super(newstr, newstr(self)).join(iterable))
+
+    @no('newbytes')
+    def find(self, sub, *args):
+        return super(newstr, self).find(sub, *args)
+
+    @no('newbytes')
+    def rfind(self, sub, *args):
+        return super(newstr, self).rfind(sub, *args)
+
+    @no('newbytes', (1, 2))
+    def replace(self, old, new, *args):
+        return newstr(super(newstr, self).replace(old, new, *args))
+
+    def decode(self, *args):
+        raise AttributeError("decode method has been disabled in newstr")
+
+    def encode(self, encoding='utf-8', errors='strict'):
+        """
+        Returns bytes
+
+        Encode S using the codec registered for encoding. Default encoding
+        is 'utf-8'. errors may be given to set a different error
+        handling scheme. Default is 'strict' meaning that encoding errors raise
+        a UnicodeEncodeError. Other possible values are 'ignore', 'replace' and
+        'xmlcharrefreplace' as well as any other name registered with
+        codecs.register_error that can handle UnicodeEncodeErrors.
+        """
+        from future.types.newbytes import newbytes
+        # Py2 unicode.encode() takes encoding and errors as optional parameter,
+        # not keyword arguments as in Python 3 str.
+
+        # For the surrogateescape error handling mechanism, the
+        # codecs.register_error() function seems to be inadequate for an
+        # implementation of it when encoding. (Decoding seems fine, however.)
+        # For example, in the case of
+        #     u'\udcc3'.encode('ascii', 'surrogateescape_handler')
+        # after registering the ``surrogateescape_handler`` function in
+        # future.utils.surrogateescape, both Python 2.x and 3.x raise an
+        # exception anyway after the function is called because the unicode
+        # string it has to return isn't encodable strictly as ASCII.
+
+        if errors == 'surrogateescape':
+            if encoding == 'utf-16':
+                # Known to fail here. See test_encoding_works_normally()
+                raise NotImplementedError('FIXME: surrogateescape handling is '
+                                          'not yet implemented properly')
+            # Encode char by char, building up list of byte-strings
+            mybytes = []
+            for c in self:
+                code = ord(c)
+                if 0xD800 <= code <= 0xDCFF:
+                    mybytes.append(newbytes([code - 0xDC00]))
+                else:
+                    mybytes.append(c.encode(encoding=encoding))
+            return newbytes(b'').join(mybytes)
+        return newbytes(super(newstr, self).encode(encoding, errors))
+
+    @no('newbytes', 1)
+    def startswith(self, prefix, *args):
+        if isinstance(prefix, Iterable):
+            for thing in prefix:
+                if isnewbytes(thing):
+                    raise TypeError(self.no_convert_msg.format(type(thing)))
+        return super(newstr, self).startswith(prefix, *args)
+
+    @no('newbytes', 1)
+    def endswith(self, prefix, *args):
+        # Note we need the decorator above as well as the isnewbytes()
+        # check because prefix can be either a bytes object or e.g. a
+        # tuple of possible prefixes. (If it's a bytes object, each item
+        # in it is an int.)
+        if isinstance(prefix, Iterable):
+            for thing in prefix:
+                if isnewbytes(thing):
+                    raise TypeError(self.no_convert_msg.format(type(thing)))
+        return super(newstr, self).endswith(prefix, *args)
+
+    @no('newbytes', 1)
+    def split(self, sep=None, maxsplit=-1):
+        # Py2 unicode.split() takes maxsplit as an optional parameter,
+        # not as a keyword argument as in Python 3 str.
+        parts = super(newstr, self).split(sep, maxsplit)
+        return [newstr(part) for part in parts]
+
+    @no('newbytes', 1)
+    def rsplit(self, sep=None, maxsplit=-1):
+        # Py2 unicode.rsplit() takes maxsplit as an optional parameter,
+        # not as a keyword argument as in Python 3 str.
+        parts = super(newstr, self).rsplit(sep, maxsplit)
+        return [newstr(part) for part in parts]
+
+    @no('newbytes', 1)
+    def partition(self, sep):
+        parts = super(newstr, self).partition(sep)
+        return tuple(newstr(part) for part in parts)
+
+    @no('newbytes', 1)
+    def rpartition(self, sep):
+        parts = super(newstr, self).rpartition(sep)
+        return tuple(newstr(part) for part in parts)
+
+    @no('newbytes', 1)
+    def index(self, sub, *args):
+        """
+        Like newstr.find() but raise ValueError when the substring is not
+        found.
+        """
+        pos = self.find(sub, *args)
+        if pos == -1:
+            raise ValueError('substring not found')
+        return pos
+
+    def splitlines(self, keepends=False):
+        """
+        S.splitlines(keepends=False) -> list of strings
+
+        Return a list of the lines in S, breaking at line boundaries.
+        Line breaks are not included in the resulting list unless keepends
+        is given and true.
+        """
+        # Py2 unicode.splitlines() takes keepends as an optional parameter,
+        # not as a keyword argument as in Python 3 str.
+        parts = super(newstr, self).splitlines(keepends)
+        return [newstr(part) for part in parts]
+
+    def __eq__(self, other):
+        if (isinstance(other, unicode) or
+            isinstance(other, bytes) and not isnewbytes(other)):
+            return super(newstr, self).__eq__(other)
+        else:
+            return NotImplemented
+
+    def __hash__(self):
+        if (isinstance(self, unicode) or
+            isinstance(self, bytes) and not isnewbytes(self)):
+            return super(newstr, self).__hash__()
+        else:
+            raise NotImplementedError()
+
+    def __ne__(self, other):
+        if (isinstance(other, unicode) or
+            isinstance(other, bytes) and not isnewbytes(other)):
+            return super(newstr, self).__ne__(other)
+        else:
+            return True
+
+    unorderable_err = 'unorderable types: str() and {0}'
+
+    def __lt__(self, other):
+        if (isinstance(other, unicode) or
+            isinstance(other, bytes) and not isnewbytes(other)):
+            return super(newstr, self).__lt__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __le__(self, other):
+        if (isinstance(other, unicode) or
+            isinstance(other, bytes) and not isnewbytes(other)):
+            return super(newstr, self).__le__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __gt__(self, other):
+        if (isinstance(other, unicode) or
+            isinstance(other, bytes) and not isnewbytes(other)):
+            return super(newstr, self).__gt__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __ge__(self, other):
+        if (isinstance(other, unicode) or
+            isinstance(other, bytes) and not isnewbytes(other)):
+            return super(newstr, self).__ge__(other)
+        raise TypeError(self.unorderable_err.format(type(other)))
+
+    def __getattribute__(self, name):
+        """
+        A trick to cause the ``hasattr`` builtin-fn to return False for
+        the 'decode' method on Py2.
+        """
+        if name in ['decode', u'decode']:
+            raise AttributeError("decode method has been disabled in newstr")
+        return super(newstr, self).__getattribute__(name)
+
+    def __native__(self):
+        """
+        A hook for the future.utils.native() function.
+        """
+        return unicode(self)
+
+    @staticmethod
+    def maketrans(x, y=None, z=None):
+        """
+        Return a translation table usable for str.translate().
+
+        If there is only one argument, it must be a dictionary mapping Unicode
+        ordinals (integers) or characters to Unicode ordinals, strings or None.
+        Character keys will be then converted to ordinals.
+        If there are two arguments, they must be strings of equal length, and
+        in the resulting dictionary, each character in x will be mapped to the
+        character at the same position in y. If there is a third argument, it
+        must be a string, whose characters will be mapped to None in the result.
+        """
+
+        if y is None:
+            assert z is None
+            if not isinstance(x, dict):
+                raise TypeError('if you give only one argument to maketrans it must be a dict')
+            result = {}
+            for (key, value) in x.items():
+                if len(key) > 1:
+                    raise ValueError('keys in translate table must be strings or integers')
+                result[ord(key)] = value
+        else:
+            if not isinstance(x, unicode) and isinstance(y, unicode):
+                raise TypeError('x and y must be unicode strings')
+            if not len(x) == len(y):
+                raise ValueError('the first two maketrans arguments must have equal length')
+            result = {}
+            for (xi, yi) in zip(x, y):
+                if len(xi) > 1:
+                    raise ValueError('keys in translate table must be strings or integers')
+                result[ord(xi)] = ord(yi)
+
+        if z is not None:
+            for char in z:
+                result[ord(char)] = None
+        return result
+
+    def translate(self, table):
+        """
+        S.translate(table) -> str
+
+        Return a copy of the string S, where all characters have been mapped
+        through the given translation table, which must be a mapping of
+        Unicode ordinals to Unicode ordinals, strings, or None.
+        Unmapped characters are left untouched. Characters mapped to None
+        are deleted.
+        """
+        l = []
+        for c in self:
+            if ord(c) in table:
+                val = table[ord(c)]
+                if val is None:
+                    continue
+                elif isinstance(val, unicode):
+                    l.append(val)
+                else:
+                    l.append(chr(val))
+            else:
+                l.append(c)
+        return ''.join(l)
+
+    def isprintable(self):
+        raise NotImplementedError('fixme')
+
+    def isidentifier(self):
+        raise NotImplementedError('fixme')
+
+    def format_map(self):
+        raise NotImplementedError('fixme')
+
+
+__all__ = ['newstr']