Repository: rsgalloway/pyseq

Issue:
Error encoding special character in a filename when performing pyseq.walk()
Hi,
I have built a QT filebrowser that uses pyseq instead of the default walk functions, in order to show only one entry for filesequences.

Everything worked just fine, until someone dropped a file with a special character in there (™).
Quick fix is to rename the file, however it might happen again in the future, so I thought I'd report it.

```
File "/python/2.7/pyseq.py", line 1189, in walk
    yield root, dirs, get_sequences(files)
  File "/python/2.7/pyseq.py", line 1047, in get_sequences
    items = sorted(source, key=lambda x: str(x))
  File "/python/2.7/pyseq.py", line 1047, in <lambda>
    items = sorted(source, key=lambda x: str(x))
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2122' in position 45: ordinal not in range(128)
```



Discussion:

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index e1ba0d5..947090d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,14 @@
 CHANGELOG
 =========
 
+v0.7.0
+======
+
+* Performance improvements
+* Deprecates support for Python 2
+* Adds %H human readable size directive
+* Removes debug logging
+
 v0.6.1
 ======
 
diff --git a/LICENSE b/LICENSE
index 177c8a5..03ce5cf 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 PySeq
 
-Copyright (c) 2010-2015, Ryan Galloway (http://rsgalloway.com)
+Copyright (c) 2010-2024, Ryan Galloway (http://rsg.io)
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..81a5b17
--- /dev/null
+++ b/README.md
@@ -0,0 +1,118 @@
+PySeq
+=====
+
+PySeq is a python module that finds groups of items that follow a naming convention containing 
+a numerical sequence index (e.g. fileA.001.png, fileA.002.png, fileA.003.png...) and serializes
+them into a compressed sequence string representing the entire sequence (e.g. fileA.1-3.png). It 
+should work regardless of where the numerical sequence index is embedded in the name. For examples,
+see basic usage below or http://rsgalloway.github.io/pyseq
+
+Installation
+------------
+
+The easiest way to install pyseq:
+
+```bash
+$ pip install pyseq
+```
+
+Basic Usage
+-----------
+
+Using the "z1" file sequence example in the "tests" directory, we start by listing the directory
+contents using `ls`:
+
+```bash
+$ ls tests/files/z1*
+tests/files/z1_001_v1.1.png  tests/files/z1_001_v1.4.png  tests/files/z1_002_v1.3.png   tests/files/z1_002_v2.11.png
+tests/files/z1_001_v1.2.png  tests/files/z1_002_v1.1.png  tests/files/z1_002_v1.4.png   tests/files/z1_002_v2.12.png
+tests/files/z1_001_v1.3.png  tests/files/z1_002_v1.2.png  tests/files/z1_002_v2.10.png  tests/files/z1_002_v2.9.png
+```
+
+Now we list the same directory contents using `lss`, which will find the sequences and display them
+in the default compressed format:
+
+```bash
+$ lss tests/files/z1*
+   4 z1_001_v1.%d.png [1-4]
+   4 z1_002_v1.%d.png [1-4]
+   4 z1_002_v2.%d.png [9-12]
+```
+
+With a custom format:
+
+```bash
+$ lss tests/files/z1* -f "%h%r%t"
+z1_001_v1.1-4.png
+z1_002_v1.1-4.png
+z1_002_v2.9-12.png
+```
+
+Recursive:
+
+```bash
+$ lss -r tests
+tests
+├── test_pyseq.py
+└── files
+    ├── 012_vb_110_v001.1-10.png
+    ├── 012_vb_110_v002.1-10.png
+    ├── a.1-14.tga
+    ├── alpha.txt
+    ├── bnc01_TinkSO_tx_0_ty_0.101-105.tif
+    ├── bnc01_TinkSO_tx_0_ty_1.101-105.tif
+    ├── bnc01_TinkSO_tx_1_ty_0.101-105.tif
+    ├── bnc01_TinkSO_tx_1_ty_1.101-105.tif
+    ├── file.1-2.tif
+    ├── file.info.03.rgb
+    ├── file01.1-4.j2k
+    ├── file01_40-43.rgb
+    ├── file02_44-47.rgb
+    ├── file1-4.03.rgb
+    ├── fileA.1-3.jpg
+    ├── fileA.1-3.png
+    ├── file_02.tif
+    ├── z1_001_v1.1-4.png
+    ├── z1_002_v1.1-4.png
+    └── z1_002_v2.9-12.png
+```
+
+
+API Examples
+------------
+
+Compression, or serialization, of lists of items:
+
+```python
+>>> s = Sequence(['file.0001.jpg', 'file.0002.jpg', 'file.0003.jpg'])
+>>> print(s)
+file.1-3.jpg
+>>> s.append('file.0006.jpg')
+>>> print(s.format("%h%p%t %R"))
+file.%04d.jpg [1-3, 6]
+```
+
+Uncompression, or deserialization, of compressed sequences strings:
+
+```python
+>>> s = uncompress('./tests/012_vb_110_v001.%04d.png 1-1001', fmt='%h%p%t %r')
+>>> len(s)
+1001
+>>> print(s.format('%04l %h%p%t %R'))
+1001 012_vb_110_v001.%04d.png [1-1001]
+```
+
+Testing
+-------
+
+To run the unit tests, simply run `pytest` in a shell:
+
+```bash
+$ pytest
+```
+
+Or if you don't have `pytest`, you can run:
+
+```bash
+$ python tests/test_pyseq.py
+```
\ No newline at end of file
diff --git a/README.rst b/README.rst
deleted file mode 100644
index b70e31f..0000000
--- a/README.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-PySeq
-=====
-
-PySeq is a python module that finds groups of items that follow a naming convention containing 
-a numerical sequence index (e.g. fileA.001.png, fileA.002.png, fileA.003.png...) and serializes
-them into a compressed sequence string representing the entire sequence (e.g. fileA.1-3.png). It 
-should work regardless of where the numerical sequence index is embedded in the name. For examples,
-see basic usage below or http://rsgalloway.github.io/pyseq
-
-Installation
-------------
-
-Installation using setuputils: ::
-
-  % pip install pyseq
-
-
-Basic Usage
------------
-
-Using the "z1" file sequence example in the "tests" directory, we start by listing the directory
-contents using ``ls``. ::
-
-    $ ls tests/files/z1*
-    tests/files/z1_001_v1.1.png tests/files/z1_002_v1.3.png
-    tests/files/z1_001_v1.2.png tests/files/z1_002_v1.4.png
-    tests/files/z1_001_v1.3.png tests/files/z1_002_v2.1.png
-    tests/files/z1_001_v1.4.png tests/files/z1_002_v2.2.png
-    tests/files/z1_002_v1.1.png tests/files/z1_002_v2.3.png
-    tests/files/z1_002_v1.2.png tests/files/z1_002_v2.4.png
-
-Now we list the same directory contents using `lss`, which will find the sequences and display them
-in the default compressed format. ::
-
-    $ lss tests/files/z1*
-       4 z1_001_v1.%d.png [1-4]
-       4 z1_002_v1.%d.png [1-4]
-       4 z1_002_v2.%d.png [1-4]
-
-... with a custom format: ::
-
-    $ lss tests/files/z1* -f "%h%r%t"
-    z1_001_v1.1-4.png
-    z1_002_v1.1-4.png
-    z1_002_v2.1-4.png
-
-... recursive: ::
-
-    $ lss -r tests
-    tests
-    ├── test_pyseq.py
-    └── files
-        ├── 012_vb_110_v001.1-10.png
-        └── 012_vb_110_v002.1-10.png
-
-
-API Examples
-------------
-
-Compression, or serialization, of lists of items ::
-
-    >>> s = Sequence(['file.0001.jpg', 'file.0002.jpg', 'file.0003.jpg'])
-    >>> print(s)
-    file.1-3.jpg
-    >>> s.append('file.0006.jpg')
-    >>> print(s.format("%h%p%t %R"))
-    file.%04d.jpg [1-3, 6]
-
-Uncompression, or deserialization, of compressed sequences strings ::
-
-    >>> s = uncompress('./tests/012_vb_110_v001.%04d.png 1-1001', fmt='%h%p%t %r')
-    >>> len(s)
-    1001
-    >>> print(s.format('%04l %h%p%t %R'))
-    1001 012_vb_110_v001.%04d.png [1-1001]
diff --git a/__init__.py b/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/lss b/lss
index 8bd92df..0c94a78 100755
--- a/lss
+++ b/lss
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # ---------------------------------------------------------------------------------------------
-# Copyright (c) 2011-2022, Ryan Galloway (ryan@rsgalloway.com)
+# Copyright (c) 2011-2024, Ryan Galloway (ryan@rsgalloway.com)
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
@@ -30,12 +30,13 @@
 # POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 
-import os
-import sys
 import glob
-import pyseq
 import logging
 import optparse
+import os
+import sys
+
+import pyseq
 
 
 def tree(source, level, seq_format):
@@ -89,9 +90,9 @@ def tree(source, level, seq_format):
             print("".join([sp, "├── ", "%s%s%s" % (blue, base, endc)]))
             sp += "│   "
 
-        l = len(seqs)
+        sequence_length = len(seqs)
         for i, seq in enumerate(seqs):
-            if i == (l - 1) and len(dirs) == 0:
+            if i == (sequence_length - 1) and len(dirs) == 0:
                 print("".join([sp, "└── ", seq.format(seq_format)]))
             else:
                 print("".join([sp, "├── ", seq.format(seq_format)]))
@@ -99,8 +100,7 @@ def tree(source, level, seq_format):
 
 
 def _recur_cb(option, opt_str, value, parser):
-    """Callback for the `recursive` argument.
-    """
+    """Callback for the `recursive` argument."""
     if value is None:
         value = -1
     else:
@@ -109,10 +109,10 @@ def _recur_cb(option, opt_str, value, parser):
 
 
 def main():
-    """Command-line interface.
-    """
+    """Command-line interface."""
 
-    usage = """
+    usage = (
+        """
 lss [path] [-f format] [-d] [-r]
 
 Formatting options:
@@ -136,18 +136,38 @@ Formatting options:
         %%t   string after the sequence number
 
     Format directives support padding, for example: "%%04l".
-    """ % pyseq.global_format
-
-    parser = optparse.OptionParser(usage=usage,
-        version="%prog " + pyseq.__version__)
-    parser.add_option("-f", "--format", dest="format",
-        default=None, help="format the output string")
-    parser.add_option("-d", "--debug", dest="debug", action="store_true",
-        default=False, help="set logging level to debug")
-    parser.add_option("-r", "--recursive", dest="recursive", action="callback",
-        callback=_recur_cb, help="Walks the entire directory structure.")
-    parser.add_option("-s", "--strict", dest="strict", action="store_true",
-        default=pyseq.strict_pad, help="strict padding (default false)")
+    """
+        % pyseq.global_format
+    )
+
+    parser = optparse.OptionParser(usage=usage, version="%prog " + pyseq.__version__)
+    parser.add_option(
+        "-f", "--format", dest="format", default=None, help="format the output string"
+    )
+    parser.add_option(
+        "-d",
+        "--debug",
+        dest="debug",
+        action="store_true",
+        default=False,
+        help="set logging level to debug",
+    )
+    parser.add_option(
+        "-r",
+        "--recursive",
+        dest="recursive",
+        action="callback",
+        callback=_recur_cb,
+        help="Walks the entire directory structure.",
+    )
+    parser.add_option(
+        "-s",
+        "--strict",
+        dest="strict",
+        action="store_true",
+        default=pyseq.strict_pad,
+        help="strict padding (default false)",
+    )
     (options, args) = parser.parse_args()
 
     if options.debug:
@@ -181,5 +201,5 @@ Formatting options:
     return 0
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/pyseq.py b/pyseq.py
index fa49faf..8e83c26 100755
--- a/pyseq.py
+++ b/pyseq.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # ---------------------------------------------------------------------------------------------
-# Copyright (c) 2011-2022, Ryan Galloway (ryan@rsgalloway.com)
+# Copyright (c) 2011-2024, Ryan Galloway (ryan@rsgalloway.com)
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
@@ -40,7 +40,7 @@ sequence, e.g. ::
 
     fileA.1-3.png
 
-It should work regardless of where the numerical sequence index is embedded 
+It should work regardless of where the numerical sequence index is embedded
 in the name.
 
 Docs and latest version available for download at
@@ -48,79 +48,78 @@ Docs and latest version available for download at
    http://github.com/rsgalloway/pyseq
 """
 
+import functools
+import logging
 import os
 import re
-import logging
 import warnings
-import functools
-from glob import glob
-from glob import iglob
-from datetime import datetime
+from collections import deque
+from glob import glob, iglob
 
-__version__ = "0.6.1"
+__version__ = "0.7.0"
 
 # default serialization format string
-global_format = '%4l %h%p%t %R'
-default_format = '%h%r%t'
+global_format = "%4l %h%p%t %R"
+default_format = "%h%r%t"
 
 # use strict padding on sequences (pad length must match)
-#    $ export PYSEQ_STRICT_PAD=1 or 
+#    $ export PYSEQ_STRICT_PAD=1 or
 #    $ export PYSEQ_NOT_STRICT=1
 # to enable/disable. disabled by default.
-strict_pad = int(os.getenv('PYSEQ_STRICT_PAD', 0)) == 1 or \
-    int(os.getenv('PYSEQ_NOT_STRICT', 1)) == 0
+strict_pad = (
+    int(os.getenv("PYSEQ_STRICT_PAD", 0)) == 1
+    or int(os.getenv("PYSEQ_NOT_STRICT", 1)) == 0
+)
 
 # regex for matching numerical characters
-digits_re = re.compile(r'\d+')
+digits_re = re.compile(r"\d+")
 
 # regex for matching format directives
-format_re = re.compile(r'%(?P<pad>\d+)?(?P<var>\w+)')
+format_re = re.compile(r"%(?P<pad>\d+)?(?P<var>\w+)")
 
 # character to join explicit frame ranges on
-range_join = os.environ.get('PYSEQ_RANGE_SEP', ', ')
+range_join = os.environ.get("PYSEQ_RANGE_SEP", ", ")
 
 __all__ = [
-    'SequenceError', 'FormatError', 'Item', 'Sequence', 'diff', 'uncompress',
-    'getSequences', 'get_sequences', 'walk'
+    "SequenceError",
+    "FormatError",
+    "Item",
+    "Sequence",
+    "diff",
+    "uncompress",
+    "get_sequences",
+    "walk",
 ]
 
 # logging handlers
-log = logging.getLogger('pyseq')
+log = logging.getLogger("pyseq")
 log.addHandler(logging.StreamHandler())
-log.setLevel(int(os.environ.get('PYSEQ_LOG_LEVEL', logging.INFO)))
-
-# python 3 strings
-try:
-    unicode = unicode
-except NameError:
-    str = str
-    unicode = str
-    bytes = bytes
-    basestring = (str, bytes)
-else:
-    str = str
-    unicode = unicode
-    bytes = str
-    basestring = basestring
+log.setLevel(int(os.environ.get("PYSEQ_LOG_LEVEL", logging.INFO)))
 
 
 def _natural_key(x):
-    """ Splits a string into characters and digits.  This helps in sorting file
-    names in a 'natural' way.
+    """Splits a string into characters and digits.
+
+    :param x: The string to be split.
+    :return: A list of characters and digits.
     """
-    return [int(c) if c.isdigit() else c.lower() for c in re.split("(\d+)", x)]
+    return [int(c) if c.isdigit() else c.lower() for c in re.split(r"(\d+)", x)]
 
 
 def _ext_key(x):
-    """ Similar to '_natural_key' except this one uses the file extension at
+    """Similar to `_natural_key` except this one uses the file extension at
     the head of split string.  This fixes issues with files that are named
     similar but with different file extensions:
+
     This example:
+
         file.001.jpg
         file.001.tiff
         file.002.jpg
         file.002.tiff
+
     Would get properly sorted into:
+
         file.001.jpg
         file.002.jpg
         file.001.tiff
@@ -130,185 +129,306 @@ def _ext_key(x):
     return [ext] + _natural_key(name)
 
 
+@functools.lru_cache(maxsize=None)
 def natural_sort(items):
+    """
+    Sorts a list of items in natural order.
+
+    :param items: The list of items to be sorted.
+    :return: The sorted list of items.
+    """
     return sorted(items, key=_natural_key)
 
 
 class SequenceError(Exception):
-    """Special exception for Sequence errors
-    """
+    """Special exception for Sequence errors."""
+
     pass
 
 
 class FormatError(Exception):
-    """Special exception for Sequence format errors
-    """
+    """Special exception for Sequence format errors."""
+
     pass
 
 
 def deprecated(func):
-    """Deprecation warning decorator
-    """
+    """Deprecation warning decorator."""
+
     def inner(*args, **kwargs):
-        warnings.warn("Call to deprecated method {}".format(func.__name__),
-                      category=DeprecationWarning, stacklevel=2)
+        warnings.warn(
+            "Call to deprecated method {}".format(func.__name__),
+            category=DeprecationWarning,
+            stacklevel=2,
+        )
         return func(*args, **kwargs)
+
     inner.__name__ = func.__name__
     inner.__doc__ = func.__doc__
     inner.__dict__.update(func.__dict__)
     return inner
 
 
-class Item(str):
-    """Sequence member file class
+def padsize(item, frame):
+    """
+    Determines the pad size for a given Item. Return value may depend on
+    whether strict padding is enabled or not.
+
+    For example: the file item.001.exr will have a pad size of 3, and the
+    file test.001001.exr will have a pad size of 6.
 
-    :param item: Path to file.
+    :param item: Item object.
+    :param frame: the frame number as a string.
+    :returns: the size of the frame pad as an int.
+    """
+
+    # strict: frame size (%d) must match between frames (default)
+    # for example: test.09.jpg, test.10.jpg, test.11.jpg
+    if strict_pad:
+        return item.pad or len(frame)
+
+    # not strict: frame size can change between frames
+    # for example: test.9.jpg, test.10.jpg, test.11.jpg
+    else:
+        return item.pad or len(frame) if frame.startswith("0") else 0
+
+
+class Item(str):
+    """
+    Represents a file in a sequence.
     """
 
     def __init__(self, item):
+        """
+        Initializes a new instance of the Item class.
+
+        :param item: Path to the file.
+        """
         super(Item, self).__init__()
-        log.debug('adding %s', item)
         self.item = item
-        self.__path = getattr(item, 'path', None)
+        self.__path = getattr(item, "path", None)
         if self.__path is None:
-            self.__path = os.path.abspath(str(item))
-        self.__dirname, self.__filename = os.path.split(self.__path)
-        self.__digits = digits_re.findall(self.name)
+            self.__path = str(item)
+        self.__filename = os.path.basename(self.__path)
+        self.__number_matches = []
         self.__parts = digits_re.split(self.name)
         self.__stat = None
 
         # modified by self.is_sibling()
         self.frame = None
         self.head = self.name
-        self.tail = ''
+        self.tail = ""
         self.pad = None
 
     def __eq__(self, other):
+        """
+        Checks if this Item is equal to another Item.
+
+        :param other: Another Item instance.
+        :return: True if the Items are equal, False otherwise.
+        """
         return self.path == other.path
 
     def __ne__(self, other):
+        """
+        Checks if this Item is not equal to another Item.
+
+        :param other: Another Item instance.
+        :return: True if the Items are not equal, False otherwise.
+        """
         return self.path != other.path
 
     def __lt__(self, other):
+        """
+        Checks if this Item is less than another Item.
+
+        :param other: Another Item instance.
+        :return: True if this Item is less than the other Item, False otherwise.
+        """
         return self.frame < other.frame
 
     def __gt__(self, other):
+        """
+        Checks if this Item is greater than another Item.
+
+        :param other: Another Item instance.
+        :return: True if this Item is greater than the other Item, False otherwise.
+        """
         return self.frame > other.frame
 
     def __ge__(self, other):
+        """
+        Checks if this Item is greater than or equal to another Item.
+
+        :param other: Another Item instance.
+        :return: True if this Item is greater than or equal to the other Item, False otherwise.
+        """
         return self.frame >= other.frame
 
     def __le__(self, other):
+        """
+        Checks if this Item is less than or equal to another Item.
+
+        :param other: Another Item instance.
+        :return: True if this Item is less than or equal to the other Item, False otherwise.
+        """
         return self.frame <= other.frame
 
+    def __hash__(self):
+        """
+        Returns the hash value of this Item.
+
+        :return: The hash value.
+        """
+        return hash(self.path)
+
     def __str__(self):
+        """
+        Returns the string representation of this Item.
+
+        :return: The string representation.
+        """
         return str(self.name)
 
     def __repr__(self):
+        """
+        Returns the official string representation of this Item.
+
+        :return: The official string representation.
+        """
         return '<pyseq.Item "%s">' % self.name
 
     def __getattr__(self, key):
+        """
+        Retrieves the value of the specified attribute.
+
+        :param key: The name of the attribute.
+        :return: The value of the attribute.
+        """
         return getattr(self.item, key)
 
     @property
     def path(self):
-        """Item absolute path, if a filesystem item.
+        """
+        Gets the absolute path of the Item, if it is a filesystem item.
+
+        :return: The absolute path.
         """
         return self.__path
 
     @property
     def name(self):
-        """Item base name attribute
+        """
+        Gets the base name of the Item.
+
+        :return: The base name.
         """
         return self.__filename
 
     @property
     def dirname(self):
-        """"Item directory name, if a filesystem item."
         """
-        return self.__dirname
+        Gets the directory name of the Item, if it is a filesystem item.
+
+        :return: The directory name.
+        """
+        return os.path.dirname(self.__path)
 
     @property
     def digits(self):
-        """Numerical components of item name.
         """
-        return self.__digits
+        Returns the numerical components of the Item as a list of strings.
+
+        :return: The numerical components.
+        """
+        return digits_re.findall(self.__filename)
+
+    @property
+    def number_matches(self):
+        """
+        Returns the numerical components of the Item as a list of regex match objects.
+
+        :return: The numerical components.
+        """
+        if not self.__number_matches:
+            self.__number_matches = list(digits_re.finditer(self.__filename))
+        return self.__number_matches
 
     @property
     def parts(self):
-        """Non-numerical components of item name
+        """
+        Returns the non-numerical components of the Item.
+
+        :return: The non-numerical components.
         """
         return self.__parts
 
     @property
     def exists(self):
-        """Returns True if this item exists on disk
+        """
+        Checks if this Item exists on disk.
+
+        :return: True if the Item exists, False otherwise.
         """
         return os.path.isfile(self.__path)
 
     @property
     def size(self):
-        """Returns the size of the Item, reported by os.stat
+        """
+        Returns the size of the Item, reported by os.stat.
+
+        :return: The size of the Item.
         """
         return self.stat.st_size
 
     @property
     def mtime(self):
-        """Returns the modification time of the Item
+        """
+        Returns the modification time of the Item.
+
+        :return: The modification time.
         """
         return self.stat.st_mtime
 
     @property
+    @functools.lru_cache(maxsize=None)
     def stat(self):
-        """ Returns the os.stat object for this file.
+        """
+        Returns the os.stat object for this file.
+
+        :return: The os.stat object.
         """
         if self.__stat is None:
             self.__stat = os.stat(self.__path)
         return self.__stat
 
-    @deprecated
-    def isSibling(self, item):
-        """Deprecated: use is_sibling instead
-        """
-        return self.is_sibling(item)
-
     def is_sibling(self, item):
-        """Determines if this and item are part of the same sequence.
+        """
+        Determines if this Item and another Item are part of the same sequence.
 
-        :param item: An :class:`.Item` instance.
+        :param item: Another Item instance.
 
-        :return: True if this and item are sequential siblings.
+        :return: True if this Item and the other Item are sequential siblings, False otherwise.
         """
         if not isinstance(item, Item):
             item = Item(item)
 
-        # strict: frame size (%d) must match between frames (default)
-        # for example: test.09.jpg, test.10.jpg, test.11.jpg
-        if strict_pad:
-            padsize = lambda x: item.pad or len(x)
-
-        # not strict: frame size can change between frames
-        # for example: test.9.jpg, test.10.jpg, test.11.jpg
-        else:
-            padsize = lambda x: item.pad or len(x) if x.startswith('0') else 0
-
         # diff these two items to determine siblinghood
         d = diff(self, item)
         is_sibling = (len(d) == 1) and (self.parts == item.parts)
 
-        # if these items are in the same sequence, set some common
-        # attributes on both items
+        # if these items are in the same sequence, set some common attributes on both items
         if is_sibling:
-            frame = d[0]['frames'][0]
+            frame = d[0]["frames"][0]
             self.frame = int(frame)
-            self.pad = padsize(frame)
-            self.head = self.name[:d[0]['start']]
-            self.tail = self.name[d[0]['end']:]
-            frame = d[0]['frames'][1]
+            self.pad = padsize(item, frame)
+            self.head = self.name[: d[0]["start"]]
+            self.tail = self.name[d[0]["end"] :]  # noqa
+            frame = d[0]["frames"][1]
             item.frame = int(frame)
             item.pad = self.pad
-            item.head = item.name[:d[0]['start']]
-            item.tail = item.name[d[0]['end']:]
+            item.head = item.name[: d[0]["start"]]
+            item.tail = item.name[d[0]["end"] :]  # noqa
 
         return is_sibling
 
@@ -343,19 +463,17 @@ class Sequence(list):
         :return: pyseq.Sequence class instance.
         """
         # otherwise Sequence consumes the list
-        items = items[::]
-        super(Sequence, self).__init__([Item(items.pop(0))])
+        items = deque(items[::])
+        super(Sequence, self).__init__([Item(items.popleft())])
         self.__missing = []
         self.__dirty = False
         self.__frames = None
 
         while items:
-            f = Item(items.pop(0))
+            f = Item(items.popleft())
             try:
                 self.append(f)
-                log.debug('+Item belongs to sequence.')
             except SequenceError:
-                log.debug('-Item does not belong to sequence.')
                 continue
             except KeyboardInterrupt:
                 log.info("Stopping.")
@@ -364,19 +482,20 @@ class Sequence(list):
     def __attrs__(self):
         """Replaces format directives with callables to get their values."""
         return {
-            'l': self.length,
-            's': self.start,
-            'e': self.end,
-            'f': self.frames,
-            'm': self.missing,
-            'M': functools.partial(self._get_framerange, self.missing(), missing=True),
-            'd': lambda *x: self.size,
-            'D': self.directory,
-            'p': self._get_padding,
-            'r': functools.partial(self._get_framerange, self.frames(), missing=False),
-            'R': functools.partial(self._get_framerange, self.frames(), missing=True),
-            'h': self.head,
-            't': self.tail
+            "l": self.length,
+            "s": self.start,
+            "e": self.end,
+            "f": self.frames,
+            "m": self.missing,
+            "M": functools.partial(self._get_framerange, self.missing(), missing=True),
+            "d": lambda *x: self.size,
+            "H": lambda *x: self.human,
+            "D": self.directory,
+            "p": self._get_padding,
+            "r": functools.partial(self._get_framerange, self.frames(), missing=False),
+            "R": functools.partial(self._get_framerange, self.frames(), missing=True),
+            "h": self.head,
+            "t": self.tail,
         }
 
     def __str__(self):
@@ -392,16 +511,15 @@ class Sequence(list):
         super(Sequence, self).__contains__(Item(item))
 
     def __setitem__(self, index, item):
-        """ Used to set a particular element in the sequence
-        """
+        """Used to set a particular element in the sequence"""
         if type(index) is slice:
             if index.step not in (1, None):
-                raise ValueError('only step=1 supported')
-            if isinstance(item, basestring):
+                raise ValueError("only step=1 supported")
+            if isinstance(item, str):
                 item = Sequence([item])
             super(Sequence, self).__setitem__(index, item)
             return
-        if type(item) is not Item:
+        if not isinstance(item, Item):
             item = Item(item)
         if self.includes(item):
             super(Sequence, self).__setitem__(index, item)
@@ -411,23 +529,22 @@ class Sequence(list):
             raise SequenceError("Item is not a member of sequence.")
 
     def __setslice__(self, start, end, item):
-        if isinstance(item, basestring):
+        if isinstance(item, str):
             item = Sequence([item])
         if isinstance(item, list) is False:
             raise TypeError("Invalid type to add to sequence")
         for i in item:
             if self.includes(i) is False:
-                raise SequenceError("Item (%s) is not a member of sequence."
-                                    % i)
+                raise SequenceError("Item (%s) is not a member of sequence." % i)
         super(Sequence, self).__setslice__(start, end, item)
         self.__frames = None
         self.__missing = None
 
     def __add__(self, item):
-        """ return a new sequence with the item appended.  Accepts an Item,
-            a string, or a list.
+        """return a new sequence with the item appended.  Accepts an Item,
+        a string, or a list.
         """
-        if isinstance(item, basestring):
+        if isinstance(item, str):
             item = Sequence([item])
         if isinstance(item, list) is False:
             raise TypeError("Invalid type to add to sequence")
@@ -436,7 +553,7 @@ class Sequence(list):
         return ns
 
     def __iadd__(self, item):
-        if isinstance(item, basestring) or type(item) is Item:
+        if isinstance(item, str) or isinstance(item, Item):
             item = [item]
         if isinstance(item, list) is False:
             raise TypeError("Invalid type to add to sequence")
@@ -472,6 +589,8 @@ class Sequence(list):
         +-----------+--------------------------------------+
         | ``%d``    | disk usage                           |
         +-----------+--------------------------------------+
+        | ``%H``    | disk usage (human readable)          |
+        +-----------+--------------------------------------+
         | ``%D``    | parent directory                     |
         +-----------+--------------------------------------+
         | ``%h``    | string preceding sequence number     |
@@ -484,31 +603,32 @@ class Sequence(list):
         :return: Formatted string.
         """
         format_char_types = {
-            's': 'i',
-            'e': 'i',
-            'l': 'i',
-            'f': 's',
-            'm': 's',
-            'M': 's',
-            'p': 's',
-            'r': 's',
-            'R': 's',
-            'd': 's',
-            'D': 's',
-            'h': 's',
-            't': 's'
+            "s": "i",
+            "e": "i",
+            "l": "i",
+            "f": "s",
+            "m": "s",
+            "M": "s",
+            "p": "s",
+            "r": "s",
+            "R": "s",
+            "d": "s",
+            "H": "s",
+            "D": "s",
+            "h": "s",
+            "t": "s",
         }
 
         atts = self.__attrs__()
         for m in format_re.finditer(fmt):
-            var = m.group('var')
-            pad = m.group('pad')
+            var = m.group("var")
+            pad = m.group("pad")
             try:
                 fmt_char = format_char_types[var]
-            except KeyError as err:
+            except KeyError:
                 raise FormatError("Bad directive: %%%s" % var)
-            _old = '%s%s' % (pad or '', var)
-            _new = '(%s)%s%s' % (var, pad or '', fmt_char)
+            _old = "%s%s" % (pad or "", var)
+            _new = "(%s)%s%s" % (var, pad or "", fmt_char)
             fmt = fmt.replace(_old, _new)
             val = atts[var]
             # only execute the callable once, just in case
@@ -520,8 +640,7 @@ class Sequence(list):
 
     @property
     def mtime(self):
-        """Returns the latest mtime of all items
-        """
+        """Returns the latest mtime of all items."""
         maxDate = list()
         for i in self:
             maxDate.append(i.mtime)
@@ -529,13 +648,23 @@ class Sequence(list):
 
     @property
     def size(self):
-        """Returns the size all items (divide by 1024*1024 for MBs)
-        """
+        """Returns the size all items in bytes."""
         tempSize = list()
         for i in self:
             tempSize.append(i.size)
         return sum(tempSize)
 
+    @property
+    def human(self):
+        """Returns the size of all items in human-readable format."""
+        total_size = self.size
+        units = ["B", "K", "M", "G", "T"]
+        unit_index = 0
+        while total_size >= 1024 and unit_index < len(units) - 1:
+            total_size /= 1024
+            unit_index += 1
+        return f"{total_size:7.1f}{units[unit_index]}"
+
     def directory(self):
         return self[0].dirname + os.sep
 
@@ -545,22 +674,20 @@ class Sequence(list):
 
     def frames(self):
         """:return: List of files in sequence."""
-        if not hasattr(self, '__frames') or not self.__frames or self.__dirty:
+        if not hasattr(self, "__frames") or not self.__frames or self.__dirty:
             self.__frames = self._get_frames()
             self.__frames.sort()
         return self.__frames
 
     def start(self):
-        """:return: First index number in sequence
-        """
+        """:return: First index number in sequence."""
         try:
             return self.frames()[0]
         except IndexError:
             return 0
 
     def end(self):
-        """:return: Last index number in sequence
-        """
+        """:return: Last index number in sequence."""
         try:
             return self.frames()[-1]
         except IndexError:
@@ -568,7 +695,7 @@ class Sequence(list):
 
     def missing(self):
         """:return: List of missing files."""
-        if not hasattr(self, '__missing') or not self.__missing:
+        if not hasattr(self, "__missing") or not self.__missing:
             self.__missing = self._get_missing()
         return self.__missing
 
@@ -582,12 +709,12 @@ class Sequence(list):
 
     def path(self):
         """:return: Absolute path to sequence."""
-        _dirname = str(os.path.dirname(os.path.abspath(self[0].path)))
+        _dirname = str(os.path.dirname(self[0].path))
         return os.path.join(_dirname, str(self))
 
     def includes(self, item):
-        """Checks if the item can be contained in this sequence that is if it
-        is a sibling of any of the items in the list
+        """Checks if the item can be contained in this sequence, i.e. if it
+        is a sibling of any of the items in the list.
 
         For example:
 
@@ -598,20 +725,25 @@ class Sequence(list):
             True
             >>> s.includes('fileB.0003.jpg')
             False
+
+        :param item: pyseq.Item class object.
+        :return: True if item is a sequence member.
         """
-        if len(self) > 0:
-            if not isinstance(item, Item):
-                item = Item(item)
-            if self[-1] != item:
-                return self[-1].is_sibling(item)
-            elif self[0] != item:
-                return self[0].is_sibling(item)
-            else:
-                # it should be the only item in the list
-                if self[0] == item:
-                    return True
 
-        return True
+        if not self:
+            return True
+
+        if not isinstance(item, Item):
+            item = Item(item)
+
+        if self[-1] != item:
+            return self[-1].is_sibling(item)
+        elif self[0] != item:
+            return self[0].is_sibling(item)
+        elif self[0] == item:
+            return True
+
+        return False
 
     def contains(self, item):
         """Checks for sequence membership. Calls Item.is_sibling() and returns
@@ -628,66 +760,67 @@ class Sequence(list):
             False
 
         :param item: pyseq.Item class object.
-
         :return: True if item is a sequence member.
         """
+
         if len(self) > 0:
             if not isinstance(item, Item):
                 item = Item(item)
-            return self.includes(item)\
-                and self.end() >= item.frame >= self.start()
+            return self.includes(item) and self.end() >= item.frame >= self.start()
 
         return False
 
-    def append(self, item):
+    def append(self, item, check_membership=True):
         """Adds another member to the sequence.
 
         :param item: pyseq.Item object.
-
+        :param check_membership: Check if `item` is a member. Can be useful if membership
+            is checked prior to appending.
         :exc:`SequenceError` raised if item is not a sequence member.
         """
-        if type(item) is not Item:
+
+        if not isinstance(item, Item):
             item = Item(item)
 
-        if self.includes(item):
+        if not check_membership:
             super(Sequence, self).append(item)
-            self.__frames = None
-            self.__missing = None
         else:
-            raise SequenceError('Item is not a member of this sequence')
+            if self.includes(item):
+                super(Sequence, self).append(item)
+            else:
+                raise SequenceError(f"Item {item} is not a member of this sequence.")
+
+    def insert(self, index, item, check_membership=True):
+        """Add another member to the sequence at the given index.
 
-    def insert(self, index, item):
-        """ Add another member to the sequence at the given index.
-            :param item: pyseq.Item object.
-            :exc: `SequenceError` raised if item is not a sequence member.
+        :param item: pyseq.Item object.
+        :param check_membership: Check if `item` is a member. Can be useful if membership
+            is checked prior to appending.
+        :exc: `SequenceError` Raised if item is not a sequence member.
         """
-        if type(item) is not Item:
+
+        if not isinstance(item, Item):
             item = Item(item)
 
-        if self.includes(item):
+        if not check_membership:
             super(Sequence, self).insert(index, item)
-            self.__frames = None
-            self.__missing = None
         else:
-            raise SequenceError("Item is not a member of this sequence.")
+            if self.includes(item):
+                super(Sequence, self).insert(index, item)
+            else:
+                raise SequenceError(f"Item {item} is not a member of this sequence.")
 
-    def extend(self, items):
-        """ Add members to the sequence.
-            :param items: list of pyseq.Item objects.
-            :exc: `SequenceError` raised if any items are not a sequence
-                  member.
+    def extend(self, items, check_membership=True):
+        """Add members to the sequence.
+
+        :param items: List of pyseq.Item objects.
+        :param check_membership: Check if `item` is a member. Can be useful if membership
+            is checked prior to appending.
+        :exc: `SequenceError` Raised if any items are not a sequence member.
         """
-        for item in items:
-            if type(item) is not Item:
-                item = Item(item)
 
-            if self.includes(item):
-                super(Sequence, self).append(item)
-                self.__frames = None
-                self.__missing = None
-            else:
-                raise SequenceError("Item (%s) is not a member of this "
-                                    "sequence." % item)
+        for item in items:
+            self.append(item, check_membership=check_membership)
 
     def reIndex(self, offset, padding=None):
         """Renames and reindexes the items in the sequence, e.g. ::
@@ -697,32 +830,34 @@ class Sequence(list):
         will add a 100 frame offset to each Item in `seq`, and rename
         the files on disk.
 
-        :param offset: the frame offset to apply to each item
-        :param padding: change the padding
+        :param offset: The frame offset to apply to each item.
+        :param padding: Change the padding.
         """
+
         if not padding:
             padding = self.format("%p")
 
         if offset > 0:
-            gen = ((image, frame) for (image, frame) in zip(reversed(self),
-                reversed(self.frames())))
+            gen = (
+                (image, frame)
+                for (image, frame) in zip(reversed(self), reversed(self.frames()))
+            )
         else:
             gen = ((image, frame) for (image, frame) in zip(self, self.frames()))
 
         for image, frame in gen:
             oldName = image.path
             newFrame = padding % (frame + offset)
-            newFileName = "%s%s%s" % (self.format("%h"), newFrame,
-                self.format("%t"))
+            newFileName = "%s%s%s" % (self.format("%h"), newFrame, self.format("%t"))
             newName = os.path.join(image.dirname, newFileName)
 
             try:
                 import shutil
+
                 shutil.move(oldName, newName)
             except Exception as err:
                 log.error(err)
             else:
-                log.debug('renaming %s %s' % (oldName, newName))
                 self.__dirty = True
                 image.frame = int(newFrame)
 
@@ -730,73 +865,73 @@ class Sequence(list):
             self.frames()
 
     def _get_padding(self):
-        """:return: padding string, e.g. %07d"""
+        """:return: Padding string (e.g. %07d)."""
         try:
             pad = min([i.pad for i in self])
             if pad is None:
-                return ''
+                return ""
             if pad < 2:
-                return '%d'
-            return '%%%02dd' % pad
+                return "%d"
+            return "%%%02dd" % pad
         except IndexError:
-            return ''
+            return ""
 
     def _get_framerange(self, frames, missing=True):
         """Returns frame range string, e.g. [1-500].
-        
-        :param frames: list of ints like [1,4,8,12,15].
-        :param missing: Expand sequence to exclude missing sequence indices.
 
-        :return: formatted frame range string.
+        :param frames: List of ints like [1,4,8,12,15].
+        :param missing: Expand sequence to exclude missing sequence indices.
+        :return: Formatted frame range string.
         """
+
         frange = []
-        start = ''
-        end = ''
+        start = ""
+        end = ""
         if not missing:
             if frames:
-                return '%s-%s' % (self.start(), self.end())
+                return "%s-%s" % (self.start(), self.end())
             else:
-                return ''
+                return ""
 
         if not frames:
-            return ''
+            return ""
 
         for i in range(0, len(frames)):
             frame = frames[i]
-            if type(frame) == range:
-                frange.append('%s-%s' % (frame[0], frame[-1]))
+            if isinstance(frame, range):
+                frange.append("%s-%s" % (frame[0], frame[-1]))
                 continue
             prev = frames[i - 1]
             if i != 0 and frame != prev + 1:
                 if start != end:
-                    frange.append('%s-%s' % (str(start), str(end)))
+                    frange.append("%s-%s" % (str(start), str(end)))
                 elif start == end:
                     frange.append(str(start))
                 start = end = frame
                 continue
-            if start == '' or int(start) > frame:
+            if start == "" or int(start) > frame:
                 start = frame
-            if end == '' or int(end) < frame:
+            if end == "" or int(end) < frame:
                 end = frame
         if start == end:
             frange.append(str(start))
         else:
-            frange.append('%s-%s' % (str(start), str(end)))
+            frange.append("%s-%s" % (str(start), str(end)))
         return "[%s]" % range_join.join(frange)
 
     def _get_frames(self):
-        """finds the sequence indexes from item names
-        """
+        """Finds the sequence indexes from item names."""
         return [f.frame for f in self if f.frame is not None]
 
     def _get_missing(self, max_size=100000):
-        """looks for missing sequence indexes in sequence
+        """Looks for missing sequence indexes in the sequence.
 
         :param max_size: maximum missing frame sequence size for
-            returning explcit frames, otherwise use ranges
+            returning explcit frames, otherwise use ranges.
         :return: List of missing frames, or ranges of frames if
-            sequence size is greater than max_size
+            sequence size is greater than max_size.
         """
+
         missing = []
         frames = self.frames()
 
@@ -807,12 +942,13 @@ class Sequence(list):
 
         r = range(frames[0], frames[-1] + 1)
         if len(r) <= max_size:
-            # this can be expensive with large lists (high memory)
-            return sorted(list(set(frames).symmetric_difference(r)))
+            frames_set = set(frames)
+            r_set = set(r)
+            symmetric_diff = frames_set.symmetric_difference(r_set)
+            return sorted(symmetric_diff)
         else:
-            log.debug("frame range is large, using ranges")
             for i, f in enumerate(frames[:-1]):
-                missing.append(range(f+1, frames[i+1]))
+                missing.append(range(f + 1, frames[i + 1]))
             return missing
 
 
@@ -828,34 +964,29 @@ def diff(f1, f2):
         [{'frames': ('3', '4'), 'start': 4, 'end': 5}]
 
     :param f1: pyseq.Item object.
-    :param f2: pyseq.Item object, for comparison.
-
-    :return: Dictionary with keys: frames, start, end.
+    :param f2: pyseq.Item object to diff.
+    :return: A dictionary with keys 'frames', 'start', and 'end'.
     """
-    log.debug('diff: %s %s' % (f1, f2))
-    if not type(f1) == Item:
+
+    if not isinstance(f1, Item):
         f1 = Item(f1)
-    if not type(f2) == Item:
+    if not isinstance(f2, Item):
         f2 = Item(f2)
 
-    l1 = [m for m in digits_re.finditer(f1.name)]
-    l2 = [m for m in digits_re.finditer(f2.name)]
-
     d = []
-    if len(l1) == len(l2):
-        for i in range(0, len(l1)):
-            m1 = l1.pop(0)
-            m2 = l2.pop(0)
+    if len(f1.number_matches) == len(f2.number_matches):
+        for m1, m2 in zip(f1.number_matches, f2.number_matches):
             if (m1.start() == m2.start()) and (m1.group() != m2.group()):
                 if strict_pad is True and (len(m1.group()) != len(m2.group())):
                     continue
-                d.append({
-                    'start': m1.start(),
-                    'end': m1.end(),
-                    'frames': (m1.group(), m2.group())
-                })
+                d.append(
+                    {
+                        "start": m1.start(),
+                        "end": m1.end(),
+                        "frames": (m1.group(), m2.group()),
+                    }
+                )
 
-    log.debug(d)
     return d
 
 
@@ -884,78 +1015,66 @@ def uncompress(seq_string, fmt=global_format):
 
     :param seq_string: Compressed sequence string.
     :param fmt: Format of sequence string.
-
     :return: :class:`.Sequence` instance.
     """
+
     dirname = os.path.dirname(seq_string)
+
     # remove directory
     if "%D" in fmt:
         fmt = fmt.replace("%D", "")
     name = os.path.basename(seq_string)
-    log.debug('uncompress: %s' % name)
 
     # map of directives to regex
     remap = {
-        's': '\d+',
-        'e': '\d+',
-        'l': '\d+',
-        'h': '(\S+)?',
-        't': '(\S+)?',
-        'r': '\d+-\d+',
-        'R': '\[[\d\s?\-%s?]+\]' % re.escape(range_join),
-        'p': '%\d+d',
-        'm': '\[.*\]',
-        'f': '\[.*\]',
+        "s": r"\d+",
+        "e": r"\d+",
+        "l": r"\d+",
+        "h": r"(\S+)?",
+        "t": r"(\S+)?",
+        "r": r"\d+-\d+",
+        "R": r"\[[\d\s?\-%s?]+\]" % re.escape(range_join),
+        "p": r"%\d+d",
+        "m": r"\[.*\]",
+        "f": r"\[.*\]",
     }
 
-    log.debug('fmt in: %s' % fmt)
-
     # escape any re chars in format
     fmt = re.escape(fmt)
 
     # replace \% with % back again
-    fmt = fmt.replace('\\%', '%')
-
-    log.debug('fmt escaped: %s' % fmt)
+    fmt = fmt.replace("\\%", "%")
 
     for m in format_re.finditer(fmt):
-        _old = '%%%s%s' % (m.group('pad') or '', m.group('var'))
-        _new = '(?P<%s>%s)' % (
-            m.group('var'),
-            remap.get(m.group('var'), '\w+')
-        )
+        _old = "%%%s%s" % (m.group("pad") or "", m.group("var"))
+        _new = "(?P<%s>%s)" % (m.group("var"), remap.get(m.group("var"), r"\w+"))
         fmt = fmt.replace(_old, _new)
 
-    log.debug('fmt: %s' % fmt)
-
     regex = re.compile(fmt)
     match = regex.match(name)
 
-    log.debug("match: %s" % match.groupdict() if match else "")
-
     frames = []
     missing = []
     s = None
     e = None
 
     if not match:
-        log.debug('No matches.')
         return
 
     try:
-        pad = match.group('p')
+        pad = match.group("p")
 
     except IndexError:
         pad = "%d"
 
     try:
-        R = match.group('R')
+        R = match.group("R")
         R = R[1:-1]
         number_groups = R.split(range_join)
         pad_len = 0
         for number_group in number_groups:
-            if '-' in number_group:
-                splits = number_group.split('-')
+            if "-" in number_group:
+                splits = number_group.split("-")
                 pad_len = max(pad_len, len(splits[0]), len(splits[1]))
                 start = int(splits[0])
                 end = int(splits[1])
@@ -970,22 +1089,22 @@ def uncompress(seq_string, fmt=global_format):
 
     except IndexError:
         try:
-            r = match.group('r')
-            s, e = r.split('-')
+            r = match.group("r")
+            s, e = r.split("-")
             frames = range(int(s), int(e) + 1)
 
         except IndexError:
-            s = match.group('s')
-            e = match.group('e')
+            s = match.group("s")
+            e = match.group("e")
 
     try:
-        frames = eval(match.group('f'))
+        frames = eval(match.group("f"))
 
     except IndexError:
         pass
 
     try:
-        missing = eval(match.group('m'))
+        missing = eval(match.group("m"))
 
     except IndexError:
         pass
@@ -996,18 +1115,20 @@ def uncompress(seq_string, fmt=global_format):
             if i in missing:
                 continue
             f = pad % i
-            name = '%s%s%s' % (
-                match.groupdict().get('h', ''), f, 
-                match.groupdict().get('t', '')
+            name = "%s%s%s" % (
+                match.groupdict().get("h", ""),
+                f,
+                match.groupdict().get("t", ""),
             )
             items.append(Item(os.path.join(dirname, name)))
 
     else:
         for i in frames:
             f = pad % i
-            name = '%s%s%s' % (
-                match.groupdict().get('h', ''), f, 
-                match.groupdict().get('t', '')
+            name = "%s%s%s" % (
+                match.groupdict().get("h", ""),
+                f,
+                match.groupdict().get("t", ""),
             )
             items.append(Item(os.path.join(dirname, name)))
 
@@ -1017,20 +1138,13 @@ def uncompress(seq_string, fmt=global_format):
     return seqs
 
 
-@deprecated
-def getSequences(source):
-    """Deprecated: use get_sequences instead
-    """
-    return get_sequences(source)
-
-
 def get_sequences(source):
     """Returns a list of Sequence objects given a directory or list that contain
     sequential members.
 
     Get sequences in a directory:
 
-        >>> seqs = get_sequences('./tests/files/')
+        >>> seqs = get_sequences('tests/files/')
         >>> for s in seqs: print(s)
         ...
         012_vb_110_v001.1-10.png
@@ -1059,55 +1173,44 @@ def get_sequences(source):
         fileA.1-2.rgb
         fileB.1.rgb
 
-    Get sequences from a list of objects, preserving object attrs:
-
-        >>> seqs = get_sequences(repo.files())
-        >>> seqs[0].date
-        datetime.datetime(2011, 3, 21, 17, 31, 24)
-
     :param source: Can be directory path, list of strings, or sortable list of objects.
-
     :return: List of pyseq.Sequence class objects.
     """
-    start = datetime.now()
 
-    # list for storing sequences to be returned later
     seqs = []
 
     if isinstance(source, list):
         items = sorted(source, key=lambda x: str(x))
 
-    elif isinstance(source, basestring):
+    elif isinstance(source, str):
         if os.path.isdir(source):
-            items = sorted(glob(os.path.join(source, '*')))
+            items = sorted(glob(os.path.join(source, "*")))
         else:
             items = sorted(glob(source))
 
     else:
-        raise TypeError('Unsupported format for source argument')
+        raise TypeError("Unsupported format for source argument")
 
-    log.debug('Found %s files' % len(items))
+    items = deque(items)
 
     # organize the items into sequences
     while items:
-        item = Item(items.pop(0))
+        item = Item(items.popleft())
         found = False
-        for seq in seqs[::-1]:
+        for seq in reversed(seqs):
             if seq.includes(item):
-                seq.append(item)
+                seq.append(item, check_membership=False)
                 found = True
                 break
         if not found:
             seq = Sequence([item])
             seqs.append(seq)
 
-    log.debug('time: %s' % (datetime.now() - start))
-
-    return list(seqs)
+    return seqs
 
 
 def iget_sequences(source):
-    """ Generator version of get_sequences.  Creates Sequences from a various
+    """Generator version of get_sequences.  Creates Sequences from a various
     source files.  A notable difference is the sort order of iget_sequences
     versus get_sequences.  iget_sequences uses an adaption of natural sorting
     that starts with the file extension.  Because of this, Sequences are
@@ -1149,12 +1252,10 @@ def iget_sequences(source):
         fileA.1-2.rgb
         fileB.1.rgb
 
-
     :param source: Can be directory path, list of strings, or sortable list of objects.
-
     :return: List of pyseq.Sequence class objects.
     """
-    start = datetime.now()
+
     if isinstance(source, list):
         items = source
     elif isinstance(source, str):
@@ -1166,47 +1267,39 @@ def iget_sequences(source):
     else:
         raise TypeError("Unsupported format for source argument")
 
-    items = sorted(items, key=_ext_key)
-    log.debug("Found %d files", len(items))
+    items = deque(sorted(items, key=_ext_key))
 
     seq = None
     while items:
-        item = Item(items.pop(0))
+        item = Item(items.popleft())
         if seq is None:
             seq = Sequence([item])
         elif seq.includes(item):
-            seq.append(item)
+            seq.append(item, check_membership=False)
         else:
             yield seq
             seq = Sequence([item])
 
     if seq is not None:
         yield seq
-    log.debug("time: %s", datetime.now() - start)
 
 
 def walk(source, level=-1, topdown=True, onerror=None, followlinks=False, hidden=False):
     """Generator that traverses a directory structure starting at
     source looking for sequences.
 
-    :param source: valid folder path to traverse
-    :param level: int, if < 0 traverse entire structure otherwise
-                  traverse to given depth
-    :param topdown: walk from the top down
-    :param onerror: callable to handle os.listdir errors
-    :param followlinks: whether to follow links
-    :param hidden: include hidden files and dirs
+    :param source: Valid folder path to traverse.
+    :param level: int, if < 0 traverse entire structure otherwise traverse to given depth.
+    :param topdown: Walk from the top down.
+    :param onerror: Callable to handle os.listdir errors.
+    :param followlinks: Whether to follow links.
+    :param hidden: Include hidden files and dirs.
     """
-    start = datetime.now()
-    assert isinstance(source, basestring) is True
-    assert os.path.exists(source) is True
-    source = os.path.abspath(source)
 
     for root, dirs, files in os.walk(source, topdown, onerror, followlinks):
-
         if not hidden:
-            files = [f for f in files if not f[0] == '.']
-            dirs[:] = [d for d in dirs if not d[0] == '.']
+            files = [f for f in files if not f[0] == "."]
+            dirs[:] = [d for d in dirs if not d[0] == "."]
 
         files = [os.path.join(root, f) for f in files]
 
@@ -1218,5 +1311,3 @@ def walk(source, level=-1, topdown=True, onerror=None, followlinks=False, hidden
                 del dirs[:]
 
         yield root, dirs, get_sequences(files)
-
-    log.debug('time: %s' % (datetime.now() - start))
diff --git a/setup.py b/setup.py
index e9b3aef..13ad51d 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (c) 2011-2022 Ryan Galloway (ryan@rsgalloway.com)
+# Copyright (c) 2011-2024 Ryan Galloway (ryan@rsgalloway.com)
 #
 # This module is part of Shotman and is released under
 # the BSD License: http://www.opensource.org/licenses/bsd-license.php
@@ -9,19 +9,20 @@ from distutils.core import setup
 from pyseq import __version__
 
 from os import path
+
 this_directory = path.abspath(path.dirname(__file__))
-with open(path.join(this_directory, 'README.rst')) as f:
+with open(path.join(this_directory, "README.md")) as f:
     long_description = f.read()
 
 setup(
-    name='pyseq',
+    name="pyseq",
     version=__version__,
-    description='Compressed Sequence String Module',
+    description="Compressed File Sequence String Module",
     long_description=long_description,
-    long_description_content_type='text/x-rst',
-    author='Ryan Galloway',
-    author_email='ryan@rsgalloway.com',
-    url='http://github.com/rsgalloway/pyseq',
-    py_modules=['pyseq'],
-    scripts = ['lss']
+    long_description_content_type="text/markdown",
+    author="Ryan Galloway",
+    author_email="ryan@rsgalloway.com",
+    url="http://github.com/rsgalloway/pyseq",
+    py_modules=["pyseq"],
+    scripts=["lss"],
 )


Test patch:
diff --git a/tests/test_pyseq.py b/tests/test_pyseq.py
index 65530f5..0eeaf12 100644
--- a/tests/test_pyseq.py
+++ b/tests/test_pyseq.py
@@ -36,23 +36,36 @@ import random
 import unittest
 import subprocess
 import sys
+
 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 from pyseq import Item, Sequence, diff, uncompress, get_sequences
 from pyseq import SequenceError
 import pyseq
-pyseq.default_format = '%h%r%t'
+
+pyseq.default_format = "%h%r%t"
 
 
 class ItemTestCase(unittest.TestCase):
-    """tests the Item class
-    """
+    """tests the Item class"""
 
     def setUp(self):
-        """set up the test
-        """
-        self.test_path =\
-         os.path.abspath(os.path.join(os.sep,'mnt', 'S', 'Some','Path','to','a','file','with','numbers','file.0010.exr'))
-         
+        """set up the test"""
+        self.test_path = os.path.abspath(
+            os.path.join(
+                os.sep,
+                "mnt",
+                "S",
+                "Some",
+                "Path",
+                "to",
+                "a",
+                "file",
+                "with",
+                "numbers",
+                "file.0010.exr",
+            )
+        )
+
     def test_initializing_with_a_string(self):
         """testing if initializing an Item with a string showing the path of a
         file is working properly
@@ -61,156 +74,104 @@ class ItemTestCase(unittest.TestCase):
         self.assertTrue(isinstance(i, Item))
 
     def test_path_attribute_is_working_properly(self):
-        """testing if the path attribute is working properly
-        """
+        """testing if the path attribute is working properly"""
         i = Item(self.test_path)
-        self.assertEqual(
-            self.test_path,
-            i.path
-        )
+        self.assertEqual(self.test_path, i.path)
 
     def test_path_attribute_is_read_only(self):
-        """testing if the path attribute is read only
-        """
+        """testing if the path attribute is read only"""
         i = Item(self.test_path)
         with self.assertRaises(AttributeError) as cm:
-            setattr(i, 'path', 'some value')
+            setattr(i, "path", "some value")
 
-        self.assertEqual(
-            str(cm.exception),
-            "can't set attribute"
-        )
+        self.assertEqual(str(cm.exception), "can't set attribute")
 
     def test_name_attribute_is_working_properly(self):
-        """testing if the name attribute is working properly
-        """
+        """testing if the name attribute is working properly"""
         i = Item(self.test_path)
-        self.assertEqual(
-            i.name,
-            'file.0010.exr'
-        )
+        self.assertEqual(i.name, "file.0010.exr")
 
     def test_name_attribute_is_read_only(self):
-        """testing if the name attribute is read only
-        """
+        """testing if the name attribute is read only"""
         i = Item(self.test_path)
         with self.assertRaises(AttributeError) as cm:
-            setattr(i, 'name', 'some value')
+            setattr(i, "name", "some value")
 
-        self.assertEqual(
-            str(cm.exception),
-            "can't set attribute"
-        )
+        self.assertEqual(str(cm.exception), "can't set attribute")
 
     def test_dirname_attribute_is_working_properly(self):
-        """testing if the dirname attribute is working properly
-        """
-        
+        """testing if the dirname attribute is working properly"""
+
         i = Item(self.test_path)
-        self.assertEqual(
-            i.dirname,
-            os.path.dirname(self.test_path)
-        )
+        self.assertEqual(i.dirname, os.path.dirname(self.test_path))
 
     def test_dirname_attribute_is_read_only(self):
-        """testing if the dirname attribute is read only
-        """
+        """testing if the dirname attribute is read only"""
         i = Item(self.test_path)
         with self.assertRaises(AttributeError) as cm:
-            setattr(i, 'dirname', 'some value')
+            setattr(i, "dirname", "some value")
 
-        self.assertEqual(
-            str(cm.exception),
-            "can't set attribute"
-        )
+        self.assertEqual(str(cm.exception), "can't set attribute")
 
     def test_digits_attribute_is_working_properly(self):
-        """testing if the digits attribute is working properly
-        """
+        """testing if the digits attribute is working properly"""
         i = Item(self.test_path)
-        self.assertEqual(
-            i.digits,
-            ['0010']
-        )
+        self.assertEqual(i.digits, ["0010"])
 
     def test_digits_attribute_is_read_only(self):
-        """testing if the digits attribute is read only
-        """
+        """testing if the digits attribute is read only"""
         i = Item(self.test_path)
         with self.assertRaises(AttributeError) as cm:
-            setattr(i, 'digits', 'some value')
+            setattr(i, "digits", "some value")
 
-        self.assertEqual(
-            str(cm.exception),
-            "can't set attribute"
-        )
+        self.assertEqual(str(cm.exception), "can't set attribute")
 
     def test_parts_attribute_is_working_properly(self):
-        """testing if the parts attribute is working properly
-        """
+        """testing if the parts attribute is working properly"""
         i = Item(self.test_path)
-        self.assertEqual(
-            i.parts,
-            ['file.', '.exr']
-        )
+        self.assertEqual(i.parts, ["file.", ".exr"])
 
     def test_parts_attribute_is_read_only(self):
-        """testing if the parts attribute is read only
-        """
+        """testing if the parts attribute is read only"""
         i = Item(self.test_path)
         with self.assertRaises(AttributeError) as cm:
-            setattr(i, 'parts', 'some value')
+            setattr(i, "parts", "some value")
 
-        self.assertEqual(
-            str(cm.exception),
-            "can't set attribute"
-        )
+        self.assertEqual(str(cm.exception), "can't set attribute")
 
     def test_is_sibling_method_is_working_properly(self):
-        """testing if the is_sibling() is working properly
-        """
-        item1 = Item('/mnt/S/Some/Path/to/a/file/with/numbers/file.0010.exr')
-        item2 = Item('/mnt/S/Some/Path/to/a/file/with/numbers/file.0101.exr')
+        """testing if the is_sibling() is working properly"""
+        item1 = Item("/mnt/S/Some/Path/to/a/file/with/numbers/file.0010.exr")
+        item2 = Item("/mnt/S/Some/Path/to/a/file/with/numbers/file.0101.exr")
 
         self.assertTrue(item1.is_sibling(item2))
         self.assertTrue(item2.is_sibling(item1))
 
 
 class SequenceTestCase(unittest.TestCase):
-    """tests the pyseq
-    """
+    """tests the pyseq"""
 
     def setUp(self):
-        """set the test up
-        """
-        self.files = ['file.0001.jpg', 'file.0002.jpg', 'file.0003.jpg']
+        """set the test up"""
+        self.files = ["file.0001.jpg", "file.0002.jpg", "file.0003.jpg"]
 
     def test_from_list(self):
         """testing if Sequence instance can be initialized with a list of file
         names
         """
         seq = Sequence(self.files)
-        self.assertEqual(
-            str(seq),
-            'file.1-3.jpg'
-        )
+        self.assertEqual(str(seq), "file.1-3.jpg")
 
     def test_appending_a_new_file(self):
         """testing if it is possible to append a new item to the list by giving
         the file name
         """
         seq = Sequence(self.files)
-        test_file = 'file.0006.jpg'
+        test_file = "file.0006.jpg"
         seq.append(test_file)
-        self.assertTrue(
-            seq.contains('file.0005.jpg')
-        )
-        self.assertTrue(
-            seq.contains(test_file)
-        )
-        self.assertFalse(
-            seq.contains('file.0015.jpg')
-        )
+        self.assertTrue(seq.contains("file.0005.jpg"))
+        self.assertTrue(seq.contains(test_file))
+        self.assertFalse(seq.contains("file.0015.jpg"))
 
     def test___setitem__(self):
         s = Sequence(["file.01.ext", "file.05.ext"])
@@ -300,7 +261,7 @@ class SequenceTestCase(unittest.TestCase):
         self.assertEqual(s[0], Item("file.001.ext"))
         self.assertEqual(s[1], Item("file.002.ext"))
 
-        self.assertRaises(SequenceError, s.__setslice__, 1, 2, 'item.001.ext')
+        self.assertRaises(SequenceError, s.__setslice__, 1, 2, "item.001.ext")
 
     def test_insert(self):
         s = Sequence(["file.001.ext"])
@@ -322,66 +283,53 @@ class SequenceTestCase(unittest.TestCase):
         self.assertRaises(SequenceError, s.extend, ["item"])
 
     def test_includes_is_working_properly(self):
-        """testing if Sequence.includes() method is working properly
-        """
+        """testing if Sequence.includes() method is working properly"""
         seq = Sequence(self.files)
-        self.assertTrue(seq.includes('file.0009.jpg'))
-        self.assertFalse(seq.includes('file.0009.pic'))
+        self.assertTrue(seq.includes("file.0009.jpg"))
+        self.assertFalse(seq.includes("file.0009.pic"))
 
     def test_contains_is_working_properly(self):
-        """test if Sequence.contains() method is working properly
-        """
+        """test if Sequence.contains() method is working properly"""
         seq = Sequence(self.files)
-        self.assertFalse(seq.contains('file.0009.jpg'))
-        self.assertFalse(seq.contains('file.0009.pic'))
+        self.assertFalse(seq.contains("file.0009.jpg"))
+        self.assertFalse(seq.contains("file.0009.pic"))
 
     def test_format_is_working_properly_1(self):
-        """test if format is working properly
-        """
+        """test if format is working properly"""
         seq = Sequence(self.files)
-        seq.append('file.0006.jpg')
-        self.assertEqual(
-            seq.format('%h%p%t %r (%R)'),
-            'file.%04d.jpg 1-6 ([1-3, 6])'
-        )
+        seq.append("file.0006.jpg")
+        self.assertEqual(seq.format("%h%p%t %r (%R)"), "file.%04d.jpg 1-6 ([1-3, 6])")
 
     def test_format_is_working_properly_2(self):
-        """test if format is working properly
-        """
+        """test if format is working properly"""
         seq = Sequence(self.files)
-        seq.append('file.0006.jpg')
+        seq.append("file.0006.jpg")
         self.assertEqual(
-            'file.0001-0006.jpg',
-            seq.format('%h%04s-%04e%t'),
+            "file.0001-0006.jpg",
+            seq.format("%h%04s-%04e%t"),
         )
         self.assertEqual(
-            'file.   1-   6.jpg',
-            seq.format('%h%4s-%4e%t'),
+            "file.   1-   6.jpg",
+            seq.format("%h%4s-%4e%t"),
         )
-        
+
     def test_format_is_working_properly_3(self):
-        """test if format is working properly
-        """
+        """test if format is working properly"""
         seq = Sequence(self.files)
-        seq.append('file.0006.jpg')
-        seq.append('file.0010.jpg')
+        seq.append("file.0006.jpg")
+        seq.append("file.0010.jpg")
         self.assertEqual(
-            seq.format('%h%p%t %r (missing %M)'),
-            'file.%04d.jpg 1-10 (missing [4-5, 7-9])'
+            seq.format("%h%p%t %r (missing %M)"),
+            "file.%04d.jpg 1-10 (missing [4-5, 7-9])",
         )
 
     def test_format_directory_attribute(self):
-        dir_name = os.path.dirname(
-            os.path.abspath(self.files[0])) + os.sep
+        dir_name = os.path.dirname(self.files[0]) + os.sep
         seq = Sequence(self.files)
-        self.assertEqual(
-            seq.format("%D"),
-            dir_name
-            )
+        self.assertEqual(seq.format("%D"), dir_name)
 
     def test__get_missing(self):
-        """test that _get_missing works
-        """
+        """test that _get_missing works"""
         # Can't initialize Sequence without an item
         # seq = Sequence([])
         # self.assertEqual(seq._get_missing(), [])
@@ -411,11 +359,60 @@ class SequenceTestCase(unittest.TestCase):
                 missing.append(i)
 
         # remove ending random frames
-        while missing[-1] > int(re.search("file\.(\d{4})\.jpg", seq[-1]).group(1)):
+        while missing[-1] > int(re.search(r"file\.(\d{4})\.jpg", seq[-1]).group(1)):
             missing.pop(-1)
         self.assertEqual(seq._get_missing(), missing)
 
 
+class PadSizeTestCase(unittest.TestCase):
+    def test_padsize_with_default_padding(self):
+        item = Item("file.001.exr")
+        frame = "001"
+        expected_padsize = 3
+
+        result = pyseq.padsize(item, frame)
+
+        self.assertEqual(result, expected_padsize)
+
+    def test_padsize_with_custom_padding(self):
+        item = Item("file.001001.exr")
+        frame = "001001"
+        expected_padsize = 6
+
+        result = pyseq.padsize(item, frame)
+
+        self.assertEqual(result, expected_padsize)
+
+    def test_padsize_with_no_padding(self):
+        item = Item("file.9.jpg")
+        frame = "9"
+        expected_padsize = 0
+
+        result = pyseq.padsize(item, frame)
+
+        self.assertEqual(result, expected_padsize)
+
+    def test_padsize_with_strict_padding_disabled(self):
+        pyseq.strict_pad = False
+        item = Item("file.9.jpg")
+        frame = "9"
+        expected_padsize = 0
+
+        result = pyseq.padsize(item, frame)
+
+        self.assertEqual(result, expected_padsize)
+
+    def test_padsize_with_strict_padding_enabled(self):
+        pyseq.strict_pad = True
+        item = Item("file.09.jpg")
+        frame = "09"
+        expected_padsize = 2
+
+        result = pyseq.padsize(item, frame)
+
+        self.assertEqual(result, expected_padsize)
+
+
 class HelperFunctionsTestCase(unittest.TestCase):
     """tests the helper functions like
     pyseq.diff()
@@ -424,108 +421,58 @@ class HelperFunctionsTestCase(unittest.TestCase):
     """
 
     def test_diff_is_working_properly_1(self):
-        """testing if diff is working properly
-        """
+        """testing if diff is working properly"""
         self.assertEqual(
-            diff('file01_0040.rgb', 'file01_0041.rgb'),
-            [{'frames': ('0040', '0041'), 'start': 7, 'end': 11}]
+            diff("file01_0040.rgb", "file01_0041.rgb"),
+            [{"frames": ("0040", "0041"), "start": 7, "end": 11}],
         )
 
     def test_diff_is_working_properly_2(self):
-        """testing if diff is working properly
-        """
+        """testing if diff is working properly"""
         self.assertEqual(
-            diff('file3.03.rgb', 'file4.03.rgb'),
-            [{'frames': ('3', '4'), 'start': 4, 'end': 5}]
+            diff("file3.03.rgb", "file4.03.rgb"),
+            [{"frames": ("3", "4"), "start": 4, "end": 5}],
         )
 
     def test_uncompress_is_working_properly_1(self):
-        """testing if uncompress is working properly
-        """
-        seq = uncompress(
-            './tests/files/012_vb_110_v001.%04d.png 1-10',
-            fmt='%h%p%t %r'
-        )
-        self.assertEqual(
-            '012_vb_110_v001.1-10.png',
-            str(seq)
-        )
+        """testing if uncompress is working properly"""
+        seq = uncompress("./tests/files/012_vb_110_v001.%04d.png 1-10", fmt="%h%p%t %r")
+        self.assertEqual("012_vb_110_v001.1-10.png", str(seq))
 
         self.assertEqual(10, len(seq))
 
     def test_uncompress_is_working_properly_2(self):
-        """testing if uncompress is working properly
-        """
-        seq2 = uncompress(
-            './tests/files/a.%03d.tga [1-3, 10, 12-14]',
-            fmt='%h%p%t %R'
-        )
-        self.assertEqual(
-            'a.1-14.tga',
-            str(seq2)
-        )
+        """testing if uncompress is working properly"""
+        seq2 = uncompress("./tests/files/a.%03d.tga [1-3, 10, 12-14]", fmt="%h%p%t %R")
+        self.assertEqual("a.1-14.tga", str(seq2))
 
-        self.assertEqual(
-            7,
-            len(seq2)
-        )
+        self.assertEqual(7, len(seq2))
 
     def test_uncompress_is_working_properly_3(self):
-        """testing if uncompress is working properly
-        """
-        seq3 = uncompress(
-            'a.%03d.tga 1-14 ([1-3, 10, 12-14])',
-            fmt='%h%p%t %r (%R)'
-        )
-        self.assertEqual(
-            'a.1-14.tga',
-            str(seq3)
-        )
+        """testing if uncompress is working properly"""
+        seq3 = uncompress("a.%03d.tga 1-14 ([1-3, 10, 12-14])", fmt="%h%p%t %r (%R)")
+        self.assertEqual("a.1-14.tga", str(seq3))
 
-        self.assertEqual(
-            7,
-            len(seq3)
-        )
+        self.assertEqual(7, len(seq3))
 
     def test_uncompress_is_working_properly_4(self):
-        """testing if uncompress is working properly
-        """
-        seq4 = uncompress(
-            'a.%03d.tga 1-14 ([1-3, 10, 12-14])',
-            fmt='%h%p%t %s-%e (%R)'
-        )
-        self.assertEqual(
-            'a.1-14.tga',
-            str(seq4)
-        )
+        """testing if uncompress is working properly"""
+        seq4 = uncompress("a.%03d.tga 1-14 ([1-3, 10, 12-14])", fmt="%h%p%t %s-%e (%R)")
+        self.assertEqual("a.1-14.tga", str(seq4))
 
     def test_uncompress_is_working_properly_5(self):
-        """testing if uncompress is working properly
-        """
-        seq5 = uncompress('a.%03d.tga 1-14 [1-14]', fmt='%h%p%t %r %R')
-        self.assertEqual(
-            'a.1-14.tga',
-            str(seq5)
-        )
+        """testing if uncompress is working properly"""
+        seq5 = uncompress("a.%03d.tga 1-14 [1-14]", fmt="%h%p%t %r %R")
+        self.assertEqual("a.1-14.tga", str(seq5))
 
-        self.assertEqual(
-            14,
-            len(seq5)
-        )
+        self.assertEqual(14, len(seq5))
 
     def test_uncompress_is_working_properly_6(self):
-        """testing if uncompress is working properly
-        """
-        seq6 = uncompress('a.%03d.tga 1-14 ([1-14])', fmt='%h%p%t %r (%R)')
-        self.assertEqual(
-            'a.1-14.tga',
-            str(seq6)
-        )
+        """testing if uncompress is working properly"""
+        seq6 = uncompress("a.%03d.tga 1-14 ([1-14])", fmt="%h%p%t %r (%R)")
+        self.assertEqual("a.1-14.tga", str(seq6))
 
-        self.assertEqual(
-            14,
-            len(seq6)
-        )
+        self.assertEqual(14, len(seq6))
 
     def test_uncompress_is_working_properly_7(self):
         """testing if uncompress is working properly,
@@ -535,151 +482,99 @@ class HelperFunctionsTestCase(unittest.TestCase):
         # enable strict pad checking
         pyseq.strict_pad = True
 
-        seq7 = uncompress(
-            'a.%03d.tga 1-100000 ([1-10, 100000])',
-            fmt='%h%p%t %r (%R)'
-        )
-        self.assertEqual(
-            'a.1-10.tga',
-            str(seq7)
-        )
+        seq7 = uncompress("a.%03d.tga 1-100000 ([1-10, 100000])", fmt="%h%p%t %r (%R)")
+        self.assertEqual("a.1-10.tga", str(seq7))
 
-        self.assertEqual(
-            10,
-            len(seq7)
-        )
+        self.assertEqual(10, len(seq7))
 
         # disable strict pad checking
         pyseq.strict_pad = False
 
-        seq7 = uncompress(
-            'a.%03d.tga 1-100000 ([1-10, 100000])',
-            fmt='%h%p%t %r (%R)'
-        )
-        self.assertEqual(
-            'a.1-100000.tga',
-            str(seq7)
-        )
+        seq7 = uncompress("a.%03d.tga 1-100000 ([1-10, 100000])", fmt="%h%p%t %r (%R)")
+        self.assertEqual("a.1-100000.tga", str(seq7))
 
-        self.assertEqual(
-            11,
-            len(seq7)
-        )
+        self.assertEqual(11, len(seq7))
 
     def test_uncompress_is_working_properly_8(self):
-        """testing if uncompress is working properly
-        """
-        seq8 = uncompress(
-            'a.%03d.tga 1-100 ([10, 20, 40, 50])',
-            fmt='%h%p%t %r (%m)'
-        )
-        self.assertEqual(
-            'a.1-100.tga',
-            str(seq8)
-        )
+        """testing if uncompress is working properly"""
+        seq8 = uncompress("a.%03d.tga 1-100 ([10, 20, 40, 50])", fmt="%h%p%t %r (%m)")
+        self.assertEqual("a.1-100.tga", str(seq8))
 
-        self.assertEqual(
-            96,
-            len(seq8)
-        )
+        self.assertEqual(96, len(seq8))
 
     def test_get_sequences_is_working_properly_1(self):
-        """testing if get_sequences is working properly
-        """
-        seqs = get_sequences('./files/')
+        """testing if get_sequences is working properly"""
+        seqs = get_sequences("./files/")
 
         expected_results = [
-            '012_vb_110_v001.1-10.png',
-            '012_vb_110_v002.1-10.png',
-            'a.1-14.tga',
-            'alpha.txt',
-            'bnc01_TinkSO_tx_0_ty_0.101-105.tif',
-            'bnc01_TinkSO_tx_0_ty_1.101-105.tif',
-            'bnc01_TinkSO_tx_1_ty_0.101-105.tif',
-            'bnc01_TinkSO_tx_1_ty_1.101-105.tif',
-            'file.1-2.tif',
-            'file.info.03.rgb',
-            'file01.1-4.j2k',
-            'file01_40-43.rgb',
-            'file02_44-47.rgb',
-            'file1-4.03.rgb',
-            'fileA.1-3.jpg',
-            'fileA.1-3.png',
-            'file_02.tif',
-            'z1_001_v1.1-4.png',
-            'z1_002_v1.1-4.png',
-            'z1_002_v2.1-4.png',
+            "012_vb_110_v001.1-10.png",
+            "012_vb_110_v002.1-10.png",
+            "a.1-14.tga",
+            "alpha.txt",
+            "bnc01_TinkSO_tx_0_ty_0.101-105.tif",
+            "bnc01_TinkSO_tx_0_ty_1.101-105.tif",
+            "bnc01_TinkSO_tx_1_ty_0.101-105.tif",
+            "bnc01_TinkSO_tx_1_ty_1.101-105.tif",
+            "file.1-2.tif",
+            "file.info.03.rgb",
+            "file01.1-4.j2k",
+            "file01_40-43.rgb",
+            "file02_44-47.rgb",
+            "file1-4.03.rgb",
+            "fileA.1-3.jpg",
+            "fileA.1-3.png",
+            "file_02.tif",
+            "z1_001_v1.1-4.png",
+            "z1_002_v1.1-4.png",
+            "z1_002_v2.1-4.png",
         ]
         for seq, expected_result in zip(seqs, expected_results):
-            self.assertEqual(
-                expected_result,
-                str(seq)
-            )
+            self.assertEqual(expected_result, str(seq))
 
     def test_get_sequences_is_working_properly_2(self):
-        """testing if get_sequences is working properly
-        """
-        seqs = get_sequences(['fileA.1.rgb', 'fileA.2.rgb', 'fileB.1.rgb'])
-        expected_results = [
-            'fileA.1-2.rgb',
-            'fileB.1.rgb'
-        ]
+        """testing if get_sequences is working properly"""
+        seqs = get_sequences(["fileA.1.rgb", "fileA.2.rgb", "fileB.1.rgb"])
+        expected_results = ["fileA.1-2.rgb", "fileB.1.rgb"]
         for seq, expected_result in zip(seqs, expected_results):
-            self.assertEqual(
-                expected_result,
-                str(seq)
-            )
+            self.assertEqual(expected_result, str(seq))
 
     def test_get_sequences_is_working_properly_3(self):
-        """testing if get_sequences is working properly
-        """
-        seqs = get_sequences('./tests/files/bnc*')
+        """testing if get_sequences is working properly"""
+        seqs = get_sequences("./tests/files/bnc*")
         expected_results = [
-            'bnc01_TinkSO_tx_0_ty_0.%04d.tif 101-105',
-            'bnc01_TinkSO_tx_0_ty_1.%04d.tif 101-105',
-            'bnc01_TinkSO_tx_1_ty_0.%04d.tif 101-105',
-            'bnc01_TinkSO_tx_1_ty_1.%04d.tif 101-105',
+            "bnc01_TinkSO_tx_0_ty_0.%04d.tif 101-105",
+            "bnc01_TinkSO_tx_0_ty_1.%04d.tif 101-105",
+            "bnc01_TinkSO_tx_1_ty_0.%04d.tif 101-105",
+            "bnc01_TinkSO_tx_1_ty_1.%04d.tif 101-105",
         ]
         for seq, expected_result in zip(seqs, expected_results):
-            self.assertEqual(
-                expected_result,
-                seq.format('%h%p%t %r')
-            )
+            self.assertEqual(expected_result, seq.format("%h%p%t %r"))
 
 
 class LSSTestCase(unittest.TestCase):
-    """Tests lss command
-    """
+    """Tests lss command"""
 
     def run_command(self, *args):
-        """a simple wrapper for subprocess.Popen
-        """
-        process = subprocess.Popen(args, stdout=subprocess.PIPE,
-            universal_newlines=True
+        """a simple wrapper for subprocess.Popen"""
+        process = subprocess.Popen(
+            args, stdout=subprocess.PIPE, universal_newlines=True
         )
         stdout, _ = process.communicate()
         return stdout
 
     def setUp(self):
-        """
-        """
+        """ """
         self.here = os.path.dirname(__file__)
-        self.lss = os.path.realpath(os.path.join(os.path.dirname(self.here), 'lss'))
+        self.lss = os.path.realpath(os.path.join(os.path.dirname(self.here), "lss"))
 
     def test_lss_is_working_properly_1(self):
-        """testing if the lss command is working properly
-        """
-        test_files = os.path.join(
-            os.path.dirname(os.path.realpath(__file__)),
-            "files"
-        )
+        """testing if the lss command is working properly"""
+        test_files = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files")
 
-        result = self.run_command(
-            self.lss,
-            test_files
-        )
+        result = self.run_command(self.lss, test_files)
 
-        self.assertEqual("""  10 012_vb_110_v001.%04d.png [1-10]
+        self.assertEqual(
+            """  10 012_vb_110_v001.%04d.png [1-10]
   10 012_vb_110_v002.%04d.png [1-10]
    7 a.%03d.tga [1-3, 10, 12-14]
    1 alpha.txt 
@@ -700,7 +595,7 @@ class LSSTestCase(unittest.TestCase):
    4 z1_002_v1.%d.png [1-4]
    4 z1_002_v2.%d.png [9-12]
 """,
-            result
+            result,
         )
 
 
@@ -715,60 +610,95 @@ class TestIssues(unittest.TestCase):
         pyseq.strict_pad = False
 
         items = [
-            'file.7.jpg', 'file.8.jpg', 'file.9.jpg',
-            'file.10.jpg', 'file.11.jpg', 'file.12.jpg',
-            'file.87.jpg', 'file.99.jpg', 'file.111.jpg'
+            "file.7.jpg",
+            "file.8.jpg",
+            "file.9.jpg",
+            "file.10.jpg",
+            "file.11.jpg",
+            "file.12.jpg",
+            "file.87.jpg",
+            "file.99.jpg",
+            "file.111.jpg",
         ]
         seq = pyseq.get_sequences(items)[0]
         self.assertEqual(len(items), len(seq))
-        self.assertEqual(seq._get_padding(), '%d')
+        self.assertEqual(seq._get_padding(), "%d")
 
         items = [
-            'file.7.jpg', 'file.8.jpg', 'file.9.jpg',
-            'file.10.jpg', 'file.11.jpg', 'file.12.jpg'
+            "file.7.jpg",
+            "file.8.jpg",
+            "file.9.jpg",
+            "file.10.jpg",
+            "file.11.jpg",
+            "file.12.jpg",
         ]
         seq = pyseq.get_sequences(items)[0]
         self.assertEqual(len(items), len(seq))
-        self.assertEqual(seq._get_padding(), '%d')
-
-        seq = pyseq.get_sequences([
-            'file.1.jpg', 'file.100.jpg', 'file.101.jpg',
-        ])[0]
+        self.assertEqual(seq._get_padding(), "%d")
+
+        seq = pyseq.get_sequences(
+            [
+                "file.1.jpg",
+                "file.100.jpg",
+                "file.101.jpg",
+            ]
+        )[0]
         self.assertEqual(len(seq), 3)
-        self.assertEqual(seq._get_padding(), '%d')
-
-        seq = pyseq.get_sequences([
-            'file.10.jpg', 'file.11.jpg', 'file.12.jpg',
-        ])[0]
+        self.assertEqual(seq._get_padding(), "%d")
+
+        seq = pyseq.get_sequences(
+            [
+                "file.10.jpg",
+                "file.11.jpg",
+                "file.12.jpg",
+            ]
+        )[0]
         self.assertEqual(len(seq), 3)
-        self.assertEqual(seq._get_padding(), '%d')
-
-        seq = pyseq.get_sequences([
-            'file.100.jpg', 'file.101.jpg', 'file.102.jpg',
-        ])[0]
+        self.assertEqual(seq._get_padding(), "%d")
+
+        seq = pyseq.get_sequences(
+            [
+                "file.100.jpg",
+                "file.101.jpg",
+                "file.102.jpg",
+            ]
+        )[0]
         self.assertEqual(len(seq), 3)
-        self.assertEqual(seq._get_padding(), '%d')
-
-        seq = pyseq.get_sequences([
-            'file.9.jpg', 'file.99.jpg', 'file.999.jpg', 'file.9999.jpg',
-        ])[0]
+        self.assertEqual(seq._get_padding(), "%d")
+
+        seq = pyseq.get_sequences(
+            [
+                "file.9.jpg",
+                "file.99.jpg",
+                "file.999.jpg",
+                "file.9999.jpg",
+            ]
+        )[0]
         self.assertEqual(len(seq), 4)
-        self.assertEqual(seq._get_padding(), '%d')
-
-        seq = pyseq.get_sequences([
-            'file.007.jpg', 'file.010.jpg', 'file.101.jpg',
-        ])[0]
+        self.assertEqual(seq._get_padding(), "%d")
+
+        seq = pyseq.get_sequences(
+            [
+                "file.007.jpg",
+                "file.010.jpg",
+                "file.101.jpg",
+            ]
+        )[0]
         self.assertEqual(len(seq), 3)
-        self.assertEqual(seq._get_padding(), '%03d')
+        self.assertEqual(seq._get_padding(), "%03d")
 
         # revert to strict
         pyseq.strict_pad = True
 
-        seq = pyseq.get_sequences([
-            'file.007.jpg', 'file.010.jpg', 'file.101.jpg',
-        ])[0]
+        seq = pyseq.get_sequences(
+            [
+                "file.007.jpg",
+                "file.010.jpg",
+                "file.101.jpg",
+            ]
+        )[0]
         self.assertEqual(len(seq), 3)
-        self.assertEqual(seq._get_padding(), '%03d')
+        self.assertEqual(seq._get_padding(), "%03d")
 
     def test_issue_67(self):
         """tests issue 67. hangs on many of missing frames."""
@@ -781,14 +711,14 @@ class TestIssues(unittest.TestCase):
 
         # normal test case (missing frame count w/i tolerance)
         files = [
-            'image-1.jpg',
-            'image-383.jpg',
-            'image-844.jpg',
-            'image-2500.jpg',
-            'image-4529.jpg',
-            'image-5000.jpg'
+            "image-1.jpg",
+            "image-383.jpg",
+            "image-844.jpg",
+            "image-2500.jpg",
+            "image-4529.jpg",
+            "image-5000.jpg",
         ]
- 
+
         seqs = get_sequences(files)
         self.assertEqual(len(seqs), 1)
         self.assertEqual(len(seqs[0]), len(files))
@@ -796,14 +726,10 @@ class TestIssues(unittest.TestCase):
         frames = seqs[0].frames()
         missing = seqs[0]._get_missing()
         self.assertEqual(len(frames), len(files))
-        self.assertEqual(len(missing), 5000-len(files))
+        self.assertEqual(len(missing), 5000 - len(files))
 
         # high missing frame count test 1
-        files = [
-            'image-1.jpg',
-            'image-1000.jpg',
-            'image-50000000.jpg'
-        ]
+        files = ["image-1.jpg", "image-1000.jpg", "image-50000000.jpg"]
 
         seqs = get_sequences(files)
         self.assertEqual(len(seqs), 1)
@@ -814,26 +740,19 @@ class TestIssues(unittest.TestCase):
         self.assertEqual(len(frames), len(files))
         r = get_range(frames)
         self.assertEqual(len(r), 50000000)
-        self.assertTrue(len(missing), 2) #<-- diff behavior: len is num. ranges
+        self.assertTrue(len(missing), 2)  # <-- diff behavior: len is num. ranges
         self.assertEqual(missing[0][0], 2)
         self.assertEqual(missing[0][-1], 999)
         self.assertEqual(missing[1][0], 1001)
         self.assertEqual(missing[1][-1], 49999999)
-        self.assertEqual(seqs[0].format(),
-            "   3 image-%d.jpg [1, 1000, 50000000]"
-        )
-        self.assertEqual(seqs[0].format("%M"),
-            "[2-999, 1001-49999999, ]"
-        )
+        self.assertEqual(seqs[0].format(), "   3 image-%d.jpg [1, 1000, 50000000]")
+        self.assertEqual(seqs[0].format("%M"), "[2-999, 1001-49999999, ]")
 
         # test with strict padding enabled
         pyseq.strict_pad = True
 
         # high missing frame count test 2
-        files = [
-            'image-100000000-2048x2048.jpg',
-            'image-500000000-2048x2048.jpg'
-        ]
+        files = ["image-100000000-2048x2048.jpg", "image-500000000-2048x2048.jpg"]
 
         seqs = get_sequences(files)
         self.assertEqual(len(seqs), 1)
@@ -847,63 +766,57 @@ class TestIssues(unittest.TestCase):
         self.assertTrue(len(missing), 5000000)
         self.assertEqual(missing[0][0], 100000001)
         self.assertEqual(missing[0][-1], 499999999)
-        self.assertEqual(seqs[0].format(),
-            "   2 image-%09d-2048x2048.jpg [100000000, 500000000]"
-        )
-        self.assertEqual(seqs[0].format("%M"),
-            "[100000001-499999999, ]"
+        self.assertEqual(
+            seqs[0].format(), "   2 image-%09d-2048x2048.jpg [100000000, 500000000]"
         )
+        self.assertEqual(seqs[0].format("%M"), "[100000001-499999999, ]")
 
         # high missing frame count test 3 (from the issue)
         files = [
-            'file-1364769281-2048x2048.jpg',
-            'file-530573048-2048x2048.jpg',
-            'file-1127718214-2048x2048.jpg',
-            'file-470543560-2048x2048.jpg',
-            'file-155374807-2048x2048.jpg',
-            'file-1182189546-2048x2048.jpg',
-            'file-157742535-2048x2048.jpg'
+            "file-1364769281-2048x2048.jpg",
+            "file-530573048-2048x2048.jpg",
+            "file-1127718214-2048x2048.jpg",
+            "file-470543560-2048x2048.jpg",
+            "file-155374807-2048x2048.jpg",
+            "file-1182189546-2048x2048.jpg",
+            "file-157742535-2048x2048.jpg",
         ]
 
         seqs = get_sequences(files)
         self.assertEqual(len(seqs), 2)
 
         # files with frame padding=10
-        self.assertEqual(seqs[0].frames(),
-            [1127718214, 1182189546, 1364769281]
-        )
+        self.assertEqual(seqs[0].frames(), [1127718214, 1182189546, 1364769281])
 
         # frame ranges are in the millions
         frames = seqs[0].frames()
         r = get_range(frames)
         self.assertEqual(len(r), 237051068)
-    
+
         frames = seqs[1].frames()
         r = get_range(frames)
         self.assertEqual(len(r), 375198242)
 
         # files with frame padding=9
-        self.assertEqual(seqs[1].frames(),
-            [155374807, 157742535, 470543560, 530573048]
-        )
+        self.assertEqual(seqs[1].frames(), [155374807, 157742535, 470543560, 530573048])
 
     def test_issue_69(self):
         """tests issue 69. more strict padding tests."""
 
         # padded frames
         padded = [
-            'file.08.jpg',
-            'file.09.jpg',
-            'file.10.jpg',
-            'file.11.jpg',
+            "file.08.jpg",
+            "file.09.jpg",
+            "file.10.jpg",
+            "file.11.jpg",
         ]
 
         # unpadded frames
         unpadded = [
-            'file.8.jpg',
-            'file.9.jpg',
-            'file.10.jpg',
-            'file.11.jpg',
+            "file.8.jpg",
+            "file.9.jpg",
+            "file.10.jpg",
+            "file.11.jpg",
         ]
 
         seqformat = "%4l %h%p%t %R"
@@ -947,5 +860,5 @@ class TestIssues(unittest.TestCase):
         self.assertEqual(len(s), 150)
 
 
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+if __name__ == "__main__":
+    unittest.main()