diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index ce17264..85f4f7a 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -26,15 +26,21 @@ jobs:
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip setuptools wheel
-        pip install flake8 pytest
+        pip install black
+        pip install flake8
+        pip install pytest
         pip install coverage
         if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+    - name: Format code with black
+      run: |
+        # stop the build if code does not conform to black formatting
+        black --check --line-length=79 .
     - name: Lint with flake8
       run: |
         # stop the build if there are Python syntax errors or undefined names
         flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
         # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
-        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=80 --statistics
     - name: Test with pytest and coverage
       run: |
         coverage run --source=pdbx -m pytest
@@ -51,4 +57,4 @@ jobs:
       uses: actions/upload-artifact@v2
       with:
         name: coverage-html
-        path: htmlcov/*
\ No newline at end of file
+        path: htmlcov/*
diff --git a/.gitignore b/.gitignore
index b6e4761..4a467df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,6 @@ dmypy.json
 
 # Pyre type checker
 .pyre/
+
+# IDE configurations
+.vscode
diff --git a/docs/conf.py b/docs/conf.py
index 4a7e601..652f3ff 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -12,15 +12,16 @@
 
 import os
 import sys
-import sphinx_rtd_theme
 
-sys.path.insert(0, os.path.abspath('../'))
+# import sphinx_rtd_theme
+
+sys.path.insert(0, os.path.abspath("../"))
 
 
 # -- Project information -----------------------------------------------------
 
-project = 'mmcif_pdbx'
-author = 'John Westbrook'
+project = "mmcif_pdbx"
+author = "John Westbrook"
 release = "1.1.1"
 
 # -- General configuration ---------------------------------------------------
@@ -29,9 +30,13 @@ release = "1.1.1"
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode',
-    'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
-    'sphinx_rtd_theme'
+    "sphinx.ext.autodoc",
+    "sphinx.ext.intersphinx",
+    "sphinx.ext.viewcode",
+    "sphinx.ext.napoleon",
+    "sphinx.ext.todo",
+    "sphinx.ext.autosummary",
+    "sphinx_rtd_theme",
 ]
 
 autosummary_generate = True
@@ -39,12 +44,12 @@ autosummary_imported_members = False
 autosummary_generate_overwrite = True
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 # This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
 
 master_doc = "index"
 
@@ -53,9 +58,9 @@ master_doc = "index"
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
diff --git a/pdbx/__init__.py b/pdbx/__init__.py
index 1ad970d..98239a7 100644
--- a/pdbx/__init__.py
+++ b/pdbx/__init__.py
@@ -9,10 +9,15 @@ See http://mmcif.wwpdb.org/docs/sw-examples/python/html/ for more information
 about this package, including examples.
 """
 
+# import pdbx.reader
+from .errors import PdbxSyntaxError, PdbxError  # noqa: F401
+from .containers import DataCategory, DataContainer  # noqa: F401
+
 
 def load(fp) -> list:
     """Parse a CIF file into a list of DataContainer objects"""
     from .reader import PdbxReader
+
     data = []
     PdbxReader(fp).read(data)
     return data
@@ -21,18 +26,21 @@ def load(fp) -> list:
 def loads(s: str) -> list:
     """Parse a CIF string into a list of DataContainer objects"""
     import io
+
     return load(io.StringIO(s))
 
 
 def dump(datacontainers: list, fp):
     """Write a list of DataContainer objects to a CIF file"""
     from .writer import PdbxWriter
+
     PdbxWriter(fp).write(datacontainers)
 
 
 def dumps(datacontainers: list) -> str:
     """Serialize a list of DataContainer objects to a CIF formatted string"""
     import io
+
     fp = io.StringIO()
     dump(datacontainers, fp)
     return fp.getvalue()
diff --git a/pdbx/containers.py b/pdbx/containers.py
index e185162..43d89fd 100644
--- a/pdbx/containers.py
+++ b/pdbx/containers.py
@@ -46,6 +46,7 @@ __email__ = "jwest@rcsb.rutgers.edu"
 
 class CifName:
     """Class of utilities for CIF-style data names."""
+
     @staticmethod
     def category_part(name):
         """Get the category part of the name."""
@@ -68,11 +69,12 @@ class CifName:
         if i == -1:
             return None
         else:
-            return name[i + 1:]
+            return name[i + 1 :]  # noqa : E203
 
 
 class ContainerBase:
     """Container base class for data and definition objects."""
+
     def __init__(self, name):
         # The enclosing scope of the data container (e.g. data_/save_)
         self.__name = name
@@ -124,20 +126,19 @@ class ContainerBase:
 
     def replace(self, obj):
         """Replace an existing object with the input object."""
-        if (obj.name is not None) and (
-                obj.name in self.__object_catalog):
+        if (obj.name is not None) and (obj.name in self.__object_catalog):
             self.__object_catalog[obj.name] = obj
 
     def print_it(self, file_=stdout, type_="brief"):
         """Dump information about container to specified file object."""
         file_.write(
-            "+ %s container: %30s contains %4d categories\n" % (
-                self.get_type(), self.name,
-                len(self.__object_name_list)))
+            "+ %s container: %30s contains %4d categories\n"
+            % (self.get_type(), self.name, len(self.__object_name_list))
+        )
         for name in self.__object_name_list:
             file_.write("--------------------------------------------\n")
             file_.write("Data category: %s\n" % name)
-            if type_ == 'brief':
+            if type_ == "brief":
                 self.__object_catalog[name].print_it(file_)
             else:
                 self.__object_catalog[name].dump_it(file_)
@@ -148,7 +149,8 @@ class ContainerBase:
             i = self.__object_name_list.index(current_name)
             self.__object_name_list[i] = new_name
             self.__object_catalog[new_name] = self.__object_catalog[
-                current_name]
+                current_name
+            ]
             self.__object_catalog[new_name].set_name(new_name)
             return True
         except KeyError:
@@ -175,24 +177,26 @@ class DefinitionContainer(ContainerBase):
 
     def __init__(self, name):
         super(DefinitionContainer, self).__init__(name)
-        self.set_type('definition')
+        self.set_type("definition")
 
     def is_category(self):
         """Determine if container contains category objects."""
-        if self.exists('category'):
+        if self.exists("category"):
             return True
         return False
 
     def is_attribute(self):
         """Determine if container contains item objects."""
-        if self.exists('item'):
+        if self.exists("item"):
             return True
         return False
 
     def print_it(self, fh=stdout, type_="brief"):
         """Print information about container to file object."""
-        fh.write("Definition container: %30s contains %4d categories\n" %
-                 (self.name, len(self.get_object_name_list())))
+        fh.write(
+            "Definition container: %30s contains %4d categories\n"
+            % (self.name, len(self.get_object_name_list()))
+        )
         if self.is_category():
             fh.write("Definition type: category\n")
         elif self.is_attribute():
@@ -203,7 +207,7 @@ class DefinitionContainer(ContainerBase):
         for name in self.get_object_name_list():
             fh.write("--------------------------------------------\n")
             fh.write("Definition category: %s\n" % name)
-            if type_ == 'brief':
+            if type_ == "brief":
                 self.get_object(name).print_it(fh)
             else:
                 self.get_object(name).dumpId(fh)
@@ -214,7 +218,7 @@ class DataContainer(ContainerBase):
 
     def __init__(self, name):
         super(DataContainer, self).__init__(name)
-        self.set_type('data')
+        self.set_type("data")
         self.__global_flag = False
         self.__current_row = None
 
@@ -292,18 +296,30 @@ class DataCategory(DataCategoryBase):
         self.__whitespace_single_quote_re = re.compile(r"('\s)|(\s')")
         self.__double_quote_re = re.compile(r'["]')
         self.__whitespace_double_quote_re = re.compile(r'("\s)|(\s")')
-        self.__integer_re = re.compile(r'^[0-9]+$')
+        self.__integer_re = re.compile(r"^[0-9]+$")
         self.__float_re = re.compile(
-            r'^-?(([0-9]+)[.]?|([0-9]*[.][0-9]+))([(][0-9]+[)])?([eE][+-]?[0-9]+)?$')
+            r"^-?(([0-9]+)[.]?|([0-9]*[.][0-9]+))"
+            r"([(][0-9]+[)])?([eE][+-]?[0-9]+)?$"
+        )
         self.__data_type_list = [
-            'DT_NULL_VALUE', 'DT_INTEGER', 'DT_FLOAT', 'DT_UNQUOTED_STRING',
-            'DT_ITEM_NAME', 'DT_DOUBLE_QUOTED_STRING',
-            'DT_SINGLE_QUOTED_STRING', 'DT_MULTI_LINE_STRING',
+            "DT_NULL_VALUE",
+            "DT_INTEGER",
+            "DT_FLOAT",
+            "DT_UNQUOTED_STRING",
+            "DT_ITEM_NAME",
+            "DT_DOUBLE_QUOTED_STRING",
+            "DT_SINGLE_QUOTED_STRING",
+            "DT_MULTI_LINE_STRING",
         ]
         self.__format_type_list = [
-            'FT_NULL_VALUE', 'FT_NUMBER', 'FT_NUMBER', 'FT_UNQUOTED_STRING',
-            'FT_QUOTED_STRING', 'FT_QUOTED_STRING', 'FT_QUOTED_STRING',
-            'FT_MULTI_LINE_STRING'
+            "FT_NULL_VALUE",
+            "FT_NUMBER",
+            "FT_NUMBER",
+            "FT_UNQUOTED_STRING",
+            "FT_QUOTED_STRING",
+            "FT_QUOTED_STRING",
+            "FT_QUOTED_STRING",
+            "FT_MULTI_LINE_STRING",
         ]
 
     def __getitem__(self, item):
@@ -355,8 +371,7 @@ class DataCategory(DataCategoryBase):
     def remove_row(self, index):
         """Remove specified row."""
         try:
-            #if ((index >= 0) and (index < len(self._row_list))):
-            if (index >= 0 and self._row_list):
+            if index >= 0 and self._row_list:
                 del self._row_list[index]
                 if self.__current_row_index >= len(self._row_list):
                     self.__current_row_index = len(self._row_list) - 1
@@ -371,11 +386,12 @@ class DataCategory(DataCategoryBase):
         """Return a full row based on the length of the the attribute list."""
         try:
             if len(self._row_list[index]) < self._num_attributes:
-                self._row_list[index] += ['?'] * (
-                    self._num_attributes - len(self._row_list[index]))
+                self._row_list[index] += ["?"] * (
+                    self._num_attributes - len(self._row_list[index])
+                )
             return self._row_list[index]
         except IndexError:
-            return ['?'] * len(self._num_attributes)
+            return ["?"] * len(self._num_attributes)
 
     @property
     def name(self):
@@ -426,7 +442,8 @@ class DataCategory(DataCategoryBase):
         attribute_name_lower = attribute_name.lower()
         if attribute_name_lower in self._catalog:
             index = self._attribute_name_list.index(
-                self._catalog[attribute_name_lower])
+                self._catalog[attribute_name_lower]
+            )
             self._attribute_name_list[index] = attribute_name
             self._catalog[attribute_name_lower] = attribute_name
         else:
@@ -439,16 +456,17 @@ class DataCategory(DataCategoryBase):
         attribute_name_lower = attribute_name.lower()
         if attribute_name_lower in self._catalog:
             index = self._attribute_name_list.index(
-                self._catalog[attribute_name_lower])
+                self._catalog[attribute_name_lower]
+            )
             self._attribute_name_list[index] = attribute_name
             self._catalog[attribute_name_lower] = attribute_name
             self.__lfh.write(
-                "Appending existing attribute %s\n" % attribute_name)
+                "Appending existing attribute %s\n" % attribute_name
+            )
         else:
             self._attribute_name_list.append(attribute_name)
             self._catalog[attribute_name_lower] = attribute_name
             # add a placeholder to any existing rows for the new attribute.
-            #if len(self._row_list) > 0:
             for row in self._row_list:
                 row.append("?")
         self._num_attributes = len(self._attribute_name_list)
@@ -465,7 +483,8 @@ class DataCategory(DataCategoryBase):
             index = row_index
         if isinstance(attribute, str) and isinstance(index, int):
             return self._row_list[index][
-                self._attribute_name_list.index(attribute)]
+                self._attribute_name_list.index(attribute)
+            ]
         raise IndexError(attribute)
 
     def set_value(self, value, attribute_name=None, row_index=None):
@@ -488,19 +507,22 @@ class DataCategory(DataCategoryBase):
                 # extend the list if needed
                 if ind >= row_len:
                     self._row_list[index].extend(
-                        [None for _ in range(2 * ind - row_len)])
+                        [None for _ in range(2 * ind - row_len)]
+                    )
                 self._row_list[index][ind] = value
             except IndexError:
                 self.__lfh.write(
                     "DataCategory(setvalue) index error category"
-                    " %s attribute %s index %d value %r\n" %
-                    (self._name, attribute, index, value))
+                    " %s attribute %s index %d value %r\n"
+                    % (self._name, attribute, index, value)
+                )
                 traceback.print_exc(file=self.__lfh)
             except ValueError:
                 self.__lfh.write(
                     "DataCategory(setvalue) value error category"
-                    " %s attribute %s index %d value %r\n" %
-                    (self._name, attribute, index, value))
+                    " %s attribute %s index %d value %r\n"
+                    % (self._name, attribute, index, value)
+                )
                 traceback.print_exc(file=self.__lfh)
 
     @property
@@ -583,8 +605,9 @@ class DataCategory(DataCategoryBase):
         """Print container information."""
         file_.write("--------------------------------------------\n")
         file_.write(
-            " Category: %s attribute list length: %d\n" %
-            (self._name, len(self._attribute_name_list)))
+            " Category: %s attribute list length: %d\n"
+            % (self._name, len(self._attribute_name_list))
+        )
         for attr in self._attribute_name_list:
             file_.write(" Category: %s attribute: %s\n" % (self._name, attr))
 
@@ -592,135 +615,154 @@ class DataCategory(DataCategoryBase):
         for row in self._row_list[:2]:
             if len(row) == len(self._attribute_name_list):
                 for index, value in enumerate(row):
-                    file_.write(" %30s: %s ...\n" % (
-                        self._attribute_name_list[index], str(value)[:30]))
+                    file_.write(
+                        " %30s: %s ...\n"
+                        % (self._attribute_name_list[index], str(value)[:30])
+                    )
             else:
                 file_.write(
                     "+WARNING - %s data length %d attribute name length %s "
-                    "mismatched\n" % (
-                        self._name, len(row),
-                        len(self._attribute_name_list)))
+                    "mismatched\n"
+                    % (self._name, len(row), len(self._attribute_name_list))
+                )
 
     def dump_it(self, file_=stdout):
         """Dump contents of container."""
         file_.write("--------------------------------------------\n")
         file_.write(
-            " Category: %s attribute list length: %d\n" %
-            (self._name, len(self._attribute_name_list)))
+            " Category: %s attribute list length: %d\n"
+            % (self._name, len(self._attribute_name_list))
+        )
         for attr in self._attribute_name_list:
             file_.write(" Category: %s attribute: %s\n" % (self._name, attr))
 
         file_.write(" Value list length: %d\n" % len(self._row_list))
         for row in self._row_list:
             for index, value in enumerate(row):
-                file_.write(" %30s: %s\n" % (
-                    self._attribute_name_list[index], value))
+                file_.write(
+                    " %30s: %s\n" % (self._attribute_name_list[index], value)
+                )
 
     def __format_pdbx(self, inp):
         """Format input data following PDBx quoting rules."""
         try:
             if inp is None:
-                return ("?", 'DT_NULL_VALUE')
+                return ("?", "DT_NULL_VALUE")
             # pure numerical values are returned as unquoted strings
-            if (isinstance(inp, int) or self.__integer_re.search(str(inp))):
-                return ([str(inp)], 'DT_INTEGER')
-            if (isinstance(inp, float) or self.__float_re.search(str(inp))):
-                return ([str(inp)], 'DT_FLOAT')
+            if isinstance(inp, int) or self.__integer_re.search(str(inp)):
+                return ([str(inp)], "DT_INTEGER")
+            if isinstance(inp, float) or self.__float_re.search(str(inp)):
+                return ([str(inp)], "DT_FLOAT")
             # null value handling
             if inp in (".", "?"):
-                return (self.__double_quoted_list(inp),
-                        'DT_DOUBLE_QUOTED_STRING')
+                return (
+                    self.__double_quoted_list(inp),
+                    "DT_DOUBLE_QUOTED_STRING",
+                )
             if inp == "":
-                return (["."], 'DT_NULL_VALUE')
+                return (["."], "DT_NULL_VALUE")
             # Contains white space or quotes ?
             if not self.__whitespace_quotes_re.search(inp):
                 if inp.startswith("_"):
-                    return (self.__double_quoted_list(inp), 'DT_ITEM_NAME')
+                    return (self.__double_quoted_list(inp), "DT_ITEM_NAME")
                 else:
-                    return ([str(inp)], 'DT_UNQUOTED_STRING')
+                    return ([str(inp)], "DT_UNQUOTED_STRING")
             else:
                 if self.__newline_re.search(inp):
                     return (
                         self.__semicolon_quoted_list(inp),
-                        'DT_MULTI_LINE_STRING')
+                        "DT_MULTI_LINE_STRING",
+                    )
                 else:
                     if self.__avoid_embedded_quoting:
-                        # change priority to choose double quoting where possible.
-                        if (
-                                not self.__double_quote_re.search(inp) and not
-                                self.__whitespace_single_quote_re.search(inp)):
+                        # change priority to choose double quoting where
+                        # possible.
+                        if not self.__double_quote_re.search(
+                            inp
+                        ) and not self.__whitespace_single_quote_re.search(
+                            inp
+                        ):
                             return (
                                 self.__double_quoted_list(inp),
-                                'DT_DOUBLE_QUOTED_STRING')
-                        elif (
-                                not self.__single_quote_re.search(inp) and not
-                                self.__whitespace_double_quote_re.search(inp)):
+                                "DT_DOUBLE_QUOTED_STRING",
+                            )
+                        elif not self.__single_quote_re.search(
+                            inp
+                        ) and not self.__whitespace_double_quote_re.search(
+                            inp
+                        ):
                             return (
                                 self.__single_quoted_list(inp),
-                                'DT_SINGLE_QUOTED_STRING')
+                                "DT_SINGLE_QUOTED_STRING",
+                            )
                         else:
                             return (
                                 self.__semicolon_quoted_list(inp),
-                                'DT_MULTI_LINE_STRING')
+                                "DT_MULTI_LINE_STRING",
+                            )
                     else:
-                        # change priority to choose double quoting where possible.
+                        # change priority to choose double quoting where
+                        # possible.
                         if not self.__double_quote_re.search(inp):
                             return (
                                 self.__double_quoted_list(inp),
-                                'DT_DOUBLE_QUOTED_STRING')
+                                "DT_DOUBLE_QUOTED_STRING",
+                            )
                         elif not self.__single_quote_re.search(inp):
                             return (
                                 self.__single_quoted_list(inp),
-                                'DT_SINGLE_QUOTED_STRING')
+                                "DT_SINGLE_QUOTED_STRING",
+                            )
                         else:
                             return (
                                 self.__semicolon_quoted_list(inp),
-                                'DT_MULTI_LINE_STRING')
+                                "DT_MULTI_LINE_STRING",
+                            )
         except ValueError:
             traceback.print_exc(file=self.__lfh)
 
     def __data_type_pdbx(self, inp):
         """Detect the PDBx data type."""
         if inp is None:
-            return 'DT_NULL_VALUE'
+            return "DT_NULL_VALUE"
         # pure numerical values are returned as unquoted strings
         if isinstance(inp, int) or self.__integer_re.search(str(inp)):
-            return 'DT_INTEGER'
+            return "DT_INTEGER"
         if isinstance(inp, float) or self.__float_re.search(str(inp)):
-            return 'DT_FLOAT'
+            return "DT_FLOAT"
         # null value handling
         if inp in (".", "?"):
-            return 'DT_DOUBLE_QUOTED_STRING'
+            return "DT_DOUBLE_QUOTED_STRING"
         if inp == "":
-            return 'DT_NULL_VALUE'
+            return "DT_NULL_VALUE"
         # Contains white space or quotes ?
         if not self.__whitespace_quotes_re.search(inp):
             if inp.startswith("_"):
-                return 'DT_ITEM_NAME'
+                return "DT_ITEM_NAME"
             else:
-                return 'DT_UNQUOTED_STRING'
+                return "DT_UNQUOTED_STRING"
         else:
             if self.__newline_re.search(inp):
-                return 'DT_MULTI_LINE_STRING'
+                return "DT_MULTI_LINE_STRING"
             else:
                 if self.__avoid_embedded_quoting:
-                    if (
-                            not self.__single_quote_re.search(inp) and not
-                            self.__whitespace_double_quote_re.search(inp)):
-                        return 'DT_DOUBLE_QUOTED_STRING'
-                    elif (
-                            not self.__double_quote_re.search(inp) and not
-                            self.__whitespace_single_quote_re.search(inp)):
-                        return 'DT_SINGLE_QUOTED_STRING'
+                    if not self.__single_quote_re.search(
+                        inp
+                    ) and not self.__whitespace_double_quote_re.search(inp):
+                        return "DT_DOUBLE_QUOTED_STRING"
+                    elif not self.__double_quote_re.search(
+                        inp
+                    ) and not self.__whitespace_single_quote_re.search(inp):
+                        return "DT_SINGLE_QUOTED_STRING"
                     else:
-                        return 'DT_MULTI_LINE_STRING'
+                        return "DT_MULTI_LINE_STRING"
                 else:
                     if not self.__single_quote_re.search(inp):
-                        return 'DT_DOUBLE_QUOTED_STRING'
+                        return "DT_DOUBLE_QUOTED_STRING"
                     elif not self.__double_quote_re.search(inp):
-                        return 'DT_SINGLE_QUOTED_STRING'
+                        return "DT_SINGLE_QUOTED_STRING"
                     else:
-                        return 'DT_MULTI_LINE_STRING'
+                        return "DT_MULTI_LINE_STRING"
 
     @staticmethod
     def __single_quoted_list(inp):
@@ -735,7 +777,7 @@ class DataCategory(DataCategoryBase):
     @staticmethod
     def __semicolon_quoted_list(inp):
         """Generate a semicolon-delimited quoted list from the input."""
-        if inp[-1] == '\n':
+        if inp[-1] == "\n":
             return ["\n", ";"] + [inp] + [";", "\n"]
         else:
             return ["\n", ";"] + [inp] + ["\n", ";", "\n"]
@@ -754,19 +796,23 @@ class DataCategory(DataCategoryBase):
             try:
                 list_, _ = self.__format_pdbx(
                     self._row_list[index][
-                        self._attribute_name_list.index(attribute)])
+                        self._attribute_name_list.index(attribute)
+                    ]
+                )
                 return "".join(list_)
             except IndexError:
                 self.__lfh.write(
-                    "attribute_name %s index %r rowdata %r\n" %
-                    (attribute_name, index, self._row_list[index]))
+                    "attribute_name %s index %r rowdata %r\n"
+                    % (attribute_name, index, self._row_list[index])
+                )
                 raise IndexError
         raise TypeError(attribute)
 
     def get_value_formatted_by_index(self, attribute_index, row_index):
         """Get value formatted by index."""
         list_, _ = self.__format_pdbx(
-            self._row_list[row_index][attribute_index])
+            self._row_list[row_index][attribute_index]
+        )
         return "".join(list_)
 
     def get_max_attribute_list_length(self, steps=1):
@@ -780,18 +826,19 @@ class DataCategory(DataCategoryBase):
     def get_format_type_list(self, steps=1):
         """Get a formatted type list."""
         try:
-            current_data_type_list = ['DT_NULL_VALUE'] * len(
-                self._attribute_name_list)
+            current_data_type_list = ["DT_NULL_VALUE"] * len(
+                self._attribute_name_list
+            )
             for row in self._row_list[::steps]:
                 for index, value in enumerate(self._attribute_name_list):
                     data_type = self.__data_type_pdbx(value)
                     data_index = self.__data_type_list.index(data_type)
                     current_type = current_data_type_list[index]
-                    current_index = self.__data_type_list.index(
-                        current_type)
+                    current_index = self.__data_type_list.index(current_type)
                     current_index = max(current_index, data_index)
                     current_data_type_list[index] = self.__data_type_list[
-                        current_index]
+                        current_index
+                    ]
             # Map the format types to the data types
             current_format_type_list = []
             for data_type in current_data_type_list:
@@ -800,15 +847,16 @@ class DataCategory(DataCategoryBase):
         except IndexError:
             self.__lfh.write(
                 "PdbxDataCategory(get_format_type_list) ++Index error at "
-                "index %d in row %r\n" %
-                (index, row))
+                "index %d in row %r\n" % (index, row)
+            )
         return current_format_type_list, current_data_type_list
 
     @property
     def get_format_type_list_x(self):
         """Alternate version of format type list."""
-        current_data_type_list = ['DT_NULL_VALUE'] * len(
-            self._attribute_name_list)
+        current_data_type_list = ["DT_NULL_VALUE"] * len(
+            self._attribute_name_list
+        )
         for _ in self._row_list:
             for index, value in enumerate(self._attribute_name_list):
                 data_type = self.__data_type_pdbx(value)
@@ -817,7 +865,8 @@ class DataCategory(DataCategoryBase):
                 current_index = self.__data_type_list.index(current_type)
                 current_index = max(current_index, data_index)
                 current_data_type_list[index] = self.__data_type_list[
-                    current_index]
+                    current_index
+                ]
         # Map the format types to the data types
         current_format_type_list = []
         for data_type in current_data_type_list:
diff --git a/pdbx/reader.py b/pdbx/reader.py
index 95394f5..f510274 100644
--- a/pdbx/reader.py
+++ b/pdbx/reader.py
@@ -17,12 +17,13 @@ The tokenizer used in this module is modeled after the clever parser design
 used in the PyMMLIB package.
 
 PyMMLib Development Group
-Authors: Ethan Merritt: merritt@u.washington.ed & Jay Painter: jay.painter@gmail.com
+Authors: Ethan Merritt: merritt@u.washington.edu
+         Jay Painter: jay.painter@gmail.com
 See: http://pymmlib.sourceforge.net/
 """
 import re
 from .containers import DataCategory, DefinitionContainer, DataContainer
-from .errors import PdbxSyntaxError, PdbxError
+from .errors import PdbxSyntaxError
 
 
 class PdbxReader:
@@ -37,7 +38,7 @@ class PdbxReader:
             "loop": "ST_TABLE",
             "global": "ST_GLOBAL_CONTAINER",
             "save": "ST_DEFINITION",
-            "stop": "ST_STOP"
+            "stop": "ST_STOP",
         }
 
     def read(self, container_list):
@@ -95,8 +96,12 @@ class PdbxReader:
         state = None
 
         # Find the first reserved word and begin capturing data.
-        for (current_category_name, current_attribute_name,
-             current_quoted_string, current_word) in tokenizer:
+        for (
+            current_category_name,
+            current_attribute_name,
+            current_quoted_string,
+            current_word,
+        ) in tokenizer:
             if current_word is None:
                 continue
             reserved_word, state = self.__get_state(current_word)
@@ -126,13 +131,15 @@ class PdbxReader:
                     # A new category is encountered - create a container and
                     # add a row
                     category_index[current_category_name] = DataCategory(
-                        current_category_name)
+                        current_category_name
+                    )
                     current_category = category_index[current_category_name]
                     try:
                         current_container.append(current_category)
                     except AttributeError:
                         self.__syntax_error(
-                            "Category cannot be added to data_ block")
+                            "Category cannot be added to data_ block"
+                        )
                         return
                     current_row = []
                     current_category.append(current_row)
@@ -142,42 +149,51 @@ class PdbxReader:
                         current_row = current_category[0]
                     except IndexError:
                         self.__syntax_error(
-                            "Internal index error accessing category data")
+                            "Internal index error accessing category data"
+                        )
                         return
                 # Check for duplicate attributes and add attribute to table.
                 if current_attribute_name in current_category.attribute_list:
                     self.__syntax_error(
-                        "Duplicate attribute encountered in category")
+                        "Duplicate attribute encountered in category"
+                    )
                     return
                 else:
                     current_category.append_attribute(current_attribute_name)
                 # Get the data for this attribute from the next token
                 tok_category, _, current_quoted_string, current_word = next(
-                    tokenizer)
-                if (tok_category is not None or (
-                        current_quoted_string is None
-                        and current_word is None)):
+                    tokenizer
+                )
+                if tok_category is not None or (
+                    current_quoted_string is None and current_word is None
+                ):
                     self.__syntax_error(
-                        "Missing data for item _%s.%s" % (
-                            current_category_name, current_attribute_name))
-                if current_word == '?':
+                        "Missing data for item _%s.%s"
+                        % (current_category_name, current_attribute_name)
+                    )
+                if current_word == "?":
                     current_row.append(None)
-                elif current_word == '.':
-                    current_row.append('')
+                elif current_word == ".":
+                    current_row.append("")
                 elif current_word is not None:
                     # Validation check token for misplaced reserved words
                     reserved_word, state = self.__get_state(current_word)
                     if reserved_word is not None:
                         self.__syntax_error(
-                            "Unexpected reserved word: %s" % (reserved_word))
+                            "Unexpected reserved word: %s" % (reserved_word)
+                        )
                     current_row.append(current_word)
                 elif current_quoted_string is not None:
                     current_row.append(current_quoted_string)
                 else:
                     self.__syntax_error("Missing value in item-value pair")
                 try:
-                    current_category_name, current_attribute_name, \
-                        current_quoted_string, current_word = next(tokenizer)
+                    (
+                        current_category_name,
+                        current_attribute_name,
+                        current_quoted_string,
+                        current_word,
+                    ) = next(tokenizer)
                 except StopIteration:
                     return
                 continue
@@ -187,17 +203,24 @@ class PdbxReader:
                 # The category name in the next current_category_name,
                 # current_attribute_name pair defines the name of the category
                 # container.
-                current_category_name, current_attribute_name, \
-                    current_quoted_string, current_word = next(tokenizer)
+                (
+                    current_category_name,
+                    current_attribute_name,
+                    current_quoted_string,
+                    current_word,
+                ) = next(tokenizer)
                 if (current_category_name is None) or (
-                        current_attribute_name is None):
+                    current_attribute_name is None
+                ):
                     self.__syntax_error(
-                        "Unexpected token in loop_ declaration")
+                        "Unexpected token in loop_ declaration"
+                    )
                     return
                 # Check for a previous category declaration.
                 if current_category_name in category_index:
                     self.__syntax_error(
-                        "Duplicate category declaration in loop_")
+                        "Duplicate category declaration in loop_"
+                    )
                     return
                 current_category = DataCategory(current_category_name)
                 try:
@@ -205,17 +228,23 @@ class PdbxReader:
                 except AttributeError:
                     self.__syntax_error(
                         "loop_ declaration outside of data_ block or save_ "
-                        "frame")
+                        "frame"
+                    )
                     return
                 current_category.append_attribute(current_attribute_name)
                 # Read the rest of the loop_ declaration
-                for (current_category_name, current_attribute_name,
-                     current_quoted_string, current_word) in tokenizer:
+                for (
+                    current_category_name,
+                    current_attribute_name,
+                    current_quoted_string,
+                    current_word,
+                ) in tokenizer:
                     if current_category_name is None:
                         break
                     if current_category_name != current_category.name:
                         self.__syntax_error(
-                            "Changed category name in loop_ declaration")
+                            "Changed category name in loop_ declaration"
+                        )
                         return
                     current_category.append_attribute(current_attribute_name)
                 else:
@@ -231,24 +260,28 @@ class PdbxReader:
                         else:
                             self.__syntax_error(
                                 "Unexpected reserved word after loop "
-                                "declaration: %s" % (reserved_word))
+                                "declaration: %s" % (reserved_word)
+                            )
                 # Read the table of data for this loop_
                 while True:
                     current_row = []
                     current_category.append(current_row)
                     for _ in current_category.attribute_list:
-                        if current_word == '?':
+                        if current_word == "?":
                             current_row.append(None)
-                        elif current_word == '.':
-                            current_row.append('')
+                        elif current_word == ".":
+                            current_row.append("")
                         elif current_word is not None:
                             current_row.append(current_word)
                         elif current_quoted_string is not None:
                             current_row.append(current_quoted_string)
                         try:
-                            current_category_name, current_attribute_name, \
-                                current_quoted_string, current_word = next(
-                                    tokenizer)
+                            (
+                                current_category_name,
+                                current_attribute_name,
+                                current_quoted_string,
+                                current_word,
+                            ) = next(tokenizer)
                         except StopIteration:
                             return
                     # loop_ data processing ends if a new _category.attribute
@@ -288,14 +321,19 @@ class PdbxReader:
                 current_category = None
             elif state == "ST_UNKNOWN":
                 self.__syntax_error(
-                    "Unrecognized syntax element: " + str(current_word))
+                    "Unrecognized syntax element: " + str(current_word)
+                )
                 return
             else:
                 assert False, f"unhandled state {state}"
 
             try:
-                current_category_name, current_attribute_name, \
-                    current_quoted_string, current_word = next(tokenizer)
+                (
+                    current_category_name,
+                    current_attribute_name,
+                    current_quoted_string,
+                    current_word,
+                ) = next(tokenizer)
             except StopIteration:
                 return
 
@@ -313,11 +351,15 @@ class PdbxReader:
         # handled outside of this regex.
         mmcif_re = re.compile(
             r"(?:"
-            r"(?:_(.+?)[.](\S+))" "|"  # _category.attribute
-            r"(?:['](.*?)(?:[']\s|[']$))" "|"  # single quoted strings
-            r"(?:[\"](.*?)(?:[\"]\s|[\"]$))" "|"  # double quoted strings
-            r"(?:\s*#.*$)" "|"  # comments (dumped)
-            r"(\S+)" # unquoted words
+            r"(?:_(.+?)[.](\S+))"
+            "|"  # _category.attribute
+            r"(?:['](.*?)(?:[']\s|[']$))"
+            "|"  # single quoted strings
+            r"(?:[\"](.*?)(?:[\"]\s|[\"]$))"
+            "|"  # double quoted strings
+            r"(?:\s*#.*$)"
+            "|"  # comments (dumped)
+            r"(\S+)"  # unquoted words
             r")"
         )
         file_iterator = iter(input_file)
@@ -356,8 +398,11 @@ class PdbxReader:
                     else:
                         quoted_string = None
                     groups = (
-                        match_groups[0], match_groups[1], quoted_string,
-                        match_groups[4])
+                        match_groups[0],
+                        match_groups[1],
+                        quoted_string,
+                        match_groups[4],
+                    )
                     yield groups
 
     def __tokenizer_org(self, input_file):
@@ -372,9 +417,12 @@ class PdbxReader:
         # handled outside of this regex.
         mmcif_re = re.compile(
             r"(?:"
-            r"(?:_(.+?)[.](\S+))" "|" # _category.attribute
-            r"(?:['\"](.*?)(?:['\"]\s|['\"]$))" "|" # quoted strings
-            r"(?:\s*#.*$)" "|"  # comments (dumped)
+            r"(?:_(.+?)[.](\S+))"
+            "|"  # _category.attribute
+            r"(?:['\"](.*?)(?:['\"]\s|['\"]$))"
+            "|"  # quoted strings
+            r"(?:\s*#.*$)"
+            "|"  # comments (dumped)
             r"(\S+)"  # unquoted words
             r")"
         )
diff --git a/pdbx/writer.py b/pdbx/writer.py
index 89fde51..d962784 100644
--- a/pdbx/writer.py
+++ b/pdbx/writer.py
@@ -27,6 +27,7 @@ class PdbxWriter:
     """Write PDBx data files or dictionaries.
     Use the input container or container list.
     """
+
     def __init__(self, output_file=stdout):
         self.__output_file = output_file
         self.__container_list = []
@@ -78,8 +79,9 @@ class PdbxWriter:
                 self.__write_table_format(obj)
             else:
                 raise PdbxError(
-                    "len(object_list) = %d and len(obj.attribute_list) = %d" %
-                    (len(object_list), len(obj.attribute_list)))
+                    "len(object_list) = %d and len(obj.attribute_list) = %d"
+                    % (len(object_list), len(obj.attribute_list))
+                )
             if self._do_definition_indent:
                 self.__write(indent_string + "#")
             else:
@@ -99,10 +101,11 @@ class PdbxWriter:
         attribute_name_max_length = 0
         for attribute_name in category.attribute_list:
             attribute_name_max_length = max(
-                attribute_name_max_length, len(attribute_name))
+                attribute_name_max_length, len(attribute_name)
+            )
         item_name_max_length = (
-            self.__spacing + len(category.name)
-            + attribute_name_max_length + 2)
+            self.__spacing + len(category.name) + attribute_name_max_length + 2
+        )
         line_list = []
         line_list.append("#\n")
         for attribute_name, _ in category.attribute_list_with_order:
@@ -119,12 +122,12 @@ class PdbxWriter:
         """Write table format data."""
         # Write the declaration of the loop_
         line_list = []
-        line_list.append('#\n')
+        line_list.append("#\n")
         if self._do_definition_indent:
             line_list.append(self.__indent_space)
         line_list.append("loop_")
         for attribute_name in category.attribute_list:
-            line_list.append('\n')
+            line_list.append("\n")
             if self._do_definition_indent:
                 line_list.append(self.__indent_space)
             item_name = "_%s.%s" % (category.name, attribute_name)
@@ -137,23 +140,25 @@ class PdbxWriter:
         else:
             num_steps = 1
         format_type_list, _ = category.get_format_type_list(steps=num_steps)
-        max_length_list = category.get_max_attribute_list_length(steps=num_steps)
+        max_length_list = category.get_max_attribute_list_length(
+            steps=num_steps
+        )
         spacing = " " * self.__spacing
         for irow in range(category.row_count):
             line_list = []
-            line_list.append('\n')
+            line_list.append("\n")
             if self._do_definition_indent:
                 line_list.append(self.__indent_space + " ")
             for iattr in range(category.attribute_count):
                 format_type = format_type_list[iattr]
                 max_length = max_length_list[iattr]
-                if format_type in ('FT_UNQUOTED_STRING', 'FT_NULL_VALUE'):
+                if format_type in ("FT_UNQUOTED_STRING", "FT_NULL_VALUE"):
                     val = category.get_value_formatted_by_index(iattr, irow)
                     line_list.append(val.ljust(max_length))
-                elif format_type == 'FT_NUMBER':
+                elif format_type == "FT_NUMBER":
                     val = category.get_value_formatted_by_index(iattr, irow)
                     line_list.append(val.rjust(max_length))
-                elif format_type == 'FT_QUOTED_STRING':
+                elif format_type == "FT_QUOTED_STRING":
                     val = category.get_value_formatted_by_index(iattr, irow)
                     line_list.append(val.ljust(max_length + 2))
                 elif format_type == "FT_MULTI_LINE_STRING":
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..b8c0838
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+pytest==5.4.3
+sphinx_rtd_theme==0.5.0
diff --git a/setup.py b/setup.py
index d046ab6..5ff1c6d 100644
--- a/setup.py
+++ b/setup.py
@@ -28,9 +28,9 @@ setuptools.setup(
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Topic :: Scientific/Engineering :: Bio-Informatics",
-        "Topic :: Scientific/Engineering :: Chemistry"
+        "Topic :: Scientific/Engineering :: Chemistry",
     ],
-    python_requires='>=3.6',
+    python_requires=">=3.6",
     test_suite="tests",
-    zip_safe=True
+    zip_safe=True,
 )

Flatten Namespace
Looking again at json and pickle: 

They put everything API relevant in the top-level module namespace. E.g. json.JSONDecoder instead of json.decoder.JSONDecoder (actual implementation). "Flat is better than nested" (The Zen of Python) so to speak.
diff --git a/tests/reader_test.py b/tests/reader_test.py
index f685180..391d6a6 100644
--- a/tests/reader_test.py
+++ b/tests/reader_test.py
@@ -9,15 +9,14 @@
 #
 ##
 """Test cases for reading PDBx/mmCIF data files reader class."""
-import io
 import logging
 from pathlib import Path
 import pytest
-from pdbx.errors import PdbxSyntaxError
+from pdbx import PdbxSyntaxError
+
 from pdbx.reader import PdbxReader
 from pdbx import loads as read_cifstr
 
-
 DATA_DIR = Path("tests/data")
 LOGGER = logging.getLogger()
 
@@ -46,70 +45,71 @@ def test_structure_factor_file(input_cif):
 
 
 def test_empty_file():
-    assert read_cifstr('') == []
+    assert read_cifstr("") == []
 
 
 def test_empty_data_block():
-    assert read_cifstr('data_test')[0].get_object_name_list() == []
+    assert read_cifstr("data_test")[0].get_object_name_list() == []
 
 
 def test_missing_value_eof():
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test _a.x')
+        read_cifstr("data_test _a.x")
     assert "end of file" in str(excinfo.value)
 
 
 def test_missing_value_not_eof():
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test _a.x _a.y A')
+        read_cifstr("data_test _a.x _a.y A")
     assert "Missing data for item _a.x" in str(excinfo.value)
 
 
 def test_missing_key():
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test _a.x A B')
+        read_cifstr("data_test _a.x A B")
     assert "Unrecognized syntax element: B" in str(excinfo.value)
 
 
 def test_empty_loop_header_eof():
     # formal CIF 1.1 grammar expects at least one tag
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test loop_')
+        read_cifstr("data_test loop_")
     assert "end of file" in str(excinfo.value)
 
 
 def test_empty_loop_header_not_eof():
     # formal CIF 1.1 grammar expects at least one tag
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test loop_ loop_')
+        read_cifstr("data_test loop_ loop_")
     assert "Unexpected token" in str(excinfo.value)
 
 
 def test_empty_loop_body_eof():
     # formal CIF 1.1 grammar expects at least one value
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test loop_ _a.x')
+        read_cifstr("data_test loop_ _a.x")
     assert "loop_ without values" in str(excinfo.value)
 
 
 def test_empty_loop_body_not_eof():
     # formal CIF 1.1 grammar expects at least one value
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test loop_ _a.x loop_')
+        read_cifstr("data_test loop_ _a.x loop_")
     assert "Unexpected reserved word" in str(excinfo.value)
 
 
 def test_loop_value_count_mismatch():
     # https://www.iucr.org/resources/cif/spec/version1.1/cifsyntax § 63:
-    # The number of values in the body must be a multiple of the number of tags in the header
+    # The number of values in the body must be a multiple of the number of
+    # tags in the header
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test loop_ _a.x _a.y A')
+        read_cifstr("data_test loop_ _a.x _a.y A")
         pytest.skip("does not raise yet")  # TODO
 
 
 def test_incomplete_multiline_string():
-    read_cifstr('data_test _a.x\n;A\n;')  # correct (terminated)
-    read_cifstr('data_test _a.x ;A')  # correct (not a multi-line string)
+    read_cifstr("data_test _a.x\n;A\n;")  # correct (terminated)
+    read_cifstr("data_test _a.x ;A")  # correct (not a multi-line string)
     with pytest.raises(PdbxSyntaxError) as excinfo:
-        read_cifstr('data_test _a.x\n;A')
+        read_cifstr("data_test _a.x\n;A")
     assert "unterminated multi-line" in str(excinfo.value)
diff --git a/tests/readwrite_test.py b/tests/readwrite_test.py
index a15145f..4e44adf 100644
--- a/tests/readwrite_test.py
+++ b/tests/readwrite_test.py
@@ -14,7 +14,7 @@ from pathlib import Path
 import pdbx
 from pdbx.reader import PdbxReader
 from pdbx.writer import PdbxWriter
-from pdbx.reader import DataCategory, DataContainer
+from pdbx import DataCategory, DataContainer
 
 
 _LOGGER = logging.getLogger()
@@ -24,8 +24,17 @@ DATA_DIR = Path("tests/data")
 def test_init_write_read(tmp_path):
     """Test initialization, writing, and reading."""
     attribute_name_list = [
-        'aOne', 'aTwo', 'aThree', 'aFour', 'aFive', 'aSix', 'aSeven',
-        'aEight', 'aNine', 'aTen']
+        "aOne",
+        "aTwo",
+        "aThree",
+        "aFour",
+        "aFive",
+        "aSix",
+        "aSeven",
+        "aEight",
+        "aNine",
+        "aTen",
+    ]
     row_list = [
         [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
         [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
@@ -36,8 +45,9 @@ def test_init_write_read(tmp_path):
         [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
         [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
         [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
-        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
-    category_name = 'category'
+        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+    ]
+    category_name = "category"
     container = DataContainer("myblock")
     category = DataCategory(category_name, attribute_name_list, row_list)
     category.print_it()
@@ -54,8 +64,7 @@ def test_init_write_read(tmp_path):
         reader.read(container_list)
     for container in container_list:
         for object_name in container.get_object_name_list():
-            name, attr_list, row_list = container.get_object(
-                object_name).get()
+            name, attr_list, row_list = container.get_object(object_name).get()
             _LOGGER.info("Recovered data category  %s\n", name)
             _LOGGER.info("Attribute list           %r\n", repr(attr_list))
             _LOGGER.info("Row list                 %r\n", repr(row_list))
@@ -88,16 +97,17 @@ def test_update_data_file(tmp_path):
         reader.read(data_list)
     block = data_list[0]
     block.print_it()
-    category = block.get_object('pdbx_seqtool_mapping_ref')
+    category = block.get_object("pdbx_seqtool_mapping_ref")
     category.print_it()
     for irow in range(category.row_count):
-        category.set_value('some value', 'ref_mon_id', irow)
-        category.set_value(100, 'ref_mon_num', irow)
+        category.set_value("some value", "ref_mon_id", irow)
+        category.set_value(100, "ref_mon_num", irow)
     cif_path = Path(tmp_path) / Path("test-output-2.cif")
     with open(cif_path, "wt") as output_file:
         writer = PdbxWriter(output_file)
         writer.write(data_list)
 
+
 def test_read_write_data_file(tmp_path):
     """Data file read/write test."""
     input_path = Path(DATA_DIR) / Path("1kip.cif")
@@ -108,7 +118,9 @@ def test_read_write_data_file(tmp_path):
         pdbx.dump(data_list, output_path)
 
 
-ROUNDTRIPPABLE_CIF_STR = '''data_foo
+# TODO: 2020/07/16 intendo - are the spaces at the end of the line needed
+#       for testing or should they be removed to make flake8 happy?
+ROUNDTRIPPABLE_CIF_STR = """data_foo
 #
 #
 _cat1.key1  123
@@ -132,7 +144,8 @@ _cat3.strings
 .           "."      
 ?           "?"      
 ##
-'''
+"""
+
 
 def test_roundtrip():
     containers = pdbx.loads(ROUNDTRIPPABLE_CIF_STR)
diff --git a/tests/writer_test.py b/tests/writer_test.py
index b2c82d1..58e56de 100644
--- a/tests/writer_test.py
+++ b/tests/writer_test.py
@@ -11,7 +11,7 @@
 """Test PDBx/mmCIF write and formatting operations."""
 import logging
 from pathlib import Path
-from pdbx.containers import DataContainer, DataCategory
+from pdbx import DataContainer, DataCategory
 from pdbx.writer import PdbxWriter
 
 

pip install -e .
parse_log_pytest
pytest
pytest
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
certifi @ file:///croot/certifi_1671487769961/work/certifi
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
-e git+https://github.com/Electrostatics/mmcif_pdbx.git@6aebce381a8958024ad25c9f909fe3faebbe803c#egg=mmcif_pdbx
packaging @ file:///croot/packaging_1671697413597/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pytest==7.1.2
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
zipp @ file:///croot/zipp_1672387121353/work

name: mmcif_pdbx
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
prefix: /opt/conda/envs/mmcif_pdbx

tests/reader_test.py::test_data_file[1kip.cif] tests/reader_test.py::test_data_file[1ffk.cif] tests/reader_test.py::test_structure_factor_file[1kip-sf.cif] tests/reader_test.py::test_empty_file tests/reader_test.py::test_empty_data_block tests/reader_test.py::test_missing_value_eof tests/reader_test.py::test_missing_value_not_eof tests/reader_test.py::test_missing_key tests/reader_test.py::test_empty_loop_header_eof tests/reader_test.py::test_empty_loop_header_not_eof tests/reader_test.py::test_empty_loop_body_eof tests/reader_test.py::test_empty_loop_body_not_eof tests/reader_test.py::test_incomplete_multiline_string tests/readwrite_test.py::test_init_write_read tests/readwrite_test.py::test_update_data_file tests/readwrite_test.py::test_read_write_data_file tests/readwrite_test.py::test_roundtrip tests/writer_test.py::test_write_data_file
docs/conf.py pdbx/writer.py pdbx/containers.py setup.py .gitignore pdbx/reader.py requirements.txt .github/workflows/python-package.yml pdbx/__init__.py
docs/conf.py pdbx/writer.py pdbx/containers.py setup.py .gitignore pdbx/reader.py requirements.txt .github/workflows/python-package.yml pdbx/__init__.py