Third Party packages eric7

Tue, 24 May 2022 11:00:52 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 24 May 2022 11:00:52 +0200
branch
eric7
changeset 9099
0e511e0e94a3
parent 9098
fb9351497cea
child 9100
9d3475ca1735

Third Party packages
- upgraded coverage to 6.4.0

docs/changelog file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/cmdline.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/collector.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/config.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/control.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/debug.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/doc/CHANGES.rst file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/doc/CONTRIBUTORS.txt file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/doc/README.rst file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/html.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/htmlfiles/coverage_html.js file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/htmlfiles/index.html file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/htmlfiles/pyfile.html file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/htmlfiles/style.css file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/htmlfiles/style.scss file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/inorout.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/jsonreport.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/misc.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/parser.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/phystokens.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/python.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/results.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/sqldata.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/summary.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/tomlconfig.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/version.py file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/coverage/xmlreport.py file | annotate | diff | comparison | revisions
--- a/docs/changelog	Tue May 24 10:22:46 2022 +0200
+++ b/docs/changelog	Tue May 24 11:00:52 2022 +0200
@@ -21,6 +21,7 @@
      function
 - Third Party packages
   -- upgraded pip-licenses to version 3.5.4
+  -- upgraded coverage to 6.4.0
 
 Version 22.5:
 - bug fixes
--- a/eric7/DebugClients/Python/coverage/cmdline.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/cmdline.py	Tue May 24 11:00:52 2022 +0200
@@ -232,9 +232,7 @@
     """
 
     def __init__(self, *args, **kwargs):
-        super().__init__(
-            add_help_option=False, *args, **kwargs
-            )
+        super().__init__(add_help_option=False, *args, **kwargs)
         self.set_defaults(
             # Keep these arguments alphabetized by their names.
             action=None,
@@ -267,7 +265,7 @@
             timid=None,
             title=None,
             version=None,
-            )
+        )
 
         self.disable_interspersed_args()
 
@@ -352,7 +350,7 @@
     Opts.debug,
     Opts.help,
     Opts.rcfile,
-    ]
+]
 
 COMMANDS = {
     'annotate': CmdOptionParser(
@@ -473,7 +471,7 @@
             Opts.output_lcov,
             Opts.omit,
             Opts.quiet,
-        ] + GLOBAL_ARGS,
+            ] + GLOBAL_ARGS,
         usage="[options] [modules]",
         description="Generate an LCOV report of coverage results.",
     ),
@@ -648,7 +646,7 @@
             check_preimported=True,
             context=options.context,
             messages=not options.quiet,
-            )
+        )
 
         if options.action == "debug":
             return self.do_debug(args)
@@ -675,7 +673,7 @@
             omit=omit,
             include=include,
             contexts=contexts,
-            )
+        )
 
         # We need to be able to import from the current directory, because
         # plugins may try to, for example, to read Django settings.
@@ -692,7 +690,7 @@
                 skip_empty=options.skip_empty,
                 sort=options.sort,
                 **report_args
-                )
+            )
         elif options.action == "annotate":
             self.coverage.annotate(directory=options.directory, **report_args)
         elif options.action == "html":
@@ -704,25 +702,25 @@
                 show_contexts=options.show_contexts,
                 title=options.title,
                 **report_args
-                )
+            )
         elif options.action == "xml":
             total = self.coverage.xml_report(
                 outfile=options.outfile,
                 skip_empty=options.skip_empty,
                 **report_args
-                )
+            )
         elif options.action == "json":
             total = self.coverage.json_report(
                 outfile=options.outfile,
                 pretty_print=options.pretty_print,
                 show_contexts=options.show_contexts,
                 **report_args
-                )
+            )
         elif options.action == "lcov":
             total = self.coverage.lcov_report(
                 outfile=options.outfile,
                 **report_args
-                )
+            )
         else:
             # There are no other possible actions.
             raise AssertionError
--- a/eric7/DebugClients/Python/coverage/collector.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/collector.py	Tue May 24 11:00:52 2022 +0200
@@ -145,6 +145,7 @@
             raise ConfigError(f"Conflicting concurrency settings: {show}")
         do_threading = False
 
+        tried = "nothing"  # to satisfy pylint
         try:
             if "greenlet" in concurrencies:
                 tried = "greenlet"
@@ -327,8 +328,7 @@
         self._collectors.append(self)
 
         # Replay all the events from fullcoverage into the new trace function.
-        for args in traces0:
-            (frame, event, arg), lineno = args
+        for (frame, event, arg), lineno in traces0:
             try:
                 fn(frame, event, arg, lineno=lineno)
             except TypeError as ex:
--- a/eric7/DebugClients/Python/coverage/config.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/config.py	Tue May 24 11:00:52 2022 +0200
@@ -190,6 +190,7 @@
         self.relative_files = False
         self.run_include = None
         self.run_omit = None
+        self.sigterm = False
         self.source = None
         self.source_pkgs = []
         self.timid = False
@@ -364,6 +365,7 @@
         ('relative_files', 'run:relative_files', 'boolean'),
         ('run_include', 'run:include', 'list'),
         ('run_omit', 'run:omit', 'list'),
+        ('sigterm', 'run:sigterm', 'boolean'),
         ('source', 'run:source', 'list'),
         ('source_pkgs', 'run:source_pkgs', 'list'),
         ('timid', 'run:timid', 'boolean'),
@@ -499,7 +501,7 @@
         """Make a list of (name, value) pairs for writing debug info."""
         return human_sorted_items(
             (k, v) for k, v in self.__dict__.items() if not k.startswith("_")
-            )
+        )
 
 
 def config_files_to_try(config_file):
--- a/eric7/DebugClients/Python/coverage/control.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/control.py	Tue May 24 11:00:52 2022 +0200
@@ -250,7 +250,7 @@
             source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug,
             report_omit=omit, report_include=include,
             concurrency=concurrency, context=context,
-            )
+        )
 
         # If we have sub-process measurement happening automatically, then we
         # want any explicit creation of a Coverage object to mean, this process
@@ -489,7 +489,7 @@
             branch=self.config.branch,
             warn=self._warn,
             concurrency=concurrency,
-            )
+        )
 
         suffix = self._data_suffix_specified
         if suffix:
@@ -515,10 +515,10 @@
                     ", ".join(
                         plugin._coverage_plugin_name
                             for plugin in self._plugins.file_tracers
-                        ),
+                    ),
                     self._collector.tracer_name(),
-                    )
                 )
+            )
             for plugin in self._plugins.file_tracers:
                 plugin._coverage_enabled = False
 
@@ -536,12 +536,13 @@
 
         # Register our clean-up handlers.
         atexit.register(self._atexit)
-        is_main = (threading.current_thread() == threading.main_thread())
-        if is_main and not env.WINDOWS:
-            # The Python docs seem to imply that SIGTERM works uniformly even
-            # on Windows, but that's not my experience, and this agrees:
-            # https://stackoverflow.com/questions/35772001/x/35792192#35792192
-            self._old_sigterm = signal.signal(signal.SIGTERM, self._on_sigterm)
+        if self.config.sigterm:
+            is_main = (threading.current_thread() == threading.main_thread())
+            if is_main and not env.WINDOWS:
+                # The Python docs seem to imply that SIGTERM works uniformly even
+                # on Windows, but that's not my experience, and this agrees:
+                # https://stackoverflow.com/questions/35772001/x/35792192#35792192
+                self._old_sigterm = signal.signal(signal.SIGTERM, self._on_sigterm)
 
     def _init_data(self, suffix):
         """Create a data file if we don't have one yet."""
@@ -835,7 +836,7 @@
             sorted(analysis.excluded),
             sorted(analysis.missing),
             analysis.missing_formatted(),
-            )
+        )
 
     def _analyze(self, it):
         """Analyze a single morf or code unit.
@@ -1146,7 +1147,7 @@
                 )
             )),
             ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))),
-            ]
+        ]
 
         if self._inorout:
             info.extend(self._inorout.sys_info())
--- a/eric7/DebugClients/Python/coverage/debug.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/debug.py	Tue May 24 11:00:52 2022 +0200
@@ -211,7 +211,7 @@
             klass=self.__class__.__name__,
             id=id(self),
             attrs=" ".join(f"{k}={v!r}" for k, v in show_attrs),
-            )
+        )
 
 
 def simplify(v):                                            # pragma: debugging
--- a/eric7/DebugClients/Python/coverage/doc/CHANGES.rst	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/doc/CHANGES.rst	Tue May 24 11:00:52 2022 +0200
@@ -17,6 +17,59 @@
     ..  Version 9.8.1 — 2027-07-27
     ..  --------------------------
 
+.. _changes_64:
+
+Version 6.4 — 2022-05-22
+------------------------
+
+- A new setting, :ref:`config_run_sigterm`, controls whether a SIGTERM signal
+  handler is used.  In 6.3, the signal handler was always installed, to capture
+  data at unusual process ends.  Unfortunately, this introduced other problems
+  (see `issue 1310`_).  Now the signal handler is only used if you opt-in by
+  setting ``[run] sigterm = true``.
+
+- Small changes to the HTML report:
+
+  - Added links to next and previous file, and more keyboard shortcuts: ``[``
+    and ``]`` for next file and previous file; ``u`` for up to the index; and
+    ``?`` to open/close the help panel.  Thanks, `J. M. F. Tsang
+    <pull 1364_>`_.
+
+  - The timestamp and version are displayed at the top of the report.  Thanks,
+    `Ammar Askar <pull 1354_>`_. Closes `issue 1351`_.
+
+- A new debug option ``debug=sqldata`` adds more detail to ``debug=sql``,
+  logging all the data being written to the database.
+
+- Previously, running ``coverage report`` (or any of the reporting commands) in
+  an empty directory would create a .coverage data file.  Now they do not,
+  fixing `issue 1328`_.
+
+- On Python 3.11, the ``[toml]`` extra no longer installs tomli, instead using
+  tomllib from the standard library.  Thanks `Shantanu <pull 1359_>`_.
+
+- In-memory CoverageData objects now properly update(), closing `issue 1323`_.
+
+.. _issue 1310: https://github.com/nedbat/coveragepy/issues/1310
+.. _issue 1323: https://github.com/nedbat/coveragepy/issues/1323
+.. _issue 1328: https://github.com/nedbat/coveragepy/issues/1328
+.. _issue 1351: https://github.com/nedbat/coveragepy/issues/1351
+.. _pull 1354: https://github.com/nedbat/coveragepy/pull/1354
+.. _pull 1359: https://github.com/nedbat/coveragepy/pull/1359
+.. _pull 1364: https://github.com/nedbat/coveragepy/pull/1364
+
+
+.. _changes_633:
+
+Version 6.3.3 — 2022-05-12
+--------------------------
+
+- Fix: Coverage.py now builds successfully on CPython 3.11 (3.11.0b1) again.
+  Closes `issue 1367`_.  Some results for generators may have changed.
+
+.. _issue 1367: https://github.com/nedbat/coveragepy/issues/1367
+
+
 .. _changes_632:
 
 Version 6.3.2 — 2022-02-20
--- a/eric7/DebugClients/Python/coverage/doc/CONTRIBUTORS.txt	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/doc/CONTRIBUTORS.txt	Tue May 24 11:00:52 2022 +0200
@@ -14,6 +14,7 @@
 Alex Sandro
 Alexander Todorov
 Alexander Walters
+Ammar Askar
 Andrew Hoos
 Anthony Sottile
 Arcadiy Ivanov
@@ -75,6 +76,7 @@
 Ilia Meerovich
 Imri Goldberg
 Ionel Cristian Mărieș
+J. M. F. Tsang
 JT Olds
 Jerin Peter George
 Jessamyn Smith
@@ -137,6 +139,7 @@
 Stefan Behnel
 Stephan Richter
 Stephen Finucane
+Steve Dower
 Steve Leonard
 Steve Peak
 S. Y. Lee
--- a/eric7/DebugClients/Python/coverage/doc/README.rst	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/doc/README.rst	Tue May 24 11:00:52 2022 +0200
@@ -7,11 +7,17 @@
 
 Code coverage testing for Python.
 
+.. image:: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg
+    :target: https://vshymanskyy.github.io/StandWithUkraine
+    :alt: Stand with Ukraine
+
+-------------
+
 |  |license| |versions| |status|
 |  |test-status| |quality-status| |docs| |metacov|
 |  |kit| |downloads| |format| |repos|
 |  |stars| |forks| |contributors|
-|  |tidelift| |twitter-coveragepy| |twitter-nedbat|
+|  |tidelift| |sponsor| |twitter-coveragepy| |twitter-nedbat|
 
 Coverage.py measures code coverage, typically during test execution. It uses
 the code analysis tools and tracing hooks provided in the Python standard
@@ -21,7 +27,7 @@
 
 .. PYVERSIONS
 
-* CPython 3.7 through 3.11.0a4.
+* CPython 3.7 through 3.11.0b1.
 * PyPy3 7.3.8.
 
 Documentation is on `Read the Docs`_.  Code repository and issue tracker are on
@@ -148,3 +154,6 @@
 .. |twitter-nedbat| image:: https://img.shields.io/twitter/follow/nedbat.svg?label=nedbat&style=flat&logo=twitter&logoColor=4FADFF
     :target: https://twitter.com/nedbat
     :alt: nedbat on Twitter
+.. |sponsor| image:: https://img.shields.io/badge/%E2%9D%A4-Sponsor%20me-brightgreen?style=flat&logo=GitHub
+    :target: https://github.com/sponsors/nedbat
+    :alt: Sponsor me on GitHub
--- a/eric7/DebugClients/Python/coverage/html.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/html.py	Tue May 24 11:00:52 2022 +0200
@@ -122,6 +122,15 @@
         return file_data
 
 
+class FileToReport:
+    """A file we're considering reporting."""
+    def __init__(self, fr, analysis):
+        self.fr = fr
+        self.analysis = analysis
+        self.rootname = flat_rootname(fr.relative_filename())
+        self.html_filename = self.rootname + ".html"
+
+
 class HtmlReporter:
     """HTML reporting."""
 
@@ -165,6 +174,8 @@
         self.datagen = HtmlDataGeneration(self.coverage)
         self.totals = Numbers(precision=self.config.precision)
         self.directory_was_empty = False
+        self.first_fr = None
+        self.final_fr = None
 
         self.template_globals = {
             # Functions available in the templates.
@@ -188,7 +199,7 @@
                 'mis': 'mis show_mis',
                 'par': 'par run show_par',
                 'run': 'run',
-            }
+            },
         }
         self.pyfile_html_source = read_data("pyfile.html")
         self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
@@ -204,9 +215,28 @@
         self.incr.read()
         self.incr.check_global_data(self.config, self.pyfile_html_source)
 
-        # Process all the files.
+        # Process all the files. For each page we need to supply a link
+        # to the next and previous page.
+        files_to_report = []
+
         for fr, analysis in get_analysis_to_report(self.coverage, morfs):
-            self.html_file(fr, analysis)
+            ftr = FileToReport(fr, analysis)
+            should = self.should_report_file(ftr)
+            if should:
+                files_to_report.append(ftr)
+            else:
+                file_be_gone(os.path.join(self.directory, ftr.html_filename))
+
+        for i, ftr in enumerate(files_to_report):
+            if i == 0:
+                prev_html = "index.html"
+            else:
+                prev_html = files_to_report[i - 1].html_filename
+            if i == len(files_to_report) - 1:
+                next_html = "index.html"
+            else:
+                next_html = files_to_report[i + 1].html_filename
+            self.write_html_file(ftr, prev_html, next_html)
 
         if not self.all_files_nums:
             raise NoDataError("No data to report.")
@@ -214,11 +244,22 @@
         self.totals = sum(self.all_files_nums)
 
         # Write the index file.
-        self.index_file()
+        if files_to_report:
+            first_html = files_to_report[0].html_filename
+            final_html = files_to_report[-1].html_filename
+        else:
+            first_html = final_html = "index.html"
+        self.index_file(first_html, final_html)
 
         self.make_local_static_report_files()
         return self.totals.n_statements and self.totals.pc_covered
 
+    def make_directory(self):
+        """Make sure our htmlcov directory exists."""
+        ensure_dir(self.directory)
+        if not os.listdir(self.directory):
+            self.directory_was_empty = True
+
     def make_local_static_report_files(self):
         """Make local instances of static files for HTML report."""
         # The files we provide must always be copied.
@@ -236,17 +277,10 @@
         if self.extra_css:
             shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css))
 
-    def html_file(self, fr, analysis):
-        """Generate an HTML file for one source file."""
-        rootname = flat_rootname(fr.relative_filename())
-        html_filename = rootname + ".html"
-        ensure_dir(self.directory)
-        if not os.listdir(self.directory):
-            self.directory_was_empty = True
-        html_path = os.path.join(self.directory, html_filename)
-
+    def should_report_file(self, ftr):
+        """Determine if we'll report this file."""
         # Get the numbers for this file.
-        nums = analysis.numbers
+        nums = ftr.analysis.numbers
         self.all_files_nums.append(nums)
 
         if self.skip_covered:
@@ -255,24 +289,28 @@
             no_missing_branches = (nums.n_partial_branches == 0)
             if no_missing_lines and no_missing_branches:
                 # If there's an existing file, remove it.
-                file_be_gone(html_path)
                 self.skipped_covered_count += 1
-                return
+                return False
 
         if self.skip_empty:
             # Don't report on empty files.
             if nums.n_statements == 0:
-                file_be_gone(html_path)
                 self.skipped_empty_count += 1
-                return
+                return False
+
+        return True
+
+    def write_html_file(self, ftr, prev_html, next_html):
+        """Generate an HTML file for one source file."""
+        self.make_directory()
 
         # Find out if the file on disk is already correct.
-        if self.incr.can_skip_file(self.data, fr, rootname):
-            self.file_summaries.append(self.incr.index_info(rootname))
+        if self.incr.can_skip_file(self.data, ftr.fr, ftr.rootname):
+            self.file_summaries.append(self.incr.index_info(ftr.rootname))
             return
 
         # Write the HTML page for this file.
-        file_data = self.datagen.data_for_file(fr, analysis)
+        file_data = self.datagen.data_for_file(ftr.fr, ftr.analysis)
         for ldata in file_data.lines:
             # Build the HTML for the line.
             html = []
@@ -292,7 +330,7 @@
                 ldata.annotate = ",&nbsp;&nbsp; ".join(
                     f"{ldata.number}&#x202F;&#x219B;&#x202F;{d}"
                     for d in ldata.short_annotations
-                    )
+                )
             else:
                 ldata.annotate = None
 
@@ -306,7 +344,7 @@
                         ", ".join(
                             f"{num:d}) {ann_long}"
                             for num, ann_long in enumerate(longs, start=1)
-                            ),
+                        ),
                     )
             else:
                 ldata.annotate_long = None
@@ -316,20 +354,26 @@
                 css_classes.append(self.template_globals['category'][ldata.category])
             ldata.css_class = ' '.join(css_classes) or "pln"
 
-        html = self.source_tmpl.render(file_data.__dict__)
+        html_path = os.path.join(self.directory, ftr.html_filename)
+        html = self.source_tmpl.render({
+            **file_data.__dict__,
+            'prev_html': prev_html,
+            'next_html': next_html,
+        })
         write_html(html_path, html)
 
         # Save this file's information for the index file.
         index_info = {
-            'nums': nums,
-            'html_filename': html_filename,
-            'relative_filename': fr.relative_filename(),
+            'nums': ftr.analysis.numbers,
+            'html_filename': ftr.html_filename,
+            'relative_filename': ftr.fr.relative_filename(),
         }
         self.file_summaries.append(index_info)
-        self.incr.set_index_info(rootname, index_info)
+        self.incr.set_index_info(ftr.rootname, index_info)
 
-    def index_file(self):
+    def index_file(self, first_html, final_html):
         """Write the index.html file for this report."""
+        self.make_directory()
         index_tmpl = Templite(read_data("index.html"), self.template_globals)
 
         skipped_covered_msg = skipped_empty_msg = ""
@@ -345,6 +389,8 @@
             'totals': self.totals,
             'skipped_covered_msg': skipped_covered_msg,
             'skipped_empty_msg': skipped_empty_msg,
+            'first_html': first_html,
+            'final_html': final_html,
         })
 
         index_file = os.path.join(self.directory, "index.html")
--- a/eric7/DebugClients/Python/coverage/htmlfiles/coverage_html.js	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/htmlfiles/coverage_html.js	Tue May 24 11:00:52 2022 +0200
@@ -25,6 +25,13 @@
     return !(rect.bottom < viewTop || rect.top >= viewBottom);
 }
 
+function on_click(sel, fn) {
+    const elt = document.querySelector(sel);
+    if (elt) {
+        elt.addEventListener("click", fn);
+    }
+}
+
 // Helpers for table sorting
 function getCellValue(row, column = 0) {
     const cell = row.cells[column]
@@ -193,6 +200,11 @@
             direction: th.getAttribute("aria-sort"),
         }));
     });
+
+    on_click(".button_prev_file", coverage.to_prev_file);
+    on_click(".button_next_file", coverage.to_next_file);
+
+    on_click(".button_show_hide_help", coverage.show_hide_help);
 };
 
 // -- pyfile stuff --
@@ -209,12 +221,6 @@
         coverage.set_sel(0);
     }
 
-    const on_click = function(sel, fn) {
-        const elt = document.querySelector(sel);
-        if (elt) {
-            elt.addEventListener("click", fn);
-        }
-    }
     on_click(".button_toggle_run", coverage.toggle_lines);
     on_click(".button_toggle_mis", coverage.toggle_lines);
     on_click(".button_toggle_exc", coverage.toggle_lines);
@@ -225,6 +231,12 @@
     on_click(".button_top_of_page", coverage.to_top);
     on_click(".button_first_chunk", coverage.to_first_chunk);
 
+    on_click(".button_prev_file", coverage.to_prev_file);
+    on_click(".button_next_file", coverage.to_next_file);
+    on_click(".button_to_index", coverage.to_index);
+
+    on_click(".button_show_hide_help", coverage.show_hide_help);
+
     coverage.filters = undefined;
     try {
         coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
@@ -299,6 +311,23 @@
     coverage.to_next_chunk();
 };
 
+coverage.to_prev_file = function () {
+    window.location = document.getElementById("prevFileLink").href;
+}
+
+coverage.to_next_file = function () {
+    window.location = document.getElementById("nextFileLink").href;
+}
+
+coverage.to_index = function () {
+    location.href = document.getElementById("indexLink").href;
+}
+
+coverage.show_hide_help = function () {
+    const helpCheck = document.getElementById("help_panel_state")
+    helpCheck.checked = !helpCheck.checked;
+}
+
 // Return a string indicating what kind of chunk this line belongs to,
 // or null if not a chunk.
 coverage.chunk_indicator = function (line_elt) {
--- a/eric7/DebugClients/Python/coverage/htmlfiles/index.html	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/htmlfiles/index.html	Tue May 24 11:00:52 2022 +0200
@@ -21,15 +21,15 @@
             <span class="pc_cov">{{totals.pc_covered_str}}%</span>
         </h1>
 
-        <div id="help_panel_wrapper">
+        <aside id="help_panel_wrapper">
             <input id="help_panel_state" type="checkbox">
             <label for="help_panel_state">
                 <img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
             </label>
             <div id="help_panel">
                 <p class="legend">Shortcuts on this page</p>
-                <div>
-                    <p class="keyhelp">
+                <div class="keyhelp">
+                    <p>
                         <kbd>n</kbd>
                         <kbd>s</kbd>
                         <kbd>m</kbd>
@@ -38,15 +38,29 @@
                         <kbd>b</kbd>
                         <kbd>p</kbd>
                         {% endif %}
-                        <kbd>c</kbd> &nbsp; change column sorting
+                        <kbd>c</kbd>
+                        &nbsp; change column sorting
+                    </p>
+                    <p>
+                        <kbd>[</kbd>
+                        <kbd>]</kbd>
+                        &nbsp; prev/next file
+                    </p>
+                    <p>
+                        <kbd>?</kbd> &nbsp; show/hide this help
                     </p>
                 </div>
             </div>
-        </div>
+        </aside>
 
         <form id="filter_container">
             <input id="filter" type="text" value="" placeholder="filter..." />
         </form>
+
+        <p class="text">
+            <a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
+            created at {{ time_stamp }}
+        </p>
     </div>
 </header>
 
@@ -115,6 +129,13 @@
             created at {{ time_stamp }}
         </p>
     </div>
+    <aside class="hidden">
+        <a id="prevFileLink" class="nav" href="{{ final_html }}"/>
+        <a id="nextFileLink" class="nav" href="{{ first_html }}"/>
+        <button type="button" class="button_prev_file" data-shortcut="["/>
+        <button type="button" class="button_next_file" data-shortcut="]"/>
+        <button type="button" class="button_show_hide_help" data-shortcut="?"/>
+    </aside>
 </footer>
 
 </body>
--- a/eric7/DebugClients/Python/coverage/htmlfiles/pyfile.html	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/htmlfiles/pyfile.html	Tue May 24 11:00:52 2022 +0200
@@ -5,9 +5,6 @@
 <html>
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    {# IE8 rounds line-height incorrectly, and adding this emulateIE7 line makes it right! #}
-    {# http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/7684445e-f080-4d8f-8529-132763348e21 #}
-    <meta http-equiv="X-UA-Compatible" content="IE=emulateIE7" />
     <title>Coverage for {{relative_filename|escape}}: {{nums.pc_covered_str}}%</title>
     <link rel="icon" sizes="32x32" href="favicon_32.png">
     <link rel="stylesheet" href="style.css" type="text/css">
@@ -25,15 +22,15 @@
             <span class="pc_cov">{{nums.pc_covered_str}}%</span>
         </h1>
 
-        <div id="help_panel_wrapper">
+        <aside id="help_panel_wrapper">
             <input id="help_panel_state" type="checkbox">
             <label for="help_panel_state">
                 <img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
             </label>
             <div id="help_panel">
                 <p class="legend">Shortcuts on this page</p>
-                <div>
-                    <p class="keyhelp">
+                <div class="keyhelp">
+                    <p>
                         <kbd>r</kbd>
                         <kbd>m</kbd>
                         <kbd>x</kbd>
@@ -42,19 +39,31 @@
                         {% endif %}
                         &nbsp; toggle line displays
                     </p>
-                    <p class="keyhelp">
+                    <p>
                         <kbd>j</kbd>
-                        <kbd>k</kbd> &nbsp; next/prev highlighted chunk
+                        <kbd>k</kbd>
+                        &nbsp; next/prev highlighted chunk
                     </p>
-                    <p class="keyhelp">
+                    <p>
                         <kbd>0</kbd> &nbsp; (zero) top of page
                     </p>
-                    <p class="keyhelp">
+                    <p>
                         <kbd>1</kbd> &nbsp; (one) first highlighted chunk
                     </p>
+                    <p>
+                        <kbd>[</kbd>
+                        <kbd>]</kbd>
+                        &nbsp; prev/next file
+                    </p>
+                    <p>
+                        <kbd>u</kbd> &nbsp; up to the index
+                    </p>
+                    <p>
+                        <kbd>?</kbd> &nbsp; show/hide this help
+                    </p>
                 </div>
             </div>
-        </div>
+        </aside>
 
         <h2>
             <span class="text">{{nums.n_statements}} statements &nbsp;</span>
@@ -66,12 +75,25 @@
             {% endif %}
         </h2>
 
-        <div style="display: none;">
-            <button type="button" class="button_next_chunk" data-shortcut="j">Next highlighted chunk</button>
-            <button type="button" class="button_prev_chunk" data-shortcut="k">Previous highlighted chunk</button>
-            <button type="button" class="button_top_of_page" data-shortcut="0">Goto top of page</button>
-            <button type="button" class="button_first_chunk" data-shortcut="1">Goto first highlighted chunk</button>
-        </div>
+        <p class="text">
+            <a id="prevFileLink" class="nav" href="{{ prev_html }}">&#xab; prev</a> &nbsp; &nbsp;
+            <a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
+            <a id="nextFileLink" class="nav" href="{{ next_html }}">&#xbb; next</a>
+            &nbsp; &nbsp; &nbsp;
+            <a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
+            created at {{ time_stamp }}
+        </p>
+
+        <aside class="hidden">
+            <button type="button" class="button_next_chunk" data-shortcut="j"/>
+            <button type="button" class="button_prev_chunk" data-shortcut="k"/>
+            <button type="button" class="button_top_of_page" data-shortcut="0"/>
+            <button type="button" class="button_first_chunk" data-shortcut="1"/>
+            <button type="button" class="button_prev_file" data-shortcut="["/>
+            <button type="button" class="button_next_file" data-shortcut="]"/>
+            <button type="button" class="button_to_index" data-shortcut="u"/>
+            <button type="button" class="button_show_hide_help" data-shortcut="?"/>
+        </aside>
     </div>
 </header>
 
@@ -110,7 +132,11 @@
 <footer>
     <div class="content">
         <p>
-            <a class="nav" href="index.html">&#xab; index</a> &nbsp; &nbsp; <a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
+            <a id="prevFileLink" class="nav" href="{{ prev_html }}">&#xab; prev</a> &nbsp; &nbsp;
+            <a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
+            <a id="nextFileLink" class="nav" href="{{ next_html }}">&#xbb; next</a>
+            &nbsp; &nbsp; &nbsp;
+            <a class="nav" href="{{__url__}}">coverage.py v{{__version__}}</a>,
             created at {{ time_stamp }}
         </p>
     </div>
--- a/eric7/DebugClients/Python/coverage/htmlfiles/style.css	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/htmlfiles/style.css	Tue May 24 11:00:52 2022 +0200
@@ -28,6 +28,8 @@
 
 a.nav:hover { text-decoration: underline; color: inherit; }
 
+.hidden { display: none; }
+
 header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; }
 
 @media (prefers-color-scheme: dark) { header { background: black; } }
@@ -38,6 +40,10 @@
 
 header h2 { margin-top: .5em; font-size: 1em; }
 
+header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; }
+
+@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } }
+
 header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; }
 
 header.sticky .text { display: none; }
@@ -52,9 +58,7 @@
 
 main { position: relative; z-index: 1; }
 
-.indexfile footer { margin: 1rem 3.5rem; }
-
-.pyfile footer { margin: 1rem 1rem; }
+footer { margin: 1rem 3.5rem; }
 
 footer .content { padding: 0; color: #666; font-style: italic; }
 
@@ -124,7 +128,9 @@
 
 #help_panel_state { display: none; }
 
-#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; }
+#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; }
+
+#help_panel .keyhelp p { margin-top: .75em; }
 
 #help_panel .legend { font-style: italic; margin-bottom: 1em; }
 
@@ -134,8 +140,6 @@
 
 #help_panel_state:checked ~ #help_panel { display: block; }
 
-.keyhelp { margin-top: .75em; }
-
 kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; }
 
 #source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
--- a/eric7/DebugClients/Python/coverage/htmlfiles/style.scss	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/htmlfiles/style.scss	Tue May 24 11:00:52 2022 +0200
@@ -156,6 +156,10 @@
     }
 }
 
+.hidden {
+    display: none;
+}
+
 // Page structure
 header {
     background: $light-gray1;
@@ -174,6 +178,13 @@
         font-size: 1em;
     }
 
+    p.text {
+        margin: .5em 0 -.5em;
+        color: $light-gray5;
+        @include color-dark($dark-gray5);
+        font-style: italic;
+    }
+
     &.sticky {
         position: fixed;
         left: 0;
@@ -208,19 +219,15 @@
     z-index: 1;
 }
 
-.indexfile footer {
+footer {
     margin: 1rem $left-gutter;
-}
 
-.pyfile footer {
-    margin: 1rem 1rem;
-}
-
-footer .content {
-    padding: 0;
-    color: $light-gray5;
-    @include color-dark($dark-gray5);
-    font-style: italic;
+    .content {
+        padding: 0;
+        color: $light-gray5;
+        @include color-dark($dark-gray5);
+        font-style: italic;
+    }
 }
 
 #index {
@@ -351,6 +358,12 @@
     padding: .75em;
     border: 1px solid #883;
 
+    color: #333;
+
+    .keyhelp p {
+        margin-top: .75em;
+    }
+
     .legend {
         font-style: italic;
         margin-bottom: 1em;
@@ -358,12 +371,10 @@
 
     .indexfile & {
         width: 25em;
-        //min-height: 4em;
     }
 
     .pyfile & {
         width: 18em;
-        //min-height: 8em;
     }
 
     #help_panel_state:checked ~ & {
@@ -371,10 +382,6 @@
     }
 }
 
-.keyhelp {
-    margin-top: .75em;
-}
-
 kbd {
     border: 1px solid black;
     border-color: #888 #333 #333 #888;
--- a/eric7/DebugClients/Python/coverage/inorout.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/inorout.py	Tue May 24 11:00:52 2022 +0200
@@ -591,7 +591,7 @@
             'source_match', 'source_pkgs_match',
             'include_match', 'omit_match',
             'cover_match', 'pylib_match', 'third_match',
-            ]
+        ]
 
         for matcher_name in matcher_names:
             matcher = getattr(self, matcher_name)
--- a/eric7/DebugClients/Python/coverage/jsonreport.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/jsonreport.py	Tue May 24 11:00:52 2022 +0200
@@ -70,7 +70,7 @@
         json.dump(
             self.report_data,
             outfile,
-            indent=4 if self.config.json_pretty_print else None
+            indent=(4 if self.config.json_pretty_print else None),
         )
 
         return self.total.n_statements and self.total.pc_covered
--- a/eric7/DebugClients/Python/coverage/misc.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/misc.py	Tue May 24 11:00:52 2022 +0200
@@ -272,7 +272,7 @@
 
     raise NotImplementedError(
         f"{thing} {name!r} needs to implement {func_name}()"
-        )
+    )
 
 
 class DefaultValue:
--- a/eric7/DebugClients/Python/coverage/parser.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/parser.py	Tue May 24 11:00:52 2022 +0200
@@ -1231,15 +1231,15 @@
             if with_block.break_from:
                 self.process_break_exits(
                     self._combine_finally_starts(with_block.break_from, with_exit)
-                    )
+                )
             if with_block.continue_from:
                 self.process_continue_exits(
                     self._combine_finally_starts(with_block.continue_from, with_exit)
-                    )
+                )
             if with_block.return_from:
                 self.process_return_exits(
                     self._combine_finally_starts(with_block.return_from, with_exit)
-                    )
+                )
         return exits
 
     _handle__AsyncWith = _handle__With
@@ -1287,6 +1287,7 @@
             self.add_arc(start, -start, None, f"didn't finish the {noun} on line {start}")
         return _code_object__expression_callable
 
+    # pylint: disable=too-many-function-args
     _code_object__Lambda = _make_expression_code_method("lambda")
     _code_object__GeneratorExp = _make_expression_code_method("generator expression")
     _code_object__DictComp = _make_expression_code_method("dictionary comprehension")
--- a/eric7/DebugClients/Python/coverage/phystokens.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/phystokens.py	Tue May 24 11:00:52 2022 +0200
@@ -60,7 +60,7 @@
                         99999, "\\\n",
                         (slineno, ccol), (slineno, ccol+2),
                         last_line
-                        )
+                    )
             last_line = ltext
         if ttype not in (tokenize.NEWLINE, tokenize.NL):
             last_ttext = ttext
--- a/eric7/DebugClients/Python/coverage/python.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/python.py	Tue May 24 11:00:52 2022 +0200
@@ -202,8 +202,8 @@
     def no_branch_lines(self):
         no_branch = self.parser.lines_matching(
             join_regex(self.coverage.config.partial_list),
-            join_regex(self.coverage.config.partial_always_list)
-            )
+            join_regex(self.coverage.config.partial_always_list),
+        )
         return no_branch
 
     @expensive
--- a/eric7/DebugClients/Python/coverage/results.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/results.py	Tue May 24 11:00:52 2022 +0200
@@ -271,10 +271,10 @@
         nums.n_branches = self.n_branches + other.n_branches
         nums.n_partial_branches = (
             self.n_partial_branches + other.n_partial_branches
-            )
+        )
         nums.n_missing_branches = (
             self.n_missing_branches + other.n_missing_branches
-            )
+        )
         return nums
 
     def __radd__(self, other):
--- a/eric7/DebugClients/Python/coverage/sqldata.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/sqldata.py	Tue May 24 11:00:52 2022 +0200
@@ -1,10 +1,7 @@
 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 
-"""Sqlite coverage data."""
-
-# TODO: factor out dataop debugging to a wrapper class?
-# TODO: make sure all dataop debugging is in place somehow
+"""SQLite coverage data."""
 
 import collections
 import datetime
@@ -17,6 +14,7 @@
 import socket
 import sqlite3
 import sys
+import textwrap
 import threading
 import zlib
 
@@ -30,7 +28,7 @@
 os = isolate_module(os)
 
 # If you change the schema, increment the SCHEMA_VERSION, and update the
-# docs in docs/dbschema.rst also.
+# docs in docs/dbschema.rst by running "make cogdoc".
 
 SCHEMA_VERSION = 7
 
@@ -252,10 +250,10 @@
 
     def _reset(self):
         """Reset our attributes."""
-        if self._dbs:
+        if not self._no_disk:
             for db in self._dbs.values():
                 db.close()
-        self._dbs = {}
+            self._dbs = {}
         self._file_map = {}
         self._have_used = False
         self._current_context_id = None
@@ -293,7 +291,7 @@
                 self._has_arcs = bool(int(row[0]))
                 self._has_lines = not self._has_arcs
 
-            for path, file_id in db.execute("select path, id from file"):
+            for file_id, path in db.execute("select id, path from file"):
                 self._file_map[path] = file_id
 
     def _init_db(self, db):
@@ -389,8 +387,10 @@
         if filename not in self._file_map:
             if add:
                 with self._connect() as con:
-                    cur = con.execute("insert or replace into file (path) values (?)", (filename,))
-                    self._file_map[filename] = cur.lastrowid
+                    self._file_map[filename] = con.execute_for_rowid(
+                        "insert or replace into file (path) values (?)",
+                        (filename,)
+                    )
         return self._file_map.get(filename)
 
     def _context_id(self, context):
@@ -427,8 +427,10 @@
             self._current_context_id = context_id
         else:
             with self._connect() as con:
-                cur = con.execute("insert into context (context) values (?)", (context,))
-                self._current_context_id = cur.lastrowid
+                self._current_context_id = con.execute_for_rowid(
+                    "insert into context (context) values (?)",
+                    (context,)
+                )
 
     def base_filename(self):
         """The base filename for storing data.
@@ -501,9 +503,6 @@
             self._set_context_id()
             for filename, arcs in arc_data.items():
                 file_id = self._file_id(filename, add=True)
-                from coverage import env
-                if env.PYVERSION == (3, 11, 0, "alpha", 4, 0):
-                    arcs = [(a, b) for a, b in arcs if a is not None and b is not None]
                 data = [(file_id, self._current_context_id, fromno, tono) for fromno, tono in arcs]
                 con.executemany(
                     "insert or ignore into arc " +
@@ -616,19 +615,19 @@
 
         # Collector for all arcs, lines and tracers
         other_data.read()
-        with other_data._connect() as conn:
+        with other_data._connect() as con:
             # Get files data.
-            cur = conn.execute("select path from file")
+            cur = con.execute("select path from file")
             files = {path: aliases.map(path) for (path,) in cur}
             cur.close()
 
             # Get contexts data.
-            cur = conn.execute("select context from context")
+            cur = con.execute("select context from context")
             contexts = [context for (context,) in cur]
             cur.close()
 
             # Get arc data.
-            cur = conn.execute(
+            cur = con.execute(
                 "select file.path, context.context, arc.fromno, arc.tono " +
                 "from arc " +
                 "inner join file on file.id = arc.file_id " +
@@ -638,17 +637,17 @@
             cur.close()
 
             # Get line data.
-            cur = conn.execute(
+            cur = con.execute(
                 "select file.path, context.context, line_bits.numbits " +
                 "from line_bits " +
                 "inner join file on file.id = line_bits.file_id " +
                 "inner join context on context.id = line_bits.context_id"
-                )
+            )
             lines = {(files[path], context): numbits for (path, context, numbits) in cur}
             cur.close()
 
             # Get tracer data.
-            cur = conn.execute(
+            cur = con.execute(
                 "select file.path, tracer " +
                 "from tracer " +
                 "inner join file on file.id = tracer.file_id"
@@ -656,38 +655,39 @@
             tracers = {files[path]: tracer for (path, tracer) in cur}
             cur.close()
 
-        with self._connect() as conn:
-            conn.con.isolation_level = "IMMEDIATE"
+        with self._connect() as con:
+            con.con.isolation_level = "IMMEDIATE"
 
             # Get all tracers in the DB. Files not in the tracers are assumed
             # to have an empty string tracer. Since Sqlite does not support
             # full outer joins, we have to make two queries to fill the
             # dictionary.
-            this_tracers = {path: "" for path, in conn.execute("select path from file")}
+            this_tracers = {path: "" for path, in con.execute("select path from file")}
             this_tracers.update({
                 aliases.map(path): tracer
-                for path, tracer in conn.execute(
+                for path, tracer in con.execute(
                     "select file.path, tracer from tracer " +
                     "inner join file on file.id = tracer.file_id"
                 )
             })
 
             # Create all file and context rows in the DB.
-            conn.executemany(
+            con.executemany(
                 "insert or ignore into file (path) values (?)",
                 ((file,) for file in files.values())
             )
             file_ids = {
                 path: id
-                for id, path in conn.execute("select id, path from file")
+                for id, path in con.execute("select id, path from file")
             }
-            conn.executemany(
+            self._file_map.update(file_ids)
+            con.executemany(
                 "insert or ignore into context (context) values (?)",
                 ((context,) for context in contexts)
             )
             context_ids = {
                 context: id
-                for id, context in conn.execute("select id, context from context")
+                for id, context in con.execute("select id, context from context")
             }
 
             # Prepare tracers and fail, if a conflict is found.
@@ -715,12 +715,12 @@
             )
 
             # Get line data.
-            cur = conn.execute(
+            cur = con.execute(
                 "select file.path, context.context, line_bits.numbits " +
                 "from line_bits " +
                 "inner join file on file.id = line_bits.file_id " +
                 "inner join context on context.id = line_bits.context_id"
-                )
+            )
             for path, context, numbits in cur:
                 key = (aliases.map(path), context)
                 if key in lines:
@@ -732,7 +732,7 @@
                 self._choose_lines_or_arcs(arcs=True)
 
                 # Write the combined data.
-                conn.executemany(
+                con.executemany(
                     "insert or ignore into arc " +
                     "(file_id, context_id, fromno, tono) values (?, ?, ?, ?)",
                     arc_rows
@@ -740,8 +740,8 @@
 
             if lines:
                 self._choose_lines_or_arcs(lines=True)
-                conn.execute("delete from line_bits")
-                conn.executemany(
+                con.execute("delete from line_bits")
+                con.executemany(
                     "insert into line_bits " +
                     "(file_id, context_id, numbits) values (?, ?, ?)",
                     [
@@ -749,14 +749,15 @@
                         for (file, context), numbits in lines.items()
                     ]
                 )
-            conn.executemany(
+            con.executemany(
                 "insert or ignore into tracer (file_id, tracer) values (?, ?)",
                 ((file_ids[filename], tracer) for filename, tracer in tracer_map.items())
             )
 
-        # Update all internal cache data.
-        self._reset()
-        self.read()
+        if not self._no_disk:
+            # Update all internal cache data.
+            self._reset()
+            self.read()
 
     def erase(self, parallel=False):
         """Erase the data in this object.
@@ -782,8 +783,9 @@
 
     def read(self):
         """Start using an existing data file."""
-        with self._connect():       # TODO: doesn't look right
-            self._have_used = True
+        if os.path.exists(self._filename):
+            with self._connect():
+                self._have_used = True
 
     def write(self):
         """Ensure the data is written to the data file."""
@@ -977,7 +979,7 @@
                     "select l.numbits, c.context from line_bits l, context c " +
                     "where l.context_id = c.id " +
                     "and file_id = ?"
-                    )
+                )
                 data = [file_id]
                 if self._query_context_ids is not None:
                     ids_array = ", ".join("?" * len(self._query_context_ids))
@@ -999,9 +1001,7 @@
         with SqliteDb(":memory:", debug=NoDebugging()) as db:
             temp_store = [row[0] for row in db.execute("pragma temp_store")]
             copts = [row[0] for row in db.execute("pragma compile_options")]
-            # Yes, this is overkill. I don't like the long list of options
-            # at the end of "debug sys", but I don't want to omit information.
-            copts = ["; ".join(copts[i:i + 3]) for i in range(0, len(copts), 3)]
+            copts = textwrap.wrap(", ".join(copts), width=75)
 
         return [
             ("sqlite3_version", sqlite3.version),
@@ -1042,7 +1042,7 @@
 
     """
     def __init__(self, filename, debug):
-        self.debug = debug if debug.should("sql") else None
+        self.debug = debug
         self.filename = filename
         self.nest = 0
         self.con = None
@@ -1057,7 +1057,7 @@
         # effectively causing a nested context. However, given the idempotent
         # nature of the tracer operations, sharing a connection among threads
         # is not a problem.
-        if self.debug:
+        if self.debug.should("sql"):
             self.debug.write(f"Connecting to {self.filename!r}")
         try:
             self.con = sqlite3.connect(self.filename, check_same_thread=False)
@@ -1093,13 +1093,13 @@
                 self.con.__exit__(exc_type, exc_value, traceback)
                 self.close()
             except Exception as exc:
-                if self.debug:
+                if self.debug.should("sql"):
                     self.debug.write(f"EXCEPTION from __exit__: {exc}")
                 raise DataError(f"Couldn't end data file {self.filename!r}: {exc}") from exc
 
     def execute(self, sql, parameters=()):
         """Same as :meth:`python:sqlite3.Connection.execute`."""
-        if self.debug:
+        if self.debug.should("sql"):
             tail = f" with {parameters!r}" if parameters else ""
             self.debug.write(f"Executing {sql!r}{tail}")
         try:
@@ -1124,10 +1124,18 @@
                         )
             except Exception:   # pragma: cant happen
                 pass
-            if self.debug:
+            if self.debug.should("sql"):
                 self.debug.write(f"EXCEPTION from execute: {msg}")
             raise DataError(f"Couldn't use data file {self.filename!r}: {msg}") from exc
 
+    def execute_for_rowid(self, sql, parameters=()):
+        """Like execute, but returns the lastrowid."""
+        con = self.execute(sql, parameters)
+        rowid = con.lastrowid
+        if self.debug.should("sqldata"):
+            self.debug.write(f"Row id result: {rowid!r}")
+        return rowid
+
     def execute_one(self, sql, parameters=()):
         """Execute a statement and return the one row that results.
 
@@ -1147,9 +1155,13 @@
 
     def executemany(self, sql, data):
         """Same as :meth:`python:sqlite3.Connection.executemany`."""
-        if self.debug:
+        if self.debug.should("sql"):
             data = list(data)
-            self.debug.write(f"Executing many {sql!r} with {len(data)} rows")
+            final = ":" if self.debug.should("sqldata") else ""
+            self.debug.write(f"Executing many {sql!r} with {len(data)} rows{final}")
+            if self.debug.should("sqldata"):
+                for i, row in enumerate(data):
+                    self.debug.write(f"{i:4d}: {row!r}")
         try:
             return self.con.executemany(sql, data)
         except Exception:   # pragma: cant happen
@@ -1160,7 +1172,7 @@
 
     def executescript(self, script):
         """Same as :meth:`python:sqlite3.Connection.executescript`."""
-        if self.debug:
+        if self.debug.should("sql"):
             self.debug.write("Executing script with {} chars: {}".format(
                 len(script), clipped_repr(script, 100),
             ))
--- a/eric7/DebugClients/Python/coverage/summary.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/summary.py	Tue May 24 11:00:52 2022 +0200
@@ -127,11 +127,11 @@
         if self.config.skip_covered and self.skipped_count:
             self.writeout(
                 fmt_skip_covered % (self.skipped_count, 's' if self.skipped_count > 1 else '')
-                )
+            )
         if self.config.skip_empty and self.empty_count:
             self.writeout(
                 fmt_skip_empty % (self.empty_count, 's' if self.empty_count > 1 else '')
-                )
+            )
 
         return self.total.n_statements and self.total.pc_covered
 
--- a/eric7/DebugClients/Python/coverage/tomlconfig.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/tomlconfig.py	Tue May 24 11:00:52 2022 +0200
@@ -7,15 +7,21 @@
 import os
 import re
 
+from coverage import env
 from coverage.exceptions import ConfigError
 from coverage.misc import import_third_party, substitute_variables
 
-# TOML support is an install-time extra option. (Import typing is here because
-# import_third_party will unload any module that wasn't already imported.
-# tomli imports typing, and if we unload it, later it's imported again, and on
-# Python 3.6, this causes infinite recursion.)
-import typing   # pylint: disable=unused-import, wrong-import-order
-tomli = import_third_party("tomli")
+
+if env.PYVERSION >= (3, 11):
+    import tomllib      # pylint: disable=import-error
+else:
+    # TOML support on Python 3.10 and below is an install-time extra option.
+    # (Import typing is here because import_third_party will unload any module
+    # that wasn't already imported. tomli imports typing, and if we unload it,
+    # later it's imported again, and on Python 3.6, this causes infinite
+    # recursion.)
+    import typing   # pylint: disable=unused-import
+    tomllib = import_third_party("tomli")
 
 
 class TomlDecodeError(Exception):
@@ -45,11 +51,11 @@
                 toml_text = fp.read()
         except OSError:
             return []
-        if tomli is not None:
+        if tomllib is not None:
             toml_text = substitute_variables(toml_text, os.environ)
             try:
-                self.data = tomli.loads(toml_text)
-            except tomli.TOMLDecodeError as err:
+                self.data = tomllib.loads(toml_text)
+            except tomllib.TOMLDecodeError as err:
                 raise TomlDecodeError(str(err)) from err
             return [filename]
         else:
--- a/eric7/DebugClients/Python/coverage/version.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/version.py	Tue May 24 11:00:52 2022 +0200
@@ -5,7 +5,7 @@
 # This file is exec'ed in setup.py, don't import anything!
 
 # Same semantics as sys.version_info.
-version_info = (6, 3, 2, "final", 0)
+version_info = (6, 4, 0, "final", 0)
 
 
 def _make_version(major, minor, micro, releaselevel, serial):
--- a/eric7/DebugClients/Python/coverage/xmlreport.py	Tue May 24 10:22:46 2022 +0200
+++ b/eric7/DebugClients/Python/coverage/xmlreport.py	Tue May 24 11:00:52 2022 +0200
@@ -68,7 +68,7 @@
         xcoverage.setAttribute("timestamp", str(int(time.time()*1000)))
         xcoverage.appendChild(self.xml_out.createComment(
             f" Generated by coverage.py: {__url__} "
-            ))
+        ))
         xcoverage.appendChild(self.xml_out.createComment(f" Based on {DTD_URL} "))
 
         # Call xml_file for each file in the data.
@@ -193,7 +193,7 @@
                     xline.setAttribute(
                         "condition-coverage",
                         "%d%% (%d/%d)" % (100*taken//total, taken, total)
-                        )
+                    )
                 if line in missing_branch_arcs:
                     annlines = ["exit" if b < 0 else str(b) for b in missing_branch_arcs[line]]
                     xline.setAttribute("missing-branches", ",".join(annlines))

eric ide

mercurial