Browse Source

Apply Black to grass package, use Python 3 style everywhere (#1382)

This applies Black 20.8b1 formatting to grass Python package and all related tests.
ctypes is not formatted by Black and two additional files are ignored because
of bug in Black which breaks strings with escape sequence for tab.

This adds pyproject.toml file with Black configuration for the project.
In the CI, instead of mutliple Black runs, just run once for the whole tree and ignore directories which are not formatted yet.
Black is very fast (so no need for multiple job) and only one configuration is needed (unlike Flake8)

Newly, only Python 3 versions are specified as targets for Black,
so also string literals starting with u (Python 2) are replaced by
simple strings.

This also applies Python 3 targeted Black to already formatted code.
This replaces unicode literals (Python 2) by plain Python 3 strings and adds commas to kwargs in function calls.

Update Flake8 config for use with Black. Enable Flake8 whitespace checks and fix remaining issues not
touched by Black. Ignore many E226 in images2gif.py which is now ignored by Black.
Enable long line warning in Flake8. Fix or ignore lines not fixed by Black.
Vaclav Petras 4 years ago
parent
commit
1aab3bbcff
100 changed files with 5773 additions and 4170 deletions
  1. 1 10
      .github/workflows/black.yml
  2. 12 12
      man/sphinx/conf.py
  3. 39 0
      pyproject.toml
  4. 17 20
      python/grass/.flake8
  5. 7 7
      python/grass/__init__.py
  6. 37 46
      python/grass/bandref/reader.py
  7. 159 102
      python/grass/docs/conf.py
  8. 5 5
      python/grass/exceptions/__init__.py
  9. 5 6
      python/grass/exceptions/testsuite/test_ScriptError.py
  10. 63 46
      python/grass/grassdb/checks.py
  11. 545 363
      python/grass/gunittest/case.py
  12. 96 74
      python/grass/gunittest/checkers.py
  13. 26 19
      python/grass/gunittest/gmodules.py
  14. 19 12
      python/grass/gunittest/gutils.py
  15. 130 87
      python/grass/gunittest/invoker.py
  16. 53 38
      python/grass/gunittest/loader.py
  17. 103 62
      python/grass/gunittest/main.py
  18. 340 176
      python/grass/gunittest/multireport.py
  19. 82 43
      python/grass/gunittest/multirunner.py
  20. 446 397
      python/grass/gunittest/reporters.py
  21. 57 47
      python/grass/gunittest/runner.py
  22. 6 6
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_errors/testsuite/test_error.py
  23. 8 6
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_errors/testsuite/test_import_error.py
  24. 1 1
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_gfatalerror.py
  25. 1 1
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_osexit_one.py
  26. 1 1
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_osexit_zero.py
  27. 3 3
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_segfaut.py
  28. 1 1
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_sysexit_one.py
  29. 1 1
      python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_sysexit_zero.py
  30. 1 1
      python/grass/gunittest/testsuite/data/samplecode/submodule_test_fail/testsuite/test_fail.py
  31. 3 2
      python/grass/gunittest/testsuite/data/samplecode/testsuite/test_good_and_bad.py
  32. 2 1
      python/grass/gunittest/testsuite/data/samplecode/testsuite/test_python_unittest.py
  33. 2 1
      python/grass/gunittest/testsuite/data/samplecode/testsuite/test_success.py
  34. 206 164
      python/grass/gunittest/testsuite/test_assertions.py
  35. 114 83
      python/grass/gunittest/testsuite/test_assertions_rast3d.py
  36. 188 110
      python/grass/gunittest/testsuite/test_assertions_vect.py
  37. 151 122
      python/grass/gunittest/testsuite/test_checkers.py
  38. 92 53
      python/grass/gunittest/testsuite/test_gmodules.py
  39. 9 7
      python/grass/gunittest/testsuite/test_gunitest_doctests.py
  40. 23 16
      python/grass/gunittest/testsuite/test_module_assertions.py
  41. 3 1
      python/grass/gunittest/utils.py
  42. 35 21
      python/grass/imaging/images2avi.py
  43. 13 7
      python/grass/imaging/images2gif.py
  44. 15 15
      python/grass/imaging/images2ims.py
  45. 132 130
      python/grass/imaging/images2swf.py
  46. 16 11
      python/grass/imaging/operations.py
  47. 18 26
      python/grass/pydispatch/dispatcher.py
  48. 2 10
      python/grass/pydispatch/robust.py
  49. 17 17
      python/grass/pydispatch/robustapply.py
  50. 20 16
      python/grass/pydispatch/saferef.py
  51. 9 4
      python/grass/pydispatch/signal.py
  52. 9 7
      python/grass/pygrass/errors.py
  53. 98 70
      python/grass/pygrass/gis/__init__.py
  54. 248 200
      python/grass/pygrass/gis/region.py
  55. 1 2
      python/grass/pygrass/gis/testsuite/test_gis.py
  56. 17 11
      python/grass/pygrass/gis/testsuite/test_pygrass_gis_doctests.py
  57. 107 100
      python/grass/pygrass/messages/__init__.py
  58. 9 7
      python/grass/pygrass/messages/testsuite/test_pygrass_messages_doctests.py
  59. 178 121
      python/grass/pygrass/modules/grid/grid.py
  60. 28 11
      python/grass/pygrass/modules/grid/patch.py
  61. 19 10
      python/grass/pygrass/modules/grid/split.py
  62. 10 8
      python/grass/pygrass/modules/grid/testsuite/test_pygrass_modules_grid_doctests.py
  63. 5 1
      python/grass/pygrass/modules/interface/__init__.py
  64. 2 0
      python/grass/pygrass/modules/interface/docstring.py
  65. 12 8
      python/grass/pygrass/modules/interface/env.py
  66. 28 20
      python/grass/pygrass/modules/interface/flag.py
  67. 128 78
      python/grass/pygrass/modules/interface/module.py
  68. 106 67
      python/grass/pygrass/modules/interface/parameter.py
  69. 42 34
      python/grass/pygrass/modules/interface/read.py
  70. 12 12
      python/grass/pygrass/modules/interface/testsuite/test_flag.py
  71. 15 10
      python/grass/pygrass/modules/interface/testsuite/test_modules.py
  72. 227 114
      python/grass/pygrass/modules/interface/testsuite/test_parameter.py
  73. 9 7
      python/grass/pygrass/modules/interface/testsuite/test_pygrass_modules_interface_doctests.py
  74. 12 5
      python/grass/pygrass/modules/interface/typedict.py
  75. 56 48
      python/grass/pygrass/modules/shortcuts.py
  76. 19 11
      python/grass/pygrass/modules/testsuite/test_import_isolation.py
  77. 10 8
      python/grass/pygrass/modules/testsuite/test_pygrass_modules_doctests.py
  78. 40 38
      python/grass/pygrass/orderdict.py
  79. 130 107
      python/grass/pygrass/raster/__init__.py
  80. 105 65
      python/grass/pygrass/raster/abstract.py
  81. 15 12
      python/grass/pygrass/raster/buffer.py
  82. 91 83
      python/grass/pygrass/raster/history.py
  83. 25 15
      python/grass/pygrass/raster/raster_type.py
  84. 23 20
      python/grass/pygrass/raster/rowio.py
  85. 48 40
      python/grass/pygrass/raster/segment.py
  86. 16 13
      python/grass/pygrass/raster/testsuite/test_category.py
  87. 12 7
      python/grass/pygrass/raster/testsuite/test_history.py
  88. 10 8
      python/grass/pygrass/raster/testsuite/test_numpy.py
  89. 20 18
      python/grass/pygrass/raster/testsuite/test_pygrass_raster.py
  90. 26 15
      python/grass/pygrass/raster/testsuite/test_pygrass_raster_doctests.py
  91. 19 24
      python/grass/pygrass/raster/testsuite/test_raster_img.py
  92. 18 11
      python/grass/pygrass/raster/testsuite/test_raster_region.py
  93. 211 188
      python/grass/pygrass/rpc/__init__.py
  94. 44 38
      python/grass/pygrass/rpc/base.py
  95. 15 10
      python/grass/pygrass/rpc/testsuite/test_pygrass_rpc_doctests.py
  96. 22 6
      python/grass/pygrass/shell/conversion.py
  97. 1 1
      python/grass/pygrass/shell/show.py
  98. 9 7
      python/grass/pygrass/shell/testsuite/test_pygrass_shell_doctests.py
  99. 131 64
      python/grass/pygrass/tests/benchmark.py
  100. 0 0
      python/grass/pygrass/tests/set_mapset.py

+ 1 - 10
.github/workflows/black.yml

@@ -6,16 +6,8 @@ on:
 
 jobs:
   run-black:
-    name: ${{ matrix.directory }}
+    name: Check
     runs-on: ubuntu-20.04
-    strategy:
-      matrix:
-        directory:
-          - lib/init
-          - man
-          - scripts
-          - utils
-      fail-fast: false
 
     steps:
       - uses: actions/checkout@v2
@@ -32,5 +24,4 @@ jobs:
 
       - name: Run Black
         run: |
-          cd ${{ matrix.directory }}
           black --check --diff .

+ 12 - 12
man/sphinx/conf.py

@@ -38,8 +38,8 @@ source_suffix = ".txt"
 master_doc = "index"
 
 # General information about the project.
-project = u"GRASS 7.9 Documentation"
-copyright = u"2019, GRASS Development Team"
+project = "GRASS 7.9 Documentation"
+copyright = "2019, GRASS Development Team"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -182,8 +182,8 @@ latex_documents = [
     (
         "content",
         "grass79Documentation.tex",
-        u"GRASS 7.9 Documentation",
-        u"GRASS Development Team",
+        "GRASS 7.9 Documentation",
+        "GRASS Development Team",
         "manual",
     ),
 ]
@@ -217,8 +217,8 @@ man_pages = [
     (
         "content",
         "grass79documentation",
-        u"GRASS 7.9 Documentation",
-        [u"GRASS Development Team"],
+        "GRASS 7.9 Documentation",
+        ["GRASS Development Team"],
         1,
     )
 ]
@@ -236,8 +236,8 @@ texinfo_documents = [
     (
         "content",
         "grass79Documentation",
-        u"GRASS 7.9 Documentation",
-        u"GRASS Development Team",
+        "GRASS 7.9 Documentation",
+        "GRASS Development Team",
         "grass79Documentation",
         "One line description of project.",
         "Miscellaneous",
@@ -257,10 +257,10 @@ texinfo_documents = [
 # -- Options for Epub output ---------------------------------------------------
 
 # Bibliographic Dublin Core info.
-epub_title = u"GRASS 7.9 Documentation"
-epub_author = u"GRASS Development Team"
-epub_publisher = u"GRASS Development Team"
-epub_copyright = u"2017, GRASS Development Team"
+epub_title = "GRASS 7.9 Documentation"
+epub_author = "GRASS Development Team"
+epub_publisher = "GRASS Development Team"
+epub_copyright = "2017, GRASS Development Team"
 
 # The language of the text. It defaults to the language option
 # or en if the language is not set.

+ 39 - 0
pyproject.toml

@@ -0,0 +1,39 @@
+[tool.black]
+line-length = 88
+target-version = ['py36', 'py37', 'py38']
+include = '\.pyi?$'
+exclude = '''
+(
+    # exclude directories in the root of the project
+    /(
+          \.eggs
+        | \.git
+        | \.hg
+        | \.mypy_cache
+        | \.tox
+        | \.venv
+        | _build
+        | buck-out
+        | build
+        | bin\.
+        | dist\.
+    )/
+    | python/grass/ctypes
+    # Bug in Black related to tab escape prevents these from being formatted correctly.
+    # https://github.com/psf/black/issues/1970
+    | python/grass/imaging/images2gif.py
+    | python/grass/pygrass/raster/category.py
+    # Directories and files not yet under Black
+    | db
+    | doc
+    | general
+    | gui
+    | imagery
+    | lib/gis
+    | temporal
+    | raster
+    | vector
+    | docker/testdata/test_grass_session.py
+    | display/d.mon/render_cmd.py
+)
+'''

+ 17 - 20
python/grass/.flake8

@@ -1,24 +1,8 @@
 [flake8]
 ignore =
-    E121, # continuation line under-indented for hanging indent
-    E125, # continuation line with same indent as next logical line
-    E127, # continuation line over-indented for visual indent
-    E128, # continuation line under-indented for visual indent
-    E202, # whitespace before ')'
-    E211, # whitespace before '('
-    E221, # multiple spaces before operator
-    E226, # missing whitespace around arithmetic operator
-    E231, # missing whitespace after ':'
-    E251, # unexpected spaces around keyword / parameter equals
-    E261, # at least two spaces before inline comment
-    E265, # block comment should start with '# '
+    E203,  # whitespace before ':' (Black)
+    W503,  # line break before binary operator (Black)
     E266, # too many leading '#' for block comment
-    E271, # multiple spaces after keyword
-    E272, # multiple spaces before keyword
-    E302, # expected 2 blank lines, found 1
-    E303, # too many blank lines (3)
-    E305, # expected 2 blank lines after class or function definition, found 1
-    E501, # line too long (183 > 150 characters)
     E722, # do not use bare 'except'
     E741, # ambiguous variable name 'l'
     F403, # 'from ctypes import *' used; unable to detect undefined names
@@ -34,13 +18,26 @@ per-file-ignores =
     # TODO: Is this really needed?
     pygrass/vector/__init__.py: E402,
     pygrass/raster/__init__.py: E402,
-    pygrass/utils.py: E402,
+    # Files and directories which need fixes or specific exceptions
+    gunittest/*.py: E501  # These are mainly just todo comments
+    pygrass/vector/table.py: E501
+    pygrass/vector/__init__.py: E501, E402
+    pygrass/modules/interface/*.py: E501, F401
+    pygrass/modules/grid/*.py: E501, F401
+    pygrass/raster/*.py: E501
+    pygrass/rpc/__init__.py: E501, F401
+    pygrass/utils.py: E402, E501
+    script/db.py: E501
+    script/vector.py: E501  # Long doctest lines which need review anyway
+    temporal/*.py: E501
     # Current benchmarks/tests are changing sys.path before import.
     # Possibly, a different approach should be taken there anyway.
-    pygrass/tests/benchmark.py: E402, F401, F821
+    pygrass/tests/benchmark.py: E501, E402, F401, F821
     # Configuration file for Sphinx:
     # Ignoring import/code mix and line length.
     docs/conf.py: E402, E501,
+    # Files not managed by Black
+    imaging/images2gif.py: E226, E501
     # Unused imports
     */__init__.py: F401,
     */*/__init__.py: F401,

+ 7 - 7
python/grass/__init__.py

@@ -18,15 +18,15 @@ import six
 # - https://pymotw.com/2//gettext/index.html#application-vs-module-localization
 # - https://www.wefearchange.org/2012/06/the-right-way-to-internationalize-your.html
 #
-_LOCALE_DIR = os.path.join(os.getenv("GISBASE"), 'locale')
+_LOCALE_DIR = os.path.join(os.getenv("GISBASE"), "locale")
 if six.PY2:
-    gettext.install('grasslibs', _LOCALE_DIR, unicode=True)
-    gettext.install('grassmods', _LOCALE_DIR, unicode=True)
-    gettext.install('grasswxpy', _LOCALE_DIR, unicode=True)
+    gettext.install("grasslibs", _LOCALE_DIR, unicode=True)
+    gettext.install("grassmods", _LOCALE_DIR, unicode=True)
+    gettext.install("grasswxpy", _LOCALE_DIR, unicode=True)
 else:
-    gettext.install('grasslibs', _LOCALE_DIR)
-    gettext.install('grassmods', _LOCALE_DIR)
-    gettext.install('grasswxpy', _LOCALE_DIR)
+    gettext.install("grasslibs", _LOCALE_DIR)
+    gettext.install("grassmods", _LOCALE_DIR)
+    gettext.install("grasswxpy", _LOCALE_DIR)
 
 
 __all__ = ["script", "temporal"]

+ 37 - 46
python/grass/bandref/reader.py

@@ -14,15 +14,17 @@ from collections import OrderedDict
 # https://github.com/radiantearth/stac-spec/blob/master/extensions/eo/README.md#band-object
 # custom names must be possible
 
+
 class BandReferenceReaderError(Exception):
     pass
 
+
 class BandReferenceReader:
     """Band references reader"""
 
     def __init__(self):
         self._json_files = glob.glob(
-            os.path.join(os.environ['GISBASE'], 'etc', 'g.bands', '*.json')
+            os.path.join(os.environ["GISBASE"], "etc", "g.bands", "*.json")
         )
         if not self._json_files:
             raise BandReferenceReaderError("No band definitions found")
@@ -35,15 +37,11 @@ class BandReferenceReader:
         for json_file in self._json_files:
             try:
                 with open(json_file) as fd:
-                    config = json.load(
-                        fd,
-                        object_pairs_hook=OrderedDict
-                    )
+                    config = json.load(fd, object_pairs_hook=OrderedDict)
             except json.decoder.JSONDecodeError as e:
                 raise BandReferenceReaderError(
-                    "Unable to parse '{}': {}".format(
-                        json_file, e
-                    ))
+                    "Unable to parse '{}': {}".format(json_file, e)
+                )
 
             # check if configuration is valid
             self._check_config(config)
@@ -59,12 +57,12 @@ class BandReferenceReader:
         :param dict config: configuration to be validated
         """
         for items in config.values():
-            for item in ('shortcut', 'bands'):
+            for item in ("shortcut", "bands"):
                 if item not in items.keys():
                     raise BandReferenceReaderError(
-                        "Invalid band definition: <{}> is missing".format(item
-                                                                          ))
-            if len(items['bands']) < 1:
+                        "Invalid band definition: <{}> is missing".format(item)
+                    )
+            if len(items["bands"]) < 1:
                 raise BandReferenceReaderError(
                     "Invalid band definition: no bands defined"
                 )
@@ -76,25 +74,24 @@ class BandReferenceReader:
         :param str band: band identifier
         :param str item: items to be printed out
         """
+
         def print_kv(k, v, indent):
             if isinstance(v, OrderedDict):
-                print ('{}{}:'.format(' ' * indent * 2, k))
+                print("{}{}:".format(" " * indent * 2, k))
                 for ki, vi in v.items():
                     print_kv(ki, vi, indent * 2)
             else:
-                print ('{}{}: {}'.format(' ' * indent * 2, k, v))
+                print("{}{}: {}".format(" " * indent * 2, k, v))
 
         indent = 4
-        print ('{}band: {}'.format(
-            ' ' * indent, band
-        ))
+        print("{}band: {}".format(" " * indent, band))
         for k, v in item[band].items():
             print_kv(k, v, indent)
 
     def _print_band(self, shortcut, band, tag=None):
         sys.stdout.write(self._band_identifier(shortcut, band))
         if tag:
-            sys.stdout.write(' {}'.format(tag))
+            sys.stdout.write(" {}".format(tag))
         sys.stdout.write(os.linesep)
 
     def print_info(self, shortcut=None, band=None, extended=False):
@@ -110,48 +107,41 @@ class BandReferenceReader:
         for root in self.config.values():
             for item in root.values():
                 try:
-                    if shortcut and re.match(shortcut, item['shortcut']) is None:
+                    if shortcut and re.match(shortcut, item["shortcut"]) is None:
                         continue
                 except re.error as e:
-                    raise BandReferenceReaderError(
-                        "Invalid pattern: {}".format(e)
-                    )
+                    raise BandReferenceReaderError("Invalid pattern: {}".format(e))
 
                 found = True
-                if band and band not in item['bands']:
+                if band and band not in item["bands"]:
                     raise BandReferenceReaderError(
-                        "Band <{}> not found in <{}>".format(
-                            band, shortcut
-                        ))
+                        "Band <{}> not found in <{}>".format(band, shortcut)
+                    )
 
                 # print generic information
                 if extended:
                     for subitem in item.keys():
-                        if subitem == 'bands':
+                        if subitem == "bands":
                             # bands item is processed bellow
                             continue
-                        print ('{}: {}'.format(
-                            subitem, item[subitem]
-                        ))
+                        print("{}: {}".format(subitem, item[subitem]))
 
                     # print detailed band information
                     if band:
-                        self._print_band_extended(band, item['bands'])
+                        self._print_band_extended(band, item["bands"])
                     else:
-                        for iband in item['bands']:
-                            self._print_band_extended(iband, item['bands'])
+                        for iband in item["bands"]:
+                            self._print_band_extended(iband, item["bands"])
                 else:
                     # basic information only
                     if band:
                         self._print_band(
-                            item['shortcut'], band,
-                            item['bands'][band].get('tag')
+                            item["shortcut"], band, item["bands"][band].get("tag")
                         )
                     else:
-                        for iband in item['bands']:
+                        for iband in item["bands"]:
                             self._print_band(
-                                item['shortcut'], iband,
-                                item['bands'][iband].get('tag')
+                                item["shortcut"], iband, item["bands"][iband].get("tag")
                             )
 
         # raise error when defined shortcut not found
@@ -170,7 +160,7 @@ class BandReferenceReader:
         :return str: file basename if found or None
         """
         try:
-            shortcut, band = band_reference.split('_')
+            shortcut, band = band_reference.split("_")
         except ValueError:
             # raise BandReferenceReaderError("Invalid band identifier <{}>".format(
             #    band_reference
@@ -180,8 +170,11 @@ class BandReferenceReader:
 
         for filename, config in self.config.items():
             for root in config.keys():
-                if config[root]['shortcut'].upper() == shortcut.upper() and \
-                   band.upper() in map(lambda x: x.upper(), config[root]['bands'].keys()):
+                if config[root][
+                    "shortcut"
+                ].upper() == shortcut.upper() and band.upper() in map(
+                    lambda x: x.upper(), config[root]["bands"].keys()
+                ):
                     return filename
 
         return None
@@ -194,12 +187,10 @@ class BandReferenceReader:
         bands = []
         for root in self.config.values():
             for item in root.values():
-                for band in item['bands']:
-                    bands.append(
-                        self._band_identifier(item['shortcut'], band)
-                    )
+                for band in item["bands"]:
+                    bands.append(self._band_identifier(item["shortcut"], band))
         return bands
 
     @staticmethod
     def _band_identifier(shortcut, band):
-        return '{}_{}'.format(shortcut, band)
+        return "{}_{}".format(shortcut, band)

+ 159 - 102
python/grass/docs/conf.py

@@ -21,17 +21,59 @@ from shutil import copy
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-if not os.getenv('GISBASE'):
+if not os.getenv("GISBASE"):
     sys.exit("GISBASE not defined")
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'ctypes')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'exceptions')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'gunittest')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'imaging')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'pydispatch')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'pygrass')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'script')))
-sys.path.insert(0, os.path.abspath(os.path.join(os.environ['GISBASE'], 'etc', 'python', 'grass', 'temporal')))
+sys.path.insert(
+    0, os.path.abspath(os.path.join(os.environ["GISBASE"], "etc", "python", "grass"))
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "ctypes")
+    ),
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "exceptions")
+    ),
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "gunittest")
+    ),
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "imaging")
+    ),
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "pydispatch")
+    ),
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "pygrass")
+    ),
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "script")
+    ),
+)
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "temporal")
+    ),
+)
 
 from grass.script import core
 
@@ -41,9 +83,10 @@ footer_tmpl = string.Template(
 <p><a href="../index.html">Help Index</a> | <a href="../topics.html">Topics Index</a> | <a href="../keywords.html">Keywords Index</a> | <a href="../full_index.html">Full Index</a></p>
 <p>&copy; 2003-${year} <a href="https://grass.osgeo.org">GRASS Development Team</a>, GRASS GIS ${grass_version} Reference Manual</p>
 {% endblock %}
-""")
+"""
+)
 
-grass_version = core.version()['version']
+grass_version = core.version()["version"]
 today = date.today()
 
 copy("_templates/layout.html.template", "_templates/layout.html")
@@ -54,149 +97,149 @@ with open("_templates/layout.html", "a") as f:
 # -- General configuration -----------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.doctest',
-    'sphinx.ext.todo',
-    'sphinx.ext.coverage',
-    'sphinx.ext.mathjax',
-    'sphinx.ext.ifconfig',
-    'sphinx.ext.viewcode',
+    "sphinx.ext.autodoc",
+    "sphinx.ext.doctest",
+    "sphinx.ext.todo",
+    "sphinx.ext.coverage",
+    "sphinx.ext.mathjax",
+    "sphinx.ext.ifconfig",
+    "sphinx.ext.viewcode",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'Python library documentation'
-copyright = u'2014, GRASS Development Team'
+project = "Python library documentation"
+copyright = "2014, GRASS Development Team"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-#version = '0.1'
+# version = '0.1'
 # The full version, including alpha/beta/rc tags.
-#release = '0.1'
+# release = '0.1'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 # http://sphinx-doc.org/config.html#options-for-internationalization
-language = 'en'
+language = "en"
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
-exclude_patterns = ['_build']
+exclude_patterns = ["_build"]
 
 # The reST default role (used for this markup: `text`) to use for all
 # documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 # If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
 
 
 # -- Options for HTML output ----------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'traditional'
+html_theme = "traditional"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-#html_theme_options = {}
+# html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # 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"]
 
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
 # directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-html_sidebars = {"**":["localtoc.html",'relations.html','searchbox.html']}
+html_sidebars = {"**": ["localtoc.html", "relations.html", "searchbox.html"]}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
 html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
 html_show_sourcelink = True
@@ -210,55 +253,58 @@ html_show_copyright = True
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'PythonLibdoc'
+htmlhelp_basename = "PythonLibdoc"
 
 
 # -- Options for LaTeX output ---------------------------------------------
 
 latex_elements = {
     # The paper size ('letterpaper' or 'a4paper').
-    'papersize': 'a4paper',
-
+    "papersize": "a4paper",
     # The font size ('10pt', '11pt' or '12pt').
-    'pointsize': '10pt',
-
+    "pointsize": "10pt",
     # Additional stuff for the LaTeX preamble.
-    #'preamble': '',
+    # 'preamble': '',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-  ('index', 'PythonLib.tex', u'Python Library Documentation',
-   u'GRASS Development Team', 'manual'),
+    (
+        "index",
+        "PythonLib.tex",
+        "Python Library Documentation",
+        "GRASS Development Team",
+        "manual",
+    ),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
 
 # If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
 
 
 # -- Options for manual page output ---------------------------------------
@@ -266,12 +312,17 @@ latex_documents = [
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('index', 'PythonLib', u'Python Library Documentation',
-     [u'GRASS Development Team'], 1)
+    (
+        "index",
+        "PythonLib",
+        "Python Library Documentation",
+        ["GRASS Development Team"],
+        1,
+    )
 ]
 
 # If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
 
 
 # -- Options for Texinfo output -------------------------------------------
@@ -280,89 +331,95 @@ man_pages = [
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'PythonLib', u'Python Library Documentation',
-   u'GRASS Development Team', 'PythonLib', 'One line description of project.',
-   'Miscellaneous'),
+    (
+        "index",
+        "PythonLib",
+        "Python Library Documentation",
+        "GRASS Development Team",
+        "PythonLib",
+        "One line description of project.",
+        "Miscellaneous",
+    ),
 ]
 
 # Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
 
 # If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
 
 
 # -- Options for Epub output ----------------------------------------------
 
 # Bibliographic Dublin Core info.
-epub_title = u'PythonLib'
-epub_author = u'GRASS Development Team'
-epub_publisher = u'GRASS Development Team'
-epub_copyright = u'2014, GRASS Development Team'
+epub_title = "PythonLib"
+epub_author = "GRASS Development Team"
+epub_publisher = "GRASS Development Team"
+epub_copyright = "2014, GRASS Development Team"
 
 # The basename for the epub file. It defaults to the project name.
-#epub_basename = u'wxGUI'
+# epub_basename = u'wxGUI'
 
 # The HTML theme for the epub output. Since the default themes are not optimized
 # for small screen space, using the same theme for HTML and epub output is
 # usually not wise. This defaults to 'epub', a theme designed to save visual
 # space.
-#epub_theme = 'epub'
+# epub_theme = 'epub'
 
 # The language of the text. It defaults to the language option
 # or en if the language is not set.
-#epub_language = ''
+# epub_language = ''
 
 # The scheme of the identifier. Typical schemes are ISBN or URL.
-#epub_scheme = ''
+# epub_scheme = ''
 
 # The unique identifier of the text. This can be a ISBN number
 # or the project homepage.
-#epub_identifier = ''
+# epub_identifier = ''
 
 # A unique identification for the text.
-#epub_uid = ''
+# epub_uid = ''
 
 # A tuple containing the cover image and cover page html template filenames.
-#epub_cover = ()
+# epub_cover = ()
 
 # A sequence of (type, uri, title) tuples for the guide element of content.opf.
-#epub_guide = ()
+# epub_guide = ()
 
 # HTML files that should be inserted before the pages created by sphinx.
 # The format is a list of tuples containing the path and title.
-#epub_pre_files = []
+# epub_pre_files = []
 
 # HTML files shat should be inserted after the pages created by sphinx.
 # The format is a list of tuples containing the path and title.
-#epub_post_files = []
+# epub_post_files = []
 
 # A list of files that should not be packed into the epub file.
-epub_exclude_files = ['search.html']
+epub_exclude_files = ["search.html"]
 
 # The depth of the table of contents in toc.ncx.
-#epub_tocdepth = 3
+# epub_tocdepth = 3
 
 # Allow duplicate toc entries.
-#epub_tocdup = True
+# epub_tocdup = True
 
 # Choose between 'default' and 'includehidden'.
-#epub_tocscope = 'default'
+# epub_tocscope = 'default'
 
 # Fix unsupported image types using the PIL.
-#epub_fix_images = False
+# epub_fix_images = False
 
 # Scale large images.
-#epub_max_image_width = 0
+# epub_max_image_width = 0
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#epub_show_urls = 'inline'
+# epub_show_urls = 'inline'
 
 # If false, no index is generated.
-#epub_use_index = True
+# epub_use_index = True

+ 5 - 5
python/grass/exceptions/__init__.py

@@ -35,11 +35,11 @@ class ParameterError(Exception):
 class ScriptError(Exception):
     """Raised during script execution. ::
 
-        >>> error = ScriptError('My error message!')
-        >>> error.value
-        'My error message!'
-        >>> print(error)
-        My error message!
+    >>> error = ScriptError('My error message!')
+    >>> error.value
+    'My error message!'
+    >>> print(error)
+    My error message!
     """
 
     def __init__(self, value):

+ 5 - 6
python/grass/exceptions/testsuite/test_ScriptError.py

@@ -6,15 +6,14 @@ from grass.exceptions import ScriptError
 
 
 class TestTextAssertions(TestCase):
-
     def test_get_value(self):
-        error = ScriptError('error')
-        self.assertEqual('error', error.value)
+        error = ScriptError("error")
+        self.assertEqual("error", error.value)
 
     def test_str(self):
-        error = ScriptError('error')
-        self.assertEqual('error', str(error))
+        error = ScriptError("error")
+        self.assertEqual("error", str(error))
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 63 - 46
python/grass/grassdb/checks.py

@@ -65,17 +65,18 @@ def is_location_valid(database, location):
 
 def is_mapset_current(database, location, mapset):
     genv = gisenv()
-    if (database == genv['GISDBASE'] and
-            location == genv['LOCATION_NAME'] and
-            mapset == genv['MAPSET']):
+    if (
+        database == genv["GISDBASE"]
+        and location == genv["LOCATION_NAME"]
+        and mapset == genv["MAPSET"]
+    ):
         return True
     return False
 
 
 def is_location_current(database, location):
     genv = gisenv()
-    if (database == genv['GISDBASE'] and
-            location == genv['LOCATION_NAME']):
+    if database == genv["GISDBASE"] and location == genv["LOCATION_NAME"]:
         return True
     return False
 
@@ -90,7 +91,7 @@ def is_current_user_mapset_owner(mapset_path):
         # Mapset just needs to be accessible for writing.
         return os.access(mapset_path, os.W_OK)
     # Mapset needs to be owned by user.
-    if sys.platform == 'win32':
+    if sys.platform == "win32":
         return True
     stat_info = os.stat(mapset_path)
     mapset_uid = stat_info.st_uid
@@ -105,7 +106,7 @@ def is_different_mapset_owner(mapset_path):
 def get_mapset_owner(mapset_path):
     """Returns mapset owner name or None if owner name unknown.
     On Windows it always returns None."""
-    if sys.platform == 'win32':
+    if sys.platform == "win32":
         return None
     try:
         path = Path(mapset_path)
@@ -115,7 +116,7 @@ def get_mapset_owner(mapset_path):
 
 
 def is_current_mapset_in_demolocation():
-    return gisenv()['LOCATION_NAME'] == "world_latlong_wgs84"
+    return gisenv()["LOCATION_NAME"] == "world_latlong_wgs84"
 
 
 def is_mapset_locked(mapset_path):
@@ -146,13 +147,14 @@ def get_mapset_lock_info(mapset_path):
     """
     info = {}
     lock_name = ".gislock"
-    info['lockpath'] = os.path.join(mapset_path, lock_name)
+    info["lockpath"] = os.path.join(mapset_path, lock_name)
     try:
-        info['owner'] = Path(info['lockpath']).owner()
+        info["owner"] = Path(info["lockpath"]).owner()
     except KeyError:
-        info['owner'] = None
-    info['timestamp'] = (datetime.datetime.fromtimestamp(
-        os.path.getmtime(info['lockpath']))).replace(microsecond=0)
+        info["owner"] = None
+    info["timestamp"] = (
+        datetime.datetime.fromtimestamp(os.path.getmtime(info["lockpath"]))
+    ).replace(microsecond=0)
     return info
 
 
@@ -338,18 +340,21 @@ def get_mapset_name_invalid_reason(database, location, mapset_name):
         message = _(
             "Name '{}' is not a valid name for location or mapset. "
             "Please use only ASCII characters excluding characters {} "
-            "and space.").format(mapset_name, '/"\'@,=*~')
+            "and space."
+        ).format(mapset_name, "/\"'@,=*~")
     # Check reserved mapset name
-    elif mapset_name.lower() == 'ogr':
+    elif mapset_name.lower() == "ogr":
         message = _(
             "Name '{}' is reserved for direct "
             "read access to OGR layers. Please use "
-            "another name for your mapset.").format(mapset_name)
+            "another name for your mapset."
+        ).format(mapset_name)
     # Check whether mapset exists
     elif mapset_exists(database, location, mapset_name):
         message = _(
             "Mapset  <{mapset}> already exists. Please consider using "
-            "another name for your mapset.").format(mapset=mapset_path)
+            "another name for your mapset."
+        ).format(mapset=mapset_path)
 
     return message
 
@@ -371,12 +376,14 @@ def get_location_name_invalid_reason(grassdb, location_name):
         message = _(
             "Name '{}' is not a valid name for location or mapset. "
             "Please use only ASCII characters excluding characters {} "
-            "and space.").format(location_name, '/"\'@,=*~')
+            "and space."
+        ).format(location_name, "/\"'@,=*~")
     # Check whether location exists
     elif location_exists(grassdb, location_name):
         message = _(
             "Location  <{location}> already exists. Please consider using "
-            "another name for your location.").format(location=location_path)
+            "another name for your location."
+        ).format(location=location_path)
 
     return message
 
@@ -386,8 +393,11 @@ def is_mapset_name_valid(database, location, mapset_name):
 
     Returns True if mapset name is valid, otherwise False.
     """
-    return gs.legal_name(mapset_name) and mapset_name.lower() != "ogr" and not \
-        mapset_exists(database, location, mapset_name)
+    return (
+        gs.legal_name(mapset_name)
+        and mapset_name.lower() != "ogr"
+        and not mapset_exists(database, location, mapset_name)
+    )
 
 
 def is_location_name_valid(database, location_name):
@@ -395,8 +405,7 @@ def is_location_name_valid(database, location_name):
 
     Returns True if location name is valid, otherwise False.
     """
-    return gs.legal_name(location_name) and not \
-        location_exists(database, location_name)
+    return gs.legal_name(location_name) and not location_exists(database, location_name)
 
 
 def get_reasons_mapsets_not_removable(mapsets, check_permanent):
@@ -410,8 +419,9 @@ def get_reasons_mapsets_not_removable(mapsets, check_permanent):
     """
     messages = []
     for grassdb, location, mapset in mapsets:
-        message = get_reason_mapset_not_removable(grassdb, location,
-                                                  mapset, check_permanent)
+        message = get_reason_mapset_not_removable(
+            grassdb, location, mapset, check_permanent
+        )
         if message:
             messages.append(message)
     return messages
@@ -431,19 +441,21 @@ def get_reason_mapset_not_removable(grassdb, location, mapset, check_permanent):
     # Check if mapset is permanent
     if check_permanent and mapset == "PERMANENT":
         message = _("Mapset <{mapset}> is required for a valid location.").format(
-            mapset=mapset_path)
+            mapset=mapset_path
+        )
     # Check if mapset is current
     elif is_mapset_current(grassdb, location, mapset):
         message = _("Mapset <{mapset}> is the current mapset.").format(
-            mapset=mapset_path)
+            mapset=mapset_path
+        )
     # Check whether mapset is in use
     elif is_mapset_locked(mapset_path):
-        message = _("Mapset <{mapset}> is in use.").format(
-            mapset=mapset_path)
+        message = _("Mapset <{mapset}> is in use.").format(mapset=mapset_path)
     # Check whether mapset is owned by different user
     elif is_different_mapset_owner(mapset_path):
         message = _("Mapset <{mapset}> is owned by a different user.").format(
-            mapset=mapset_path)
+            mapset=mapset_path
+        )
 
     return message
 
@@ -471,20 +483,22 @@ def get_reasons_location_not_removable(grassdb, location):
 
     # Check if location is current
     if is_location_current(grassdb, location):
-        messages.append(_("Location <{location}> is the current location.").format(
-            location=location_path))
+        messages.append(
+            _("Location <{location}> is the current location.").format(
+                location=location_path
+            )
+        )
         return messages
 
     # Find mapsets in particular location
-    tmp_gisrc_file, env = gs.create_environment(grassdb, location, 'PERMANENT')
-    env['GRASS_SKIP_MAPSET_OWNER_CHECK'] = '1'
+    tmp_gisrc_file, env = gs.create_environment(grassdb, location, "PERMANENT")
+    env["GRASS_SKIP_MAPSET_OWNER_CHECK"] = "1"
 
-    g_mapsets = gs.read_command(
-        'g.mapsets',
-        flags='l',
-        separator='comma',
-        quiet=True,
-        env=env).strip().split(',')
+    g_mapsets = (
+        gs.read_command("g.mapsets", flags="l", separator="comma", quiet=True, env=env)
+        .strip()
+        .split(",")
+    )
 
     # Append to the list of tuples
     mapsets = []
@@ -507,9 +521,12 @@ def get_reasons_grassdb_not_removable(grassdb):
     genv = gisenv()
 
     # Check if grassdb is current
-    if grassdb == genv['GISDBASE']:
-        messages.append(_("GRASS database <{grassdb}> is the current database.").format(
-            grassdb=grassdb))
+    if grassdb == genv["GISDBASE"]:
+        messages.append(
+            _("GRASS database <{grassdb}> is the current database.").format(
+                grassdb=grassdb
+            )
+        )
         return messages
 
     g_locations = get_list_of_locations(grassdb)
@@ -532,9 +549,9 @@ def get_list_of_locations(dbase):
     """
     locations = list()
     for location in glob.glob(os.path.join(dbase, "*")):
-        if os.path.join(
-                location, "PERMANENT") in glob.glob(
-                os.path.join(location, "*")):
+        if os.path.join(location, "PERMANENT") in glob.glob(
+            os.path.join(location, "*")
+        ):
             locations.append(os.path.basename(location))
 
     locations.sort(key=lambda x: x.lower())

File diff suppressed because it is too large
+ 545 - 363
python/grass/gunittest/case.py


+ 96 - 74
python/grass/gunittest/checkers.py

@@ -50,13 +50,12 @@ def unify_projection(dic):
     """
     # the lookup variable is a list of list, each list contains all the
     # possible name for a projection system
-    lookup = [['Universal Transverse Mercator',
-               'Universe Transverse Mercator']]
+    lookup = [["Universal Transverse Mercator", "Universe Transverse Mercator"]]
     dic = dict(dic)
     for l in lookup:
-        for n in range(len(dic['name'])):
-            if dic['name'][n] in l:
-                dic['name'][n] = l[0]
+        for n in range(len(dic["name"])):
+            if dic["name"][n] in l:
+                dic["name"][n] = l[0]
     return dic
 
 
@@ -77,27 +76,32 @@ def unify_units(dic):
     """
     # the lookup variable is a list of list, each list contains all the
     # possible name for a units
-    lookup = [['meter', 'metre'], ['meters', 'metres'],
-              ['Meter', 'Metre'], ['Meters', 'Metres'],
-              ['kilometer', 'kilometre'], ['kilometers', 'kilometres'],
-              ['Kilometer', 'Kilometre'], ['Kilometers', 'Kilometres'],
-              ]
+    lookup = [
+        ["meter", "metre"],
+        ["meters", "metres"],
+        ["Meter", "Metre"],
+        ["Meters", "Metres"],
+        ["kilometer", "kilometre"],
+        ["kilometers", "kilometres"],
+        ["Kilometer", "Kilometre"],
+        ["Kilometers", "Kilometres"],
+    ]
     dic = dict(dic)
     for l in lookup:
-        if not isinstance(dic['unit'], str):
-            for n in range(len(dic['unit'])):
-                if dic['unit'][n] in l:
-                    dic['unit'][n] = l[0]
+        if not isinstance(dic["unit"], str):
+            for n in range(len(dic["unit"])):
+                if dic["unit"][n] in l:
+                    dic["unit"][n] = l[0]
         else:
-            if dic['unit'] in l:
-                dic['unit'] = l[0]
-        if not isinstance(dic['units'], str):
-            for n in range(len(dic['units'])):
-                if dic['units'][n] in l:
-                    dic['units'][n] = l[0]
+            if dic["unit"] in l:
+                dic["unit"] = l[0]
+        if not isinstance(dic["units"], str):
+            for n in range(len(dic["units"])):
+                if dic["units"][n] in l:
+                    dic["units"][n] = l[0]
         else:
-            if dic['units'] in l:
-                dic['units'] = l[0]
+            if dic["units"] in l:
+                dic["units"] = l[0]
     return dic
 
 
@@ -138,9 +142,15 @@ def value_from_string(value):
 
 
 # TODO: what is the default separator?
-def text_to_keyvalue(text, sep=":", val_sep=",", functions=None,
-                     skip_invalid=False, skip_empty=False,
-                     from_string=value_from_string):
+def text_to_keyvalue(
+    text,
+    sep=":",
+    val_sep=",",
+    functions=None,
+    skip_invalid=False,
+    skip_empty=False,
+    from_string=value_from_string,
+):
     """Convert test to key-value pairs (dictionary-like KeyValue object).
 
     Converts a key-value text file, where entries are separated
@@ -199,18 +209,23 @@ def text_to_keyvalue(text, sep=":", val_sep=",", functions=None,
                     # TODO: here should go _ for translation
                     # TODO: the error message is not really informative
                     # in case of skipping lines we may get here with no key
-                    msg = ("Empty line in the parsed text.")
+                    msg = "Empty line in the parsed text."
                     if kvdict:
                         # key is the one from previous line
-                        msg = ("Empty line in the parsed text."
-                               " Previous line's key is <%s>") % key
+                        msg = (
+                            "Empty line in the parsed text."
+                            " Previous line's key is <%s>"
+                        ) % key
                     raise ValueError(msg)
             else:
                 # line contains something but not separator
                 if not skip_invalid:
                     # TODO: here should go _ for translation
-                    raise ValueError(("Line <{l}> does not contain"
-                                      " separator <{s}>.").format(l=line, s=sep))
+                    raise ValueError(
+                        ("Line <{l}> does not contain" " separator <{s}>.").format(
+                            l=line, s=sep
+                        )
+                    )
             # if we get here we are silently ignoring the line
             # because it is invalid (does not contain key-value separator) or
             # because it is empty
@@ -259,8 +274,9 @@ def values_equal(value_a, value_b, precision=0.000001):
         if abs(value_a - value_b) > precision:
             return False
 
-    elif (isinstance(value_a, float) and isinstance(value_b, int)) or \
-            (isinstance(value_b, float) and isinstance(value_a, int)):
+    elif (isinstance(value_a, float) and isinstance(value_b, int)) or (
+        isinstance(value_b, float) and isinstance(value_a, int)
+    ):
         # on is float the other is int
         # don't accept None
         precision = float(precision)
@@ -270,8 +286,12 @@ def values_equal(value_a, value_b, precision=0.000001):
         if abs(value_a - value_b) > precision:
             return False
 
-    elif isinstance(value_a, int) and isinstance(value_b, int) and \
-            precision and int(precision) > 0:
+    elif (
+        isinstance(value_a, int)
+        and isinstance(value_b, int)
+        and precision
+        and int(precision) > 0
+    ):
         # both int but precision applies for them
         if abs(value_a - value_b) > precision:
             return False
@@ -289,9 +309,9 @@ def values_equal(value_a, value_b, precision=0.000001):
     return True
 
 
-def keyvalue_equals(dict_a, dict_b, precision,
-                    def_equal=values_equal, key_equal=None,
-                    a_is_subset=False):
+def keyvalue_equals(
+    dict_a, dict_b, precision, def_equal=values_equal, key_equal=None, a_is_subset=False
+):
     """Compare two dictionaries.
 
     .. note::
@@ -350,9 +370,9 @@ def keyvalue_equals(dict_a, dict_b, precision,
 
 # TODO: should the return depend on the a_is_subset parameter?
 # this function must have the same interface and behavior as keyvalue_equals
-def diff_keyvalue(dict_a, dict_b, precision,
-                  def_equal=values_equal, key_equal=None,
-                  a_is_subset=False):
+def diff_keyvalue(
+    dict_a, dict_b, precision, def_equal=values_equal, key_equal=None, a_is_subset=False
+):
     """Determine the difference of two dictionaries.
 
     The function returns missing keys and different values for common keys::
@@ -410,26 +430,30 @@ def diff_keyvalue(dict_a, dict_b, precision,
 
 def proj_info_equals(text_a, text_b):
     """Test if two PROJ_INFO texts are equal."""
+
     def compare_sums(list_a, list_b, precision):
         """Compare difference of sums of two list using precision"""
         # derived from the code in grass.script.core
         if abs(sum(list_a) - sum(list_b)) > precision:
             return False
-    sep = ':'
-    val_sep = ','
-    key_equal = {'+towgs84': compare_sums}
-    dict_a = text_to_keyvalue(text_a, sep=sep, val_sep=val_sep,
-                              functions=[unify_projection])
-    dict_b = text_to_keyvalue(text_b, sep=sep, val_sep=val_sep,
-                              functions=[unify_projection])
-    return keyvalue_equals(dict_a, dict_b,
-                            precision=0.000001,
-                            def_equal=values_equal,
-                            key_equal=key_equal)
+
+    sep = ":"
+    val_sep = ","
+    key_equal = {"+towgs84": compare_sums}
+    dict_a = text_to_keyvalue(
+        text_a, sep=sep, val_sep=val_sep, functions=[unify_projection]
+    )
+    dict_b = text_to_keyvalue(
+        text_b, sep=sep, val_sep=val_sep, functions=[unify_projection]
+    )
+    return keyvalue_equals(
+        dict_a, dict_b, precision=0.000001, def_equal=values_equal, key_equal=key_equal
+    )
 
 
 def proj_units_equals(text_a, text_b):
     """Test if two PROJ_UNITS texts are equal."""
+
     def lowercase_equals(string_a, string_b, precision=None):
         # we don't need a warning for unused precision
         # pylint: disable=W0613
@@ -438,17 +462,15 @@ def proj_units_equals(text_a, text_b):
         Precision is accepted as require by `keyvalue_equals()` but ignored.
         """
         return string_a.lower() == string_b.lower()
-    sep = ':'
-    val_sep = ','
-    key_equal = {'unit': lowercase_equals, 'units': lowercase_equals}
-    dict_a = text_to_keyvalue(text_a, sep=sep, val_sep=val_sep,
-                              functions=[unify_units])
-    dict_b = text_to_keyvalue(text_b, sep, val_sep,
-                              functions=[unify_units])
-    return keyvalue_equals(dict_a, dict_b,
-                            precision=0.000001,
-                            def_equal=values_equal,
-                            key_equal=key_equal)
+
+    sep = ":"
+    val_sep = ","
+    key_equal = {"unit": lowercase_equals, "units": lowercase_equals}
+    dict_a = text_to_keyvalue(text_a, sep=sep, val_sep=val_sep, functions=[unify_units])
+    dict_b = text_to_keyvalue(text_b, sep, val_sep, functions=[unify_units])
+    return keyvalue_equals(
+        dict_a, dict_b, precision=0.000001, def_equal=values_equal, key_equal=key_equal
+    )
 
 
 # TODO: support also float (with E, e, inf, nan, ...?) and int (###, ##.)
@@ -500,8 +522,8 @@ def check_text_ellipsis(reference, actual):
     False
     """
     ref_escaped = re.escape(reference)
-    exp = re.compile(r'\\\.\\\.\\\.')  # matching escaped ...
-    ref_regexp = exp.sub('.+', ref_escaped) + "$"
+    exp = re.compile(r"\\\.\\\.\\\.")  # matching escaped ...
+    ref_regexp = exp.sub(".+", ref_escaped) + "$"
     if re.match(ref_regexp, actual, re.DOTALL):
         return True
     else:
@@ -551,19 +573,18 @@ def check_text_ellipsis_doctest(reference, actual):
     """
     # this can be also global
     checker = doctest.OutputChecker()
-    return checker.check_output(reference, actual,
-                                optionflags=doctest.ELLIPSIS)
+    return checker.check_output(reference, actual, optionflags=doctest.ELLIPSIS)
 
 
 # optimal size depends on file system and maybe on hasher.block_size
-_BUFFER_SIZE = 2**16
+_BUFFER_SIZE = 2 ** 16
 
 
 # TODO: accept also open file object
 def file_md5(filename):
     """Get MD5 (check) sum of a file."""
     hasher = hashlib.md5()
-    with open(filename, 'rb') as f:
+    with open(filename, "rb") as f:
         buf = f.read(_BUFFER_SIZE)
         while len(buf) > 0:
             hasher.update(buf)
@@ -571,8 +592,9 @@ def file_md5(filename):
     return hasher.hexdigest()
 
 
-def text_file_md5(filename, exclude_lines=None, exclude_re=None,
-                  prepend_lines=None, append_lines=None):
+def text_file_md5(
+    filename, exclude_lines=None, exclude_re=None, prepend_lines=None, append_lines=None
+):
     """Get a MD5 (check) sum of a text file.
 
     Works in the same way as `file_md5()` function but ignores newlines
@@ -594,11 +616,11 @@ def text_file_md5(filename, exclude_lines=None, exclude_re=None,
     if prepend_lines:
         for line in prepend_lines:
             hasher.update(line if sys.version_info[0] == 2 else encode(line))
-    with open(filename, 'r') as f:
+    with open(filename, "r") as f:
         for line in f:
             # replace platform newlines by standard newline
-            if os.linesep != '\n':
-                line = line.rstrip(os.linesep) + '\n'
+            if os.linesep != "\n":
+                line = line.rstrip(os.linesep) + "\n"
             if exclude_lines and line in exclude_lines:
                 continue
             if exclude_re and regexp.match(line):
@@ -621,5 +643,5 @@ def main():  # pragma: no cover
     return ret.failed
 
 
-if __name__ == '__main__':  # pragma: no cover
+if __name__ == "__main__":  # pragma: no cover
     sys.exit(main())

+ 26 - 19
python/grass/gunittest/gmodules.py

@@ -44,21 +44,27 @@ class SimpleModule(Module):
     """
 
     def __init__(self, cmd, *args, **kargs):
-        for banned in ['stdout_', 'stderr_', 'finish_', 'run_']:
+        for banned in ["stdout_", "stderr_", "finish_", "run_"]:
             if banned in kargs:
-                raise ValueError('Do not set %s parameter'
-                                 ', it would be overriden' % banned)
-        kargs['stdout_'] = subprocess.PIPE
-        kargs['stderr_'] = subprocess.PIPE
-        kargs['finish_'] = True
-        kargs['run_'] = False
+                raise ValueError(
+                    "Do not set %s parameter" ", it would be overriden" % banned
+                )
+        kargs["stdout_"] = subprocess.PIPE
+        kargs["stderr_"] = subprocess.PIPE
+        kargs["finish_"] = True
+        kargs["run_"] = False
 
         Module.__init__(self, cmd, *args, **kargs)
 
 
-def call_module(module, stdin=None,
-                merge_stderr=False, capture_stdout=True, capture_stderr=True,
-                **kwargs):
+def call_module(
+    module,
+    stdin=None,
+    merge_stderr=False,
+    capture_stdout=True,
+    capture_stderr=True,
+    **kwargs,
+):
     r"""Run module with parameters given in `kwargs` and return its output.
 
     >>> print (call_module('g.region', flags='pg'))  # doctest: +ELLIPSIS
@@ -88,7 +94,8 @@ def call_module(module, stdin=None,
     :param merge_stderr: if the standard error output should be merged with stdout
     :param kwargs: module parameters
 
-    :returns: module standard output (stdout) as string or None if apture_stdout is False
+    :returns: module standard output (stdout) as string or None
+              if capture_stdout is False
 
     :raises CalledModuleError: if module return code is non-zero
     :raises ValueError: if the parameters are not correct
@@ -101,27 +108,27 @@ def call_module(module, stdin=None,
     do_doctest_gettext_workaround()
     # implementation inspired by subprocess.check_output() function
     if stdin:
-        if 'input' in kwargs and kwargs['input'] != '-':
+        if "input" in kwargs and kwargs["input"] != "-":
             raise ValueError(_("input='-' must be used when stdin is specified"))
         if stdin == subprocess.PIPE:
             raise ValueError(_("stdin must be string or buffer, not PIPE"))
-        kwargs['stdin'] = subprocess.PIPE  # to be able to send data to stdin
-    elif 'input' in kwargs and kwargs['input'] == '-':
+        kwargs["stdin"] = subprocess.PIPE  # to be able to send data to stdin
+    elif "input" in kwargs and kwargs["input"] == "-":
         raise ValueError(_("stdin must be used when input='-'"))
     if merge_stderr and not (capture_stdout and capture_stderr):
         raise ValueError(_("You cannot merge stdout and stderr and not capture them"))
-    if 'stdout' in kwargs:
+    if "stdout" in kwargs:
         raise TypeError(_("stdout argument not allowed, it could be overridden"))
-    if 'stderr' in kwargs:
+    if "stderr" in kwargs:
         raise TypeError(_("stderr argument not allowed, it could be overridden"))
 
     if capture_stdout:
-        kwargs['stdout'] = subprocess.PIPE
+        kwargs["stdout"] = subprocess.PIPE
     if capture_stderr:
         if merge_stderr:
-            kwargs['stderr'] = subprocess.STDOUT
+            kwargs["stderr"] = subprocess.STDOUT
         else:
-            kwargs['stderr'] = subprocess.PIPE
+            kwargs["stderr"] = subprocess.PIPE
     process = start_command(module, **kwargs)
     # input=None means no stdin (our default)
     # for no stdout, output is None which is out interface

+ 19 - 12
python/grass/gunittest/gutils.py

@@ -18,7 +18,8 @@ from .checkers import text_to_keyvalue
 
 def get_current_mapset():
     """Get curret mapset name as a string"""
-    return call_module('g.mapset', flags='p').strip()
+    return call_module("g.mapset", flags="p").strip()
+
 
 def is_map_in_mapset(name, type, mapset=None):
     """Check is map is present in the mapset (current mapset by default)
@@ -38,22 +39,28 @@ def is_map_in_mapset(name, type, mapset=None):
     # so anything accepted by g.findfile will work but this can change in the
     # future (the documentation is clear about what's legal)
     # supporting both short and full names
-    if type == 'rast' or  type == 'raster':
-        type = 'cell'
-    elif type == 'rast3d' or type == 'raster3d':
-        type = 'grid3'
-    elif type == 'vect':
-        type = 'vector'
+    if type == "rast" or type == "raster":
+        type = "cell"
+    elif type == "rast3d" or type == "raster3d":
+        type = "grid3"
+    elif type == "vect":
+        type = "vector"
     # g.findfile returns non-zero when file was not found
     # se we ignore return code and just focus on stdout
-    process = start_command('g.findfile', flags='n',
-                            element=type, file=name, mapset=mapset,
-                            stdout=PIPE, stderr=PIPE)
+    process = start_command(
+        "g.findfile",
+        flags="n",
+        element=type,
+        file=name,
+        mapset=mapset,
+        stdout=PIPE,
+        stderr=PIPE,
+    )
     output, errors = process.communicate()
-    info = text_to_keyvalue(decode(output), sep='=')
+    info = text_to_keyvalue(decode(output), sep="=")
     # file is the key questioned in grass.script.core find_file()
     # return code should be equivalent to checking the output
-    if info['file']:
+    if info["file"]:
         return True
     else:
         return False

+ 130 - 87
python/grass/gunittest/invoker.py

@@ -17,11 +17,16 @@ import subprocess
 from .checkers import text_to_keyvalue
 
 from .loader import GrassTestLoader, discover_modules
-from .reporters import (GrassTestFilesMultiReporter,
-                        GrassTestFilesTextReporter, GrassTestFilesHtmlReporter,
-                        TestsuiteDirReporter, GrassTestFilesKeyValueReporter,
-                        get_svn_path_authors,
-                        NoopFileAnonymizer, keyvalue_to_text)
+from .reporters import (
+    GrassTestFilesMultiReporter,
+    GrassTestFilesTextReporter,
+    GrassTestFilesHtmlReporter,
+    TestsuiteDirReporter,
+    GrassTestFilesKeyValueReporter,
+    get_svn_path_authors,
+    NoopFileAnonymizer,
+    keyvalue_to_text,
+)
 from .utils import silent_rmtree, ensure_dir
 
 from grass.script.utils import decode, _get_encoding
@@ -43,8 +48,8 @@ import collections
 # TODO: this might be more extend then update
 def update_keyval_file(filename, module, returncode):
     if os.path.exists(filename):
-        with open(filename, 'r') as keyval_file:
-            keyval = text_to_keyvalue(keyval_file.read(), sep='=')
+        with open(filename, "r") as keyval_file:
+            keyval = text_to_keyvalue(keyval_file.read(), sep="=")
     else:
         keyval = {}
 
@@ -52,17 +57,17 @@ def update_keyval_file(filename, module, returncode):
     test_file_authors = get_svn_path_authors(module.abs_file_path)
     # in case that SVN is not available use empty authors
     if test_file_authors is None:
-        test_file_authors = ''
+        test_file_authors = ""
 
     # always owerwrite name and status
-    keyval['name'] = module.name
-    keyval['tested_dir'] = module.tested_dir
-    if 'status' not in keyval.keys():
-        keyval['status'] = 'failed' if returncode else 'passed'
-    keyval['returncode'] = returncode
-    keyval['test_file_authors'] = test_file_authors
-
-    with open(filename, 'w') as keyval_file:
+    keyval["name"] = module.name
+    keyval["tested_dir"] = module.tested_dir
+    if "status" not in keyval.keys():
+        keyval["status"] = "failed" if returncode else "passed"
+    keyval["returncode"] = returncode
+    keyval["test_file_authors"] = test_file_authors
+
+    with open(filename, "w") as keyval_file:
         keyval_file.write(keyvalue_to_text(keyval))
     return keyval
 
@@ -74,9 +79,15 @@ class GrassTestFilesInvoker(object):
     # std stream, random outputs, saved results, profiling
     # not stdout and stderr if they contain test results
     # we can also save only failed tests, or generate only if assert fails
-    def __init__(self, start_dir,
-                 clean_mapsets=True, clean_outputs=True, clean_before=True,
-                 testsuite_dir='testsuite', file_anonymizer=None):
+    def __init__(
+        self,
+        start_dir,
+        clean_mapsets=True,
+        clean_outputs=True,
+        clean_before=True,
+        testsuite_dir="testsuite",
+        file_anonymizer=None,
+    ):
         """
 
         :param bool clean_mapsets: if the mapsets should be removed
@@ -111,8 +122,8 @@ class GrassTestFilesInvoker(object):
         # replace . to get rid of unclean path
         # TODO: clean paths
         # note that backslash cannot be at the end of raw string
-        dir_as_name = module.tested_dir.translate(maketrans(r'/\.', '___'))
-        mapset = dir_as_name + '_' + module.name
+        dir_as_name = module.tested_dir.translate(maketrans(r"/\.", "___"))
+        mapset = dir_as_name + "_" + module.name
         # TODO: use grass module to do this? but we are not in the right gisdbase
         mapset_dir = os.path.join(gisdbase, location, mapset)
         if self.clean_before:
@@ -122,21 +133,26 @@ class GrassTestFilesInvoker(object):
         # copy DEFAULT_WIND file from PERMANENT to WIND
         # TODO: this should be a function in grass.script (used also in gis_set.py, PyGRASS also has its way with Mapset)
         # TODO: are premisions an issue here?
-        shutil.copy(os.path.join(gisdbase, location, 'PERMANENT', 'DEFAULT_WIND'),
-                    os.path.join(mapset_dir, 'WIND'))
+        shutil.copy(
+            os.path.join(gisdbase, location, "PERMANENT", "DEFAULT_WIND"),
+            os.path.join(mapset_dir, "WIND"),
+        )
         return mapset, mapset_dir
 
     def _run_test_module(self, module, results_dir, gisdbase, location):
         """Run one test file."""
         self.testsuite_dirs[module.tested_dir].append(module.name)
         cwd = os.path.join(results_dir, module.tested_dir, module.name)
-        data_dir = os.path.join(module.file_dir, 'data')
+        data_dir = os.path.join(module.file_dir, "data")
         if os.path.exists(data_dir):
             # TODO: link dir instead of copy tree and remove link afterwads
             # (removing is good because of testsuite dir in samplecode)
             # TODO: use different dir name in samplecode and test if it works
-            shutil.copytree(data_dir, os.path.join(cwd, 'data'),
-                            ignore=shutil.ignore_patterns('*.svn*'))
+            shutil.copytree(
+                data_dir,
+                os.path.join(cwd, "data"),
+                ignore=shutil.ignore_patterns("*.svn*"),
+            )
         ensure_dir(os.path.abspath(cwd))
         # TODO: put this to constructor and copy here again
         env = os.environ.copy()
@@ -148,28 +164,28 @@ class GrassTestFilesInvoker(object):
         # will be long they should be stored somewhere separately
 
         # use custom gisrc, not current session gisrc
-        env['GISRC'] = gisrc
+        env["GISRC"] = gisrc
         # percentage in plain format is 0...10...20... ...100
-        env['GRASS_MESSAGE_FORMAT'] = 'plain'
+        env["GRASS_MESSAGE_FORMAT"] = "plain"
 
-        stdout_path = os.path.join(cwd, 'stdout.txt')
-        stderr_path = os.path.join(cwd, 'stderr.txt')
+        stdout_path = os.path.join(cwd, "stdout.txt")
+        stderr_path = os.path.join(cwd, "stderr.txt")
 
         self.reporter.start_file_test(module)
         # TODO: we might clean the directory here before test if non-empty
 
-        if module.file_type == 'py':
+        if module.file_type == "py":
             # ignoring shebang line to use current Python
             # and also pass parameters to it
             # add also '-Qwarn'?
             if sys.version_info.major >= 3:
-                args = [sys.executable, '-tt', module.abs_file_path]
+                args = [sys.executable, "-tt", module.abs_file_path]
             else:
-                args = [sys.executable, '-tt', '-3', module.abs_file_path]
-            p = subprocess.Popen(args, cwd=cwd, env=env,
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
-        elif module.file_type == 'sh':
+                args = [sys.executable, "-tt", "-3", module.abs_file_path]
+            p = subprocess.Popen(
+                args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+            )
+        elif module.file_type == "sh":
             # ignoring shebang line to pass parameters to shell
             # expecting system to have sh or something compatible
             # TODO: add some special checks for MS Windows
@@ -182,18 +198,24 @@ class GrassTestFilesInvoker(object):
             #                command is used to control an if, elif, while, or
             #                until; or if the command is the left hand operand
             #                of an '&&' or '||' operator.
-            p = subprocess.Popen(['sh', '-e', '-x', module.abs_file_path],
-                                 cwd=cwd, env=env,
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
+            p = subprocess.Popen(
+                ["sh", "-e", "-x", module.abs_file_path],
+                cwd=cwd,
+                env=env,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+            )
         else:
-            p = subprocess.Popen([module.abs_file_path],
-                                 cwd=cwd, env=env,
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
+            p = subprocess.Popen(
+                [module.abs_file_path],
+                cwd=cwd,
+                env=env,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+            )
         stdout, stderr = p.communicate()
         returncode = p.returncode
-        encodings = [_get_encoding(), 'utf8', 'latin-1', 'ascii']
+        encodings = [_get_encoding(), "utf8", "latin-1", "ascii"]
         detected = False
         idx = 0
         while not detected:
@@ -214,33 +236,38 @@ class GrassTestFilesInvoker(object):
                 idx += 1
                 pass
 
-        with open(stdout_path, 'w') as stdout_file:
+        with open(stdout_path, "w") as stdout_file:
             stdout_file.write(stdout)
-        with open(stderr_path, 'w') as stderr_file:
-            if type(stderr) == 'bytes':
+        with open(stderr_path, "w") as stderr_file:
+            if type(stderr) == "bytes":
                 stderr_file.write(decode(stderr))
             else:
                 if isinstance(stderr, str):
                     stderr_file.write(stderr)
                 else:
-                    stderr_file.write(stderr.encode('utf8'))
+                    stderr_file.write(stderr.encode("utf8"))
         self._file_anonymizer.anonymize([stdout_path, stderr_path])
 
         test_summary = update_keyval_file(
-            os.path.join(os.path.abspath(cwd), 'test_keyvalue_result.txt'),
-            module=module, returncode=returncode)
-        self.reporter.end_file_test(module=module, cwd=cwd,
-                                    returncode=returncode,
-                                    stdout=stdout_path, stderr=stderr_path,
-                                    test_summary=test_summary)
+            os.path.join(os.path.abspath(cwd), "test_keyvalue_result.txt"),
+            module=module,
+            returncode=returncode,
+        )
+        self.reporter.end_file_test(
+            module=module,
+            cwd=cwd,
+            returncode=returncode,
+            stdout=stdout_path,
+            stderr=stderr_path,
+            test_summary=test_summary,
+        )
         # TODO: add some try-except or with for better error handling
         os.remove(gisrc)
         # TODO: only if clean up
         if self.clean_mapsets:
             shutil.rmtree(mapset_dir)
 
-    def run_in_location(self, gisdbase, location, location_type,
-                        results_dir):
+    def run_in_location(self, gisdbase, location, location_type, results_dir):
         """Run tests in a given location
 
         Returns an object with counting attributes of GrassTestFilesCountingReporter,
@@ -249,52 +276,68 @@ class GrassTestFilesInvoker(object):
         not to one file as these will simply contain the last executed file.
         """
         if os.path.abspath(results_dir) == os.path.abspath(self.start_dir):
-            raise RuntimeError("Results root directory should not be the same"
-                               " as discovery start directory")
+            raise RuntimeError(
+                "Results root directory should not be the same"
+                " as discovery start directory"
+            )
         self.reporter = GrassTestFilesMultiReporter(
             reporters=[
                 GrassTestFilesTextReporter(stream=sys.stderr),
                 GrassTestFilesHtmlReporter(
                     file_anonymizer=self._file_anonymizer,
-                    main_page_name='testfiles.html'),
+                    main_page_name="testfiles.html",
+                ),
                 GrassTestFilesKeyValueReporter(
-                    info=dict(location=location, location_type=location_type))
-            ])
-        self.testsuite_dirs = collections.defaultdict(list)  # reset list of dirs each time
+                    info=dict(location=location, location_type=location_type)
+                ),
+            ]
+        )
+        self.testsuite_dirs = collections.defaultdict(
+            list
+        )  # reset list of dirs each time
         # TODO: move constants out of loader class or even module
-        modules = discover_modules(start_dir=self.start_dir,
-                                   grass_location=location_type,
-                                   file_regexp=r'.*\.(py|sh)$',
-                                   skip_dirs=GrassTestLoader.skip_dirs,
-                                   testsuite_dir=GrassTestLoader.testsuite_dir,
-                                   all_locations_value=GrassTestLoader.all_tests_value,
-                                   universal_location_value=GrassTestLoader.universal_tests_value,
-                                   import_modules=False)
+        modules = discover_modules(
+            start_dir=self.start_dir,
+            grass_location=location_type,
+            file_regexp=r".*\.(py|sh)$",
+            skip_dirs=GrassTestLoader.skip_dirs,
+            testsuite_dir=GrassTestLoader.testsuite_dir,
+            all_locations_value=GrassTestLoader.all_tests_value,
+            universal_location_value=GrassTestLoader.universal_tests_value,
+            import_modules=False,
+        )
 
         self.reporter.start(results_dir)
         for module in modules:
-            self._run_test_module(module=module, results_dir=results_dir,
-                                  gisdbase=gisdbase, location=location)
+            self._run_test_module(
+                module=module,
+                results_dir=results_dir,
+                gisdbase=gisdbase,
+                location=location,
+            )
         self.reporter.finish()
 
         # TODO: move this to some (new?) reporter
         # TODO: add basic summary of linked files so that the page is not empty
-        with open(os.path.join(results_dir, 'index.html'), 'w') as main_index:
+        with open(os.path.join(results_dir, "index.html"), "w") as main_index:
             main_index.write(
-                '<html><body>'
-                '<h1>Tests for &lt;{location}&gt;'
-                ' using &lt;{type}&gt; type tests</h1>'
-                '<ul>'
+                "<html><body>"
+                "<h1>Tests for &lt;{location}&gt;"
+                " using &lt;{type}&gt; type tests</h1>"
+                "<ul>"
                 '<li><a href="testsuites.html">Results by testsuites</a>'
-                ' (testsuite directories)</li>'
+                " (testsuite directories)</li>"
                 '<li><a href="testfiles.html">Results by test files</a></li>'
-                '<ul>'
-                '</body></html>'
-                .format(location=location, type=location_type))
+                "<ul>"
+                "</body></html>".format(location=location, type=location_type)
+            )
 
         testsuite_dir_reporter = TestsuiteDirReporter(
-            main_page_name='testsuites.html', testsuite_page_name='index.html',
-            top_level_testsuite_page_name='testsuite_index.html')
-        testsuite_dir_reporter.report_for_dirs(root=results_dir,
-                                               directories=self.testsuite_dirs)
+            main_page_name="testsuites.html",
+            testsuite_page_name="index.html",
+            top_level_testsuite_page_name="testsuite_index.html",
+        )
+        testsuite_dir_reporter.report_for_dirs(
+            root=results_dir, directories=self.testsuite_dirs
+        )
         return self.reporter

+ 53 - 38
python/grass/gunittest/loader.py

@@ -17,20 +17,25 @@ import re
 
 
 # TODO: resolve test file versus test module
-GrassTestPythonModule = collections.namedtuple('GrassTestPythonModule',
-                                               ['name', 'module',
-                                                'file_type',
-                                                'tested_dir',
-                                                'file_dir',
-                                                'abs_file_path'])
+GrassTestPythonModule = collections.namedtuple(
+    "GrassTestPythonModule",
+    ["name", "module", "file_type", "tested_dir", "file_dir", "abs_file_path"],
+)
 
 
 # TODO: implement loading without the import
-def discover_modules(start_dir, skip_dirs, testsuite_dir,
-                     grass_location,
-                     all_locations_value, universal_location_value,
-                     import_modules, add_failed_imports=True,
-                     file_pattern=None, file_regexp=None):
+def discover_modules(
+    start_dir,
+    skip_dirs,
+    testsuite_dir,
+    grass_location,
+    all_locations_value,
+    universal_location_value,
+    import_modules,
+    add_failed_imports=True,
+    file_pattern=None,
+    file_regexp=None,
+):
     """Find all test files (modules) in a directory tree.
 
     The function is designed specifically for GRASS testing framework
@@ -94,14 +99,14 @@ def discover_modules(start_dir, skip_dirs, testsuite_dir,
                 # otherwise we can have successful because nothing was reported
                 abspath = os.path.abspath(full)
                 abs_file_path = os.path.join(abspath, file_name)
-                if file_name.endswith('.py'):
-                    if file_name == '__init__.py':
+                if file_name.endswith(".py"):
+                    if file_name == "__init__.py":
                         # we always ignore __init__.py
                         continue
-                    file_type = 'py'
+                    file_type = "py"
                     name = file_name[:-3]
-                elif file_name.endswith('.sh'):
-                    file_type = 'sh'
+                elif file_name.endswith(".sh"):
+                    file_type = "sh"
                     name = file_name[:-3]
                 else:
                     file_type = None  # alternative would be '', now equivalent
@@ -113,7 +118,7 @@ def discover_modules(start_dir, skip_dirs, testsuite_dir,
                         add = True
                     else:
                         try:
-                            locations = ['nc', 'stdmaps', 'all']
+                            locations = ["nc", "stdmaps", "all"]
                         except AttributeError:
                             add = True  # test is universal
                         else:
@@ -127,15 +132,23 @@ def discover_modules(start_dir, skip_dirs, testsuite_dir,
                     if add_failed_imports:
                         add = True
                     else:
-                        raise ImportError('Cannot import module named'
-                                          ' %s in %s (%s)'
-                                          % (name, full, e.message))
+                        raise ImportError(
+                            "Cannot import module named"
+                            " %s in %s (%s)" % (name, full, e.message)
+                        )
                         # alternative is to create TestClass which will raise
                         # see unittest.loader
                 if add:
-                    modules.append(GrassTestPythonModule(
-                        name=name, module=None, tested_dir=root, file_dir=full,
-                        abs_file_path=abs_file_path, file_type=file_type))
+                    modules.append(
+                        GrassTestPythonModule(
+                            name=name,
+                            module=None,
+                            tested_dir=root,
+                            file_dir=full,
+                            abs_file_path=abs_file_path,
+                            file_type=file_type,
+                        )
+                    )
                 # in else with some verbose we could tell about skipped test
     return modules
 
@@ -145,11 +158,11 @@ def discover_modules(start_dir, skip_dirs, testsuite_dir,
 class GrassTestLoader(unittest.TestLoader):
     """Class handles GRASS-specific loading of test modules."""
 
-    skip_dirs = ['.svn', 'dist.*', 'bin.*', 'OBJ.*']
-    testsuite_dir = 'testsuite'
-    files_in_testsuite = '*.py'
-    all_tests_value = 'all'
-    universal_tests_value = 'universal'
+    skip_dirs = [".svn", "dist.*", "bin.*", "OBJ.*"]
+    testsuite_dir = "testsuite"
+    files_in_testsuite = "*.py"
+    all_tests_value = "all"
+    universal_tests_value = "universal"
 
     def __init__(self, grass_location):
         self.grass_location = grass_location
@@ -157,21 +170,23 @@ class GrassTestLoader(unittest.TestLoader):
     # TODO: what is the purpose of top_level_dir, can it be useful?
     # probably yes, we need to know grass src or dist root
     # TODO: not using pattern here
-    def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
+    def discover(self, start_dir, pattern="test*.py", top_level_dir=None):
         """Load test modules from in GRASS testing framework way."""
-        modules = discover_modules(start_dir=start_dir,
-                                   file_pattern=self.files_in_testsuite,
-                                   skip_dirs=self.skip_dirs,
-                                   testsuite_dir=self.testsuite_dir,
-                                   grass_location=self.grass_location,
-                                   all_locations_value=self.all_tests_value,
-                                   universal_location_value=self.universal_tests_value,
-                                   import_modules=True)
+        modules = discover_modules(
+            start_dir=start_dir,
+            file_pattern=self.files_in_testsuite,
+            skip_dirs=self.skip_dirs,
+            testsuite_dir=self.testsuite_dir,
+            grass_location=self.grass_location,
+            all_locations_value=self.all_tests_value,
+            universal_location_value=self.universal_tests_value,
+            import_modules=True,
+        )
         tests = []
         for module in modules:
             tests.append(self.loadTestsFromModule(module.module))
         return self.suiteClass(tests)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     GrassTestLoader().discover()

+ 103 - 62
python/grass/gunittest/main.py

@@ -17,8 +17,7 @@ from unittest.main import TestProgram
 
 
 from .loader import GrassTestLoader
-from .runner import (GrassTestRunner, MultiTestResult,
-                     TextTestResult, KeyValueTestResult)
+from .runner import GrassTestRunner, MultiTestResult, TextTestResult, KeyValueTestResult
 from .invoker import GrassTestFilesInvoker
 from .utils import silent_rmtree
 from .reporters import FileAnonymizer
@@ -29,10 +28,17 @@ import grass.script.core as gcore
 class GrassTestProgram(TestProgram):
     """A class to be used by individual test files (wrapped in the function)"""
 
-    def __init__(self, exit_at_end, grass_location, clean_outputs=True,
-                 unittest_argv=None, module=None,
-                 verbosity=1,
-                 failfast=None, catchbreak=None):
+    def __init__(
+        self,
+        exit_at_end,
+        grass_location,
+        clean_outputs=True,
+        unittest_argv=None,
+        module=None,
+        verbosity=1,
+        failfast=None,
+        catchbreak=None,
+    ):
         """Prepares the tests in GRASS way and then runs the tests.
 
         :param bool clean_outputs: if outputs in mapset and in ?
@@ -45,32 +51,35 @@ class GrassTestProgram(TestProgram):
 
         grass_loader = GrassTestLoader(grass_location=self.grass_location)
 
-        text_result = TextTestResult(stream=sys.stderr,
-                                     descriptions=True,
-                                     verbosity=verbosity)
-        keyval_file = open('test_keyvalue_result.txt', 'w')
+        text_result = TextTestResult(
+            stream=sys.stderr, descriptions=True, verbosity=verbosity
+        )
+        keyval_file = open("test_keyvalue_result.txt", "w")
         keyval_result = KeyValueTestResult(stream=keyval_file)
         result = MultiTestResult(results=[text_result, keyval_result])
 
-        grass_runner = GrassTestRunner(verbosity=verbosity,
-                                       failfast=failfast,
-                                       buffer=buffer_stdout_stderr,
-                                       result=result)
-        super(GrassTestProgram, self).__init__(module=module,
-                                                   argv=unittest_argv,
-                                                   testLoader=grass_loader,
-                                                   testRunner=grass_runner,
-                                                   exit=exit_at_end,
-                                                   verbosity=verbosity,
-                                                   failfast=failfast,
-                                                   catchbreak=catchbreak,
-                                                   buffer=buffer_stdout_stderr)
+        grass_runner = GrassTestRunner(
+            verbosity=verbosity,
+            failfast=failfast,
+            buffer=buffer_stdout_stderr,
+            result=result,
+        )
+        super(GrassTestProgram, self).__init__(
+            module=module,
+            argv=unittest_argv,
+            testLoader=grass_loader,
+            testRunner=grass_runner,
+            exit=exit_at_end,
+            verbosity=verbosity,
+            failfast=failfast,
+            catchbreak=catchbreak,
+            buffer=buffer_stdout_stderr,
+        )
         keyval_file.close()
 
 
 def test():
-    """Run a test of a module.
-    """
+    """Run a test of a module."""
     # TODO: put the link to to the report only if available
     # TODO: how to disable Python code coverage for module and C tests?
     # TODO: we probably need to have different test  functions for C, Python modules, and Python code
@@ -79,21 +88,23 @@ def test():
     # TODO: implement coverage but only when requested by invoker and only if
     # it makes sense for tests (need to know what is tested)
     # doing_coverage = False
-    #try:
+    # try:
     #    import coverage
     #    doing_coverage = True
     #    cov = coverage.coverage(omit="*testsuite*")
     #    cov.start()
-    #except ImportError:
+    # except ImportError:
     #    pass
     # TODO: add some message somewhere
 
     # TODO: enable passing omit to exclude also gunittest or nothing
-    program = GrassTestProgram(module='__main__', exit_at_end=False, grass_location='all')
+    program = GrassTestProgram(
+        module="__main__", exit_at_end=False, grass_location="all"
+    )
     # TODO: check if we are in the directory where the test file is
     # this will ensure that data directory is available when it is requested
 
-    #if doing_coverage:
+    # if doing_coverage:
     #    cov.stop()
     #    cov.html_report(directory='testcodecoverage')
 
@@ -110,7 +121,7 @@ def discovery():
         python main.py discovery [start_directory]
     """
 
-    program = GrassTestProgram(grass_location='nc', exit_at_end=False)
+    program = GrassTestProgram(grass_location="nc", exit_at_end=False)
 
     sys.exit(not program.result.wasSuccessful())
 
@@ -119,55 +130,84 @@ def discovery():
 # TODO: create a full interface (using grass parser or argparse)
 def main():
     parser = argparse.ArgumentParser(
-        description='Run test files in all testsuite directories starting'
-        ' from the current one'
-        ' (runs on active GRASS session)')
-    parser.add_argument('--location', dest='location', action='store',
-                        help='Name of location where to perform test', required=True)
-    parser.add_argument('--location-type', dest='location_type', action='store',
-                        default='nc',
-                        help='Type of tests which should be run'
-                             ' (tag corresponding to location)')
-    parser.add_argument('--grassdata', dest='gisdbase', action='store',
-                        default=None,
-                        help='GRASS data(base) (GISDBASE) directory'
-                        ' (current GISDBASE by default)')
-    parser.add_argument('--output', dest='output', action='store',
-                        default='testreport',
-                        help='Output directory')
-    parser.add_argument('--min-success', dest='min_success', action='store',
-                        default='90', type=int,
-                        help=("Minimum success percentage (lower percentage"
-                              " than this will result in a non-zero return code; values 0-100)"))
+        description="Run test files in all testsuite directories starting"
+        " from the current one"
+        " (runs on active GRASS session)"
+    )
+    parser.add_argument(
+        "--location",
+        dest="location",
+        action="store",
+        help="Name of location where to perform test",
+        required=True,
+    )
+    parser.add_argument(
+        "--location-type",
+        dest="location_type",
+        action="store",
+        default="nc",
+        help="Type of tests which should be run" " (tag corresponding to location)",
+    )
+    parser.add_argument(
+        "--grassdata",
+        dest="gisdbase",
+        action="store",
+        default=None,
+        help="GRASS data(base) (GISDBASE) directory" " (current GISDBASE by default)",
+    )
+    parser.add_argument(
+        "--output",
+        dest="output",
+        action="store",
+        default="testreport",
+        help="Output directory",
+    )
+    parser.add_argument(
+        "--min-success",
+        dest="min_success",
+        action="store",
+        default="90",
+        type=int,
+        help=(
+            "Minimum success percentage (lower percentage"
+            " than this will result in a non-zero return code; values 0-100)"
+        ),
+    )
     args = parser.parse_args()
     gisdbase = args.gisdbase
     if gisdbase is None:
         # here we already rely on being in GRASS session
-        gisdbase = gcore.gisenv()['GISDBASE']
+        gisdbase = gcore.gisenv()["GISDBASE"]
     location = args.location
     location_type = args.location_type
 
     if not gisdbase:
-        sys.stderr.write("GISDBASE (grassdata directory)"
-                         " cannot be empty string\n" % gisdbase)
+        sys.stderr.write(
+            "GISDBASE (grassdata directory)" " cannot be empty string\n" % gisdbase
+        )
         sys.exit(1)
     if not os.path.exists(gisdbase):
-        sys.stderr.write("GISDBASE (grassdata directory) <%s>"
-                         " does not exist\n" % gisdbase)
+        sys.stderr.write(
+            "GISDBASE (grassdata directory) <%s>" " does not exist\n" % gisdbase
+        )
         sys.exit(1)
     if not os.path.exists(os.path.join(gisdbase, location)):
-        sys.stderr.write("GRASS Location <{loc}>"
-                         " does not exist in GRASS Database <{db}>\n".format(
-                             loc=location, db=gisdbase))
+        sys.stderr.write(
+            "GRASS Location <{loc}>"
+            " does not exist in GRASS Database <{db}>\n".format(
+                loc=location, db=gisdbase
+            )
+        )
         sys.exit(1)
     results_dir = args.output
     silent_rmtree(results_dir)  # TODO: too brute force?
 
-    start_dir = '.'
+    start_dir = "."
     abs_start_dir = os.path.abspath(start_dir)
     invoker = GrassTestFilesInvoker(
         start_dir=start_dir,
-        file_anonymizer=FileAnonymizer(paths_to_remove=[abs_start_dir]))
+        file_anonymizer=FileAnonymizer(paths_to_remove=[abs_start_dir]),
+    )
     # TODO: remove also results dir from files
     # as an enhancemnt
     # we can just iterate over all locations available in database
@@ -176,11 +216,12 @@ def main():
         gisdbase=gisdbase,
         location=location,
         location_type=location_type,
-        results_dir=results_dir
+        results_dir=results_dir,
     )
     if reporter.file_pass_per >= args.min_success:
         return 0
     return 1
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     sys.exit(main())

+ 340 - 176
python/grass/gunittest/multireport.py

@@ -24,12 +24,14 @@ from grass.gunittest.reporters import success_to_html_percent
 
 # TODO: we should be able to work without matplotlib
 import matplotlib
-matplotlib.use('Agg')
+
+matplotlib.use("Agg")
 # This counts as code already, so silence "import not at top of file".
 # Perhaps in the future, switch_backend() could be used.
 import matplotlib.pyplot as plt  # noqa: E402
 from matplotlib.dates import date2num  # noqa: E402
 
+
 class TestResultSummary(object):
     def __init__(self):
         self.timestamp = None
@@ -65,10 +67,20 @@ def plot_percents(x, xticks, xlabels, successes, failures, filename, style):
     graph = fig.add_subplot(111)
 
     # Plot the data as a red line with round markers
-    graph.plot(x, successes, color=style.success_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
-    graph.plot(x, failures, color=style.fail_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
+    graph.plot(
+        x,
+        successes,
+        color=style.success_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
+    graph.plot(
+        x,
+        failures,
+        color=style.fail_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
 
     fig.autofmt_xdate()
     graph.set_xticks(xticks)
@@ -76,7 +88,7 @@ def plot_percents(x, xticks, xlabels, successes, failures, filename, style):
 
     percents = range(0, 110, 10)
     graph.set_yticks(percents)
-    graph.set_yticklabels(['%d%%' % p for p in percents])
+    graph.set_yticklabels(["%d%%" % p for p in percents])
 
     fig.savefig(filename)
 
@@ -109,14 +121,15 @@ def plot_percent_successful(x, xticks, xlabels, successes, filename, style):
     smedian = median(successes)
     smax = max(successes)
     if successes[-1] < smedian:
-        color = 'r'
+        color = "r"
     else:
-        color = 'g'
+        color = "g"
     # another possibility is to color according to the gradient, ideally
     # on the whole curve but that's much more complicated
 
-    graph.plot(x, successes, color=color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
+    graph.plot(
+        x, successes, color=color, linestyle=style.linestyle, linewidth=style.linewidth
+    )
 
     # rotates the xlabels
     fig.autofmt_xdate()
@@ -128,7 +141,7 @@ def plot_percent_successful(x, xticks, xlabels, successes, filename, style):
     ymax = int(smax / step) * step
     percents = range(ymin, ymax + step + 1, step)
     graph.set_yticks(percents)
-    graph.set_yticklabels(['%d%%' % p for p in percents])
+    graph.set_yticklabels(["%d%%" % p for p in percents])
 
     fig.savefig(filename)
 
@@ -143,9 +156,14 @@ def tests_successful_plot(x, xticks, xlabels, results, filename, style):
             # but we don't want any exceptions if it happens
             successes.append(0)
 
-    plot_percent_successful(x=x, xticks=xticks, xlabels=xlabels,
-                            successes=successes,
-                            filename=filename, style=style)
+    plot_percent_successful(
+        x=x,
+        xticks=xticks,
+        xlabels=xlabels,
+        successes=successes,
+        filename=filename,
+        style=style,
+    )
 
 
 def tests_plot(x, xticks, xlabels, results, filename, style):
@@ -159,12 +177,27 @@ def tests_plot(x, xticks, xlabels, results, filename, style):
 
     graph = fig.add_subplot(111)
 
-    graph.plot(x, total, color=style.total_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
-    graph.plot(x, successes, color=style.success_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
-    graph.plot(x, failures, color=style.fail_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
+    graph.plot(
+        x,
+        total,
+        color=style.total_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
+    graph.plot(
+        x,
+        successes,
+        color=style.success_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
+    graph.plot(
+        x,
+        failures,
+        color=style.fail_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
 
     fig.autofmt_xdate()
     graph.set_xticks(xticks)
@@ -172,6 +205,7 @@ def tests_plot(x, xticks, xlabels, results, filename, style):
 
     fig.savefig(filename)
 
+
 def tests_percent_plot(x, xticks, xlabels, results, filename, style):
     successes = []
     failures = []
@@ -186,9 +220,15 @@ def tests_percent_plot(x, xticks, xlabels, results, filename, style):
             successes.append(0)
             failures.append(0)
 
-    plot_percents(x=x, xticks=xticks, xlabels=xlabels,
-                  successes=successes, failures=failures,
-                  filename=filename, style=style)
+    plot_percents(
+        x=x,
+        xticks=xticks,
+        xlabels=xlabels,
+        successes=successes,
+        failures=failures,
+        filename=filename,
+        style=style,
+    )
 
 
 def files_successful_plot(x, xticks, xlabels, results, filename, style):
@@ -201,9 +241,14 @@ def files_successful_plot(x, xticks, xlabels, results, filename, style):
             # but we don't want any exceptions if it happens
             successes.append(0)
 
-    plot_percent_successful(x=x, xticks=xticks, xlabels=xlabels,
-                            successes=successes,
-                            filename=filename, style=style)
+    plot_percent_successful(
+        x=x,
+        xticks=xticks,
+        xlabels=xlabels,
+        successes=successes,
+        filename=filename,
+        style=style,
+    )
 
 
 def files_plot(x, xticks, xlabels, results, filename, style):
@@ -215,12 +260,27 @@ def files_plot(x, xticks, xlabels, results, filename, style):
 
     graph = fig.add_subplot(111)
 
-    graph.plot(x, total, color=style.total_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
-    graph.plot(x, successes, color=style.success_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
-    graph.plot(x, failures, color=style.fail_color,
-               linestyle=style.linestyle, linewidth=style.linewidth)
+    graph.plot(
+        x,
+        total,
+        color=style.total_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
+    graph.plot(
+        x,
+        successes,
+        color=style.success_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
+    graph.plot(
+        x,
+        failures,
+        color=style.fail_color,
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
 
     fig.autofmt_xdate()
     graph.set_xticks(xticks)
@@ -242,9 +302,15 @@ def files_percent_plot(x, xticks, xlabels, results, filename, style):
             successes.append(0)
             failures.append(0)
 
-    plot_percents(x=x, xticks=xticks, xlabels=xlabels,
-                  successes=successes, failures=failures,
-                  filename=filename, style=style)
+    plot_percents(
+        x=x,
+        xticks=xticks,
+        xlabels=xlabels,
+        successes=successes,
+        failures=failures,
+        filename=filename,
+        style=style,
+    )
 
 
 def info_plot(x, xticks, xlabels, results, filename, style):
@@ -259,17 +325,41 @@ def info_plot(x, xticks, xlabels, results, filename, style):
 
     graph = fig.add_subplot(111)
 
-    graph.plot(x, names, color='b', label="Test files",
-               linestyle=style.linestyle, linewidth=style.linewidth)
-    graph.plot(x, modules, color='g', label="Tested modules",
-               linestyle=style.linestyle, linewidth=style.linewidth)
+    graph.plot(
+        x,
+        names,
+        color="b",
+        label="Test files",
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
+    graph.plot(
+        x,
+        modules,
+        color="g",
+        label="Tested modules",
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
     # dirs == testsuites
-    graph.plot(x, dirs, color='orange', label="Tested directories",
-               linestyle=style.linestyle, linewidth=style.linewidth)
-    graph.plot(x, authors, color='r', label="Test authors",
-               linestyle=style.linestyle, linewidth=style.linewidth)
+    graph.plot(
+        x,
+        dirs,
+        color="orange",
+        label="Tested directories",
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
+    graph.plot(
+        x,
+        authors,
+        color="r",
+        label="Test authors",
+        linestyle=style.linestyle,
+        linewidth=style.linewidth,
+    )
 
-    graph.legend(loc='best', shadow=False)
+    graph.legend(loc="best", shadow=False)
 
     fig.autofmt_xdate()
     graph.set_xticks(xticks)
@@ -279,25 +369,25 @@ def info_plot(x, xticks, xlabels, results, filename, style):
 
 
 # TODO: solve the directory inconsitencies, implement None
-def main_page(results, filename, images, captions, title='Test reports',
-              directory=None):
+def main_page(
+    results, filename, images, captions, title="Test reports", directory=None
+):
     filename = os.path.join(directory, filename)
-    with open(filename, 'w') as page:
+    with open(filename, "w") as page:
         page.write(
-            '<html><body>'
-            '<h1>{title}</h1>'
-            '<table>'
-            '<thead><tr>'
-            '<th>Date (timestamp)</th><th>SVN revision</th><th>Name</th>'
-            '<th>Successful files</th><th>Successful tests</th>'
-            '</tr></thead>'
-            '<tbody>'
-            .format(title=title)
+            "<html><body>"
+            "<h1>{title}</h1>"
+            "<table>"
+            "<thead><tr>"
+            "<th>Date (timestamp)</th><th>SVN revision</th><th>Name</th>"
+            "<th>Successful files</th><th>Successful tests</th>"
+            "</tr></thead>"
+            "<tbody>".format(title=title)
         )
         for result in reversed(results):
             # TODO: include name to summary file
             # now using location or test report directory as name
-            if result.location != 'unknown':
+            if result.location != "unknown":
                 name = result.location
             else:
                 name = os.path.basename(result.report)
@@ -308,40 +398,62 @@ def main_page(results, filename, images, captions, title='Test reports',
                             name = d
                             break
             per_test = success_to_html_percent(
-                total=result.total, successes=result.successes)
+                total=result.total, successes=result.successes
+            )
             per_file = success_to_html_percent(
-                total=result.files_total, successes=result.files_successes)
+                total=result.files_total, successes=result.files_successes
+            )
             report_path = os.path.relpath(path=result.report, start=directory)
             page.write(
-                '<tr>'
-                '<td><a href={report_path}/index.html>{result.timestamp}</a></td>'
-                '<td>{result.svn_revision}</td>'
-                '<td><a href={report_path}/index.html>{name}</a></td>'
-                '<td>{pfiles}</td><td>{ptests}</td>'
-                '</tr>'
-                .format(result=result, name=name, report_path=report_path,
-                        pfiles=per_file, ptests=per_test))
-        page.write('</tbody></table>')
+                "<tr>"
+                "<td><a href={report_path}/index.html>{result.timestamp}</a></td>"
+                "<td>{result.svn_revision}</td>"
+                "<td><a href={report_path}/index.html>{name}</a></td>"
+                "<td>{pfiles}</td><td>{ptests}</td>"
+                "</tr>".format(
+                    result=result,
+                    name=name,
+                    report_path=report_path,
+                    pfiles=per_file,
+                    ptests=per_test,
+                )
+            )
+        page.write("</tbody></table>")
         for image, caption in itertools.izip(images, captions):
             page.write(
-                '<h3>{caption}<h3>'
-                '<img src="{image}" alt="{caption}" title="{caption}">'
-                .format(image=image, caption=caption))
-        page.write('</body></html>')
+                "<h3>{caption}<h3>"
+                '<img src="{image}" alt="{caption}" title="{caption}">'.format(
+                    image=image, caption=caption
+                )
+            )
+        page.write("</body></html>")
 
 
 def main():
 
     parser = argparse.ArgumentParser(
-        description='Create overall report from several individual test reports')
-    parser.add_argument('reports', metavar='report_directory',
-                        type=str, nargs='+',
-                        help='Directories with reports')
-    parser.add_argument('--output', dest='output', action='store',
-                        default='testreports_summary',
-                        help='Output directory')
-    parser.add_argument('--timestamps', dest='timestamps', action='store_true',
-                        help='Use file timestamp instead of date in test summary')
+        description="Create overall report from several individual test reports"
+    )
+    parser.add_argument(
+        "reports",
+        metavar="report_directory",
+        type=str,
+        nargs="+",
+        help="Directories with reports",
+    )
+    parser.add_argument(
+        "--output",
+        dest="output",
+        action="store",
+        default="testreports_summary",
+        help="Output directory",
+    )
+    parser.add_argument(
+        "--timestamps",
+        dest="timestamps",
+        action="store_true",
+        help="Use file timestamp instead of date in test summary",
+    )
 
     args = parser.parse_args()
     output = args.output
@@ -355,40 +467,46 @@ def main():
 
     for report in reports:
         try:
-            summary_file = os.path.join(report, 'test_keyvalue_result.txt')
+            summary_file = os.path.join(report, "test_keyvalue_result.txt")
             if not os.path.exists(summary_file):
-                sys.stderr.write('WARNING: Key-value summary not available in'
-                                 ' report <%s>, skipping.\n' % summary_file)
+                sys.stderr.write(
+                    "WARNING: Key-value summary not available in"
+                    " report <%s>, skipping.\n" % summary_file
+                )
                 # skipping incomplete reports
                 # use only results list for further processing
                 continue
-            summary = text_to_keyvalue(open(summary_file).read(), sep='=')
+            summary = text_to_keyvalue(open(summary_file).read(), sep="=")
             if use_timestamps:
-                test_timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(summary_file))
+                test_timestamp = datetime.datetime.fromtimestamp(
+                    os.path.getmtime(summary_file)
+                )
             else:
-                test_timestamp = datetime.datetime.strptime(summary['timestamp'], "%Y-%m-%d %H:%M:%S")
+                test_timestamp = datetime.datetime.strptime(
+                    summary["timestamp"], "%Y-%m-%d %H:%M:%S"
+                )
 
             result = TestResultSummary()
             result.timestamp = test_timestamp
-            result.total = summary['total']
-            result.successes = summary['successes']
-            result.failures = summary['failures']
-            result.errors = summary['errors']
-
-            result.files_total = summary['files_total']
-            result.files_successes = summary['files_successes']
-            result.files_failures = summary['files_failures']
-
-            result.svn_revision = str(summary['svn_revision'])
-            result.tested_modules = summary['tested_modules']
-            result.names = summary['names']
-            result.test_files_authors = summary['test_files_authors']
-            result.tested_dirs = summary['tested_dirs']
+            result.total = summary["total"]
+            result.successes = summary["successes"]
+            result.failures = summary["failures"]
+            result.errors = summary["errors"]
+
+            result.files_total = summary["files_total"]
+            result.files_successes = summary["files_successes"]
+            result.files_failures = summary["files_failures"]
+
+            result.svn_revision = str(summary["svn_revision"])
+            result.tested_modules = summary["tested_modules"]
+            result.names = summary["names"]
+            result.test_files_authors = summary["test_files_authors"]
+            result.tested_dirs = summary["tested_dirs"]
             result.report = report
 
             # let's consider no location as valid state and use 'unknown'
-            result.location = summary.get('location', 'unknown')
-            result.location_type = summary.get('location_type', 'unknown')
+            result.location = summary.get("location", "unknown")
+            result.location_type = summary.get("location_type", "unknown")
             # grouping according to location types
             # this can cause that two actual locations tested at the same time
             # will end up together, this is not ideal but testing with
@@ -400,105 +518,151 @@ def main():
             all_results.append(result)
             del result
         except KeyError as e:
-            print('File %s does not have right values (%s)' % (report, e.message))
+            print("File %s does not have right values (%s)" % (report, e.message))
 
-    locations_main_page = open(os.path.join(output, 'index.html'), 'w')
+    locations_main_page = open(os.path.join(output, "index.html"), "w")
     locations_main_page.write(
-        '<html><body>'
-        '<h1>Test reports grouped by location type</h1>'
-        '<table>'
-        '<thead><tr>'
-        '<th>Location</th>'
-        '<th>Successful files</th><th>Successful tests</th>'
-        '</tr></thead>'
-        '<tbody>'
+        "<html><body>"
+        "<h1>Test reports grouped by location type</h1>"
+        "<table>"
+        "<thead><tr>"
+        "<th>Location</th>"
+        "<th>Successful files</th><th>Successful tests</th>"
+        "</tr></thead>"
+        "<tbody>"
     )
 
-    PlotStyle = namedtuple('PlotStyle',
-                           ['linestyle', 'linewidth',
-                           'success_color', 'fail_color', 'total_color'])
-    plot_style = PlotStyle(linestyle='-', linewidth=4.0,
-                           success_color='g', fail_color='r', total_color='b')
+    PlotStyle = namedtuple(
+        "PlotStyle",
+        ["linestyle", "linewidth", "success_color", "fail_color", "total_color"],
+    )
+    plot_style = PlotStyle(
+        linestyle="-", linewidth=4.0, success_color="g", fail_color="r", total_color="b"
+    )
 
     for location_type, results in results_in_locations.items():
-        results = sorted(results, key=operator.attrgetter('timestamp'))
+        results = sorted(results, key=operator.attrgetter("timestamp"))
         # TODO: document: location type must be a valid dir name
         directory = os.path.join(output, location_type)
         ensure_dir(directory)
 
-        if location_type == 'unknown':
-            title = 'Test reports'
+        if location_type == "unknown":
+            title = "Test reports"
         else:
-            title = ('Test reports for &lt;{type}&gt; location type'
-                     .format(type=location_type))
+            title = "Test reports for &lt;{type}&gt; location type".format(
+                type=location_type
+            )
 
         x = [date2num(result.timestamp) for result in results]
         # the following would be an alternative but it does not work with
         # labels and automatic axis limits even after removing another date fun
         # x = [result.svn_revision for result in results]
-        xlabels = [result.timestamp.strftime("%Y-%m-%d") + ' (r' + result.svn_revision + ')' for result in results]
+        xlabels = [
+            result.timestamp.strftime("%Y-%m-%d") + " (r" + result.svn_revision + ")"
+            for result in results
+        ]
         step = len(x) / 10
         xticks = x[step::step]
         xlabels = xlabels[step::step]
-        tests_successful_plot(x=x, xticks=xticks, xlabels=xlabels, results=results,
-                              filename=os.path.join(directory, 'tests_successful_plot.png'),
-                              style=plot_style)
-        files_successful_plot(x=x, xticks=xticks, xlabels=xlabels, results=results,
-                              filename=os.path.join(directory, 'files_successful_plot.png'),
-                              style=plot_style)
-        tests_plot(x=x, xticks=xticks, xlabels=xlabels, results=results,
-                   filename=os.path.join(directory, 'tests_plot.png'),
-                   style=plot_style)
-        tests_percent_plot(x=x, xticks=xticks, xlabels=xlabels, results=results,
-                           filename=os.path.join(directory, 'tests_percent_plot.png'),
-                           style=plot_style)
-        files_plot(x=x, xticks=xticks, xlabels=xlabels, results=results,
-                   filename=os.path.join(directory, 'files_plot.png'),
-                   style=plot_style)
-        files_percent_plot(x=x, xticks=xticks, xlabels=xlabels, results=results,
-                           filename=os.path.join(directory, 'files_percent_plot.png'),
-                           style=plot_style)
-        info_plot(x=x, xticks=xticks, xlabels=xlabels, results=results,
-                  filename=os.path.join(directory, 'info_plot.png'),
-                   style=plot_style)
-
-        main_page(results=results, filename='index.html',
-                  images=['tests_successful_plot.png',
-                          'files_successful_plot.png',
-                          'tests_plot.png',
-                          'files_plot.png',
-                          'tests_percent_plot.png',
-                          'files_percent_plot.png',
-                          'info_plot.png'],
-                  captions=['Success of individual tests in percents',
-                            'Success of test files in percents',
-                            'Successes, failures and number of individual tests',
-                            'Successes, failures and number of test files',
-                            'Successes and failures of individual tests in percent',
-                            'Successes and failures of test files in percents',
-                            'Additional information'],
-                  directory=directory,
-                  title=title)
+        tests_successful_plot(
+            x=x,
+            xticks=xticks,
+            xlabels=xlabels,
+            results=results,
+            filename=os.path.join(directory, "tests_successful_plot.png"),
+            style=plot_style,
+        )
+        files_successful_plot(
+            x=x,
+            xticks=xticks,
+            xlabels=xlabels,
+            results=results,
+            filename=os.path.join(directory, "files_successful_plot.png"),
+            style=plot_style,
+        )
+        tests_plot(
+            x=x,
+            xticks=xticks,
+            xlabels=xlabels,
+            results=results,
+            filename=os.path.join(directory, "tests_plot.png"),
+            style=plot_style,
+        )
+        tests_percent_plot(
+            x=x,
+            xticks=xticks,
+            xlabels=xlabels,
+            results=results,
+            filename=os.path.join(directory, "tests_percent_plot.png"),
+            style=plot_style,
+        )
+        files_plot(
+            x=x,
+            xticks=xticks,
+            xlabels=xlabels,
+            results=results,
+            filename=os.path.join(directory, "files_plot.png"),
+            style=plot_style,
+        )
+        files_percent_plot(
+            x=x,
+            xticks=xticks,
+            xlabels=xlabels,
+            results=results,
+            filename=os.path.join(directory, "files_percent_plot.png"),
+            style=plot_style,
+        )
+        info_plot(
+            x=x,
+            xticks=xticks,
+            xlabels=xlabels,
+            results=results,
+            filename=os.path.join(directory, "info_plot.png"),
+            style=plot_style,
+        )
+
+        main_page(
+            results=results,
+            filename="index.html",
+            images=[
+                "tests_successful_plot.png",
+                "files_successful_plot.png",
+                "tests_plot.png",
+                "files_plot.png",
+                "tests_percent_plot.png",
+                "files_percent_plot.png",
+                "info_plot.png",
+            ],
+            captions=[
+                "Success of individual tests in percents",
+                "Success of test files in percents",
+                "Successes, failures and number of individual tests",
+                "Successes, failures and number of test files",
+                "Successes and failures of individual tests in percent",
+                "Successes and failures of test files in percents",
+                "Additional information",
+            ],
+            directory=directory,
+            title=title,
+        )
 
         files_successes = sum(result.files_successes for result in results)
         files_total = sum(result.files_total for result in results)
         successes = sum(result.successes for result in results)
         total = sum(result.total for result in results)
-        per_test = success_to_html_percent(
-            total=total, successes=successes)
-        per_file = success_to_html_percent(
-            total=files_total, successes=files_successes)
+        per_test = success_to_html_percent(total=total, successes=successes)
+        per_file = success_to_html_percent(total=files_total, successes=files_successes)
         locations_main_page.write(
-            '<tr>'
-            '<td><a href={location}/index.html>{location}</a></td>'
-            '<td>{pfiles}</td><td>{ptests}</td>'
-            '</tr>'
-            .format(location=location_type,
-                    pfiles=per_file, ptests=per_test))
-    locations_main_page.write('</tbody></table>')
-    locations_main_page.write('</body></html>')
+            "<tr>"
+            "<td><a href={location}/index.html>{location}</a></td>"
+            "<td>{pfiles}</td><td>{ptests}</td>"
+            "</tr>".format(location=location_type, pfiles=per_file, ptests=per_test)
+        )
+    locations_main_page.write("</tbody></table>")
+    locations_main_page.write("</body></html>")
     locations_main_page.close()
     return 0
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     sys.exit(main())

+ 82 - 43
python/grass/gunittest/multirunner.py

@@ -29,7 +29,7 @@ if sys.version_info.major >= 3:
 def _get_encoding():
     encoding = locale.getdefaultlocale()[1]
     if not encoding:
-        encoding = 'UTF-8'
+        encoding = "UTF-8"
     return encoding
 
 
@@ -49,9 +49,9 @@ def encode(string, encoding=None):
 
 def text_to_string(text):
     """Convert text to str. Useful when passing text into environments,
-       in Python 2 it needs to be bytes on Windows, in Python 3 in needs unicode.
+    in Python 2 it needs to be bytes on Windows, in Python 3 in needs unicode.
     """
-    if sys.version[0] == '2':
+    if sys.version[0] == "2":
         # Python 2
         return encode(text)
     else:
@@ -59,28 +59,45 @@ def text_to_string(text):
         return decode(text)
 
 
-
 def main():
-    parser = argparse.ArgumentParser(
-        description='Run tests with new')
-    parser.add_argument('--location', '-l', required=True, action='append',
-                        dest='locations', metavar='LOCATION',
-                        help='Directories with reports')
-    parser.add_argument('--location-type', '-t', action='append',
-                        dest='location_types',
-                    default=[], metavar='TYPE',
-                    help='Add repeated values to a list',
-                        )
-    parser.add_argument('--grassbin', required=True,
-                        help='Use file timestamp instead of date in test summary')
+    parser = argparse.ArgumentParser(description="Run tests with new")
+    parser.add_argument(
+        "--location",
+        "-l",
+        required=True,
+        action="append",
+        dest="locations",
+        metavar="LOCATION",
+        help="Directories with reports",
+    )
+    parser.add_argument(
+        "--location-type",
+        "-t",
+        action="append",
+        dest="location_types",
+        default=[],
+        metavar="TYPE",
+        help="Add repeated values to a list",
+    )
+    parser.add_argument(
+        "--grassbin",
+        required=True,
+        help="Use file timestamp instead of date in test summary",
+    )
     # TODO: rename since every src can be used?
-    parser.add_argument('--grasssrc', required=True,
-                        help='GRASS GIS source code (to take tests from)')
-    parser.add_argument('--grassdata', required=True,
-                        help='GRASS GIS data base (GISDBASE)')
-    parser.add_argument('--create-main-report',
-                        help='Create also main report for all tests',
-                        action="store_true", default=False, dest='main_report')
+    parser.add_argument(
+        "--grasssrc", required=True, help="GRASS GIS source code (to take tests from)"
+    )
+    parser.add_argument(
+        "--grassdata", required=True, help="GRASS GIS data base (GISDBASE)"
+    )
+    parser.add_argument(
+        "--create-main-report",
+        help="Create also main report for all tests",
+        action="store_true",
+        default=False,
+        dest="main_report",
+    )
 
     args = parser.parse_args()
     gisdb = args.grassdata
@@ -89,33 +106,39 @@ def main():
 
     # TODO: if locations empty or just one we can suppose the same all the time
     if len(locations) != len(locations_types):
-        print("ERROR: Number of locations and their tags must be the same", file=sys.stderr)
+        print(
+            "ERROR: Number of locations and their tags must be the same",
+            file=sys.stderr,
+        )
         return 1
 
-
     main_report = args.main_report
     grasssrc = args.grasssrc  # TODO: can be guessed from dist
     # TODO: create directory according to date and revision and create reports there
 
     # some predefined variables, name of the GRASS launch script + location/mapset
-    #grass7bin = 'C:\Program Files (x86)\GRASS GIS 7.9.git\grass79dev.bat'
+    # grass7bin = 'C:\Program Files (x86)\GRASS GIS 7.9.git\grass79dev.bat'
     grass7bin = args.grassbin  # TODO: can be used if pressent
 
     ########### SOFTWARE
     # query GRASS 7 itself for its GISBASE
     # we assume that GRASS GIS' start script is available and in the PATH
     # the shell=True is here because of MS Windows? (code taken from wiki)
-    startcmd = grass7bin + ' --config path'
-    p = subprocess.Popen(startcmd, shell=True,
-                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    startcmd = grass7bin + " --config path"
+    p = subprocess.Popen(
+        startcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+    )
     out, err = p.communicate()
     if p.returncode != 0:
-        print("ERROR: Cannot find GRASS GIS 7 start script (%s):\n%s" % (startcmd, err), file=sys.stderr)
+        print(
+            "ERROR: Cannot find GRASS GIS 7 start script (%s):\n%s" % (startcmd, err),
+            file=sys.stderr,
+        )
         return 1
     gisbase = decode(out.strip())
 
     # set GISBASE environment variable
-    os.environ['GISBASE'] = text_to_string(gisbase)
+    os.environ["GISBASE"] = text_to_string(gisbase)
     # define GRASS Python environment
     grass_python_dir = os.path.join(gisbase, "etc", "python")
     sys.path.append(grass_python_dir)
@@ -124,7 +147,7 @@ def main():
     # define GRASS DATABASE
 
     # Set GISDBASE environment variable
-    os.environ['GISDBASE'] = text_to_string(gisdb)
+    os.environ["GISDBASE"] = text_to_string(gisdb)
 
     # import GRASS Python package for initialization
     import grass.script.setup as gsetup
@@ -132,28 +155,43 @@ def main():
     # launch session
     # we need some location and mapset here
     # TODO: can init work without it or is there some demo location in dist?
-    location = locations[0].split(':')[0]
-    mapset = 'PERMANENT'
+    location = locations[0].split(":")[0]
+    mapset = "PERMANENT"
     gsetup.init(gisbase, gisdb, location, mapset)
 
     reports = []
     for location, location_type in zip(locations, locations_types):
         # here it is quite a good place to parallelize
         # including also type to make it unique and preserve it for sure
-        report = 'report_for_' + location + '_' + location_type
+        report = "report_for_" + location + "_" + location_type
         absreport = os.path.abspath(report)
-        p = subprocess.Popen([sys.executable, '-tt',
-                              '-m', 'grass.gunittest.main',
-                              '--grassdata', gisdb, '--location', location,
-                              '--location-type', location_type,
-                              '--output', absreport],
-                              cwd=grasssrc)
+        p = subprocess.Popen(
+            [
+                sys.executable,
+                "-tt",
+                "-m",
+                "grass.gunittest.main",
+                "--grassdata",
+                gisdb,
+                "--location",
+                location,
+                "--location-type",
+                location_type,
+                "--output",
+                absreport,
+            ],
+            cwd=grasssrc,
+        )
         returncode = p.wait()
         reports.append(report)
 
     if main_report:
         # TODO: solve the path to source code (work now only for grass source code)
-        arguments = [sys.executable, grasssrc + '/python/grass/gunittest/' + 'multireport.py', '--timestapms']
+        arguments = [
+            sys.executable,
+            grasssrc + "/python/grass/gunittest/" + "multireport.py",
+            "--timestapms",
+        ]
         arguments.extend(reports)
         p = subprocess.Popen(arguments)
         returncode = p.wait()
@@ -163,5 +201,6 @@ def main():
 
     return 0
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     sys.exit(main())

File diff suppressed because it is too large
+ 446 - 397
python/grass/gunittest/reporters.py


+ 57 - 47
python/grass/gunittest/runner.py

@@ -24,18 +24,18 @@ __unittest = True
 class _WritelnDecorator(object):
     """Used to decorate file-like objects with a handy 'writeln' method"""
 
-    def __init__(self,stream):
+    def __init__(self, stream):
         self.stream = stream
 
     def __getattr__(self, attr):
-        if attr in ('stream', '__getstate__'):
+        if attr in ("stream", "__getstate__"):
             raise AttributeError(attr)
-        return getattr(self.stream,attr)
+        return getattr(self.stream, attr)
 
     def writeln(self, arg=None):
         if arg:
             self.write(arg)
-        self.write('\n') # text-mode streams translate to \r\n if needed
+        self.write("\n")  # text-mode streams translate to \r\n if needed
 
 
 class TestResult(unittest.TestResult):
@@ -44,9 +44,9 @@ class TestResult(unittest.TestResult):
     # where are also unused, so perhaps we can remove them
     # stream set to None and not included in interface, it would not make sense
     def __init__(self, stream=None, descriptions=None, verbosity=None):
-        super(TestResult, self).__init__(stream=stream,
-                                         descriptions=descriptions,
-                                         verbosity=verbosity)
+        super(TestResult, self).__init__(
+            stream=stream, descriptions=descriptions, verbosity=verbosity
+        )
         self.successes = []
 
     def addSuccess(self, test):
@@ -67,12 +67,14 @@ class TextTestResult(TestResult):
 
     Used by TextTestRunner.
     """
-    separator1 = '=' * 70
-    separator2 = '-' * 70
+
+    separator1 = "=" * 70
+    separator2 = "-" * 70
 
     def __init__(self, stream, descriptions, verbosity):
         super(TextTestResult, self).__init__(
-            stream=stream, descriptions=descriptions, verbosity=verbosity)
+            stream=stream, descriptions=descriptions, verbosity=verbosity
+        )
         self.stream = _WritelnDecorator(stream)
         self.showAll = verbosity > 1
         self.dots = verbosity == 1
@@ -85,7 +87,7 @@ class TextTestResult(TestResult):
     def getDescription(self, test):
         doc_first_line = test.shortDescription()
         if self.descriptions and doc_first_line:
-            return '\n'.join((str(test), doc_first_line))
+            return "\n".join((str(test), doc_first_line))
         else:
             return str(test)
 
@@ -101,7 +103,7 @@ class TextTestResult(TestResult):
         if self.showAll:
             self.stream.writeln("ok")
         elif self.dots:
-            self.stream.write('.')
+            self.stream.write(".")
             self.stream.flush()
 
     def addError(self, test, err):
@@ -109,7 +111,7 @@ class TextTestResult(TestResult):
         if self.showAll:
             self.stream.writeln("ERROR")
         elif self.dots:
-            self.stream.write('E')
+            self.stream.write("E")
             self.stream.flush()
 
     def addFailure(self, test, err):
@@ -117,7 +119,7 @@ class TextTestResult(TestResult):
         if self.showAll:
             self.stream.writeln("FAIL")
         elif self.dots:
-            self.stream.write('F')
+            self.stream.write("F")
             self.stream.flush()
 
     def addSkip(self, test, reason):
@@ -147,14 +149,13 @@ class TextTestResult(TestResult):
     def printErrors(self):
         if self.dots or self.showAll:
             self.stream.writeln()
-        self.printErrorList('ERROR', self.errors)
-        self.printErrorList('FAIL', self.failures)
+        self.printErrorList("ERROR", self.errors)
+        self.printErrorList("FAIL", self.failures)
 
     def printErrorList(self, flavour, errors):
         for test, err in errors:
             self.stream.writeln(self.separator1)
-            self.stream.writeln("%s: %s" % (flavour,
-                                            self.getDescription(test)))
+            self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
             self.stream.writeln(self.separator2)
             self.stream.writeln("%s" % err)
 
@@ -174,9 +175,9 @@ class TextTestResult(TestResult):
         self.stream.writeln()
 
         expectedFails = unexpectedSuccesses = skipped = 0
-        results = map(len, (self.expectedFailures,
-                            self.unexpectedSuccesses,
-                            self.skipped))
+        results = map(
+            len, (self.expectedFailures, self.unexpectedSuccesses, self.skipped)
+        )
         expectedFails, unexpectedSuccesses, skipped = results
 
         infos = []
@@ -206,12 +207,14 @@ class KeyValueTestResult(TestResult):
 
     Used by TextTestRunner.
     """
-    separator1 = '=' * 70
-    separator2 = '-' * 70
+
+    separator1 = "=" * 70
+    separator2 = "-" * 70
 
     def __init__(self, stream, test_type=None):
         super(KeyValueTestResult, self).__init__(
-            stream=stream, descriptions=None, verbosity=None)
+            stream=stream, descriptions=None, verbosity=None
+        )
         self._stream = _WritelnDecorator(stream)
 
         self.start_time = None
@@ -221,7 +224,7 @@ class KeyValueTestResult(TestResult):
         if test_type:
             self.test_type = test_type
         else:
-            self.test_type = 'not-specified'
+            self.test_type = "not-specified"
 
         self._grass_modules = []
         self._supplementary_files = []
@@ -233,9 +236,9 @@ class KeyValueTestResult(TestResult):
 
     def stopTest(self, test):
         super(KeyValueTestResult, self).stopTest(test)
-        if hasattr(test, 'grass_modules'):
+        if hasattr(test, "grass_modules"):
             self._grass_modules.extend(test.grass_modules)
-        if hasattr(test, 'supplementary_files'):
+        if hasattr(test, "supplementary_files"):
             self._supplementary_files.extend(test.supplementary_files)
 
     def stopTestRun(self):
@@ -251,19 +254,19 @@ class KeyValueTestResult(TestResult):
         # infos.append("name=%s" % 'unknown')
 
         infos.append("time=%.3fs" % (self.time_taken))
-#            'date={rundate}\n'
-#            'date={runtime}\n'
-#            'date={start_datetime}\n'
-#            'date={end_datetime}\n'
+        #            'date={rundate}\n'
+        #            'date={runtime}\n'
+        #            'date={start_datetime}\n'
+        #            'date={end_datetime}\n'
 
         failed, errored = map(len, (self.failures, self.errors))
         succeeded = len(self.successes)
-        results = map(len, (self.expectedFailures,
-                            self.unexpectedSuccesses,
-                            self.skipped))
+        results = map(
+            len, (self.expectedFailures, self.unexpectedSuccesses, self.skipped)
+        )
         expectedFails, unexpectedSuccesses, skipped = results
 
-        status = 'succeeded' if self.wasSuccessful() else 'failed'
+        status = "succeeded" if self.wasSuccessful() else "failed"
         infos.append("status=%s" % status)
 
         # if only errors occur, tests are not counted properly
@@ -287,8 +290,8 @@ class KeyValueTestResult(TestResult):
         infos.append("unexpected_successes=%d" % unexpectedSuccesses)
 
         # TODO: include each module just once? list good and bad modules?
-        infos.append("tested_modules=%s" % ','.join(self._grass_modules))
-        infos.append("supplementary_files=%s" % ','.join(self._supplementary_files))
+        infos.append("tested_modules=%s" % ",".join(self._grass_modules))
+        infos.append("supplementary_files=%s" % ",".join(self._supplementary_files))
 
         # module, modules?, c, c++?, python
         # TODO: include also type modules?
@@ -296,8 +299,8 @@ class KeyValueTestResult(TestResult):
         # TODO: distinguish C and Python modules?
         infos.append("test_type=%s" % (self.test_type))
 
-        self._stream.write('\n'.join(infos))
-        self._stream.write('\n')
+        self._stream.write("\n".join(infos))
+        self._stream.write("\n")
         self._stream.flush()
 
 
@@ -306,10 +309,10 @@ class MultiTestResult(TestResult):
     # included for compatibility with unittest's TestResult
     # where are also unused, so perhaps we can remove them
     # stream set to None and not included in interface, it would not make sense
-    def __init__(self, results, forgiving=False,
-                 descriptions=None, verbosity=None):
+    def __init__(self, results, forgiving=False, descriptions=None, verbosity=None):
         super(MultiTestResult, self).__init__(
-            descriptions=descriptions, verbosity=verbosity, stream=None)
+            descriptions=descriptions, verbosity=verbosity, stream=None
+        )
         self._results = results
         self._forgiving = forgiving
 
@@ -460,8 +463,15 @@ class MultiTestResult(TestResult):
 
 
 class GrassTestRunner(object):
-    def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
-                 failfast=False, buffer=False, result=None):
+    def __init__(
+        self,
+        stream=sys.stderr,
+        descriptions=True,
+        verbosity=1,
+        failfast=False,
+        buffer=False,
+        result=None,
+    ):
         self.stream = _WritelnDecorator(stream)
         self.descriptions = descriptions
         self.verbosity = verbosity
@@ -476,7 +486,7 @@ class GrassTestRunner(object):
         result.failfast = self.failfast
         result.buffer = self.buffer
         startTime = time.time()
-        startTestRun = getattr(result, 'startTestRun', None)
+        startTestRun = getattr(result, "startTestRun", None)
         if startTestRun is not None:
             startTestRun()
         try:
@@ -484,10 +494,10 @@ class GrassTestRunner(object):
         finally:
             stopTime = time.time()
             timeTaken = stopTime - startTime
-            setTimes = getattr(result, 'setTimes', None)
+            setTimes = getattr(result, "setTimes", None)
             if setTimes is not None:
                 setTimes(startTime, stopTime, timeTaken)
-            stopTestRun = getattr(result, 'stopTestRun', None)
+            stopTestRun = getattr(result, "stopTestRun", None)
             if stopTestRun is not None:
                 stopTestRun()
 

+ 6 - 6
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_errors/testsuite/test_error.py

@@ -8,7 +8,7 @@ class TestError(TestCase):
     # pylint: disable=R0904
 
     def test_something(self):
-        raise RuntimeError('Error in test function')
+        raise RuntimeError("Error in test function")
         self.assertTrue(True)
 
 
@@ -16,7 +16,7 @@ class TestErrorSetUp(TestCase):
     # pylint: disable=R0904
 
     def setUp(self):
-        raise RuntimeError('Error in setUp')
+        raise RuntimeError("Error in setUp")
 
     def test_something(self):
         self.assertTrue(True)
@@ -26,7 +26,7 @@ class TestErrorTearDown(TestCase):
     # pylint: disable=R0904
 
     def tearDown(self):
-        raise RuntimeError('Error in tearDown')
+        raise RuntimeError("Error in tearDown")
 
     def test_something(self):
         self.assertTrue(True)
@@ -37,7 +37,7 @@ class TestErrorClassSetUp(TestCase):
 
     @classmethod
     def setUpClass(cls):
-        raise RuntimeError('Error in setUpClass')
+        raise RuntimeError("Error in setUpClass")
 
     def test_something(self):
         self.assertTrue(True)
@@ -48,11 +48,11 @@ class TestErrorClassTearDown(TestCase):
 
     @classmethod
     def tearDownClass(cls):
-        raise RuntimeError('Error in tearDownClass')
+        raise RuntimeError("Error in tearDownClass")
 
     def test_something(self):
         self.assertTrue(True)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 8 - 6
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_errors/testsuite/test_import_error.py

@@ -2,7 +2,7 @@
 
 # comment to get rid of the wrong import
 # (if it is imported before all tests start and everything would fail)
-#import this_module_or_package_does_not_exists__testing_import_error
+# import this_module_or_package_does_not_exists__testing_import_error
 
 from grass.gunittest.case import TestCase
 from grass.gunittest.main import test
@@ -12,11 +12,13 @@ class TestNeverCalled(TestCase):
     # pylint: disable=R0904
 
     def test_something(self):
-        self.assertFalse("This should not be called"
-                         " if we are testing failed import."
-                         " It is all right if this fails and the wrong"
-                         " import is commented.")
+        self.assertFalse(
+            "This should not be called"
+            " if we are testing failed import."
+            " It is all right if this fails and the wrong"
+            " import is commented."
+        )
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 1 - 1
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_gfatalerror.py

@@ -12,5 +12,5 @@ class TestGFatalError(TestCase):
         libgis.G_fatal_error("Testing G_fatal_error() function call")
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 1 - 1
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_osexit_one.py

@@ -12,5 +12,5 @@ class TestOsExit(TestCase):
         os._exit(1)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 1 - 1
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_osexit_zero.py

@@ -12,5 +12,5 @@ class TestOsExit(TestCase):
         os._exit(0)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 3 - 3
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_segfaut.py

@@ -10,14 +10,14 @@ class TestSegfault(TestCase):
 
     def test_something(self):
         """Crash the Python interpreter"""
-        i = ctypes.c_char('a')
+        i = ctypes.c_char("a")
         j = ctypes.pointer(i)
         c = 0
         while True:
-            j[c] = 'a'
+            j[c] = "a"
             c += 1
         j
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 1 - 1
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_sysexit_one.py

@@ -12,5 +12,5 @@ class TestSysExit(TestCase):
         sys.exit(1)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 1 - 1
python/grass/gunittest/testsuite/data/samplecode/submodule_errors/subsubmodule_exiting/testsuite/test_sysexit_zero.py

@@ -12,5 +12,5 @@ class TestSysExit(TestCase):
         sys.exit(0)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 1 - 1
python/grass/gunittest/testsuite/data/samplecode/submodule_test_fail/testsuite/test_fail.py

@@ -11,5 +11,5 @@ class TestFail(TestCase):
         self.assertTrue(False)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 3 - 2
python/grass/gunittest/testsuite/data/samplecode/testsuite/test_good_and_bad.py

@@ -17,8 +17,9 @@ class TestSuccessAndFailure(TestCase):
         self.assertTrue(False, msg="This failed in test_good_and_bad")
 
     def test_something_erroring(self):
-        raise RuntimeError('Some error which was raised')
+        raise RuntimeError("Some error which was raised")
         self.assertTrue(True, msg="This should not fail in test_good_and_bad")
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     test()

+ 2 - 1
python/grass/gunittest/testsuite/data/samplecode/testsuite/test_python_unittest.py

@@ -36,5 +36,6 @@ class TestUnittestSuccessVerboseClassSetUp(TestCase):
     def test_something_failing(self):
         self.assertTrue(False, msg="This should fail")
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     main()

+ 2 - 1
python/grass/gunittest/testsuite/data/samplecode/testsuite/test_success.py

@@ -32,5 +32,6 @@ class TestSuccessVerboseClassSetUp(TestCase):
     def test_something(self):
         self.assertTrue(True)
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     test()

+ 206 - 164
python/grass/gunittest/testsuite/test_assertions.py

@@ -22,36 +22,37 @@ class TestTextAssertions(TestCase):
     platfrom_newline = "aaa{nl}bbb{nl}".format(nl=os.linesep)
 
     def test_assertLooksLike(self):
-        self.assertLooksLike("Generated map is <elevation>",
-                             "Generated map is <...>")
-        self.assertRaises(self.failureException,
-                          self.assertLooksLike,
-                          "Generated map is elevation.",
-                          "Generated map is <...>")
-        self.assertLooksLike("Projection string: '+proj=longlat +datum=WGS84'",
-                             "Projection string: ...")
+        self.assertLooksLike("Generated map is <elevation>", "Generated map is <...>")
+        self.assertRaises(
+            self.failureException,
+            self.assertLooksLike,
+            "Generated map is elevation.",
+            "Generated map is <...>",
+        )
+        self.assertLooksLike(
+            "Projection string: '+proj=longlat +datum=WGS84'", "Projection string: ..."
+        )
 
     def test_assertLooksLike_multiline(self):
-        self.assertLooksLike("a=123\nb=456\nc=789",
-                             "a=...\nb=...\nc=...")
+        self.assertLooksLike("a=123\nb=456\nc=789", "a=...\nb=...\nc=...")
 
     def test_assertLooksLike_multiline_platform_dependent(self):
-        self.assertLooksLike("a=123\nb=456\nc=789",
-                             "a=...{nl}b=...{nl}c=...".format(nl=os.linesep))
+        self.assertLooksLike(
+            "a=123\nb=456\nc=789", "a=...{nl}b=...{nl}c=...".format(nl=os.linesep)
+        )
 
     def test_assertLooksLike_numbers(self):
-        self.assertLooksLike("abc = 125521",
-                             "abc = 125...")
-        self.assertLooksLike("abc = 689.156",
-                             "abc = 689...")
-        self.assertLooksLike("abc = 689.159589",
-                             "abc = 689.15...")
+        self.assertLooksLike("abc = 125521", "abc = 125...")
+        self.assertLooksLike("abc = 689.156", "abc = 689...")
+        self.assertLooksLike("abc = 689.159589", "abc = 689.15...")
         # this should fail according to the implementation
         # first three dots are considered as ellipses
-        self.assertRaises(self.failureException,
-                          self.assertLooksLike,
-                          "abc = 689.159589",
-                          "abc = 689....")
+        self.assertRaises(
+            self.failureException,
+            self.assertLooksLike,
+            "abc = 689.159589",
+            "abc = 689....",
+        )
 
     def do_all_combidnations(self, first, second, function):
         function(first, first)
@@ -61,27 +62,30 @@ class TestTextAssertions(TestCase):
 
     def test_assertMultiLineEqual(self):
         r"""Test different combinations of ``\n`` and os.linesep"""
-        self.do_all_combidnations(self.std_newline, self.platfrom_newline,
-                                  function=self.assertMultiLineEqual)
+        self.do_all_combidnations(
+            self.std_newline, self.platfrom_newline, function=self.assertMultiLineEqual
+        )
 
     def test_assertMultiLineEqual_raises(self):
         """Test mixed line endings"""
-        self.assertRaises(self.failureException,
-                          self.assertMultiLineEqual,
-                          "aaa\n\rbbb\r",
-                          "aaa\nbbb\n")
+        self.assertRaises(
+            self.failureException,
+            self.assertMultiLineEqual,
+            "aaa\n\rbbb\r",
+            "aaa\nbbb\n",
+        )
 
     def test_assertEqual(self):
         """Test for of newlines for strings (uses overwritten assertMultiLineEqual())"""
-        self.do_all_combidnations(self.std_newline, self.platfrom_newline,
-                                  function=self.assertEqual)
+        self.do_all_combidnations(
+            self.std_newline, self.platfrom_newline, function=self.assertEqual
+        )
 
     def test_assertEqual_raises(self):
         """Test mixed line endings"""
-        self.assertRaises(self.failureException,
-                          self.assertEqual,
-                          "aaa\n\rbbb\r",
-                          "aaa\nbbb\n")
+        self.assertRaises(
+            self.failureException, self.assertEqual, "aaa\n\rbbb\r", "aaa\nbbb\n"
+        )
 
 
 R_UNIVAR_ELEVATION_SUBSET = """n=2025000
@@ -102,7 +106,7 @@ datatype=FCELL
 """
 
 # r.info -gre map=elevation
-ELEVATION_MAPSET_DICT = {'mapset': 'PERMANENT'}
+ELEVATION_MAPSET_DICT = {"mapset": "PERMANENT"}
 
 # r.univar map=elevation
 ELEVATION_MINMAX = """min=55.5787925720215
@@ -110,17 +114,18 @@ max=156.329864501953
 """
 
 # values rounded manually to maximal expected perecision
-ELEVATION_MINMAX_DICT = {'min': 55.58, 'max': 156.33}
+ELEVATION_MINMAX_DICT = {"min": 55.58, "max": 156.33}
 
 
 class TestAssertModuleKeyValue(TestCase):
     """Test usage of `assertModuleKeyValue` method."""
+
     # pylint: disable=R0904
 
     @classmethod
     def setUpClass(cls):
         cls.use_temp_region()
-        cls.runModule(SimpleModule('g.region', raster='elevation'))
+        cls.runModule(SimpleModule("g.region", raster="elevation"))
 
     @classmethod
     def tearDownClass(cls):
@@ -128,31 +133,38 @@ class TestAssertModuleKeyValue(TestCase):
 
     def test_pygrass_module(self):
         """Test syntax with Module and required parameters as module"""
-        module = Module('r.info', map='elevation', flags='gr',
-                        run_=False, finish_=True)
-        self.assertModuleKeyValue(module,
-                                  reference=dict(min=55.58, max=156.33),
-                                  precision=0.01, sep='=')
+        module = Module("r.info", map="elevation", flags="gr", run_=False, finish_=True)
+        self.assertModuleKeyValue(
+            module, reference=dict(min=55.58, max=156.33), precision=0.01, sep="="
+        )
 
     def test_pygrass_simple_module(self):
         """Test syntax with SimpleModule as module"""
-        module = SimpleModule('r.info', map='elevation', flags='gr')
-        self.assertModuleKeyValue(module,
-                                  reference=dict(min=55.58, max=156.33),
-                                  precision=0.01, sep='=')
+        module = SimpleModule("r.info", map="elevation", flags="gr")
+        self.assertModuleKeyValue(
+            module, reference=dict(min=55.58, max=156.33), precision=0.01, sep="="
+        )
 
     def test_direct_parameters(self):
         """Test syntax with module and its parameters as function parameters"""
-        self.assertModuleKeyValue('r.info', map='elevation', flags='gr',
-                                  reference=dict(min=55.58, max=156.33),
-                                  precision=0.01, sep='=')
+        self.assertModuleKeyValue(
+            "r.info",
+            map="elevation",
+            flags="gr",
+            reference=dict(min=55.58, max=156.33),
+            precision=0.01,
+            sep="=",
+        )
 
     def test_parameters_parameter(self):
         """Test syntax with module parameters in one parameters dictionary"""
-        self.assertModuleKeyValue(module='r.info',
-                                  parameters=dict(map='elevation', flags='gr'),
-                                  reference=dict(min=55.58, max=156.33),
-                                  precision=0.01, sep='=')
+        self.assertModuleKeyValue(
+            module="r.info",
+            parameters=dict(map="elevation", flags="gr"),
+            reference=dict(min=55.58, max=156.33),
+            precision=0.01,
+            sep="=",
+        )
 
 
 class TestRasterMapAssertions(TestCase):
@@ -162,108 +174,128 @@ class TestRasterMapAssertions(TestCase):
     def setUpClass(cls):
         cls.use_temp_region()
         # TODO: here we should actually not call self.runModule but call_module
-        cls.runModule(SimpleModule('g.region', raster='elevation'))
+        cls.runModule(SimpleModule("g.region", raster="elevation"))
 
     @classmethod
     def tearDownClass(cls):
         cls.del_temp_region()
 
     def test_assertRasterFitsUnivar(self):
-        self.assertRasterFitsUnivar('elevation', R_UNIVAR_ELEVATION_SUBSET,
-                                    precision=0.01)
-        self.assertRaises(self.failureException,
-                          self.assertRasterFitsUnivar,
-                          'geology', R_UNIVAR_ELEVATION_SUBSET, precision=0.01)
-        self.assertRaises(ValueError,
-                          self.assertRasterFitsUnivar,
-                          'elevation', RANDOM_KEYVALUES)
+        self.assertRasterFitsUnivar(
+            "elevation", R_UNIVAR_ELEVATION_SUBSET, precision=0.01
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertRasterFitsUnivar,
+            "geology",
+            R_UNIVAR_ELEVATION_SUBSET,
+            precision=0.01,
+        )
+        self.assertRaises(
+            ValueError, self.assertRasterFitsUnivar, "elevation", RANDOM_KEYVALUES
+        )
 
     def test_assertRasterFitsInfo(self):
-        self.assertRasterFitsInfo('elevation', R_INFO_ELEVATION_SUBSET)
-        self.assertRaises(self.failureException,
-                          self.assertRasterFitsInfo,
-                          'geology', R_INFO_ELEVATION_SUBSET)
-        self.assertRaises(ValueError,
-                          self.assertRasterFitsInfo,
-                          'elevation', RANDOM_KEYVALUES)
+        self.assertRasterFitsInfo("elevation", R_INFO_ELEVATION_SUBSET)
+        self.assertRaises(
+            self.failureException,
+            self.assertRasterFitsInfo,
+            "geology",
+            R_INFO_ELEVATION_SUBSET,
+        )
+        self.assertRaises(
+            ValueError, self.assertRasterFitsInfo, "elevation", RANDOM_KEYVALUES
+        )
 
     def test_common_values_info_univar(self):
-        self.assertRasterFitsUnivar('elevation',
-                                    ELEVATION_MINMAX, precision=0.01)
-        self.assertRasterFitsInfo('elevation',
-                                  ELEVATION_MINMAX, precision=0.01)
+        self.assertRasterFitsUnivar("elevation", ELEVATION_MINMAX, precision=0.01)
+        self.assertRasterFitsInfo("elevation", ELEVATION_MINMAX, precision=0.01)
 
     def test_dict_as_parameter(self):
         """This also tests if we are using r.info -e flag and that precision is
         not required for strings.
         """
-        self.assertRasterFitsInfo('elevation', ELEVATION_MAPSET_DICT)
+        self.assertRasterFitsInfo("elevation", ELEVATION_MAPSET_DICT)
 
     def test_assertRastersNoDifference(self):
         """Test basic usage of assertRastersNoDifference"""
-        self.assertRastersNoDifference(actual='elevation',
-                                       reference='elevation',
-                                       precision=0,  # this might need to be increased
-                                       msg="The same maps should have no difference")
-        self.assertRaises(self.failureException,
-                          self.assertRastersNoDifference,
-                          actual='elevation',
-                          reference='geology',
-                          precision=1,
-                          msg="Different maps should have difference")
+        self.assertRastersNoDifference(
+            actual="elevation",
+            reference="elevation",
+            precision=0,  # this might need to be increased
+            msg="The same maps should have no difference",
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertRastersNoDifference,
+            actual="elevation",
+            reference="geology",
+            precision=1,
+            msg="Different maps should have difference",
+        )
 
     def test_assertRastersNoDifference_mean(self):
         """Test usage of assertRastersNoDifference with mean"""
-        self.assertRastersNoDifference(actual='elevation',
-                                       reference='elevation',
-                                       precision=0,  # this might need to be increased
-                                       statistics=dict(mean=0),
-                                       msg="The difference of same maps should have small mean")
-        self.assertRaises(self.failureException,
-                          self.assertRastersNoDifference,
-                          actual='elevation',
-                          reference='geology',
-                          precision=1,
-                          statistics=dict(mean=0),
-                          msg="The difference of different maps should have huge mean")
+        self.assertRastersNoDifference(
+            actual="elevation",
+            reference="elevation",
+            precision=0,  # this might need to be increased
+            statistics=dict(mean=0),
+            msg="The difference of same maps should have small mean",
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertRastersNoDifference,
+            actual="elevation",
+            reference="geology",
+            precision=1,
+            statistics=dict(mean=0),
+            msg="The difference of different maps should have huge mean",
+        )
 
     def test_assertRastersEqual(self):
         """Test basic usage of assertRastersEqual"""
-        self.assertRastersEqual(actual='lakes',
-                                       reference='lakes',
-                                       precision=0,
-                                       msg="The same maps should have no difference")
-        self.assertRaises(self.failureException,
-                          self.assertRastersEqual,
-                          actual='elevation',
-                          reference='lakes',
-                          precision=1,
-                          msg="Different maps should have difference")
+        self.assertRastersEqual(
+            actual="lakes",
+            reference="lakes",
+            precision=0,
+            msg="The same maps should have no difference",
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertRastersEqual,
+            actual="elevation",
+            reference="lakes",
+            precision=1,
+            msg="Different maps should have difference",
+        )
 
 
 class TestMapExistsAssertions(TestCase):
     # pylint: disable=R0904
 
-    raster_cell = 'TestMapExistsAssertions_raster_cell'
-    raster_dcell = 'TestMapExistsAssertions_raster_dcell'
-    raster3d = 'TestMapExistsAssertions_raster3D'
-    vector = 'TestMapExistsAssertions_vector'
+    raster_cell = "TestMapExistsAssertions_raster_cell"
+    raster_dcell = "TestMapExistsAssertions_raster_dcell"
+    raster3d = "TestMapExistsAssertions_raster3D"
+    vector = "TestMapExistsAssertions_vector"
 
     @classmethod
     def setUpClass(cls):
         cls.use_temp_region()
-        cls.runModule('g.region', n=10, e=10, s=0, w=0, t=10, b=0, res=1)
-        cls.runModule('r.mapcalc', expression=cls.raster_cell + ' = 1')
-        cls.runModule('r.mapcalc', expression=cls.raster_dcell + ' = 1.0')
-        cls.runModule('r3.mapcalc', expression=cls.raster3d + ' = 1.0')
-        cls.runModule('v.edit', map=cls.vector, tool='create')
+        cls.runModule("g.region", n=10, e=10, s=0, w=0, t=10, b=0, res=1)
+        cls.runModule("r.mapcalc", expression=cls.raster_cell + " = 1")
+        cls.runModule("r.mapcalc", expression=cls.raster_dcell + " = 1.0")
+        cls.runModule("r3.mapcalc", expression=cls.raster3d + " = 1.0")
+        cls.runModule("v.edit", map=cls.vector, tool="create")
 
     @classmethod
     def tearDownClass(cls):
-        cls.runModule('g.remove', flags='f',
-                      type=['raster', 'raster3d', 'vector'],
-                      name=[cls.raster_cell, cls.raster_dcell,
-                            cls.raster3d, cls.vector])
+        cls.runModule(
+            "g.remove",
+            flags="f",
+            type=["raster", "raster3d", "vector"],
+            name=[cls.raster_cell, cls.raster_dcell, cls.raster3d, cls.vector],
+        )
         cls.del_temp_region()
 
     def test_rast_cell_exists(self):
@@ -273,34 +305,36 @@ class TestMapExistsAssertions(TestCase):
         self.assertRasterExists(self.raster_dcell)
 
     def test_rast_does_not_exist(self):
-        self.assertRaises(self.failureException,
-                          self.assertRasterExists,
-                          'does_not_exists')
+        self.assertRaises(
+            self.failureException, self.assertRasterExists, "does_not_exists"
+        )
 
     def test_rast3d_exists(self):
         self.assertRaster3dExists(self.raster3d)
 
     def test_rast3d_does_not_exist(self):
-        self.assertRaises(self.failureException,
-                          self.assertRaster3dExists,
-                          'does_not_exists')
+        self.assertRaises(
+            self.failureException, self.assertRaster3dExists, "does_not_exists"
+        )
 
     def test_vect_exists(self):
         self.assertVectorExists(self.vector)
 
     def test_vect_does_not_exist(self):
-        self.assertRaises(self.failureException,
-                          self.assertVectorExists,
-                          'does_not_exists')
+        self.assertRaises(
+            self.failureException, self.assertVectorExists, "does_not_exists"
+        )
 
     def test_rast_does_not_exist_in_current_mapset(self):
         # expecting that there is elevation in PERMANENT
         # TODO: use skip decorator
         # TODO: add the same tests but for vect and rast3d
-        self.assertRaises(self.failureException,
-                          self.assertRasterExists,
-                          'elevation',
-                          msg="Rasters from different mapsets should be ignored")
+        self.assertRaises(
+            self.failureException,
+            self.assertRasterExists,
+            "elevation",
+            msg="Rasters from different mapsets should be ignored",
+        )
 
 
 class TestFileAssertions(TestCase):
@@ -310,27 +344,27 @@ class TestFileAssertions(TestCase):
     def setUpClass(cls):
         # we expect WIND to be always present
         gisenv = gcore.gisenv()
-        cls.existing_file = os.path.join(gisenv['GISDBASE'],
-                                         gisenv['LOCATION_NAME'],
-                                         'PERMANENT', 'WIND')
-        cls.emtpy_file = cls.__name__ + '_this_is_an_empty_file'
-        open(cls.emtpy_file, 'w').close()
-        cls.file_with_md5 = cls.__name__ + '_this_is_a_file_with_known_md5'
-        file_content = 'Content of the file with known MD5.\n'
-        with open(cls.file_with_md5, 'w') as f:
+        cls.existing_file = os.path.join(
+            gisenv["GISDBASE"], gisenv["LOCATION_NAME"], "PERMANENT", "WIND"
+        )
+        cls.emtpy_file = cls.__name__ + "_this_is_an_empty_file"
+        open(cls.emtpy_file, "w").close()
+        cls.file_with_md5 = cls.__name__ + "_this_is_a_file_with_known_md5"
+        file_content = "Content of the file with known MD5.\n"
+        with open(cls.file_with_md5, "w") as f:
             f.write(file_content)
         # MD5 sum created using:
         # echo 'Content of the file with known MD5.' > some_file.txt
         # md5sum some_file.txt
-        cls.file_md5 = '807bba4ffac4bb351bc3f27853009949'
+        cls.file_md5 = "807bba4ffac4bb351bc3f27853009949"
 
-        cls.file_with_same_content = cls.__name__ + '_file_with_same_content'
-        with open(cls.file_with_same_content, 'w') as f:
+        cls.file_with_same_content = cls.__name__ + "_file_with_same_content"
+        with open(cls.file_with_same_content, "w") as f:
             f.write(file_content)
 
-        cls.file_with_different_content = cls.__name__ + '_file_with_different_content'
-        with open(cls.file_with_different_content, 'w') as f:
-            f.write(file_content + ' Something else here.')
+        cls.file_with_different_content = cls.__name__ + "_file_with_different_content"
+        with open(cls.file_with_different_content, "w") as f:
+            f.write(file_content + " Something else here.")
 
     @classmethod
     def tearDownClass(cls):
@@ -341,30 +375,38 @@ class TestFileAssertions(TestCase):
 
     def test_assertFileExists(self):
         self.assertFileExists(filename=self.existing_file)
-        self.assertRaises(self.failureException,
-                          self.assertFileExists,
-                          filename='this_one_does_not_exists')
+        self.assertRaises(
+            self.failureException,
+            self.assertFileExists,
+            filename="this_one_does_not_exists",
+        )
 
     def test_assertFileExists_empty_file(self):
         self.assertFileExists(filename=self.emtpy_file, skip_size_check=True)
-        self.assertRaises(self.failureException,
-                          self.assertFileExists,
-                          filename=self.emtpy_file)
+        self.assertRaises(
+            self.failureException, self.assertFileExists, filename=self.emtpy_file
+        )
 
     def test_assertFileMd5(self):
         self.assertFileMd5(filename=self.file_with_md5, md5=self.file_md5)
-        self.assertRaises(self.failureException,
-                          self.assertFileMd5,
-                          filename=self.file_with_md5, md5='wrongmd5')
+        self.assertRaises(
+            self.failureException,
+            self.assertFileMd5,
+            filename=self.file_with_md5,
+            md5="wrongmd5",
+        )
 
     def test_assertFilesEqualMd5(self):
-        self.assertFilesEqualMd5(filename=self.file_with_md5,
-                                 reference=self.file_with_same_content)
-        self.assertRaises(self.failureException,
-                          self.assertFilesEqualMd5,
-                          filename=self.file_with_md5,
-                          reference=self.file_with_different_content)
-
-
-if __name__ == '__main__':
+        self.assertFilesEqualMd5(
+            filename=self.file_with_md5, reference=self.file_with_same_content
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertFilesEqualMd5,
+            filename=self.file_with_md5,
+            reference=self.file_with_different_content,
+        )
+
+
+if __name__ == "__main__":
     test()

+ 114 - 83
python/grass/gunittest/testsuite/test_assertions_rast3d.py

@@ -11,117 +11,148 @@ from grass.gunittest.main import test
 
 class TestRaster3dMapAssertions(TestCase):
     # pylint: disable=R0904
-    constant_map = 'raster3d_assertions_constant'
-    rcd_increasing_map = 'raster3d_assertions_rcd_increasing'
+    constant_map = "raster3d_assertions_constant"
+    rcd_increasing_map = "raster3d_assertions_rcd_increasing"
 
     @classmethod
     def setUpClass(cls):
         cls.use_temp_region()
         # TODO: here we should actually not call self.runModule but call_module
-        cls.runModule('g.region', n=200, s=100, e=400, w=200,
-                      t=500, b=450, res3=1)
-        cls.runModule('r3.mapcalc', expression='%s = 155' % cls.constant_map)
-        cls.runModule('r3.mapcalc',
-                      expression='%s = row() + col() + depth()' % cls.rcd_increasing_map)
+        cls.runModule("g.region", n=200, s=100, e=400, w=200, t=500, b=450, res3=1)
+        cls.runModule("r3.mapcalc", expression="%s = 155" % cls.constant_map)
+        cls.runModule(
+            "r3.mapcalc",
+            expression="%s = row() + col() + depth()" % cls.rcd_increasing_map,
+        )
 
     @classmethod
     def tearDownClass(cls):
         cls.del_temp_region()
         # TODO: input as list does not work, why?
-        cls.runModule('g.remove', flags='f', type='raster_3d',
-                      name=','.join([cls.constant_map, cls.rcd_increasing_map]))
+        cls.runModule(
+            "g.remove",
+            flags="f",
+            type="raster_3d",
+            name=",".join([cls.constant_map, cls.rcd_increasing_map]),
+        )
 
     def test_assertRaster3dFitsUnivar(self):
-        reference = dict(n=1000000,
-                         null_cells=0,
-                         cells=1000000,
-                         min=155,
-                         max=155,
-                         range=0,
-                         mean=155,
-                         mean_of_abs=155,
-                         stddev=0,
-                         variance=0,
-                         coeff_var=0,
-                         sum=155000000)
-        self.assertRaster3dFitsUnivar(self.constant_map, reference=reference,
-                                      precision=0.000001)
-        self.assertRaises(self.failureException,
-                          self.assertRaster3dFitsUnivar,
-                          self.rcd_increasing_map,
-                          reference=reference, precision=1)
-        self.assertRaises(ValueError,
-                          self.assertRaster3dFitsUnivar,
-                          self.constant_map, reference=dict(a=4, b=5, c=6))
-        self.assertRaises(CalledModuleError,
-                          self.assertRaster3dFitsUnivar,
-                          'does_not_exists', reference=dict(a=4, b=5, c=6))
+        reference = dict(
+            n=1000000,
+            null_cells=0,
+            cells=1000000,
+            min=155,
+            max=155,
+            range=0,
+            mean=155,
+            mean_of_abs=155,
+            stddev=0,
+            variance=0,
+            coeff_var=0,
+            sum=155000000,
+        )
+        self.assertRaster3dFitsUnivar(
+            self.constant_map, reference=reference, precision=0.000001
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertRaster3dFitsUnivar,
+            self.rcd_increasing_map,
+            reference=reference,
+            precision=1,
+        )
+        self.assertRaises(
+            ValueError,
+            self.assertRaster3dFitsUnivar,
+            self.constant_map,
+            reference=dict(a=4, b=5, c=6),
+        )
+        self.assertRaises(
+            CalledModuleError,
+            self.assertRaster3dFitsUnivar,
+            "does_not_exists",
+            reference=dict(a=4, b=5, c=6),
+        )
 
     def test_assertRaster3dFitsInfo(self):
-        reference = dict(north=200,
-                         south=100,
-                         east=400,
-                         west=200,
-                         bottom=450,
-                         top=500,
-                         nsres=1,
-                         ewres=1,
-                         tbres=1,
-                         rows=100,
-                         cols=200,
-                         depths=50)
+        reference = dict(
+            north=200,
+            south=100,
+            east=400,
+            west=200,
+            bottom=450,
+            top=500,
+            nsres=1,
+            ewres=1,
+            tbres=1,
+            rows=100,
+            cols=200,
+            depths=50,
+        )
         self.assertRaster3dFitsInfo(self.constant_map, reference=reference)
 
-        reference['north'] = 500
-        self.assertRaises(self.failureException,
-                          self.assertRaster3dFitsInfo,
-                          self.constant_map, reference=reference)
-        self.assertRaises(ValueError,
-                          self.assertRaster3dFitsInfo,
-                          self.constant_map, reference=dict(a=5))
+        reference["north"] = 500
+        self.assertRaises(
+            self.failureException,
+            self.assertRaster3dFitsInfo,
+            self.constant_map,
+            reference=reference,
+        )
+        self.assertRaises(
+            ValueError,
+            self.assertRaster3dFitsInfo,
+            self.constant_map,
+            reference=dict(a=5),
+        )
 
     def test_common_values_info_univar(self):
         minmax = dict(min=3, max=350)
-        self.assertRaster3dFitsUnivar(self.rcd_increasing_map,
-                                      minmax, precision=0.01)
-        self.assertRaster3dFitsInfo(self.rcd_increasing_map,
-                                    minmax, precision=0.01)
+        self.assertRaster3dFitsUnivar(self.rcd_increasing_map, minmax, precision=0.01)
+        self.assertRaster3dFitsInfo(self.rcd_increasing_map, minmax, precision=0.01)
 
     def test_string_as_parameter(self):
-        self.assertRaster3dFitsInfo(self.constant_map,
-                                    reference="max=155", precision=1)
-        self.assertRaster3dFitsUnivar(self.rcd_increasing_map,
-                                      reference="n=1000000", precision=0)
+        self.assertRaster3dFitsInfo(self.constant_map, reference="max=155", precision=1)
+        self.assertRaster3dFitsUnivar(
+            self.rcd_increasing_map, reference="n=1000000", precision=0
+        )
 
     def test_assertRasters3dNoDifference(self):
         """Test basic usage of assertRastersNoDifference"""
         # precision might need to be increased
-        self.assertRasters3dNoDifference(actual=self.rcd_increasing_map,
-                                       reference=self.rcd_increasing_map,
-                                       precision=0,
-                                       msg="The same maps should have no difference")
-        self.assertRaises(self.failureException,
-                          self.assertRasters3dNoDifference,
-                          actual=self.constant_map,
-                          reference=self.rcd_increasing_map,
-                          precision=1,
-                          msg="Different maps should have difference")
+        self.assertRasters3dNoDifference(
+            actual=self.rcd_increasing_map,
+            reference=self.rcd_increasing_map,
+            precision=0,
+            msg="The same maps should have no difference",
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertRasters3dNoDifference,
+            actual=self.constant_map,
+            reference=self.rcd_increasing_map,
+            precision=1,
+            msg="Different maps should have difference",
+        )
 
     def test_assertRasters3dNoDifference_mean(self):
         """Test usage of assertRastersNoDifference with mean"""
-        self.assertRasters3dNoDifference(actual=self.rcd_increasing_map,
-                                       reference=self.rcd_increasing_map,
-                                       precision=0,  # this might need to be increased
-                                       statistics=dict(mean=0),
-                                       msg="The difference of same maps should have small mean")
-        self.assertRaises(self.failureException,
-                          self.assertRasters3dNoDifference,
-                          actual=self.constant_map,
-                          reference=self.rcd_increasing_map,
-                          precision=1,
-                          statistics=dict(mean=0),
-                          msg="The difference of different maps should have huge mean")
+        self.assertRasters3dNoDifference(
+            actual=self.rcd_increasing_map,
+            reference=self.rcd_increasing_map,
+            precision=0,  # this might need to be increased
+            statistics=dict(mean=0),
+            msg="The difference of same maps should have small mean",
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertRasters3dNoDifference,
+            actual=self.constant_map,
+            reference=self.rcd_increasing_map,
+            precision=1,
+            statistics=dict(mean=0),
+            msg="The difference of different maps should have huge mean",
+        )
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 188 - 110
python/grass/gunittest/testsuite/test_assertions_vect.py

@@ -50,7 +50,7 @@ V_UNIVAR_SCHOOLS_REGION = dict(
 
 # v.info schools -g and reduced to minimum
 V_UNIVAR_SCHOOLS_EXTENDED = dict(
-    name='schools',
+    name="schools",
     level=2,
     num_dblinks=1,
 )
@@ -58,79 +58,124 @@ V_UNIVAR_SCHOOLS_EXTENDED = dict(
 
 class TestVectorInfoAssertions(TestCase):
     """Test assertions of map meta and statistics"""
+
     # pylint: disable=R0904
 
     def test_assertVectorFitsUnivar(self):
-        self.assertVectorFitsUnivar(map='schools', column='CORECAPACI',
-                                    reference=V_UNIVAR_SCHOOLS_WIDTH_SUBSET,
-                                    precision=0.01)
-        self.assertRaises(self.failureException,
-                          self.assertVectorFitsUnivar,
-                          map='schools', column='MOBILECAPA',
-                          reference=V_UNIVAR_SCHOOLS_WIDTH_SUBSET,
-                          precision=0.01)
-        self.assertRaises(ValueError,
-                          self.assertVectorFitsUnivar,
-                          map='schools', column='CORECAPACI',
-                          reference=RANDOM_KEYVALUES)
+        self.assertVectorFitsUnivar(
+            map="schools",
+            column="CORECAPACI",
+            reference=V_UNIVAR_SCHOOLS_WIDTH_SUBSET,
+            precision=0.01,
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorFitsUnivar,
+            map="schools",
+            column="MOBILECAPA",
+            reference=V_UNIVAR_SCHOOLS_WIDTH_SUBSET,
+            precision=0.01,
+        )
+        self.assertRaises(
+            ValueError,
+            self.assertVectorFitsUnivar,
+            map="schools",
+            column="CORECAPACI",
+            reference=RANDOM_KEYVALUES,
+        )
 
     def test_assertVectorFitsTopoInfo(self):
-        self.assertVectorFitsTopoInfo('schools', V_UNIVAR_SCHOOLS_TOPO)
-        self.assertRaises(self.failureException,
-                          self.assertVectorFitsTopoInfo,
-                          'hospitals',
-                          V_UNIVAR_SCHOOLS_TOPO)
-        self.assertRaises(ValueError,
-                          self.assertVectorFitsTopoInfo,
-                          'schools', RANDOM_KEYVALUES)
-        self.assertRaises(ValueError,
-                          self.assertVectorFitsTopoInfo,
-                          'schools', V_UNIVAR_SCHOOLS_REGION)
+        self.assertVectorFitsTopoInfo("schools", V_UNIVAR_SCHOOLS_TOPO)
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorFitsTopoInfo,
+            "hospitals",
+            V_UNIVAR_SCHOOLS_TOPO,
+        )
+        self.assertRaises(
+            ValueError, self.assertVectorFitsTopoInfo, "schools", RANDOM_KEYVALUES
+        )
+        self.assertRaises(
+            ValueError,
+            self.assertVectorFitsTopoInfo,
+            "schools",
+            V_UNIVAR_SCHOOLS_REGION,
+        )
 
     def test_assertVectorFitsRegionInfo(self):
-        self.assertVectorFitsRegionInfo('schools', V_UNIVAR_SCHOOLS_REGION, precision=1.0)
-        self.assertRaises(self.failureException,
-                          self.assertVectorFitsRegionInfo,
-                          'hospitals', V_UNIVAR_SCHOOLS_REGION, precision=1.0)
-        self.assertRaises(ValueError,
-                          self.assertVectorFitsRegionInfo,
-                          'schools', RANDOM_KEYVALUES, precision=1.0)
-        self.assertRaises(ValueError,
-                          self.assertVectorFitsRegionInfo,
-                          'schools', V_UNIVAR_SCHOOLS_TOPO, precision=1.0)
+        self.assertVectorFitsRegionInfo(
+            "schools", V_UNIVAR_SCHOOLS_REGION, precision=1.0
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorFitsRegionInfo,
+            "hospitals",
+            V_UNIVAR_SCHOOLS_REGION,
+            precision=1.0,
+        )
+        self.assertRaises(
+            ValueError,
+            self.assertVectorFitsRegionInfo,
+            "schools",
+            RANDOM_KEYVALUES,
+            precision=1.0,
+        )
+        self.assertRaises(
+            ValueError,
+            self.assertVectorFitsRegionInfo,
+            "schools",
+            V_UNIVAR_SCHOOLS_TOPO,
+            precision=1.0,
+        )
 
     def test_assertVectorFitsExtendedInfo(self):
-        self.assertVectorFitsExtendedInfo('schools', V_UNIVAR_SCHOOLS_EXTENDED)
-        self.assertRaises(self.failureException,
-                          self.assertVectorFitsExtendedInfo,
-                          'hospitals',
-                          V_UNIVAR_SCHOOLS_EXTENDED)
-        self.assertRaises(ValueError,
-                          self.assertVectorFitsExtendedInfo,
-                          'schools',
-                          RANDOM_KEYVALUES)
-        self.assertRaises(ValueError,
-                          self.assertVectorFitsExtendedInfo,
-                          'schools',
-                          V_UNIVAR_SCHOOLS_TOPO)
+        self.assertVectorFitsExtendedInfo("schools", V_UNIVAR_SCHOOLS_EXTENDED)
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorFitsExtendedInfo,
+            "hospitals",
+            V_UNIVAR_SCHOOLS_EXTENDED,
+        )
+        self.assertRaises(
+            ValueError, self.assertVectorFitsExtendedInfo, "schools", RANDOM_KEYVALUES
+        )
+        self.assertRaises(
+            ValueError,
+            self.assertVectorFitsExtendedInfo,
+            "schools",
+            V_UNIVAR_SCHOOLS_TOPO,
+        )
 
     def test_assertVectorInfoEqualsVectorInfo(self):
-        self.assertVectorInfoEqualsVectorInfo('schools', 'schools', precision=0.00000001)
-        self.assertRaises(self.failureException,
-                          self.assertVectorInfoEqualsVectorInfo,
-                          'hospitals', 'schools', precision=0.00000001)
-        self.assertRaises(CalledModuleError,
-                          self.assertVectorInfoEqualsVectorInfo,
-                          'schools', 'does_not_exist', precision=0.00000001)
+        self.assertVectorInfoEqualsVectorInfo(
+            "schools", "schools", precision=0.00000001
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorInfoEqualsVectorInfo,
+            "hospitals",
+            "schools",
+            precision=0.00000001,
+        )
+        self.assertRaises(
+            CalledModuleError,
+            self.assertVectorInfoEqualsVectorInfo,
+            "schools",
+            "does_not_exist",
+            precision=0.00000001,
+        )
 
 
 class TestVectorGeometryAssertions(TestCase):
     """Test assertions of map geometry"""
+
     # pylint: disable=R0904
     maps_to_remove = []
-    simple_base_file = 'data/simple_vector_map_ascii_4p_2l_2c_3b_dp14.txt'
-    simple_modified_file = 'data/simple_vector_map_ascii_4p_2l_2c_3b_dp14_modified.txt'
-    simple_diff_header_file = 'data/simple_vector_map_ascii_4p_2l_2c_3b_dp14_diff_header.txt'
+    simple_base_file = "data/simple_vector_map_ascii_4p_2l_2c_3b_dp14.txt"
+    simple_modified_file = "data/simple_vector_map_ascii_4p_2l_2c_3b_dp14_modified.txt"
+    simple_diff_header_file = (
+        "data/simple_vector_map_ascii_4p_2l_2c_3b_dp14_diff_header.txt"
+    )
     precision = 0.00001
     digits = 14
 
@@ -141,63 +186,86 @@ class TestVectorGeometryAssertions(TestCase):
         # when invoking separately, no need to delete maps since mapset
         # is deleted
         if cls.maps_to_remove:
-            cls.runModule('g.remove', flags='f', type='vector',
-                          name=','.join(cls.maps_to_remove))
+            cls.runModule(
+                "g.remove", flags="f", type="vector", name=",".join(cls.maps_to_remove)
+            )
 
     def test_assertVectorEqualsVector_basic(self):
         """Check completely different maps."""
-        self.assertVectorEqualsVector(actual='schools', reference='schools',
-                                      precision=0.01, digits=15)
-        self.assertRaises(self.failureException,
-                          self.assertVectorEqualsVector,
-                          actual='schools', reference='hospitals',
-                          precision=0.01, digits=7)
-        self.assertRaises(CalledModuleError,
-                          self.assertVectorEqualsVector,
-                          actual='does_not_exist', reference='hospitals',
-                          precision=0.01, digits=7)
+        self.assertVectorEqualsVector(
+            actual="schools", reference="schools", precision=0.01, digits=15
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorEqualsVector,
+            actual="schools",
+            reference="hospitals",
+            precision=0.01,
+            digits=7,
+        )
+        self.assertRaises(
+            CalledModuleError,
+            self.assertVectorEqualsVector,
+            actual="does_not_exist",
+            reference="hospitals",
+            precision=0.01,
+            digits=7,
+        )
 
     def test_assertVectorEqualsVector_geometry_same_header(self):
         """Check small slighlty different maps with same header in ASCII."""
-        amap = 'simple_vector_map_base_geom'
-        bmap = 'simple_vector_map_modified_geom'
-        self.runModule('v.in.ascii', format='standard',
-                       input=self.simple_base_file,
-                       output=amap)
+        amap = "simple_vector_map_base_geom"
+        bmap = "simple_vector_map_modified_geom"
+        self.runModule(
+            "v.in.ascii", format="standard", input=self.simple_base_file, output=amap
+        )
         self.maps_to_remove.append(amap)
-        self.runModule('v.in.ascii', format='standard',
-                       input=self.simple_modified_file,
-                       output=bmap)
+        self.runModule(
+            "v.in.ascii",
+            format="standard",
+            input=self.simple_modified_file,
+            output=bmap,
+        )
         self.maps_to_remove.append(bmap)
-        self.assertVectorEqualsVector(actual=amap, reference=amap,
-                                      precision=self.precision, digits=self.digits)
-        self.assertRaises(self.failureException,
-                          self.assertVectorEqualsVector,
-                          actual=amap, reference=bmap,
-                          precision=self.precision, digits=self.digits)
+        self.assertVectorEqualsVector(
+            actual=amap, reference=amap, precision=self.precision, digits=self.digits
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorEqualsVector,
+            actual=amap,
+            reference=bmap,
+            precision=self.precision,
+            digits=self.digits,
+        )
 
     def test_assertVectorEqualsVector_geometry(self):
         """Check small slighlty different maps with different headers in ASCII."""
-        amap = 'simple_vector_map_base'
-        bmap = 'simple_vector_map_different_header'
-        self.runModule('v.in.ascii', format='standard',
-                       input=self.simple_base_file,
-                       output=amap)
+        amap = "simple_vector_map_base"
+        bmap = "simple_vector_map_different_header"
+        self.runModule(
+            "v.in.ascii", format="standard", input=self.simple_base_file, output=amap
+        )
         self.maps_to_remove.append(amap)
-        self.runModule('v.in.ascii', format='standard',
-                       input=self.simple_diff_header_file,
-                       output=bmap)
+        self.runModule(
+            "v.in.ascii",
+            format="standard",
+            input=self.simple_diff_header_file,
+            output=bmap,
+        )
         self.maps_to_remove.append(bmap)
-        self.assertVectorEqualsVector(actual=amap, reference=bmap,
-                                      precision=self.precision, digits=self.digits)
+        self.assertVectorEqualsVector(
+            actual=amap, reference=bmap, precision=self.precision, digits=self.digits
+        )
 
     def test_assertVectorAsciiEqualsVectorAscii_diff_header(self):
         """Test ASCII files with different header.
 
         Prove that files were not deleted if not requested.
         """
-        self.assertVectorAsciiEqualsVectorAscii(actual=self.simple_base_file,
-                                                reference=self.simple_diff_header_file)
+        self.assertVectorAsciiEqualsVectorAscii(
+            actual=self.simple_base_file, reference=self.simple_diff_header_file
+        )
         self.assertFileExists(self.simple_base_file)
         self.assertFileExists(self.simple_diff_header_file)
 
@@ -206,29 +274,39 @@ class TestVectorGeometryAssertions(TestCase):
 
         Prove that files were not deleted if not requested.
         """
-        self.assertRaises(self.failureException,
-                          self.assertVectorAsciiEqualsVectorAscii,
-                          actual=self.simple_base_file,
-                          reference=self.simple_modified_file)
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorAsciiEqualsVectorAscii,
+            actual=self.simple_base_file,
+            reference=self.simple_modified_file,
+        )
         self.assertFileExists(self.simple_base_file)
         self.assertFileExists(self.simple_modified_file)
 
     def test_assertVectorEqualsAscii_by_import(self):
-        amap = 'simple_vector_map_imported_base'
-        self.runModule('v.in.ascii', format='standard',
-                       input=self.simple_base_file,
-                       output=amap)
+        amap = "simple_vector_map_imported_base"
+        self.runModule(
+            "v.in.ascii", format="standard", input=self.simple_base_file, output=amap
+        )
         self.maps_to_remove.append(amap)
-        self.assertVectorEqualsAscii(amap, self.simple_diff_header_file,
-                                     precision=self.precision, digits=self.digits)
-        self.assertRaises(self.failureException,
-                          self.assertVectorEqualsAscii,
-                          amap, self.simple_modified_file,
-                          precision=self.precision, digits=self.digits)
+        self.assertVectorEqualsAscii(
+            amap,
+            self.simple_diff_header_file,
+            precision=self.precision,
+            digits=self.digits,
+        )
+        self.assertRaises(
+            self.failureException,
+            self.assertVectorEqualsAscii,
+            amap,
+            self.simple_modified_file,
+            precision=self.precision,
+            digits=self.digits,
+        )
         self.assertFileExists(self.simple_base_file)
         self.assertFileExists(self.simple_modified_file)
         self.assertFileExists(self.simple_diff_header_file)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 151 - 122
python/grass/gunittest/testsuite/test_checkers.py

@@ -19,14 +19,17 @@ from grass.script.utils import parse_key_val, try_remove
 from grass.gunittest.case import TestCase
 from grass.gunittest.main import test
 from grass.gunittest.checkers import (
-    values_equal, text_to_keyvalue,
-    keyvalue_equals, proj_info_equals, proj_units_equals,
-    file_md5, text_file_md5)
-
+    values_equal,
+    text_to_keyvalue,
+    keyvalue_equals,
+    proj_info_equals,
+    proj_units_equals,
+    file_md5,
+    text_file_md5,
+)
 
 
 class TestValuesEqual(TestCase):
-
     def test_floats(self):
         self.assertTrue(values_equal(5.0, 5.0))
         self.assertTrue(values_equal(5.1, 5.19, precision=0.1))
@@ -48,33 +51,31 @@ class TestValuesEqual(TestCase):
         self.assertFalse(values_equal(5.1, 5, precision=0.01))
 
     def test_strings(self):
-        self.assertTrue(values_equal('hello', 'hello'))
-        self.assertFalse(values_equal('Hello', 'hello'))
+        self.assertTrue(values_equal("hello", "hello"))
+        self.assertFalse(values_equal("Hello", "hello"))
 
     def test_lists(self):
         self.assertTrue(values_equal([1, 2, 3], [1, 2, 3]))
-        self.assertTrue(values_equal([1.1, 2.0, 3.9],
-                                     [1.1, 1.95, 4.0],
-                                     precision=0.2))
-        self.assertFalse(values_equal([1, 2, 3, 4, 5],
-                                      [1, 22, 3, 4, 5],
-                                      precision=1))
+        self.assertTrue(values_equal([1.1, 2.0, 3.9], [1.1, 1.95, 4.0], precision=0.2))
+        self.assertFalse(values_equal([1, 2, 3, 4, 5], [1, 22, 3, 4, 5], precision=1))
 
     def test_mixed_lists(self):
-        self.assertTrue(values_equal([1, 'abc', 8], [1, 'abc', 8.2],
-                                     precision=0.5))
+        self.assertTrue(values_equal([1, "abc", 8], [1, "abc", 8.2], precision=0.5))
 
     def test_recursive_lists(self):
-        self.assertTrue(values_equal([1, 'abc', [5, 9.6, 9.0]],
-                                     [1, 'abc', [4.9, 9.2, 9.3]],
-                                     precision=0.5))
+        self.assertTrue(
+            values_equal(
+                [1, "abc", [5, 9.6, 9.0]], [1, "abc", [4.9, 9.2, 9.3]], precision=0.5
+            )
+        )
+
 
-KEYVAL_TEXT = '''s: Hello
+KEYVAL_TEXT = """s: Hello
 str: Hello world!
 f: 1.0
 l: 1,2,3,4,5
 mixed: hello,8,-25,world!,4-1,5:2,0.1,-9.6
-'''
+"""
 
 # file location/PERMANENT/PROJ_INFO
 PROJ_INFO_TEXT_1 = """name: Lambert Conformal Conic
@@ -120,78 +121,88 @@ meters: 1
 
 class TestTextToKeyValue(TestCase):
     def test_conversion(self):
-        keyvals = text_to_keyvalue(KEYVAL_TEXT, sep=':', val_sep=',')
-        expected = {'s': 'Hello',
-                    'str': 'Hello world!',
-                    'f': 1.0,
-                    'l': [1, 2, 3, 4, 5],
-                    'mixed': ['hello', 8, -25, 'world!',
-                              '4-1', '5:2', 0.1, -9.6]}
+        keyvals = text_to_keyvalue(KEYVAL_TEXT, sep=":", val_sep=",")
+        expected = {
+            "s": "Hello",
+            "str": "Hello world!",
+            "f": 1.0,
+            "l": [1, 2, 3, 4, 5],
+            "mixed": ["hello", 8, -25, "world!", "4-1", "5:2", 0.1, -9.6],
+        }
         self.assertDictEqual(expected, keyvals)
 
     def test_single_values(self):
-        keyvals = text_to_keyvalue("a: 1.5", sep=':')
-        self.assertDictEqual({'a': 1.5}, keyvals)
-        keyvals = text_to_keyvalue("abc=1", sep='=')
-        self.assertDictEqual({'abc': 1}, keyvals)
-        keyvals = text_to_keyvalue("abc=hello", sep='=')
-        self.assertDictEqual({'abc': 'hello'}, keyvals)
+        keyvals = text_to_keyvalue("a: 1.5", sep=":")
+        self.assertDictEqual({"a": 1.5}, keyvals)
+        keyvals = text_to_keyvalue("abc=1", sep="=")
+        self.assertDictEqual({"abc": 1}, keyvals)
+        keyvals = text_to_keyvalue("abc=hello", sep="=")
+        self.assertDictEqual({"abc": "hello"}, keyvals)
 
     def test_strip(self):
-        keyvals = text_to_keyvalue("a:   2.8  ", sep=':')
-        self.assertDictEqual({'a': 2.8}, keyvals)
-        keyvals = text_to_keyvalue("a:  2  ; 2.8 ; ab cd ",
-                                   sep=':', val_sep=';')
-        self.assertDictEqual({'a': [2, 2.8, 'ab cd']}, keyvals)
-        keyvals = text_to_keyvalue("a  :  2  ; 2.8", sep=':', val_sep=';')
-        self.assertDictEqual({'a': [2, 2.8]}, keyvals)
-        keyvals = text_to_keyvalue("a  : \t 2  ;\t2.8", sep=':', val_sep=';')
-        self.assertDictEqual({'a': [2, 2.8]}, keyvals)
+        keyvals = text_to_keyvalue("a:   2.8  ", sep=":")
+        self.assertDictEqual({"a": 2.8}, keyvals)
+        keyvals = text_to_keyvalue("a:  2  ; 2.8 ; ab cd ", sep=":", val_sep=";")
+        self.assertDictEqual({"a": [2, 2.8, "ab cd"]}, keyvals)
+        keyvals = text_to_keyvalue("a  :  2  ; 2.8", sep=":", val_sep=";")
+        self.assertDictEqual({"a": [2, 2.8]}, keyvals)
+        keyvals = text_to_keyvalue("a  : \t 2  ;\t2.8", sep=":", val_sep=";")
+        self.assertDictEqual({"a": [2, 2.8]}, keyvals)
 
     def test_empty_list_item(self):
-        keyvals = text_to_keyvalue("a: 1, ,5,,", sep=':', val_sep=',')
-        self.assertDictEqual({'a': [1, '', 5, '', '']}, keyvals)
+        keyvals = text_to_keyvalue("a: 1, ,5,,", sep=":", val_sep=",")
+        self.assertDictEqual({"a": [1, "", 5, "", ""]}, keyvals)
 
     def test_empty_value(self):
-        keyvals = text_to_keyvalue("a: ", sep=':')
-        self.assertDictEqual({'a': ''}, keyvals)
-        keyvals = text_to_keyvalue("a:", sep=':')
-        self.assertDictEqual({'a': ''}, keyvals)
+        keyvals = text_to_keyvalue("a: ", sep=":")
+        self.assertDictEqual({"a": ""}, keyvals)
+        keyvals = text_to_keyvalue("a:", sep=":")
+        self.assertDictEqual({"a": ""}, keyvals)
 
     def test_wrong_lines(self):
         # we consider no key-value separator as invalid line
         # and we silently ignore these
-        keyvals = text_to_keyvalue("a", sep=':',
-                                   skip_invalid=True, skip_empty=False)
+        keyvals = text_to_keyvalue("a", sep=":", skip_invalid=True, skip_empty=False)
         self.assertDictEqual({}, keyvals)
 
-        self.assertRaises(ValueError, text_to_keyvalue, "a", sep=':',
-                          skip_invalid=False, skip_empty=False)
+        self.assertRaises(
+            ValueError,
+            text_to_keyvalue,
+            "a",
+            sep=":",
+            skip_invalid=False,
+            skip_empty=False,
+        )
 
         # text_to_keyvalue considers the empty string as valid input
-        keyvals = text_to_keyvalue("", sep=':',
-                                   skip_invalid=False, skip_empty=False)
+        keyvals = text_to_keyvalue("", sep=":", skip_invalid=False, skip_empty=False)
         self.assertDictEqual({}, keyvals)
 
-        self.assertRaises(ValueError, text_to_keyvalue, "\n", sep=':',
-                          skip_invalid=True, skip_empty=False)
+        self.assertRaises(
+            ValueError,
+            text_to_keyvalue,
+            "\n",
+            sep=":",
+            skip_invalid=True,
+            skip_empty=False,
+        )
 
-        keyvals = text_to_keyvalue("a\n\n", sep=':',
-                                   skip_invalid=True, skip_empty=True)
+        keyvals = text_to_keyvalue("a\n\n", sep=":", skip_invalid=True, skip_empty=True)
         self.assertDictEqual({}, keyvals)
 
     def test_separators(self):
-        keyvals = text_to_keyvalue("a=a;b;c", sep='=', val_sep=';')
-        self.assertDictEqual({'a': ['a', 'b', 'c']}, keyvals)
-        keyvals = text_to_keyvalue("a 1;2;3", sep=' ', val_sep=';')
-        self.assertDictEqual({'a': [1, 2, 3]}, keyvals)
+        keyvals = text_to_keyvalue("a=a;b;c", sep="=", val_sep=";")
+        self.assertDictEqual({"a": ["a", "b", "c"]}, keyvals)
+        keyvals = text_to_keyvalue("a 1;2;3", sep=" ", val_sep=";")
+        self.assertDictEqual({"a": [1, 2, 3]}, keyvals)
         # spaces as key-value separator and values separators
         # this should work (e.g. because of : in DMS),
         # although it does not support stripping (we don't merge separators)
-        keyvals = text_to_keyvalue("a 1 2 3", sep=' ', val_sep=' ')
-        self.assertDictEqual({'a': [1, 2, 3]}, keyvals)
+        keyvals = text_to_keyvalue("a 1 2 3", sep=" ", val_sep=" ")
+        self.assertDictEqual({"a": [1, 2, 3]}, keyvals)
+
+    # def test_projection_files(self):
 
-    #def test_projection_files(self):
 
 # obtained by r.univar elevation -g
 # floats removed
@@ -216,23 +227,21 @@ null_cells=57995100
 cells=60020100
 """
 
-R_UNIVAR_KEYVAL_INT_DICT = {'n': 2025000,
-                            'null_cells': 57995100, 'cells': 60020100}
+R_UNIVAR_KEYVAL_INT_DICT = {"n": 2025000, "null_cells": 57995100, "cells": 60020100}
 
 
 class TestComapreProjections(TestCase):
-
     def test_compare_proj_info(self):
         self.assertTrue(proj_info_equals(PROJ_INFO_TEXT_1, PROJ_INFO_TEXT_2))
         self.assertTrue(proj_units_equals(PROJ_UNITS_TEXT_1, PROJ_UNITS_TEXT_2))
 
 
 class TestParseKeyvalue(TestCase):
-
     def test_shell_script_style(self):
 
-        self.assertDictEqual(parse_key_val(R_UNIVAR_KEYVAL_INT, val_type=int),
-                             R_UNIVAR_KEYVAL_INT_DICT)
+        self.assertDictEqual(
+            parse_key_val(R_UNIVAR_KEYVAL_INT, val_type=int), R_UNIVAR_KEYVAL_INT_DICT
+        )
 
 
 R_UNIVAR_ELEVATION = """n=2025000
@@ -280,50 +289,62 @@ max=156.329864501953
 
 
 class TestRasterMapComparisons(TestCase):
-
     def test_compare_univars(self):
-        self.assertTrue(keyvalue_equals(text_to_keyvalue(R_UNIVAR_ELEVATION,
-                                                          sep='='),
-                                         text_to_keyvalue(R_UNIVAR_ELEVATION,
-                                                          sep='='),
-                                         precision=0))
-        self.assertFalse(keyvalue_equals(text_to_keyvalue(R_UNIVAR_ELEVATION,
-                                                           sep='='),
-                                          text_to_keyvalue(R_UNIVAR_ELEVATION_SUBSET,
-                                                           sep='='),
-                                          precision=0))
+        self.assertTrue(
+            keyvalue_equals(
+                text_to_keyvalue(R_UNIVAR_ELEVATION, sep="="),
+                text_to_keyvalue(R_UNIVAR_ELEVATION, sep="="),
+                precision=0,
+            )
+        )
+        self.assertFalse(
+            keyvalue_equals(
+                text_to_keyvalue(R_UNIVAR_ELEVATION, sep="="),
+                text_to_keyvalue(R_UNIVAR_ELEVATION_SUBSET, sep="="),
+                precision=0,
+            )
+        )
 
     def test_compare_univars_subset(self):
-        self.assertTrue(keyvalue_equals(text_to_keyvalue(R_UNIVAR_ELEVATION_SUBSET,
-                                                          sep='='),
-                                         text_to_keyvalue(R_UNIVAR_ELEVATION,
-                                                          sep='='),
-                                         a_is_subset=True, precision=0))
-        self.assertFalse(keyvalue_equals(text_to_keyvalue(R_UNIVAR_ELEVATION,
-                                                           sep='='),
-                                          text_to_keyvalue(R_UNIVAR_ELEVATION_SUBSET,
-                                                           sep='='),
-                                          a_is_subset=True, precision=0))
+        self.assertTrue(
+            keyvalue_equals(
+                text_to_keyvalue(R_UNIVAR_ELEVATION_SUBSET, sep="="),
+                text_to_keyvalue(R_UNIVAR_ELEVATION, sep="="),
+                a_is_subset=True,
+                precision=0,
+            )
+        )
+        self.assertFalse(
+            keyvalue_equals(
+                text_to_keyvalue(R_UNIVAR_ELEVATION, sep="="),
+                text_to_keyvalue(R_UNIVAR_ELEVATION_SUBSET, sep="="),
+                a_is_subset=True,
+                precision=0,
+            )
+        )
 
     def test_compare_univars_rounded(self):
-        self.assertTrue(keyvalue_equals(text_to_keyvalue(R_UNIVAR_ELEVATION,
-                                                          sep='='),
-                                         text_to_keyvalue(R_UNIVAR_ELEVATION_ROUNDED,
-                                                          sep='='),
-                                         precision=0.001))
+        self.assertTrue(
+            keyvalue_equals(
+                text_to_keyvalue(R_UNIVAR_ELEVATION, sep="="),
+                text_to_keyvalue(R_UNIVAR_ELEVATION_ROUNDED, sep="="),
+                precision=0.001,
+            )
+        )
+
 
 CORRECT_LINES = [
     "null_cells=57995100",
     "cells=60020100",
     "min=55.5787925720215",
-    "max=156.329864501953"
+    "max=156.329864501953",
 ]
 
 INCORRECT_LINES = [
     "null_cells=579951",
     "cells=60020100",
     "min=5.5787925720215",
-    "max=156.329864501953"
+    "max=156.329864501953",
 ]
 
 
@@ -343,25 +364,25 @@ class TestMd5Sums(TestCase):
     9dd6c4bb9d2cf6051b12f4b5f9d70523  test.txt
     """
 
-    correct_md5sum = '9dd6c4bb9d2cf6051b12f4b5f9d70523'
-    correct_file_name_platform_nl = 'md5_sum_correct_file_platform_nl'
-    correct_file_name_unix_nl = 'md5_sum_correct_file_unix_nl'
-    wrong_file_name = 'md5_sum_wrong_file'
+    correct_md5sum = "9dd6c4bb9d2cf6051b12f4b5f9d70523"
+    correct_file_name_platform_nl = "md5_sum_correct_file_platform_nl"
+    correct_file_name_unix_nl = "md5_sum_correct_file_unix_nl"
+    wrong_file_name = "md5_sum_wrong_file"
 
     @classmethod
     def setUpClass(cls):
-        with open(cls.correct_file_name_platform_nl, 'w') as f:
+        with open(cls.correct_file_name_platform_nl, "w") as f:
             for line in CORRECT_LINES:
                 # \n should be converted to platform newline
-                f.write(line + '\n')
-        with open(cls.correct_file_name_unix_nl, 'w') as f:
+                f.write(line + "\n")
+        with open(cls.correct_file_name_unix_nl, "w") as f:
             for line in CORRECT_LINES:
                 # binary mode will write pure \n
-                f.write(line + '\n')
-        with open(cls.wrong_file_name, 'w') as f:
+                f.write(line + "\n")
+        with open(cls.wrong_file_name, "w") as f:
             for line in INCORRECT_LINES:
                 # \n should be converted to platform newline
-                f.write(line + '\n')
+                f.write(line + "\n")
 
     @classmethod
     def tearDownClass(cls):
@@ -371,28 +392,36 @@ class TestMd5Sums(TestCase):
 
     def test_text_file_binary(self):
         r"""File with ``\n`` (LF) newlines as binary (MD5 has ``\n``)."""
-        self.assertEqual(file_md5(self.correct_file_name_unix_nl),
-                         self.correct_md5sum,
-                         msg="MD5 sums different")
+        self.assertEqual(
+            file_md5(self.correct_file_name_unix_nl),
+            self.correct_md5sum,
+            msg="MD5 sums different",
+        )
 
     def test_text_file_platfrom(self):
         r"""Text file with platform dependent newlines"""
-        self.assertEqual(text_file_md5(self.correct_file_name_platform_nl),
-                         self.correct_md5sum,
-                         msg="MD5 sums different")
+        self.assertEqual(
+            text_file_md5(self.correct_file_name_platform_nl),
+            self.correct_md5sum,
+            msg="MD5 sums different",
+        )
 
     def test_text_file_unix(self):
         r"""Text file with ``\n`` (LF) newlines"""
-        self.assertEqual(text_file_md5(self.correct_file_name_unix_nl),
-                         self.correct_md5sum,
-                         msg="MD5 sums different")
+        self.assertEqual(
+            text_file_md5(self.correct_file_name_unix_nl),
+            self.correct_md5sum,
+            msg="MD5 sums different",
+        )
 
     def test_text_file_different(self):
         r"""Text file with ``\n`` (LF) newlines"""
-        self.assertNotEqual(text_file_md5(self.wrong_file_name),
-                            self.correct_md5sum,
-                            msg="MD5 sums must be different")
+        self.assertNotEqual(
+            text_file_md5(self.wrong_file_name),
+            self.correct_md5sum,
+            msg="MD5 sums must be different",
+        )
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 92 - 53
python/grass/gunittest/testsuite/test_gmodules.py

@@ -4,7 +4,7 @@ import subprocess
 
 from grass.gunittest.case import TestCase
 from grass.gunittest.main import test
-from grass.gunittest.gmodules import (call_module, CalledModuleError)
+from grass.gunittest.gmodules import call_module, CalledModuleError
 
 G_REGION_OUTPUT = """...
 n=...
@@ -20,75 +20,114 @@ cells=...
 
 
 class TestCallModuleFunction(TestCase):
-
     def test_output(self):
-        output = call_module('g.region', flags='pg')
+        output = call_module("g.region", flags="pg")
         self.assertLooksLike(output, G_REGION_OUTPUT)
 
     def test_input_output(self):
-        output = call_module('m.proj', flags='i', input='-', stdin="50.0 41.5")
-        self.assertLooksLike(output, '...|...\n')
+        output = call_module("m.proj", flags="i", input="-", stdin="50.0 41.5")
+        self.assertLooksLike(output, "...|...\n")
 
     def test_no_output(self):
-        output = call_module('m.proj', flags='i', input='-', stdin="50.0 41.5",
-                             capture_stdout=False)
+        output = call_module(
+            "m.proj", flags="i", input="-", stdin="50.0 41.5", capture_stdout=False
+        )
         self.assertIsNone(output)
 
     def test_merge_stderr(self):
-        output = call_module('m.proj', flags='i', input='-', stdin="50.0 41.5",
-                             verbose=True,
-                             merge_stderr=True)
-        self.assertLooksLike(output, '...+proj=longlat +datum=WGS84...')
-        self.assertLooksLike(output, '...|...\n')
+        output = call_module(
+            "m.proj",
+            flags="i",
+            input="-",
+            stdin="50.0 41.5",
+            verbose=True,
+            merge_stderr=True,
+        )
+        self.assertLooksLike(output, "...+proj=longlat +datum=WGS84...")
+        self.assertLooksLike(output, "...|...\n")
 
     def test_merge_stderr_with_wrong_stdin_stderr(self):
-        self.assertRaises(ValueError,
-                          call_module,
-                          'm.proj', flags='i', input='-', stdin="50.0 41.5",
-                          verbose=True,
-                          merge_stderr=True, capture_stdout=False)
-        self.assertRaises(ValueError,
-                          call_module,
-                          'm.proj', flags='i', input='-', stdin="50.0 41.5",
-                          verbose=True,
-                          merge_stderr=True, capture_stderr=False)
-        self.assertRaises(ValueError,
-                          call_module,
-                          'm.proj', flags='i', input='-', stdin="50.0 41.5",
-                          verbose=True,
-                          merge_stderr=True,
-                          capture_stdout=False, capture_stderr=False)
+        self.assertRaises(
+            ValueError,
+            call_module,
+            "m.proj",
+            flags="i",
+            input="-",
+            stdin="50.0 41.5",
+            verbose=True,
+            merge_stderr=True,
+            capture_stdout=False,
+        )
+        self.assertRaises(
+            ValueError,
+            call_module,
+            "m.proj",
+            flags="i",
+            input="-",
+            stdin="50.0 41.5",
+            verbose=True,
+            merge_stderr=True,
+            capture_stderr=False,
+        )
+        self.assertRaises(
+            ValueError,
+            call_module,
+            "m.proj",
+            flags="i",
+            input="-",
+            stdin="50.0 41.5",
+            verbose=True,
+            merge_stderr=True,
+            capture_stdout=False,
+            capture_stderr=False,
+        )
 
     def test_wrong_module_params(self):
-        self.assertRaises(CalledModuleError,
-                          call_module,
-                          'g.region', aabbbccc='notexist')
+        self.assertRaises(
+            CalledModuleError, call_module, "g.region", aabbbccc="notexist"
+        )
 
     def test_module_input_param_wrong(self):
-        self.assertRaises(ValueError,
-                          call_module,
-                          'm.proj', flags='i', input='does_not_exist',
-                          stdin="50.0 41.5")
+        self.assertRaises(
+            ValueError,
+            call_module,
+            "m.proj",
+            flags="i",
+            input="does_not_exist",
+            stdin="50.0 41.5",
+        )
 
     def test_missing_stdin_with_input_param(self):
-        self.assertRaises(ValueError,
-                          call_module,
-                          'm.proj', flags='i', input='-')
+        self.assertRaises(ValueError, call_module, "m.proj", flags="i", input="-")
 
     def test_wrong_usage_of_popen_like_interface(self):
-        self.assertRaises(ValueError,
-                          call_module,
-                          'm.proj', flags='i', input='-',
-                          stdin=subprocess.PIPE)
-        self.assertRaises(TypeError,
-                          call_module,
-                          'm.proj', flags='i', input='-', stdin="50.0 41.5",
-                          stdout='any_value_or_type_here')
-        self.assertRaises(TypeError,
-                          call_module,
-                          'm.proj', flags='i', input='-', stdin="50.0 41.5",
-                          stderr='any_value_or_type_here')
-
-
-if __name__ == '__main__':
+        self.assertRaises(
+            ValueError,
+            call_module,
+            "m.proj",
+            flags="i",
+            input="-",
+            stdin=subprocess.PIPE,
+        )
+        self.assertRaises(
+            TypeError,
+            call_module,
+            "m.proj",
+            flags="i",
+            input="-",
+            stdin="50.0 41.5",
+            stdout="any_value_or_type_here",
+        )
+        self.assertRaises(
+            TypeError,
+            call_module,
+            "m.proj",
+            flags="i",
+            input="-",
+            stdin="50.0 41.5",
+            stderr="any_value_or_type_here",
+        )
+
+
+if __name__ == "__main__":
     test()

+ 9 - 7
python/grass/gunittest/testsuite/test_gunitest_doctests.py

@@ -18,12 +18,14 @@ import grass.gunittest.checkers
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -37,5 +39,5 @@ def load_tests(loader, tests, ignore):
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 23 - 16
python/grass/gunittest/testsuite/test_module_assertions.py

@@ -13,32 +13,39 @@ from grass.gunittest.gmodules import CalledModuleError
 
 class TestModuleAssertions(TestCase):
     """Test assertions using PyGRASS Module"""
+
     # pylint: disable=R0904
 
     def setUp(self):
         """Create two Module instances one correct and one with wrong map"""
-        self.rinfo = Module('r.info', map='elevation', flags='g',
-                            stdout_=subprocess.PIPE, run_=False, finish_=True)
+        self.rinfo = Module(
+            "r.info",
+            map="elevation",
+            flags="g",
+            stdout_=subprocess.PIPE,
+            run_=False,
+            finish_=True,
+        )
         self.rinfo_wrong = copy.deepcopy(self.rinfo)
-        self.wrong_map = 'does_not_exists'
-        self.rinfo_wrong.inputs['map'].value = self.wrong_map
+        self.wrong_map = "does_not_exists"
+        self.rinfo_wrong.inputs["map"].value = self.wrong_map
 
     def test_runModule(self):
         """Correct and incorrect Module used in runModule()"""
         self.runModule(self.rinfo)
-        self.assertTrue(self.rinfo.outputs['stdout'].value)
+        self.assertTrue(self.rinfo.outputs["stdout"].value)
         self.assertRaises(CalledModuleError, self.runModule, self.rinfo_wrong)
 
     def test_assertModule(self):
         """Correct and incorrect Module used in assertModule()"""
         self.assertModule(self.rinfo)
-        self.assertTrue(self.rinfo.outputs['stdout'].value)
+        self.assertTrue(self.rinfo.outputs["stdout"].value)
         self.assertRaises(self.failureException, self.assertModule, self.rinfo_wrong)
 
     def test_assertModuleFail(self):
         """Correct and incorrect Module used in assertModuleFail()"""
         self.assertModuleFail(self.rinfo_wrong)
-        stderr = self.rinfo_wrong.outputs['stderr'].value
+        stderr = self.rinfo_wrong.outputs["stderr"].value
         self.assertTrue(stderr)
         self.assertIn(self.wrong_map, stderr)
         self.assertRaises(self.failureException, self.assertModuleFail, self.rinfo)
@@ -46,36 +53,36 @@ class TestModuleAssertions(TestCase):
 
 class TestSimpleModuleAssertions(TestCase):
     """Test assertions using SimpleModule"""
+
     # pylint: disable=R0904
 
     def setUp(self):
-        """Create two SimpleModule instances one correct and one with wrong map
-        """
-        self.rinfo = SimpleModule('r.info', map='elevation', flags='g')
+        """Create two SimpleModule instances one correct and one with wrong map"""
+        self.rinfo = SimpleModule("r.info", map="elevation", flags="g")
         self.rinfo_wrong = copy.deepcopy(self.rinfo)
-        self.wrong_map = 'does_not_exists'
-        self.rinfo_wrong.inputs['map'].value = self.wrong_map
+        self.wrong_map = "does_not_exists"
+        self.rinfo_wrong.inputs["map"].value = self.wrong_map
 
     def test_runModule(self):
         """Correct and incorrect SimpleModule used in runModule()"""
         self.runModule(self.rinfo)
-        self.assertTrue(self.rinfo.outputs['stdout'].value)
+        self.assertTrue(self.rinfo.outputs["stdout"].value)
         self.assertRaises(CalledModuleError, self.runModule, self.rinfo_wrong)
 
     def test_assertModule(self):
         """Correct and incorrect SimpleModule used in assertModule()"""
         self.assertModule(self.rinfo)
-        self.assertTrue(self.rinfo.outputs['stdout'].value)
+        self.assertTrue(self.rinfo.outputs["stdout"].value)
         self.assertRaises(self.failureException, self.assertModule, self.rinfo_wrong)
 
     def test_assertModuleFail(self):
         """Correct and incorrect SimpleModule used in assertModuleFail()"""
         self.assertModuleFail(self.rinfo_wrong)
-        stderr = self.rinfo_wrong.outputs['stderr'].value
+        stderr = self.rinfo_wrong.outputs["stderr"].value
         self.assertTrue(stderr)
         self.assertIn(self.wrong_map, stderr)
         self.assertRaises(self.failureException, self.assertModuleFail, self.rinfo)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 3 - 1
python/grass/gunittest/utils.py

@@ -43,6 +43,7 @@ def do_doctest_gettext_workaround():
     dummy underscore function and one other function which creates the right
     environment to satisfy all. This is done by this function.
     """
+
     def new_displayhook(string):
         """A replacement for default `sys.displayhook`"""
         if string is not None:
@@ -63,6 +64,7 @@ def do_doctest_gettext_workaround():
 
 _MAX_LENGTH = 80
 
+
 # taken from unittest.util (Python 2.7) since it is not part of API
 # but we need it for the same reason as it is used un unittest's TestCase
 def safe_repr(obj, short=False):
@@ -72,4 +74,4 @@ def safe_repr(obj, short=False):
         result = object.__repr__(obj)
     if not short or len(result) < _MAX_LENGTH:
         return result
-    return result[:_MAX_LENGTH] + ' [truncated]...'
+    return result[:_MAX_LENGTH] + " [truncated]..."

+ 35 - 21
python/grass/imaging/images2avi.py

@@ -59,8 +59,15 @@ def _cleanDir(tempDir):
         print("Oops, could not fully clean up temporary files.")
 
 
-def writeAvi(filename, images, duration=0.1, encoding='mpeg4',
-             inputOptions='', outputOptions='', bg_task=False):
+def writeAvi(
+    filename,
+    images,
+    duration=0.1,
+    encoding="mpeg4",
+    inputOptions="",
+    outputOptions="",
+    bg_task=False,
+):
     """Export movie to a AVI file, which is encoded with the given
     encoding. Hint for Windows users: the 'msmpeg4v2' codec is
     natively supported on Windows.
@@ -89,21 +96,21 @@ def writeAvi(filename, images, duration=0.1, encoding='mpeg4',
     try:
         fps = float(1.0 / duration)
     except Exception:
-        raise ValueError(_('Invalid duration parameter for writeAvi.'))
+        raise ValueError(_("Invalid duration parameter for writeAvi."))
 
     # Determine temp dir and create images
-    tempDir = os.path.join(os.path.expanduser('~'), '.tempIms')
-    images2ims.writeIms(os.path.join(tempDir, 'im*.png'), images)
+    tempDir = os.path.join(os.path.expanduser("~"), ".tempIms")
+    images2ims.writeIms(os.path.join(tempDir, "im*.png"), images)
 
     # Determine formatter
     N = len(images)
-    formatter = '%04d'
+    formatter = "%04d"
     if N < 10:
-        formatter = '%d'
+        formatter = "%d"
     elif N < 100:
-        formatter = '%02d'
+        formatter = "%02d"
     elif N < 1000:
-        formatter = '%03d'
+        formatter = "%03d"
 
     # Compile command to create avi
     command = "ffmpeg -r %i %s " % (int(fps), inputOptions)
@@ -112,8 +119,9 @@ def writeAvi(filename, images, duration=0.1, encoding='mpeg4',
     command += "output.avi"
 
     # Run ffmpeg
-    S = subprocess.Popen(command, shell=True, cwd=tempDir,
-                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    S = subprocess.Popen(
+        command, shell=True, cwd=tempDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+    )
 
     # Show what ffmpeg has to say
     outPut = S.stdout.read()
@@ -122,17 +130,22 @@ def writeAvi(filename, images, duration=0.1, encoding='mpeg4',
         # Clean up
         _cleanDir(tempDir)
         if bg_task:
-            return gscript.decode(outPut) + '\n' + gscript.decode(
-                S.stderr.read()) + '\n' + _('Could not write avi.')
+            return (
+                gscript.decode(outPut)
+                + "\n"
+                + gscript.decode(S.stderr.read())
+                + "\n"
+                + _("Could not write avi.")
+            )
         else:
             # An error occurred, show
             print(gscript.decode(outPut))
             print(gscript.decode(S.stderr.read()))
-            raise RuntimeError(_('Could not write avi.'))
+            raise RuntimeError(_("Could not write avi."))
     else:
         try:
             # Copy avi
-            shutil.copy(os.path.join(tempDir, 'output.avi'), filename)
+            shutil.copy(os.path.join(tempDir, "output.avi"), filename)
         except Exception as err:
             # Clean up
             _cleanDir(tempDir)
@@ -158,20 +171,21 @@ def readAvi(filename, asNumpy=True):
 
     # Check whether it exists
     if not os.path.isfile(filename):
-        raise IOError('File not found: '+str(filename))
+        raise IOError("File not found: " + str(filename))
 
     # Determine temp dir, make sure it exists
-    tempDir = os.path.join(os.path.expanduser('~'), '.tempIms')
+    tempDir = os.path.join(os.path.expanduser("~"), ".tempIms")
     if not os.path.isdir(tempDir):
         os.makedirs(tempDir)
 
     # Copy movie there
-    shutil.copy(filename, os.path.join(tempDir, 'input.avi'))
+    shutil.copy(filename, os.path.join(tempDir, "input.avi"))
 
     # Run ffmpeg
     command = "ffmpeg -i input.avi im%d.jpg"
-    S = subprocess.Popen(command, shell=True, cwd=tempDir,
-                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    S = subprocess.Popen(
+        command, shell=True, cwd=tempDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+    )
 
     # Show what mencodec has to say
     outPut = S.stdout.read()
@@ -185,7 +199,7 @@ def readAvi(filename, asNumpy=True):
         raise RuntimeError("Could not read avi.")
     else:
         # Read images
-        images = images2ims.readIms(os.path.join(tempDir, 'im*.jpg'), asNumpy)
+        images = images2ims.readIms(os.path.join(tempDir, "im*.jpg"), asNumpy)
         # Clean up
         _cleanDir(tempDir)
 

+ 13 - 7
python/grass/imaging/images2gif.py

@@ -765,12 +765,15 @@ class NeuQuant:
         self.BETA = 1.0/1024.0
         self.BETAGAMMA = self.BETA * self.GAMMA
 
-        self.network = np.empty((self.NETSIZE, 3), dtype='float64')  # The network itself
-        self.colormap = np.empty((self.NETSIZE, 4), dtype='int32')  # The network itself
+        # The network itself
+        self.network = np.empty((self.NETSIZE, 3), dtype='float64')
+        # The network itself
+        self.colormap = np.empty((self.NETSIZE, 4), dtype='int32')
 
-        self.netindex = np.empty(256, dtype='int32') # For network lookup - really 256
+        self.netindex = np.empty(256, dtype='int32')  # For network lookup - really 256
 
-        self.bias = np.empty(self.NETSIZE, dtype='float64') # Bias and freq arrays for learning
+        # Bias and freq arrays for learning
+        self.bias = np.empty(self.NETSIZE, dtype='float64')
         self.freq = np.empty(self.NETSIZE, dtype='float64')
 
         self.pixels = None
@@ -872,7 +875,7 @@ class NeuQuant:
         p = self.network[lo + 1:hi]
         p -= np.transpose(np.transpose(p - np.array([b, g, r])) * a)
 
-    #def contest(self, b, g, r):
+    # def contest(self, b, g, r):
     #    """ Search for biased BGR values
     #            Finds closest neuron (min dist) and updates self.freq
     #            finds best neuron (min dist-self.bias) and returns position
@@ -927,8 +930,10 @@ class NeuQuant:
         if rad <= 1:
             rad = 0
 
-        print("Beginning 1D learning: samplepixels = %1.2f  rad = %i" %
-             (samplepixels, rad))
+        print(
+            "Beginning 1D learning: samplepixels = %1.2f  rad = %i"
+            % (samplepixels, rad)
+        )
         step = 0
         pos = 0
         if lengthcount % NeuQuant.PRIME1 != 0:
@@ -1090,6 +1095,7 @@ class NeuQuant:
         a = np.argmin((dists * dists).sum(1))
         return a
 
+
 if __name__ == '__main__':
     im = np.zeros((200, 200), dtype=np.uint8)
     im[10: 30, :] = 100

+ 15 - 15
python/grass/imaging/images2ims.py

@@ -80,19 +80,19 @@ def checkImages(images):
                 pass  # ok
             elif im.ndim == 3:
                 if im.shape[2] not in [3, 4]:
-                    raise ValueError('This array can not represent an image.')
+                    raise ValueError("This array can not represent an image.")
             else:
-                raise ValueError('This array can not represent an image.')
+                raise ValueError("This array can not represent an image.")
         else:
-            raise ValueError('Invalid image type: ' + str(type(im)))
+            raise ValueError("Invalid image type: " + str(type(im)))
 
     # Done
     return images2
 
 
 def _getFilenameParts(filename):
-    if '*' in filename:
-        return tuple(filename.split('*', 1))
+    if "*" in filename:
+        return tuple(filename.split("*", 1))
     else:
         return os.path.splitext(filename)
 
@@ -100,13 +100,13 @@ def _getFilenameParts(filename):
 def _getFilenameWithFormatter(filename, N):
 
     # Determine sequence number formatter
-    formatter = '%04i'
+    formatter = "%04i"
     if N < 10:
-        formatter = '%i'
+        formatter = "%i"
     elif N < 100:
-        formatter = '%02i'
+        formatter = "%02i"
     elif N < 1000:
-        formatter = '%03i'
+        formatter = "%03i"
 
     # Insert sequence number formatter
     part1, part2 = _getFilenameParts(filename)
@@ -115,11 +115,11 @@ def _getFilenameWithFormatter(filename, N):
 
 def _getSequenceNumber(filename, part1, part2):
     # Get string bit
-    seq = filename[len(part1):-len(part2)]
+    seq = filename[len(part1) : -len(part2)]
     # Get all numeric chars
-    seq2 = ''
+    seq2 = ""
     for c in seq:
-        if c in '0123456789':
+        if c in "0123456789":
             seq2 += c
         else:
             break
@@ -175,7 +175,7 @@ def writeIms(filename, images):
 
 
 def readIms(filename, asNumpy=True):
-    """ readIms(filename, asNumpy=True)
+    """readIms(filename, asNumpy=True)
 
     Read images from a series of images in a single directory. Returns a
     list of numpy arrays, or, if asNumpy is false, a list if PIL images.
@@ -198,7 +198,7 @@ def readIms(filename, asNumpy=True):
 
     # Check dir exists
     if not os.path.isdir(dirname):
-        raise IOError('Directory not found: '+str(dirname))
+        raise IOError("Directory not found: " + str(dirname))
 
     # Get two parts of the filename
     part1, part2 = _getFilenameParts(filename)
@@ -225,7 +225,7 @@ def readIms(filename, asNumpy=True):
         images = []
         for im in images2:
             # Make without palette
-            if im.mode == 'P':
+            if im.mode == "P":
                 im = im.convert()
             # Make numpy array
             a = np.asarray(im)

+ 132 - 130
python/grass/imaging/images2swf.py

@@ -86,9 +86,9 @@ except ImportError:
 # Code taken from six.py by Benjamin Peterson (MIT licensed)
 PY3 = sys.version_info[0] == 3
 
-string_types = str,
-integer_types = int,
-class_types = type,
+string_types = (str,)
+integer_types = (int,)
+class_types = (type,)
 text_type = str
 binary_type = bytes
 
@@ -97,7 +97,7 @@ binary_type = bytes
 
 
 def checkImages(images):
-    """ checkImages(images)
+    """checkImages(images)
     Check numpy images and correct intensity range etc.
     The same for all movie formats.
     """
@@ -131,11 +131,11 @@ def checkImages(images):
                 pass  # ok
             elif im.ndim == 3:
                 if im.shape[2] not in [3, 4]:
-                    raise ValueError('This array can not represent an image.')
+                    raise ValueError("This array can not represent an image.")
             else:
-                raise ValueError('This array can not represent an image.')
+                raise ValueError("This array can not represent an image.")
         else:
-            raise ValueError('Invalid image type: ' + str(type(im)))
+            raise ValueError("Invalid image type: " + str(type(im)))
 
     # Done
     return images2
@@ -161,14 +161,14 @@ class BitArray:
         return self._len  # self.data.shape[0]
 
     def __repr__(self):
-        return self.data[:self._len].tobytes()
+        return self.data[: self._len].tobytes()
 
     def _checkSize(self):
         # check length... grow if necessary
         arraylen = self.data.shape[0]
         if self._len >= arraylen:
-            tmp = np.zeros((arraylen*2,), dtype=np.uint8)
-            tmp[:self._len] = self.data[:self._len]
+            tmp = np.zeros((arraylen * 2,), dtype=np.uint8)
+            tmp[: self._len] = self.data[: self._len]
             self.data = tmp
 
     def __add__(self, value):
@@ -193,11 +193,11 @@ class BitArray:
 
     def Reverse(self):
         """ In-place reverse. """
-        tmp = self.data[:self._len].copy()
-        self.data[:self._len] = np.flipud(tmp)
+        tmp = self.data[: self._len].copy()
+        self.data[: self._len] = np.flipud(tmp)
 
     def ToBytes(self):
-        """ Convert to bytes. If necessary,
+        """Convert to bytes. If necessary,
         zeros are padded to the end (right side).
         """
         bits = str(self)
@@ -207,12 +207,12 @@ class BitArray:
         while nbytes * 8 < len(bits):
             nbytes += 1
         # pad
-        bits = bits.ljust(nbytes * 8, '0')
+        bits = bits.ljust(nbytes * 8, "0")
 
         # go from bits to bytes
         bb = binary_type()
         for i in range(nbytes):
-            tmp = int(bits[i * 8: (i + 1) * 8], 2)
+            tmp = int(bits[i * 8 : (i + 1) * 8], 2)
             bb += intToUint8(tmp)
 
         # done
@@ -220,15 +220,19 @@ class BitArray:
 
 
 if PY3:
+
     def intToUint32(i):
-        return int(i).to_bytes(4, 'little')
+        return int(i).to_bytes(4, "little")
 
     def intToUint16(i):
-        return int(i).to_bytes(2, 'little')
+        return int(i).to_bytes(2, "little")
 
     def intToUint8(i):
-        return int(i).to_bytes(1, 'little')
+        return int(i).to_bytes(1, "little")
+
+
 else:
+
     def intToUint32(i):
         number = int(i)
         n1, n2, n3, n4 = 1, 256, 256 * 256, 256 * 256 * 256
@@ -251,8 +255,8 @@ else:
 
 
 def intToBits(i, n=None):
-    """ convert int to a string of bits (0's and 1's in a string),
-    pad to n elements. Convert back using int(ss,2). """
+    """convert int to a string of bits (0's and 1's in a string),
+    pad to n elements. Convert back using int(ss,2)."""
     ii = i
 
     # make bits
@@ -266,7 +270,7 @@ def intToBits(i, n=None):
     if n is not None:
         if len(bb) > n:
             raise ValueError("intToBits fail: len larger than padlength.")
-        bb = str(bb).rjust(n, '0')
+        bb = str(bb).rjust(n, "0")
 
     # done
     return BitArray(bb)
@@ -274,32 +278,32 @@ def intToBits(i, n=None):
 
 def bitsToInt(bb, n=8):
     # Init
-    value = ''
+    value = ""
 
     # Get value in bits
     for i in range(len(bb)):
-        b = bb[i:i+1]
+        b = bb[i : i + 1]
         tmp = bin(ord(b))[2:]
-        #value += tmp.rjust(8,'0')
-        value = tmp.rjust(8, '0') + value
+        # value += tmp.rjust(8,'0')
+        value = tmp.rjust(8, "0") + value
 
     # Make decimal
-    return(int(value[:n], 2))
+    return int(value[:n], 2)
 
 
 def getTypeAndLen(bb):
-    """ bb should be 6 bytes at least
+    """bb should be 6 bytes at least
     Return (type, length, length_of_full_tag)
     """
     # Init
-    value = ''
+    value = ""
 
     # Get first 16 bits
     for i in range(2):
-        b = bb[i:i + 1]
+        b = bb[i : i + 1]
         tmp = bin(ord(b))[2:]
-        #value += tmp.rjust(8,'0')
-        value = tmp.rjust(8, '0') + value
+        # value += tmp.rjust(8,'0')
+        value = tmp.rjust(8, "0") + value
 
     # Get type and length
     type = int(value[:10], 2)
@@ -308,12 +312,12 @@ def getTypeAndLen(bb):
 
     # Long tag header?
     if L == 63:  # '111111'
-        value = ''
+        value = ""
         for i in range(2, 6):
-            b = bb[i:i + 1]  # becomes a single-byte bytes() on both PY3 and PY2
+            b = bb[i : i + 1]  # becomes a single-byte bytes() on both PY3 and PY2
             tmp = bin(ord(b))[2:]
-            #value += tmp.rjust(8,'0')
-            value = tmp.rjust(8, '0') + value
+            # value += tmp.rjust(8,'0')
+            value = tmp.rjust(8, "0") + value
         L = int(value, 2)
         L2 = L + 6
 
@@ -322,7 +326,7 @@ def getTypeAndLen(bb):
 
 
 def signedIntToBits(i, n=None):
-    """ convert signed int to a string of bits (0's and 1's in a string),
+    """convert signed int to a string of bits (0's and 1's in a string),
     pad to n elements. Negative numbers are stored in 2's complement bit
     patterns, thus positive numbers always start with a 0.
     """
@@ -341,22 +345,22 @@ def signedIntToBits(i, n=None):
     bb.Reverse()
 
     # justify
-    bb = '0' + str(bb)  # always need the sign bit in front
+    bb = "0" + str(bb)  # always need the sign bit in front
     if n is not None:
         if len(bb) > n:
             raise ValueError("signedIntToBits fail: len larger than padlength.")
-        bb = bb.rjust(n, '0')
+        bb = bb.rjust(n, "0")
 
     # was it negative? (then opposite bits)
     if i < 0:
-        bb = bb.replace('0', 'x').replace('1', '0').replace('x', '1')
+        bb = bb.replace("0", "x").replace("1", "0").replace("x", "1")
 
     # done
     return BitArray(bb)
 
 
 def twitsToBits(arr):
-    """ Given a few (signed) numbers, store them
+    """Given a few (signed) numbers, store them
     as compactly as possible in the wat specifief by the swf format.
     The numbers are multiplied by 20, assuming they
     are twits.
@@ -366,7 +370,7 @@ def twitsToBits(arr):
     # first determine length using non justified bit strings
     maxlen = 1
     for i in arr:
-        tmp = len(signedIntToBits(i*20))
+        tmp = len(signedIntToBits(i * 20))
         if tmp > maxlen:
             maxlen = tmp
 
@@ -379,7 +383,7 @@ def twitsToBits(arr):
 
 
 def floatsToBits(arr):
-    """ Given a few (signed) numbers, convert them to bits,
+    """Given a few (signed) numbers, convert them to bits,
     stored as FB (float bit values). We always use 16.16.
     Negative numbers are not (yet) possible, because I don't
     know how the're implemented (ambiguity).
@@ -399,7 +403,7 @@ def _readFrom(fp, n):
     bb = binary_type()
     try:
         while len(bb) < n:
-            tmp = fp.read(n-len(bb))
+            tmp = fp.read(n - len(bb))
             bb += tmp
             if not tmp:
                 break
@@ -410,8 +414,8 @@ def _readFrom(fp, n):
 
 ## Base Tag
 
-class Tag:
 
+class Tag:
     def __init__(self):
         self.bytes = binary_type()
         self.tagtype = -1
@@ -428,7 +432,7 @@ class Tag:
         bits = intToBits(self.tagtype, 10)
 
         # complete header uint16 thing
-        bits += '1' * 6  # = 63 = 0x3f
+        bits += "1" * 6  # = 63 = 0x3f
         # make uint16
         bb = intToUint16(int(str(bits), 2))
 
@@ -440,32 +444,32 @@ class Tag:
         return bb
 
     def MakeRectRecord(self, xmin, xmax, ymin, ymax):
-        """ Simply uses makeCompactArray to produce
-        a RECT Record. """
+        """Simply uses makeCompactArray to produce
+        a RECT Record."""
         return twitsToBits([xmin, xmax, ymin, ymax])
 
     def MakeMatrixRecord(self, scale_xy=None, rot_xy=None, trans_xy=None):
 
         # empty matrix?
         if scale_xy is None and rot_xy is None and trans_xy is None:
-            return "0"*8
+            return "0" * 8
 
         # init
         bits = BitArray()
 
         # scale
         if scale_xy:
-            bits += '1'
+            bits += "1"
             bits += floatsToBits([scale_xy[0], scale_xy[1]])
         else:
-            bits += '0'
+            bits += "0"
 
         # rotation
         if rot_xy:
-            bits += '1'
+            bits += "1"
             bits += floatsToBits([rot_xy[0], rot_xy[1]])
         else:
-            bits += '0'
+            bits += "0"
 
         # translation (no flag here)
         if trans_xy:
@@ -479,6 +483,7 @@ class Tag:
 
 ## Control tags
 
+
 class ControlTag(Tag):
     def __init__(self):
         Tag.__init__(self)
@@ -490,7 +495,7 @@ class FileAttributesTag(ControlTag):
         self.tagtype = 69
 
     def ProcessTag(self):
-        self.bytes = '\x00'.encode('ascii') * (1+3)
+        self.bytes = "\x00".encode("ascii") * (1 + 3)
 
 
 class ShowFrameTag(ControlTag):
@@ -522,7 +527,7 @@ class SetBackgroundTag(ControlTag):
 
 
 class DoActionTag(Tag):
-    def __init__(self, action='stop'):
+    def __init__(self, action="stop"):
         Tag.__init__(self)
         self.tagtype = 12
         self.actions = [action]
@@ -535,10 +540,10 @@ class DoActionTag(Tag):
 
         for action in self.actions:
             action = action.lower()
-            if action == 'stop':
-                bb += '\x07'.encode('ascii')
-            elif action == 'play':
-                bb += '\x06'.encode('ascii')
+            if action == "stop":
+                bb += "\x07".encode("ascii")
+            elif action == "play":
+                bb += "\x06".encode("ascii")
             else:
                 print("warning, unknown action: %s" % action)
 
@@ -557,7 +562,6 @@ class DefinitionTag(Tag):
 
 
 class BitmapTag(DefinitionTag):
-
     def __init__(self, im):
         DefinitionTag.__init__(self)
         self.tagtype = 36  # DefineBitsLossless2
@@ -570,8 +574,7 @@ class BitmapTag(DefinitionTag):
 
         if len(im.shape) == 3:
             if im.shape[2] in [3, 4]:
-                tmp = np.ones((im.shape[0], im.shape[1], 4),
-                              dtype=np.uint8) * 255
+                tmp = np.ones((im.shape[0], im.shape[1], 4), dtype=np.uint8) * 255
                 for i in range(3):
                     tmp[:, :, i + 1] = im[:, :, i]
                 if im.shape[2] == 4:
@@ -580,7 +583,7 @@ class BitmapTag(DefinitionTag):
                 raise ValueError("Invalid shape to be an image.")
 
         elif len(im.shape) == 2:
-            tmp = np.ones((im.shape[0], im.shape[1], 4), dtype=np.uint8)*255
+            tmp = np.ones((im.shape[0], im.shape[1], 4), dtype=np.uint8) * 255
             for i in range(3):
                 tmp[:, :, i + 1] = im[:, :]
         else:
@@ -595,11 +598,11 @@ class BitmapTag(DefinitionTag):
 
         # build tag
         bb = binary_type()
-        bb += intToUint16(self.id)   # CharacterID
-        bb += intToUint8(5)     # BitmapFormat
-        bb += intToUint16(self.imshape[1])   # BitmapWidth
-        bb += intToUint16(self.imshape[0])   # BitmapHeight
-        bb += self._data            # ZlibBitmapData
+        bb += intToUint16(self.id)  # CharacterID
+        bb += intToUint8(5)  # BitmapFormat
+        bb += intToUint16(self.imshape[1])  # BitmapWidth
+        bb += intToUint16(self.imshape[0])  # BitmapHeight
+        bb += self._data  # ZlibBitmapData
 
         self.bytes = bb
 
@@ -622,9 +625,9 @@ class PlaceObjectTag(ControlTag):
         # build PlaceObject2
         bb = binary_type()
         if self.move:
-            bb += '\x07'.encode('ascii')
+            bb += "\x07".encode("ascii")
         else:
-            bb += '\x06'.encode('ascii')  # (8 bit flags): 4:matrix, 2:character, 1:move
+            bb += "\x06".encode("ascii")  # (8 bit flags): 4:matrix, 2:character, 1:move
         bb += intToUint16(depth)  # Depth
         bb += intToUint16(id)  # character id
         bb += self.MakeMatrixRecord(trans_xy=xy).ToBytes()  # MATRIX record
@@ -652,32 +655,33 @@ class ShapeTag(DefinitionTag):
 
         # first entry: FILLSTYLEARRAY with in it a single fill style
         bb += intToUint8(1)  # FillStyleCount
-        bb += '\x41'.encode('ascii')  # FillStyleType  (0x41 or 0x43, latter is non-smoothed)
+        bb += "\x41".encode(
+            "ascii"
+        )  # FillStyleType  (0x41 or 0x43, latter is non-smoothed)
         bb += intToUint16(self.bitmapId)  # BitmapId
-        #bb += '\x00' # BitmapMatrix (empty matrix with leftover bits filled)
+        # bb += '\x00' # BitmapMatrix (empty matrix with leftover bits filled)
         bb += self.MakeMatrixRecord(scale_xy=(20, 20)).ToBytes()
 
-#         # first entry: FILLSTYLEARRAY with in it a single fill style
-#         bb += intToUint8(1)  # FillStyleCount
-#         bb += '\x00' # solid fill
-#         bb += '\x00\x00\xff' # color
+        #         # first entry: FILLSTYLEARRAY with in it a single fill style
+        #         bb += intToUint8(1)  # FillStyleCount
+        #         bb += '\x00' # solid fill
+        #         bb += '\x00\x00\xff' # color
 
         # second entry: LINESTYLEARRAY with a single line style
         bb += intToUint8(0)  # LineStyleCount
-        #bb += intToUint16(0*20) # Width
-        #bb += '\x00\xff\x00'  # Color
+        # bb += intToUint16(0*20) # Width
+        # bb += '\x00\xff\x00'  # Color
 
         # third and fourth entry: NumFillBits and NumLineBits (4 bits each)
         # I each give them four bits, so 16 styles possible.
-        bb += '\x44'.encode('ascii')
+        bb += "\x44".encode("ascii")
 
         self.bytes = bb
 
         # last entries: SHAPERECORDs ... (individual shape records not aligned)
         # STYLECHANGERECORD
         bits = BitArray()
-        bits += self.MakeStyleChangeRecord(0, 1, moveTo=(self.wh[0],
-                                                         self.wh[1]))
+        bits += self.MakeStyleChangeRecord(0, 1, moveTo=(self.wh[0], self.wh[1]))
         # STRAIGHTEDGERECORD 4x
         bits += self.MakeStraightEdgeRecord(-self.wh[0], 0)
         bits += self.MakeStraightEdgeRecord(0, -self.wh[1])
@@ -690,31 +694,30 @@ class ShapeTag(DefinitionTag):
         self.bytes += bits.ToBytes()
 
         # done
-        #self.bytes = bb
+        # self.bytes = bb
 
-    def MakeStyleChangeRecord(self, lineStyle=None, fillStyle=None,
-                              moveTo=None):
+    def MakeStyleChangeRecord(self, lineStyle=None, fillStyle=None, moveTo=None):
 
         # first 6 flags
         # Note that we use FillStyle1. If we don't flash (at least 8) does not
         # recognize the frames properly when importing to library.
 
         bits = BitArray()
-        bits += '0'  # TypeFlag (not an edge record)
-        bits += '0'  # StateNewStyles (only for DefineShape2 and Defineshape3)
+        bits += "0"  # TypeFlag (not an edge record)
+        bits += "0"  # StateNewStyles (only for DefineShape2 and Defineshape3)
         if lineStyle:
-            bits += '1'  # StateLineStyle
+            bits += "1"  # StateLineStyle
         else:
-            bits += '0'
+            bits += "0"
         if fillStyle:
-            bits += '1'  # StateFillStyle1
+            bits += "1"  # StateFillStyle1
         else:
-            bits += '0'
-        bits += '0'  # StateFillStyle0
+            bits += "0"
+        bits += "0"  # StateFillStyle0
         if moveTo:
-            bits += '1'  # StateMoveTo
+            bits += "1"  # StateMoveTo
         else:
-            bits += '0'
+            bits += "0"
 
         # give information
         # todo: nbits for fillStyle and lineStyle is hard coded.
@@ -727,7 +730,7 @@ class ShapeTag(DefinitionTag):
             bits += intToBits(lineStyle, 4)
 
         return bits
-        #return bitsToBytes(bits)
+        # return bitsToBytes(bits)
 
     def MakeStraightEdgeRecord(self, *dxdy):
         if len(dxdy) == 1:
@@ -739,23 +742,23 @@ class ShapeTag(DefinitionTag):
         nbits = max([len(xbits), len(ybits)])
 
         bits = BitArray()
-        bits += '11'  # TypeFlag and StraightFlag
-        bits += intToBits(nbits-2, 4)
-        bits += '1'  # GeneralLineFlag
+        bits += "11"  # TypeFlag and StraightFlag
+        bits += intToBits(nbits - 2, 4)
+        bits += "1"  # GeneralLineFlag
         bits += signedIntToBits(dxdy[0] * 20, nbits)
         bits += signedIntToBits(dxdy[1] * 20, nbits)
 
         # note: I do not make use of vertical/horizontal only lines...
 
         return bits
-        #return bitsToBytes(bits)
+        # return bitsToBytes(bits)
 
     def MakeEndShapeRecord(self):
         bits = BitArray()
-        bits += "0"     # TypeFlag: no edge
-        bits += "0"*5   # EndOfShape
+        bits += "0"  # TypeFlag: no edge
+        bits += "0" * 5  # EndOfShape
         return bits
-        #return bitsToBytes(bits)
+        # return bitsToBytes(bits)
 
 
 ## Last few functions
@@ -764,10 +767,10 @@ def buildFile(fp, taglist, nframes=1, framesize=(500, 500), fps=10, version=8):
 
     # compose header
     bb = binary_type()
-    bb += 'F'.encode('ascii')  # uncompressed
-    bb += 'WS'.encode('ascii')  # signature bytes
+    bb += "F".encode("ascii")  # uncompressed
+    bb += "WS".encode("ascii")  # signature bytes
     bb += intToUint8(version)  # version
-    bb += '0000'.encode('ascii')  # FileLength (leave open for now)
+    bb += "0000".encode("ascii")  # FileLength (leave open for now)
     bb += Tag().MakeRectRecord(0, framesize[0], 0, framesize[1]).ToBytes()
     bb += intToUint8(0) + intToUint8(fps)  # FrameRate
     bb += intToUint16(nframes)
@@ -778,7 +781,7 @@ def buildFile(fp, taglist, nframes=1, framesize=(500, 500), fps=10, version=8):
         fp.write(tag.GetTag())
 
     # finish with end tag
-    fp.write('\x00\x00'.encode('ascii'))
+    fp.write("\x00\x00".encode("ascii"))
 
     # set size
     sze = fp.tell()
@@ -809,7 +812,7 @@ def writeSwf(filename, images, duration=0.1, repeat=True):
         raise ValueError("Image list is empty!")
     for im in images:
         if PIL and isinstance(im, PIL.Image.Image):
-            if im.mode == 'P':
+            if im.mode == "P":
                 im = im.convert()
             im = np.asarray(im)
             if len(im.shape) == 0:
@@ -820,7 +823,7 @@ def writeSwf(filename, images, duration=0.1, repeat=True):
     taglist = [FileAttributesTag(), SetBackgroundTag(0, 0, 0)]
 
     # Check duration
-    if hasattr(duration, '__len__'):
+    if hasattr(duration, "__len__"):
         if len(duration) == len(images2):
             duration = [d for d in duration]
         else:
@@ -830,11 +833,11 @@ def writeSwf(filename, images, duration=0.1, repeat=True):
 
     # Build delays list
     minDuration = float(min(duration))
-    delays = [round(d/minDuration) for d in duration]
+    delays = [round(d / minDuration) for d in duration]
     delays = [max(1, int(d)) for d in delays]
 
     # Get FPS
-    fps = 1.0/minDuration
+    fps = 1.0 / minDuration
 
     # Produce series of tags for each image
     nframes = 0
@@ -849,10 +852,10 @@ def writeSwf(filename, images, duration=0.1, repeat=True):
         nframes += 1
 
     if not repeat:
-        taglist.append(DoActionTag('stop'))
+        taglist.append(DoActionTag("stop"))
 
     # Build file
-    fp = open(filename, 'wb')
+    fp = open(filename, "wb")
     try:
         buildFile(fp, taglist, nframes=nframes, framesize=wh, fps=fps)
     except Exception:
@@ -862,21 +865,20 @@ def writeSwf(filename, images, duration=0.1, repeat=True):
 
 
 def _readPixels(bb, i, tagType, L1):
-    """ With pf's seed after the recordheader, reads the pixeldata.
-    """
+    """With pf's seed after the recordheader, reads the pixeldata."""
 
     # Check Numpy
     if np is None:
         raise RuntimeError("Need Numpy to read an SWF file.")
 
     # Get info
-    charId = bb[i:i + 2]
+    charId = bb[i : i + 2]
     i += 2
-    format = ord(bb[i:i + 1])
+    format = ord(bb[i : i + 1])
     i += 1
-    width = bitsToInt(bb[i:i + 2], 16)
+    width = bitsToInt(bb[i : i + 2], 16)
     i += 2
-    height = bitsToInt(bb[i:i + 2], 16)
+    height = bitsToInt(bb[i : i + 2], 16)
     i += 2
 
     # If we can, get pixeldata and make nunmpy array
@@ -885,7 +887,7 @@ def _readPixels(bb, i, tagType, L1):
     else:
         # Read byte data
         offset = 2 + 1 + 2 + 2  # all the info bits
-        bb2 = bb[i:i+(L1-offset)]
+        bb2 = bb[i : i + (L1 - offset)]
 
         # Decompress and make numpy array
         data = zlib.decompress(bb2)
@@ -923,7 +925,7 @@ def readSwf(filename, asNumpy=True):
 
     # Check whether it exists
     if not os.path.isfile(filename):
-        raise IOError('File not found: '+str(filename))
+        raise IOError("File not found: " + str(filename))
 
     # Check PIL
     if (not asNumpy) and (PIL is None):
@@ -937,29 +939,29 @@ def readSwf(filename, asNumpy=True):
     images = []
 
     # Open file and read all
-    fp = open(filename, 'rb')
+    fp = open(filename, "rb")
     bb = fp.read()
 
     try:
         # Check opening tag
-        tmp = bb[0:3].decode('ascii', 'ignore')
-        if tmp.upper() == 'FWS':
+        tmp = bb[0:3].decode("ascii", "ignore")
+        if tmp.upper() == "FWS":
             pass  # ok
-        elif tmp.upper() == 'CWS':
+        elif tmp.upper() == "CWS":
             # Decompress movie
             bb = bb[:8] + zlib.decompress(bb[8:])
         else:
-            raise IOError('Not a valid SWF file: ' + str(filename))
+            raise IOError("Not a valid SWF file: " + str(filename))
 
         # Set filepointer at first tag (skipping framesize RECT and two uin16's
         i = 8
-        nbits = bitsToInt(bb[i: i + 1], 5)  # skip FrameSize
+        nbits = bitsToInt(bb[i : i + 1], 5)  # skip FrameSize
         nbits = 5 + nbits * 4
         Lrect = nbits / 8.0
         if Lrect % 1:
             Lrect += 1
         Lrect = int(Lrect)
-        i += Lrect+4
+        i += Lrect + 4
 
         # Iterate over the tags
         counter = 0
@@ -967,24 +969,24 @@ def readSwf(filename, asNumpy=True):
             counter += 1
 
             # Get tag header
-            head = bb[i:i+6]
+            head = bb[i : i + 6]
             if not head:
                 break  # Done (we missed end tag)
 
             # Determine type and length
             T, L1, L2 = getTypeAndLen(head)
             if not L2:
-                print('Invalid tag length, could not proceed')
+                print("Invalid tag length, could not proceed")
                 break
-            #print(T, L2)
+            # print(T, L2)
 
             # Read image if we can
             if T in [20, 36]:
-                im = _readPixels(bb, i+6, T, L1)
+                im = _readPixels(bb, i + 6, T, L1)
                 if im is not None:
                     images.append(im)
             elif T in [6, 21, 35, 90]:
-                print('Ignoring JPEG image: cannot read JPEG.')
+                print("Ignoring JPEG image: cannot read JPEG.")
             else:
                 pass  # Not an image tag
 

+ 16 - 11
python/grass/imaging/operations.py

@@ -58,6 +58,7 @@ for details.
 try:
     import PIL
     from PIL import Image
+
     try:
         import PIL.ImageOps as ImageOps
     except ImportError:
@@ -83,8 +84,7 @@ def crop_image(input_file, output_file=None, format=None):
     cropped_image.save(output_file, format)
 
 
-def thumbnail_image(input_file, output_file=None, size=(200, 200),
-                    format=None):
+def thumbnail_image(input_file, output_file=None, size=(200, 200), format=None):
     """Create a thumbnail of an image
 
     The image aspect ratio is kept and its height and width are adjusted
@@ -104,8 +104,9 @@ def thumbnail_image(input_file, output_file=None, size=(200, 200),
     img.save(output_file, format)
 
 
-def change_rbg_to_transparent(input_file, output_file=None, color='white',
-                              alpha=0, format=None):
+def change_rbg_to_transparent(
+    input_file, output_file=None, color="white", alpha=0, format=None
+):
     """Make a specified RGB color in the image transparent
 
     The color is specified as a RGB tuple (triplet) or string 'white'
@@ -121,9 +122,9 @@ def change_rbg_to_transparent(input_file, output_file=None, color='white',
     """
     if PIL is None:
         raise RuntimeError(_("Install PIL or Pillow to use this function"))
-    if color == 'white':
+    if color == "white":
         rgb = (255, 255, 255)
-    elif color == 'black':
+    elif color == "black":
         rgb = (0, 0, 0)
     else:
         rgb = color  # pylint: disable=redefined-variable-type
@@ -154,22 +155,26 @@ def invert_image_colors(input_file, output_file=None, format=None):
     if PIL is None:
         raise RuntimeError(_("Install PIL or Pillow to use this function"))
     if ImageOps is None:
-        raise RuntimeError(_("Install a newer version of PIL or Pillow to"
-                             " use this function (missing ImageOps module)"))
+        raise RuntimeError(
+            _(
+                "Install a newer version of PIL or Pillow to"
+                " use this function (missing ImageOps module)"
+            )
+        )
     if not output_file:
         output_file = input_file
     original_img = Image.open(input_file)
     # according to documentation (3.0.x) the module can work only on RGB
     # so we need to specifically take care of transparency if present
-    if original_img.mode == 'RGBA':
+    if original_img.mode == "RGBA":
         # split into bands
         red1, green1, blue1, alpha = original_img.split()
-        rgb_img = Image.merge('RGB', (red1, green1, blue1))
+        rgb_img = Image.merge("RGB", (red1, green1, blue1))
         # invert RGB
         inverted_rgb_img = ImageOps.invert(rgb_img)
         # put back the original alpha
         red2, green2, blue2 = inverted_rgb_img.split()
-        new_image = Image.merge('RGBA', (red2, green2, blue2, alpha))
+        new_image = Image.merge("RGBA", (red2, green2, blue2, alpha))
     else:
         new_image = ImageOps.invert(original_img)
     new_image.save(output_file, format)

+ 18 - 26
python/grass/pydispatch/dispatcher.py

@@ -49,6 +49,8 @@ class _Any(_Parameter):
     Any should react to all senders/signals, not just
     a particular sender/signal.
     """
+
+
 Any = _Any()
 
 
@@ -70,6 +72,8 @@ class _Anonymous(_Parameter):
         as though there was a single sender (Anonymous)
         being used everywhere.
     """
+
+
 Anonymous = _Anonymous()
 
 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
@@ -133,8 +137,7 @@ def connect(receiver, signal=Any, sender=Any, weak=True):
     """
     if signal is None:
         raise errors.DispatcherTypeError(
-            'Signal cannot be None (receiver=%r sender=%r)' % (receiver,
-                                                               sender)
+            "Signal cannot be None (receiver=%r sender=%r)" % (receiver, sender)
         )
     if weak:
         receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
@@ -146,8 +149,10 @@ def connect(receiver, signal=Any, sender=Any, weak=True):
     # Keep track of senders for cleanup.
     # Is Anonymous something we want to clean up?
     if sender not in (None, Anonymous, Any):
+
         def remove(object, senderkey=senderkey):
             _removeSender(senderkey=senderkey)
+
         # Skip objects that can not be weakly referenced, which means
         # they won't be automatically cleaned up, but that's too bad.
         try:
@@ -204,31 +209,25 @@ def disconnect(receiver, signal=Any, sender=Any, weak=True):
     """
     if signal is None:
         raise errors.DispatcherTypeError(
-            'Signal cannot be None (receiver=%r sender=%r)' % (receiver,
-                                                               sender)
+            "Signal cannot be None (receiver=%r sender=%r)" % (receiver, sender)
         )
-    if weak: receiver = saferef.safeRef(receiver)
+    if weak:
+        receiver = saferef.safeRef(receiver)
     senderkey = id(sender)
     try:
         signals = connections[senderkey]
         receivers = signals[signal]
     except KeyError:
         raise errors.DispatcherKeyError(
-            """No receivers found for signal %r from sender %r"""  % (
-                signal,
-                sender
-            )
+            """No receivers found for signal %r from sender %r""" % (signal, sender)
         )
     try:
         # also removes from receivers
         _removeOldBackRefs(senderkey, signal, receiver, receivers)
     except ValueError:
         raise errors.DispatcherKeyError(
-            """No connection to receiver %s for signal %s from sender %s"""  % (
-                receiver,
-                signal,
-                sender
-            )
+            """No connection to receiver %s for signal %s from sender %s"""
+            % (receiver, signal, sender)
         )
     _cleanupConnections(senderkey, signal)
 
@@ -343,11 +342,7 @@ def send(signal=Any, sender=Anonymous, *arguments, **named):
     responses = []
     for receiver in liveReceivers(getAllReceivers(sender, signal)):
         response = robustapply.robustApply(
-            receiver,
-            signal=signal,
-            sender=sender,
-            *arguments,
-            **named
+            receiver, signal=signal, sender=sender, *arguments, **named
         )
         responses.append((receiver, response))
     return responses
@@ -364,11 +359,7 @@ def sendExact(signal=Any, sender=Anonymous, *arguments, **named):
     responses = []
     for receiver in liveReceivers(getReceivers(sender, signal)):
         response = robustapply.robustApply(
-            receiver,
-            signal=signal,
-            sender=sender,
-            *arguments,
-            **named
+            receiver, signal=signal, sender=sender, *arguments, **named
         )
         responses.append((receiver, response))
     return responses
@@ -378,8 +369,8 @@ def _removeReceiver(receiver):
     """Remove receiver from connections."""
     if not sendersBack or not connections:
         # During module cleanup the objects will be replaced with None
-           # The order of replacing many change, so both variables need
-           # to be checked.
+        # The order of replacing many change, so both variables need
+        # to be checked.
         return False
     backKey = id(receiver)
     try:
@@ -454,6 +445,7 @@ def _removeBackrefs(senderkey):
             for signal, set in items:
                 for item in set:
                     yield item
+
         for receiver in allReceivers():
             _killBackref(receiver, senderkey)
 

+ 2 - 10
python/grass/pydispatch/robust.py

@@ -3,11 +3,7 @@ from grass.pydispatch.dispatcher import Any, Anonymous, liveReceivers, getAllRec
 from grass.pydispatch.robustapply import robustApply
 
 
-def sendRobust(
-    signal=Any,
-    sender=Anonymous,
-    *arguments, **named
-):
+def sendRobust(signal=Any, sender=Anonymous, *arguments, **named):
     """Send signal from sender to all connected receivers catching errors
 
     signal -- (hashable) signal value, see connect for details
@@ -45,11 +41,7 @@ def sendRobust(
     for receiver in liveReceivers(getAllReceivers(sender, signal)):
         try:
             response = robustApply(
-                receiver,
-                signal=signal,
-                sender=sender,
-                *arguments,
-                **named
+                receiver, signal=signal, sender=sender, *arguments, **named
             )
         except Exception as err:
             responses.append((receiver, err))

+ 17 - 17
python/grass/pydispatch/robustapply.py

@@ -6,16 +6,17 @@ and subset the given arguments to match only
 those which are acceptable.
 """
 import sys
+
 if sys.hexversion >= 0x3000000:
-    im_func = '__func__'
-    im_self = '__self__'
-    im_code = '__code__'
-    func_code = '__code__'
+    im_func = "__func__"
+    im_self = "__self__"
+    im_code = "__code__"
+    func_code = "__code__"
 else:
-    im_func = 'im_func'
-    im_self = 'im_self'
-    im_code = 'im_code'
-    func_code = 'func_code'
+    im_func = "im_func"
+    im_self = "im_self"
+    im_code = "im_code"
+    func_code = "func_code"
 
 
 def function(receiver):
@@ -26,26 +27,25 @@ def function(receiver):
     If fromMethod is true, then the callable already
     has its first argument bound
     """
-    if hasattr(receiver, '__call__'):
+    if hasattr(receiver, "__call__"):
         # Reassign receiver to the actual method that will be called.
-        if hasattr(receiver.__call__, im_func) or hasattr(receiver.__call__,
-                                                          im_code):
+        if hasattr(receiver.__call__, im_func) or hasattr(receiver.__call__, im_code):
             receiver = receiver.__call__
     if hasattr(receiver, im_func):
         # an instance-method...
         return receiver, getattr(getattr(receiver, im_func), func_code), 1
     elif not hasattr(receiver, func_code):
-        raise ValueError('unknown receiver type %s %s' % (receiver,
-                                                          type(receiver)))
+        raise ValueError("unknown receiver type %s %s" % (receiver, type(receiver)))
     return receiver, getattr(receiver, func_code), 0
 
 
 def robustApply(receiver, *arguments, **named):
-    """Call receiver with arguments and an appropriate subset of named
-    """
+    """Call receiver with arguments and an appropriate subset of named"""
     receiver, codeObject, startIndex = function(receiver)
-    acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
-    for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
+    acceptable = codeObject.co_varnames[
+        startIndex + len(arguments) : codeObject.co_argcount
+    ]
+    for name in codeObject.co_varnames[startIndex : startIndex + len(arguments)]:
         if name in named:
             raise TypeError(
                 """Argument %r specified both positionally and as a keyword"""

+ 20 - 16
python/grass/pydispatch/saferef.py

@@ -6,11 +6,11 @@ import traceback
 import sys
 
 if sys.hexversion >= 0x3000000:
-    im_func = '__func__'
-    im_self = '__self__'
+    im_func = "__func__"
+    im_self = "__self__"
 else:
-    im_func = 'im_func'
-    im_self = 'im_self'
+    im_func = "im_func"
+    im_self = "im_self"
 
 
 def safeRef(target, onDelete=None):
@@ -28,15 +28,12 @@ def safeRef(target, onDelete=None):
         if getattr(target, im_self) is not None:
             # Turn a bound method into a BoundMethodWeakref instance.
             # Keep track of these instances for lookup by disconnect().
-            assert hasattr(target, im_func), """safeRef target %r has %s, """ \
-                                             """but no %s, don't know how """ \
-                                             """to create reference""" % (target,
-                                                                          im_self,
-                                                                          im_func)
-            reference = BoundMethodWeakref(
-                target=target,
-                onDelete=onDelete
+            assert hasattr(target, im_func), (
+                """safeRef target %r has %s, """
+                """but no %s, don't know how """
+                """to create reference""" % (target, im_self, im_func)
             )
+            reference = BoundMethodWeakref(target=target, onDelete=onDelete)
             return reference
     if onDelete is not None:
         return weakref.ref(target, onDelete)
@@ -77,6 +74,7 @@ class BoundMethodWeakref(object):
             same BoundMethodWeakref instance.
 
     """
+
     _allInstances = weakref.WeakValueDictionary()
 
     def __new__(cls, target, onDelete=None, *arguments, **named):
@@ -116,6 +114,7 @@ class BoundMethodWeakref(object):
             collected).  Should take a single argument,
             which will be passed a pointer to this object.
         """
+
         def remove(weak, self=self):
             """Set self.isDead to true when method or instance is destroyed"""
             methods = self.deletionMethods[:]
@@ -126,15 +125,18 @@ class BoundMethodWeakref(object):
                 pass
             for function in methods:
                 try:
-                    if hasattr(function, '__call__'):
+                    if hasattr(function, "__call__"):
                         function(self)
                 except Exception as e:
                     try:
                         traceback.print_exc()
                     except AttributeError:
-                        print('''Exception during saferef %s cleanup '''
-                              '''function %s: %s''' % (self, function, e),
-                              file=sys.stderr)
+                        print(
+                            """Exception during saferef %s cleanup """
+                            """function %s: %s""" % (self, function, e),
+                            file=sys.stderr,
+                        )
+
         self.deletionMethods = [onDelete]
         self.key = self.calculateKey(target)
         self.weakSelf = weakref.ref(getattr(target, im_self), remove)
@@ -149,6 +151,7 @@ class BoundMethodWeakref(object):
         target object and the target function respectively.
         """
         return (id(getattr(target, im_self)), id(getattr(target, im_func)))
+
     calculateKey = classmethod(calculateKey)
 
     def __str__(self):
@@ -158,6 +161,7 @@ class BoundMethodWeakref(object):
             self.selfName,
             self.funcName,
         )
+
     __repr__ = __str__
 
     def __nonzero__(self):

+ 9 - 4
python/grass/pydispatch/signal.py

@@ -22,7 +22,10 @@ def _islambda(function):
     >>> _islambda(_islambda)
     False
     """
-    return isinstance(function, type(lambda: None)) and function.__name__ == (lambda: None).__name__
+    return (
+        isinstance(function, type(lambda: None))
+        and function.__name__ == (lambda: None).__name__
+    )
 
 
 class Signal(object):
@@ -107,6 +110,7 @@ class Signal(object):
     >>> signal2.emit(text='Hello')
     lambda handler: Hello
     """
+
     # TODO: use the name for debugging
 
     def __init__(self, name):
@@ -267,11 +271,12 @@ class Signal(object):
         Traceback (most recent call last):
         TypeError: mywrite() takes exactly 1 argument (0 given)
         """
-        if 'signal' in kwargs:
-            del kwargs['signal']
+        if "signal" in kwargs:
+            del kwargs["signal"]
         self.emit(*args, **kwargs)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import doctest
+
     doctest.testmod()

+ 9 - 7
python/grass/pygrass/errors.py

@@ -8,7 +8,6 @@ import grass.lib.gis as libgis
 
 
 def must_be_open(method):
-
     @wraps(method)
     def wrapper(self, *args, **kargs):
         if self.is_open():
@@ -16,28 +15,31 @@ def must_be_open(method):
         else:
             msgr = get_msgr()
             msgr.warning(_("The map is close!"))
+
     return wrapper
 
 
 def mapinfo_must_be_set(method):
-
     @wraps(method)
     def wrapper(self, *args, **kargs):
         if self.c_mapinfo:
             return method(self, *args, **kargs)
         else:
-            raise GrassError(_("The self.c_mapinfo pointer must be "
-                                 "correctly initiated"))
+            raise GrassError(
+                _("The self.c_mapinfo pointer must be " "correctly initiated")
+            )
+
     return wrapper
 
-def must_be_in_current_mapset(method):
 
+def must_be_in_current_mapset(method):
     @wraps(method)
     def wrapper(self, *args, **kargs):
         if self.mapset == libgis.G_mapset().decode():
             return method(self, *args, **kargs)
         else:
-            raise GrassError(_("Map <{}> not found in current mapset").format(
-                self.name))
+            raise GrassError(
+                _("Map <{}> not found in current mapset").format(self.name)
+            )
 
     return wrapper

+ 98 - 70
python/grass/pygrass/gis/__init__.py

@@ -1,8 +1,15 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 from os import listdir
 from os.path import join, isdir
 import shutil
@@ -19,21 +26,25 @@ from grass.pygrass.gis.region import Region
 test_vector_name = "Gis_test_vector"
 test_raster_name = "Gis_test_raster"
 
-libgis.G_gisinit('')
+libgis.G_gisinit("")
 
 
-ETYPE = {'raster': libgis.G_ELEMENT_RASTER,
-         'raster_3d': libgis.G_ELEMENT_RASTER3D,
-         'vector': libgis.G_ELEMENT_VECTOR,
-         'label': libgis.G_ELEMENT_LABEL,
-         'region': libgis.G_ELEMENT_REGION,
-         'group': libgis.G_ELEMENT_GROUP}
+ETYPE = {
+    "raster": libgis.G_ELEMENT_RASTER,
+    "raster_3d": libgis.G_ELEMENT_RASTER3D,
+    "vector": libgis.G_ELEMENT_VECTOR,
+    "label": libgis.G_ELEMENT_LABEL,
+    "region": libgis.G_ELEMENT_REGION,
+    "group": libgis.G_ELEMENT_GROUP,
+}
 
 
-CHECK_IS = {"GISBASE": libgis.G_is_gisbase,
-            "GISDBASE": lambda x: True,
-            "LOCATION_NAME": libgis.G_is_location,
-            "MAPSET": libgis.G_is_mapset}
+CHECK_IS = {
+    "GISBASE": libgis.G_is_gisbase,
+    "GISDBASE": lambda x: True,
+    "LOCATION_NAME": libgis.G_is_location,
+    "MAPSET": libgis.G_is_mapset,
+}
 
 
 def is_valid(value, path, type):
@@ -72,8 +83,9 @@ def _check_raise(value, path, type):
              if value is empty return environmental variable
     :rtype: str
     """
-    if value == '':
+    if value == "":
         from grass.pygrass.utils import getenv
+
         return getenv(type)
     if is_valid(value, path, type):
         return value
@@ -92,11 +104,11 @@ def set_current_mapset(mapset, location=None, gisdbase=None):
     :param gisdbase: Name of the gisdbase
     :type gisdbase: str
     """
-    libgis.G_setenv('MAPSET', mapset)
+    libgis.G_setenv("MAPSET", mapset)
     if location:
-        libgis.G_setenv('LOCATION_NAME', location)
+        libgis.G_setenv("LOCATION_NAME", location)
     if gisdbase:
-        libgis.G_setenv('GISDBASE', gisdbase)
+        libgis.G_setenv("GISDBASE", gisdbase)
 
 
 def make_mapset(mapset, location=None, gisdbase=None):
@@ -128,23 +140,24 @@ class Gisdbase(object):
     ..
     """
 
-    def __init__(self, gisdbase=''):
+    def __init__(self, gisdbase=""):
         self.name = gisdbase
 
     def _get_name(self):
         return self._name
 
     def _set_name(self, name):
-        self._name = _check_raise(name, '', "GISDBASE")
+        self._name = _check_raise(name, "", "GISDBASE")
 
-    name = property(fget=_get_name, fset=_set_name,
-                    doc="Set or obtain the name of GISDBASE")
+    name = property(
+        fget=_get_name, fset=_set_name, doc="Set or obtain the name of GISDBASE"
+    )
 
     def __str__(self):
         return self.name
 
     def __repr__(self):
-        return 'Gisdbase(%s)' % self.name
+        return "Gisdbase(%s)" % self.name
 
     def __getitem__(self, location):
         """Return a Location object. ::
@@ -161,7 +174,7 @@ class Gisdbase(object):
         if location in self.locations():
             return Location(location, self.name)
         else:
-            raise KeyError('Location: %s does not exist' % location)
+            raise KeyError("Location: %s does not exist" % location)
 
     def __iter__(self):
         for loc in self.locations():
@@ -181,8 +194,13 @@ class Gisdbase(object):
 
         ..
         """
-        return sorted([loc for loc in listdir(self.name)
-                       if libgis.G_is_location(encode(join(self.name, loc)))])
+        return sorted(
+            [
+                loc
+                for loc in listdir(self.name)
+                if libgis.G_is_location(encode(join(self.name, loc)))
+            ]
+        )
 
 
 class Location(object):
@@ -200,7 +218,7 @@ class Location(object):
     ..
     """
 
-    def __init__(self, location='', gisdbase=''):
+    def __init__(self, location="", gisdbase=""):
         self.gisdbase = gisdbase
         self.name = location
 
@@ -208,10 +226,11 @@ class Location(object):
         return self._gisdb
 
     def _set_gisdb(self, gisdb):
-        self._gisdb = _check_raise(gisdb, '', "GISDBASE")
+        self._gisdb = _check_raise(gisdb, "", "GISDBASE")
 
-    gisdbase = property(fget=_get_gisdb, fset=_set_gisdb,
-                        doc="Set or obtain the name of GISDBASE")
+    gisdbase = property(
+        fget=_get_gisdb, fset=_set_gisdb, doc="Set or obtain the name of GISDBASE"
+    )
 
     def _get_name(self):
         return self._name
@@ -219,19 +238,23 @@ class Location(object):
     def _set_name(self, name):
         self._name = _check_raise(name, self._gisdb, "LOCATION_NAME")
 
-    name = property(fget=_get_name, fset=_set_name,
-                    doc="Set or obtain the name of LOCATION")
+    name = property(
+        fget=_get_name, fset=_set_name, doc="Set or obtain the name of LOCATION"
+    )
 
     def __getitem__(self, mapset):
         if mapset in self.mapsets():
             return Mapset(mapset)
         else:
-            raise KeyError('Mapset: %s does not exist' % mapset)
+            raise KeyError("Mapset: %s does not exist" % mapset)
 
     def __iter__(self):
         lpath = self.path()
-        return (m for m in listdir(lpath)
-                if (isdir(join(lpath, m)) and is_valid(m, lpath, "MAPSET")))
+        return (
+            m
+            for m in listdir(lpath)
+            if (isdir(join(lpath, m)) and is_valid(m, lpath, "MAPSET"))
+        )
 
     def __len__(self):
         return len(self.mapsets())
@@ -240,7 +263,7 @@ class Location(object):
         return self.name
 
     def __repr__(self):
-        return 'Location(%r)' % self.name
+        return "Location(%r)" % self.name
 
     def mapsets(self, pattern=None, permissions=True):
         """Return a list of the available mapsets.
@@ -261,8 +284,11 @@ class Location(object):
         """
         mapsets = [mapset for mapset in self]
         if permissions:
-            mapsets = [mapset for mapset in mapsets
-                       if libgis.G_mapset_permissions(encode(mapset))]
+            mapsets = [
+                mapset
+                for mapset in mapsets
+                if libgis.G_mapset_permissions(encode(mapset))
+            ]
         if pattern:
             return fnmatch.filter(mapsets, pattern)
         return mapsets
@@ -290,7 +316,7 @@ class Mapset(object):
     ..
     """
 
-    def __init__(self, mapset='', location='', gisdbase=''):
+    def __init__(self, mapset="", location="", gisdbase=""):
         self.gisdbase = gisdbase
         self.location = location
         self.name = mapset
@@ -300,10 +326,11 @@ class Mapset(object):
         return self._gisdb
 
     def _set_gisdb(self, gisdb):
-        self._gisdb = _check_raise(gisdb, '', "GISDBASE")
+        self._gisdb = _check_raise(gisdb, "", "GISDBASE")
 
-    gisdbase = property(fget=_get_gisdb, fset=_set_gisdb,
-                        doc="Set or obtain the name of GISDBASE")
+    gisdbase = property(
+        fget=_get_gisdb, fset=_set_gisdb, doc="Set or obtain the name of GISDBASE"
+    )
 
     def _get_loc(self):
         return self._loc
@@ -311,8 +338,9 @@ class Mapset(object):
     def _set_loc(self, loc):
         self._loc = _check_raise(loc, self._gisdb, "LOCATION_NAME")
 
-    location = property(fget=_get_loc, fset=_set_loc,
-                        doc="Set or obtain the name of LOCATION")
+    location = property(
+        fget=_get_loc, fset=_set_loc, doc="Set or obtain the name of LOCATION"
+    )
 
     def _get_name(self):
         return self._name
@@ -320,14 +348,15 @@ class Mapset(object):
     def _set_name(self, name):
         self._name = _check_raise(name, join(self._gisdb, self._loc), "MAPSET")
 
-    name = property(fget=_get_name, fset=_set_name,
-                    doc="Set or obtain the name of MAPSET")
+    name = property(
+        fget=_get_name, fset=_set_name, doc="Set or obtain the name of MAPSET"
+    )
 
     def __str__(self):
         return self.name
 
     def __repr__(self):
-        return 'Mapset(%r)' % self.name
+        return "Mapset(%r)" % self.name
 
     def glist(self, type, pattern=None):
         """Return a list of grass types like:
@@ -359,9 +388,8 @@ class Mapset(object):
         """
         if type not in ETYPE:
             str_err = "Type %s is not valid, valid types are: %s."
-            raise TypeError(str_err % (type, ', '.join(ETYPE.keys())))
-        clist = libgis.G_list(ETYPE[type], self.gisdbase,
-                              self.location, self.name)
+            raise TypeError(str_err % (type, ", ".join(ETYPE.keys())))
+        clist = libgis.G_list(ETYPE[type], self.gisdbase, self.location, self.name)
         elist = []
         for el in clist:
             el_name = ct.cast(el, ct.c_char_p).value
@@ -374,9 +402,11 @@ class Mapset(object):
 
     def is_current(self):
         """Check if the MAPSET is the working MAPSET"""
-        return (self.name == getenv('MAPSET') and
-                self.location == getenv('LOCATION_NAME') and
-                self.gisdbase == getenv('GISDBASE'))
+        return (
+            self.name == getenv("MAPSET")
+            and self.location == getenv("LOCATION_NAME")
+            and self.gisdbase == getenv("GISDBASE")
+        )
 
     def current(self):
         """Set the mapset as current"""
@@ -385,7 +415,7 @@ class Mapset(object):
     def delete(self):
         """Delete the mapset"""
         if self.is_current():
-            raise GrassError('The mapset is in use.')
+            raise GrassError("The mapset is in use.")
         shutil.rmtree(self.path())
 
     def path(self):
@@ -394,14 +424,13 @@ class Mapset(object):
 
 
 class VisibleMapset(object):
-    """VisibleMapset object
-    """
+    """VisibleMapset object"""
 
-    def __init__(self, mapset, location='', gisdbase=''):
+    def __init__(self, mapset, location="", gisdbase=""):
         self.mapset = mapset
         self.location = Location(location, gisdbase)
         self._list = []
-        self.spath = join(self.location.path(), self.mapset, 'SEARCH_PATH')
+        self.spath = join(self.location.path(), self.mapset, "SEARCH_PATH")
 
     def __repr__(self):
         return repr(self.read())
@@ -416,7 +445,9 @@ class VisibleMapset(object):
             lines = f.readlines()
             if lines:
                 return [decode(l.strip()) for l in lines]
-        lns = [u'PERMANENT', ]
+        lns = [
+            "PERMANENT",
+        ]
         self._write(lns)
         return lns
 
@@ -428,7 +459,7 @@ class VisibleMapset(object):
         """
         with open(self.spath, "wb+") as f:
             ms = [decode(m) for m in self.location.mapsets()]
-            f.write(b'\n'.join([encode(m) for m in mapsets if m in ms]))
+            f.write(b"\n".join([encode(m) for m in mapsets if m in ms]))
 
     def add(self, mapset):
         """Add a mapset to the search path
@@ -438,9 +469,9 @@ class VisibleMapset(object):
         """
         if mapset not in self.read() and mapset in self.location:
             with open(self.spath, "a+") as f:
-                f.write('\n%s' % mapset)
+                f.write("\n%s" % mapset)
         else:
-            raise TypeError('Mapset not found')
+            raise TypeError("Mapset not found")
 
     def remove(self, mapset):
         """Remove mapset to the search path
@@ -466,7 +497,7 @@ class VisibleMapset(object):
 
     def reset(self):
         """Reset to the original search path"""
-        final = [self.mapset, 'PERMANENT']
+        final = [self.mapset, "PERMANENT"]
         self._write(final)
 
 
@@ -477,18 +508,15 @@ if __name__ == "__main__":
 
     utils.create_test_vector_map(test_vector_name)
     run_command("g.region", n=50, s=0, e=60, w=0, res=1)
-    run_command("r.mapcalc", expression="%s = 1" % (test_raster_name),
-                overwrite=True)
+    run_command("r.mapcalc", expression="%s = 1" % (test_raster_name), overwrite=True)
     run_command("g.region", n=40, s=0, e=40, w=0, res=2)
 
     doctest.testmod()
 
     # Remove the generated vector map, if exist
-    mset = utils.get_mapset_vector(test_vector_name, mapset='')
+    mset = utils.get_mapset_vector(test_vector_name, mapset="")
     if mset:
-        run_command("g.remove", flags='f', type='vector',
-                    name=test_vector_name)
-    mset = utils.get_mapset_raster(test_raster_name, mapset='')
+        run_command("g.remove", flags="f", type="vector", name=test_vector_name)
+    mset = utils.get_mapset_raster(test_raster_name, mapset="")
     if mset:
-        run_command("g.remove", flags='f', type='raster',
-                    name=test_raster_name)
+        run_command("g.remove", flags="f", type="raster", name=test_raster_name)

+ 248 - 200
python/grass/pygrass/gis/region.py

@@ -4,8 +4,15 @@ Created on Fri May 25 12:57:10 2012
 
 @author: Pietro Zambelli
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 import ctypes
 import grass.lib.gis as libgis
 import grass.lib.raster as libraster
@@ -18,6 +25,7 @@ from grass.pygrass.utils import get_mapset_raster
 test_vector_name = "Region_test_vector"
 test_raster_name = "Region_test_raster"
 
+
 class Region(object):
     """This class is design to easily access and modify GRASS computational
     region. ::
@@ -114,9 +122,9 @@ class Region(object):
         return ctypes.pointer(self.c_region)
 
     def _set_param(self, key, value):
-        grass.run_command('g.region', **{key: value})
+        grass.run_command("g.region", **{key: value})
 
-    #----------LIMITS----------
+    # ----------LIMITS----------
     def _get_n(self):
         """Private function to obtain north value"""
         return self.c_region.north
@@ -125,8 +133,7 @@ class Region(object):
         """Private function to set north value"""
         self.c_region.north = value
 
-    north = property(fget=_get_n, fset=_set_n,
-                     doc="Set and obtain north coordinate")
+    north = property(fget=_get_n, fset=_set_n, doc="Set and obtain north coordinate")
 
     def _get_s(self):
         """Private function to obtain south value"""
@@ -136,8 +143,7 @@ class Region(object):
         """Private function to set south value"""
         self.c_region.south = value
 
-    south = property(fget=_get_s, fset=_set_s,
-                     doc="Set and obtain south coordinate")
+    south = property(fget=_get_s, fset=_set_s, doc="Set and obtain south coordinate")
 
     def _get_e(self):
         """Private function to obtain east value"""
@@ -147,8 +153,7 @@ class Region(object):
         """Private function to set east value"""
         self.c_region.east = value
 
-    east = property(fget=_get_e, fset=_set_e,
-                    doc="Set and obtain east coordinate")
+    east = property(fget=_get_e, fset=_set_e, doc="Set and obtain east coordinate")
 
     def _get_w(self):
         """Private function to obtain west value"""
@@ -158,8 +163,7 @@ class Region(object):
         """Private function to set west value"""
         self.c_region.west = value
 
-    west = property(fget=_get_w, fset=_set_w,
-                    doc="Set and obtain west coordinate")
+    west = property(fget=_get_w, fset=_set_w, doc="Set and obtain west coordinate")
 
     def _get_t(self):
         """Private function to obtain top value"""
@@ -169,8 +173,7 @@ class Region(object):
         """Private function to set top value"""
         self.c_region.top = value
 
-    top = property(fget=_get_t, fset=_set_t,
-                   doc="Set and obtain top value")
+    top = property(fget=_get_t, fset=_set_t, doc="Set and obtain top value")
 
     def _get_b(self):
         """Private function to obtain bottom value"""
@@ -180,10 +183,9 @@ class Region(object):
         """Private function to set bottom value"""
         self.c_region.bottom = value
 
-    bottom = property(fget=_get_b, fset=_set_b,
-                      doc="Set and obtain bottom value")
+    bottom = property(fget=_get_b, fset=_set_b, doc="Set and obtain bottom value")
 
-    #----------RESOLUTION----------
+    # ----------RESOLUTION----------
     def _get_rows(self):
         """Private function to obtain rows value"""
         return self.c_region.rows
@@ -193,8 +195,7 @@ class Region(object):
         self.c_region.rows = value
         self.adjust(rows=True)
 
-    rows = property(fget=_get_rows, fset=_set_rows,
-                    doc="Set and obtain number of rows")
+    rows = property(fget=_get_rows, fset=_set_rows, doc="Set and obtain number of rows")
 
     def _get_cols(self):
         """Private function to obtain columns value"""
@@ -205,8 +206,9 @@ class Region(object):
         self.c_region.cols = value
         self.adjust(cols=True)
 
-    cols = property(fget=_get_cols, fset=_set_cols,
-                    doc="Set and obtain number of columns")
+    cols = property(
+        fget=_get_cols, fset=_set_cols, doc="Set and obtain number of columns"
+    )
 
     def _get_depths(self):
         """Private function to obtain depths value"""
@@ -216,8 +218,9 @@ class Region(object):
         """Private function to set depths value"""
         self.c_region.depths = value
 
-    depths = property(fget=_get_depths, fset=_set_depths,
-                      doc="Set and obtain number of depths")
+    depths = property(
+        fget=_get_depths, fset=_set_depths, doc="Set and obtain number of depths"
+    )
 
     def _get_nsres(self):
         """Private function to obtain north-south value"""
@@ -228,8 +231,11 @@ class Region(object):
         self.c_region.ns_res = value
         self.adjust()
 
-    nsres = property(fget=_get_nsres, fset=_set_nsres,
-                     doc="Set and obtain north-south resolution value")
+    nsres = property(
+        fget=_get_nsres,
+        fset=_set_nsres,
+        doc="Set and obtain north-south resolution value",
+    )
 
     def _get_ewres(self):
         """Private function to obtain east-west value"""
@@ -240,8 +246,11 @@ class Region(object):
         self.c_region.ew_res = value
         self.adjust()
 
-    ewres = property(fget=_get_ewres, fset=_set_ewres,
-                     doc="Set and obtain east-west resolution value")
+    ewres = property(
+        fget=_get_ewres,
+        fset=_set_ewres,
+        doc="Set and obtain east-west resolution value",
+    )
 
     def _get_tbres(self):
         """Private function to obtain top-botton 3D value"""
@@ -252,19 +261,18 @@ class Region(object):
         self.c_region.tb_res = value
         self.adjust()
 
-    tbres = property(fget=_get_tbres, fset=_set_tbres,
-                     doc="Set and obtain top-bottom 3D value")
+    tbres = property(
+        fget=_get_tbres, fset=_set_tbres, doc="Set and obtain top-bottom 3D value"
+    )
 
     @property
     def zone(self):
-        """Return the zone of projection
-        """
+        """Return the zone of projection"""
         return self.c_region.zone
 
     @property
     def proj(self):
-        """Return a code for projection
-        """
+        """Return a code for projection"""
         return self.c_region.proj
 
     @property
@@ -272,18 +280,29 @@ class Region(object):
         """Return the number of cells"""
         return self.rows * self.cols
 
-    #----------MAGIC METHODS----------
+    # ----------MAGIC METHODS----------
     def __repr__(self):
-        rg = "Region(north=%g, south=%g, east=%g, west=%g, "\
-            "nsres=%g, ewres=%g, rows=%i, cols=%i, "\
+        rg = (
+            "Region(north=%g, south=%g, east=%g, west=%g, "
+            "nsres=%g, ewres=%g, rows=%i, cols=%i, "
             "cells=%i, zone=%i, proj=%i)"
-        return rg % (self.north, self.south, self.east, self.west,
-                     self.nsres, self.ewres, self.rows, self.cols,
-                     self.cells, self.zone, self.proj)
+        )
+        return rg % (
+            self.north,
+            self.south,
+            self.east,
+            self.west,
+            self.nsres,
+            self.ewres,
+            self.rows,
+            self.cols,
+            self.cells,
+            self.zone,
+            self.proj,
+        )
 
     def _repr_html_(self):
-        return dict2html(dict(self.items()), keys=self.keys(),
-                         border='1', kdec='b')
+        return dict2html(dict(self.items()), keys=self.keys(), border="1", kdec="b")
 
     def __unicode__(self):
         return self.__repr__()
@@ -305,9 +324,22 @@ class Region(object):
 
         ..
         """
-        attrs = ['north', 'south', 'west', 'east', 'top', 'bottom',
-                 'nsres', 'ewres', 'tbres', 'rows', 'cols', 'cells',
-                 'zone', 'proj']
+        attrs = [
+            "north",
+            "south",
+            "west",
+            "east",
+            "top",
+            "bottom",
+            "nsres",
+            "ewres",
+            "tbres",
+            "rows",
+            "cols",
+            "cells",
+            "zone",
+            "proj",
+        ]
         for attr in attrs:
             if getattr(self, attr) != getattr(reg, attr):
                 return False
@@ -328,16 +360,28 @@ class Region(object):
 
         ..
         """
-        return ['proj', 'zone', 'north', 'south', 'west', 'east',
-                'top', 'bottom', 'nsres', 'ewres', 'tbres', 'rows',
-                'cols', 'cells']
+        return [
+            "proj",
+            "zone",
+            "north",
+            "south",
+            "west",
+            "east",
+            "top",
+            "bottom",
+            "nsres",
+            "ewres",
+            "tbres",
+            "rows",
+            "cols",
+            "cells",
+        ]
 
     def items(self):
-        """Return a list of tuple with key and value.
-        """
+        """Return a list of tuple with key and value."""
         return [(k, self.__getattribute__(k)) for k in self.keys()]
 
-    #----------METHODS----------
+    # ----------METHODS----------
     def zoom(self, raster_name):
         """Shrink region until it meets non-NULL data from this raster map
 
@@ -346,7 +390,7 @@ class Region(object):
         :param raster_name: the name of raster
         :type raster_name: str
         """
-        self._set_param('zoom', str(raster_name))
+        self._set_param("zoom", str(raster_name))
         self.read()
 
     def align(self, raster_name):
@@ -357,7 +401,7 @@ class Region(object):
         :param raster_name: the name of raster
         :type raster_name: str
         """
-        self._set_param('align', str(raster_name))
+        self._set_param("align", str(raster_name))
         self.read()
 
     def adjust(self, rows=False, cols=False):
@@ -370,91 +414,90 @@ class Region(object):
     def from_vect(self, vector_name):
         """Adjust bounding box of region using a vector
 
-            :param vector_name: the name of vector
-            :type vector_name: str
+        :param vector_name: the name of vector
+        :type vector_name: str
 
-            Example ::
+        Example ::
 
-            >>> reg = Region()
-            >>> reg.from_vect(test_vector_name)
-            >>> reg.get_bbox()
-            Bbox(6.0, 0.0, 14.0, 0.0)
-            >>> reg.read()
-            >>> reg.get_bbox()
-            Bbox(40.0, 0.0, 40.0, 0.0)
+        >>> reg = Region()
+        >>> reg.from_vect(test_vector_name)
+        >>> reg.get_bbox()
+        Bbox(6.0, 0.0, 14.0, 0.0)
+        >>> reg.read()
+        >>> reg.get_bbox()
+        Bbox(40.0, 0.0, 40.0, 0.0)
 
-            ..
+        ..
         """
         from grass.pygrass.vector import VectorTopo
-        with VectorTopo(vector_name, mode='r') as vect:
+
+        with VectorTopo(vector_name, mode="r") as vect:
             bbox = vect.bbox()
             self.set_bbox(bbox)
 
     def from_rast(self, raster_name):
         """Set the region from the computational region
-            of a raster map layer.
+        of a raster map layer.
 
-            :param raster_name: the name of raster
-            :type raster_name: str
+        :param raster_name: the name of raster
+        :type raster_name: str
 
-            :param mapset: the mapset of raster
-            :type mapset: str
+        :param mapset: the mapset of raster
+        :type mapset: str
 
-            call C function `Rast_get_cellhd`
+        call C function `Rast_get_cellhd`
 
-            Example ::
+        Example ::
 
-            >>> reg = Region()
-            >>> reg.from_rast(test_raster_name)
-            >>> reg.get_bbox()
-            Bbox(50.0, 0.0, 60.0, 0.0)
-            >>> reg.read()
-            >>> reg.get_bbox()
-            Bbox(40.0, 0.0, 40.0, 0.0)
+        >>> reg = Region()
+        >>> reg.from_rast(test_raster_name)
+        >>> reg.get_bbox()
+        Bbox(50.0, 0.0, 60.0, 0.0)
+        >>> reg.read()
+        >>> reg.get_bbox()
+        Bbox(40.0, 0.0, 40.0, 0.0)
 
-            ..
-           """
+        ..
+        """
         if not raster_name:
             raise ValueError("Raster name or mapset are invalid")
 
-
         mapset = get_mapset_raster(raster_name)
 
         if mapset:
-            libraster.Rast_get_cellhd(raster_name, mapset,
-                                      self.byref())
+            libraster.Rast_get_cellhd(raster_name, mapset, self.byref())
 
     def set_raster_region(self):
         """Set the computational region (window) for all raster maps in the current process.
 
-           Attention: All raster objects must be closed or the
-                      process will be terminated.
+        Attention: All raster objects must be closed or the
+                   process will be terminated.
 
-           The Raster library C function Rast_set_window() is called.
+        The Raster library C function Rast_set_window() is called.
 
         """
         libraster.Rast_set_window(self.byref())
 
     def get_current(self):
         """Get the current working region of this process
-           and store it into this Region object
+        and store it into this Region object
 
-           Previous calls to set_current() affects values returned by this function.
-           Previous calls to read() affects values returned by this function
-           only if the current working region is not initialized.
+        Previous calls to set_current() affects values returned by this function.
+        Previous calls to read() affects values returned by this function
+        only if the current working region is not initialized.
 
-            Example:
+         Example:
 
-            >>> r = Region()
-            >>> r.north
-            40.0
+         >>> r = Region()
+         >>> r.north
+         40.0
 
-            >>> r.north = 30
-            >>> r.north
-            30.0
-            >>> r.get_current()
-            >>> r.north
-            40.0
+         >>> r.north = 30
+         >>> r.north
+         30.0
+         >>> r.get_current()
+         >>> r.north
+         40.0
 
         """
         libgis.G_get_set_window(self.byref())
@@ -462,70 +505,70 @@ class Region(object):
     def set_current(self):
         """Set the current working region from this region object
 
-           This function adjusts the values before setting the region
-           so you don't have to call G_adjust_Cell_head().
-
-           Attention: Only the current process is affected.
-                      The GRASS computational region is not affected.
-
-            Example::
-
-            >>> r = Region()
-            >>> r.north
-            40.0
-            >>> r.south
-            0.0
-
-            >>> r.north = 30
-            >>> r.south = 20
-            >>> r.set_current()
-            >>> r.north
-            30.0
-            >>> r.south
-            20.0
-            >>> r.get_current()
-            >>> r.north
-            30.0
-            >>> r.south
-            20.0
-
-            >>> r.read(force_read=False)
-            >>> r.north
-            40.0
-            >>> r.south
-            0.0
-
-            >>> r.read(force_read=True)
-            >>> r.north
-            40.0
-            >>> r.south
-            0.0
+        This function adjusts the values before setting the region
+        so you don't have to call G_adjust_Cell_head().
+
+        Attention: Only the current process is affected.
+                   The GRASS computational region is not affected.
+
+         Example::
+
+         >>> r = Region()
+         >>> r.north
+         40.0
+         >>> r.south
+         0.0
+
+         >>> r.north = 30
+         >>> r.south = 20
+         >>> r.set_current()
+         >>> r.north
+         30.0
+         >>> r.south
+         20.0
+         >>> r.get_current()
+         >>> r.north
+         30.0
+         >>> r.south
+         20.0
+
+         >>> r.read(force_read=False)
+         >>> r.north
+         40.0
+         >>> r.south
+         0.0
+
+         >>> r.read(force_read=True)
+         >>> r.north
+         40.0
+         >>> r.south
+         0.0
 
         """
         libgis.G_set_window(self.byref())
 
     def read(self, force_read=True):
         """
-          Read the region into this region object
+        Read the region into this region object
 
-          Reads the region as stored in the WIND file in the user's current
-          mapset into region.
+        Reads the region as stored in the WIND file in the user's current
+        mapset into region.
 
-          3D values are set to defaults if not available in WIND file.  An
-          error message is printed and exit() is called if there is a problem
-          reading the region.
+        3D values are set to defaults if not available in WIND file.  An
+        error message is printed and exit() is called if there is a problem
+        reading the region.
 
-          <b>Note:</b> GRASS applications that read or write raster maps
-          should not use this routine since its use implies that the active
-          module region will not be used. Programs that read or write raster
-          map data (or vector data) can query the active module region using
-          Rast_window_rows() and Rast_window_cols().
+        <b>Note:</b> GRASS applications that read or write raster maps
+        should not use this routine since its use implies that the active
+        module region will not be used. Programs that read or write raster
+        map data (or vector data) can query the active module region using
+        Rast_window_rows() and Rast_window_cols().
 
-          :param force_read: If True the WIND file of the current mapset
-                             is re-readed, otherwise the initial region
-                             set at process start will be loaded from the internal
-                             static variables.
-          :type force_read: boolean
+        :param force_read: If True the WIND file of the current mapset
+                           is re-readed, otherwise the initial region
+                           set at process start will be loaded from the internal
+                           static variables.
+        :type force_read: boolean
 
         """
         # Force the reading of the WIND file
@@ -536,52 +579,51 @@ class Region(object):
     def write(self):
         """Writes the region from this region object
 
-           This function writes this region to the Region file (WIND)
-           in the users current mapset. This function should be
-           carefully used, since the user will ot notice if his region
-           was changed and would expect that only g.region will do this.
-
-            Example ::
-
-            >>> from copy import deepcopy
-            >>> r = Region()
-            >>> rn = deepcopy(r)
-            >>> r.north = 20
-            >>> r.south = 10
-
-            >>> r.write()
-            >>> r.read()
-            >>> r.north
-            20.0
-            >>> r.south
-            10.0
-
-            >>> rn.write()
-            >>> r.read()
-            >>> r.north
-            40.0
-            >>> r.south
-            0.0
-
-            >>> r.read_default()
-            >>> r.write()
-
-            ..
+        This function writes this region to the Region file (WIND)
+        in the users current mapset. This function should be
+        carefully used, since the user will ot notice if his region
+        was changed and would expect that only g.region will do this.
+
+         Example ::
+
+         >>> from copy import deepcopy
+         >>> r = Region()
+         >>> rn = deepcopy(r)
+         >>> r.north = 20
+         >>> r.south = 10
+
+         >>> r.write()
+         >>> r.read()
+         >>> r.north
+         20.0
+         >>> r.south
+         10.0
+
+         >>> rn.write()
+         >>> r.read()
+         >>> r.north
+         40.0
+         >>> r.south
+         0.0
+
+         >>> r.read_default()
+         >>> r.write()
+
+         ..
         """
         self.adjust()
         if libgis.G_put_window(self.byref()) < 0:
             raise GrassError("Cannot change region (WIND file).")
 
-
     def read_default(self):
         """
-          Get the default region
+        Get the default region
 
-          Reads the default region for the location in this Region object.
-          3D values are set to defaults if not available in WIND file.
+        Reads the default region for the location in this Region object.
+        3D values are set to defaults if not available in WIND file.
 
-          An error message is printed and exit() is called if there is a
-          problem reading the default region.
+        An error message is printed and exit() is called if there is a
+        problem reading the default region.
         """
         libgis.G_get_default_window(self.byref())
 
@@ -595,9 +637,15 @@ class Region(object):
         ..
         """
         from grass.pygrass.vector.basic import Bbox
-        return Bbox(north=self.north, south=self.south,
-                    east=self.east, west=self.west,
-                    top=self.top, bottom=self.bottom)
+
+        return Bbox(
+            north=self.north,
+            south=self.south,
+            east=self.east,
+            west=self.west,
+            top=self.top,
+            bottom=self.bottom,
+        )
 
     def set_bbox(self, bbox):
         """Set region extent from Bbox
@@ -622,6 +670,7 @@ class Region(object):
         self.east = bbox.east
         self.west = bbox.west
 
+
 if __name__ == "__main__":
 
     import doctest
@@ -630,16 +679,15 @@ if __name__ == "__main__":
 
     utils.create_test_vector_map(test_vector_name)
     run_command("g.region", n=50, s=0, e=60, w=0, res=1)
-    run_command("r.mapcalc", expression="%s = 1" % (test_raster_name),
-                             overwrite=True)
+    run_command("r.mapcalc", expression="%s = 1" % (test_raster_name), overwrite=True)
     run_command("g.region", n=40, s=0, e=40, w=0, res=2)
 
     doctest.testmod()
 
     """Remove the generated vector map, if exist"""
-    mset = utils.get_mapset_vector(test_vector_name, mapset='')
+    mset = utils.get_mapset_vector(test_vector_name, mapset="")
     if mset:
-        run_command("g.remove", flags='f', type='vector', name=test_vector_name)
-    mset = utils.get_mapset_raster(test_raster_name, mapset='')
+        run_command("g.remove", flags="f", type="vector", name=test_vector_name)
+    mset = utils.get_mapset_raster(test_raster_name, mapset="")
     if mset:
-        run_command("g.remove", flags='f', type='raster', name=test_raster_name)
+        run_command("g.remove", flags="f", type="raster", name=test_raster_name)

+ 1 - 2
python/grass/pygrass/gis/testsuite/test_gis.py

@@ -10,7 +10,6 @@ from grass.pygrass.gis.region import Region
 
 
 class RegionTestCase(TestCase):
-
     def test_bounds(self):
         reg1 = Region()
         reg2 = Region()
@@ -21,5 +20,5 @@ class RegionTestCase(TestCase):
         reg2.north = north
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 17 - 11
python/grass/pygrass/gis/testsuite/test_pygrass_gis_doctests.py

@@ -18,12 +18,14 @@ from grass.pygrass.gis import region
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -34,13 +36,16 @@ def load_tests(loader, tests, ignore):
 
     from grass.pygrass import utils
     from grass.script.core import run_command
+
     utils.create_test_vector_map(gis.test_vector_name)
     utils.create_test_vector_map(gis.region.test_vector_name)
     run_command("g.region", n=50, s=0, e=60, w=0, res=1)
-    run_command("r.mapcalc", expression="%s = 1" % (gis.test_raster_name),
-                             overwrite=True)
-    run_command("r.mapcalc", expression="%s = 1" % (gis.region.test_raster_name),
-                             overwrite=True)
+    run_command(
+        "r.mapcalc", expression="%s = 1" % (gis.test_raster_name), overwrite=True
+    )
+    run_command(
+        "r.mapcalc", expression="%s = 1" % (gis.region.test_raster_name), overwrite=True
+    )
     run_command("g.region", n=40, s=0, e=40, w=0, res=2)
 
     # this should be called at some top level
@@ -48,5 +53,6 @@ def load_tests(loader, tests, ignore):
     tests.addTests(doctest.DocTestSuite(region))
     return tests
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 107 - 100
python/grass/pygrass/messages/__init__.py

@@ -23,39 +23,39 @@ from grass.exceptions import FatalError
 
 def message_server(lock, conn):
     """The GRASS message server function designed to be a target for
-       multiprocessing.Process
+    multiprocessing.Process
 
 
-       :param lock: A multiprocessing.Lock
-       :param conn: A multiprocessing.Pipe
+    :param lock: A multiprocessing.Lock
+    :param conn: A multiprocessing.Pipe
 
-       This function will use the G_* message C-functions from grass.lib.gis
-       to provide an interface to the GRASS C-library messaging system.
+    This function will use the G_* message C-functions from grass.lib.gis
+    to provide an interface to the GRASS C-library messaging system.
 
-       The data that is send through the pipe must provide an
-       identifier string to specify which C-function should be called.
+    The data that is send through the pipe must provide an
+    identifier string to specify which C-function should be called.
 
-       The following identifiers are supported:
+    The following identifiers are supported:
 
-       - "INFO"       Prints an info message, see G_message() for details
-       - "IMPORTANT"  Prints an important info message,
-                      see G_important_message() for details
-       - "VERBOSE"    Prints a verbose message if the verbosity level is
-                      set accordingly, see G_verbose_message() for details
-       - "WARNING"    Prints a warning message, see G_warning() for details
-       - "ERROR"      Prints a message with a leading "ERROR: " string,
-                      see G_important_message() for details
-       - "PERCENT"    Prints a percent value based on three integer values: n, d and s
-                      see G_percent() for details
-       - "STOP"       Stops the server function and closes the pipe
-       - "FATAL"      Calls G_fatal_error(), this functions is only for
-                      testing purpose
+    - "INFO"       Prints an info message, see G_message() for details
+    - "IMPORTANT"  Prints an important info message,
+                   see G_important_message() for details
+    - "VERBOSE"    Prints a verbose message if the verbosity level is
+                   set accordingly, see G_verbose_message() for details
+    - "WARNING"    Prints a warning message, see G_warning() for details
+    - "ERROR"      Prints a message with a leading "ERROR: " string,
+                   see G_important_message() for details
+    - "PERCENT"    Prints a percent value based on three integer values: n, d and s
+                   see G_percent() for details
+    - "STOP"       Stops the server function and closes the pipe
+    - "FATAL"      Calls G_fatal_error(), this functions is only for
+                   testing purpose
 
-       The that is end through the pipe must be a list of values:
+    The that is end through the pipe must be a list of values:
 
-       - Messages: ["INFO|VERBOSE|WARNING|ERROR|FATAL", "MESSAGE"]
-       - Debug:    ["DEBUG", level, "MESSAGE"]
-       - Percent:  ["PERCENT", n, d, s]
+    - Messages: ["INFO|VERBOSE|WARNING|ERROR|FATAL", "MESSAGE"]
+    - Debug:    ["DEBUG", level, "MESSAGE"]
+    - Percent:  ["PERCENT", n, d, s]
 
     """
     libgis.G_debug(1, "Start messenger server")
@@ -111,60 +111,60 @@ def message_server(lock, conn):
 class Messenger(object):
     """Fast and exit-safe interface to GRASS C-library message functions
 
-       This class implements a fast and exit-safe interface to the GRASS
-       C-library message functions like: G_message(), G_warning(),
-       G_important_message(), G_verbose_message(), G_percent() and G_debug().
-
-       Note:
-
-       The C-library message functions a called via ctypes in a subprocess
-       using a pipe (multiprocessing.Pipe) to transfer the text messages.
-       Hence, the process that uses the Messenger interface will not be
-       exited, if a G_fatal_error() was invoked in the subprocess.
-       In this case the Messenger object will simply start a new subprocess
-       and restarts the pipeline.
-
-
-       Usage:
-
-       >>> msgr = Messenger()
-       >>> msgr.debug(0, "debug 0")
-       >>> msgr.verbose("verbose message")
-       >>> msgr.message("message")
-       >>> msgr.important("important message")
-       >>> msgr.percent(1, 1, 1)
-       >>> msgr.warning("Ohh")
-       >>> msgr.error("Ohh no")
-
-       >>> msgr = Messenger()
-       >>> msgr.fatal("Ohh no no no!")
-       Traceback (most recent call last):
-         File "__init__.py", line 239, in fatal
-           sys.exit(1)
-       SystemExit: 1
-
-       >>> msgr = Messenger(raise_on_error=True)
-       >>> msgr.fatal("Ohh no no no!")
-       Traceback (most recent call last):
-         File "__init__.py", line 241, in fatal
-           raise FatalError(message)
-       grass.exceptions.FatalError: Ohh no no no!
-
-       >>> msgr = Messenger(raise_on_error=True)
-       >>> msgr.set_raise_on_error(False)
-       >>> msgr.fatal("Ohh no no no!")
-       Traceback (most recent call last):
-         File "__init__.py", line 239, in fatal
-           sys.exit(1)
-       SystemExit: 1
-
-       >>> msgr = Messenger(raise_on_error=False)
-       >>> msgr.set_raise_on_error(True)
-       >>> msgr.fatal("Ohh no no no!")
-       Traceback (most recent call last):
-         File "__init__.py", line 241, in fatal
-           raise FatalError(message)
-       grass.exceptions.FatalError: Ohh no no no!
+    This class implements a fast and exit-safe interface to the GRASS
+    C-library message functions like: G_message(), G_warning(),
+    G_important_message(), G_verbose_message(), G_percent() and G_debug().
+
+    Note:
+
+    The C-library message functions a called via ctypes in a subprocess
+    using a pipe (multiprocessing.Pipe) to transfer the text messages.
+    Hence, the process that uses the Messenger interface will not be
+    exited, if a G_fatal_error() was invoked in the subprocess.
+    In this case the Messenger object will simply start a new subprocess
+    and restarts the pipeline.
+
+
+    Usage:
+
+    >>> msgr = Messenger()
+    >>> msgr.debug(0, "debug 0")
+    >>> msgr.verbose("verbose message")
+    >>> msgr.message("message")
+    >>> msgr.important("important message")
+    >>> msgr.percent(1, 1, 1)
+    >>> msgr.warning("Ohh")
+    >>> msgr.error("Ohh no")
+
+    >>> msgr = Messenger()
+    >>> msgr.fatal("Ohh no no no!")
+    Traceback (most recent call last):
+      File "__init__.py", line 239, in fatal
+        sys.exit(1)
+    SystemExit: 1
+
+    >>> msgr = Messenger(raise_on_error=True)
+    >>> msgr.fatal("Ohh no no no!")
+    Traceback (most recent call last):
+      File "__init__.py", line 241, in fatal
+        raise FatalError(message)
+    grass.exceptions.FatalError: Ohh no no no!
+
+    >>> msgr = Messenger(raise_on_error=True)
+    >>> msgr.set_raise_on_error(False)
+    >>> msgr.fatal("Ohh no no no!")
+    Traceback (most recent call last):
+      File "__init__.py", line 239, in fatal
+        sys.exit(1)
+    SystemExit: 1
+
+    >>> msgr = Messenger(raise_on_error=False)
+    >>> msgr.set_raise_on_error(True)
+    >>> msgr.fatal("Ohh no no no!")
+    Traceback (most recent call last):
+      File "__init__.py", line 241, in fatal
+        raise FatalError(message)
+    grass.exceptions.FatalError: Ohh no no no!
 
     """
 
@@ -176,18 +176,15 @@ class Messenger(object):
         self.start_server()
 
     def start_server(self):
-        """Start the messenger server and open the pipe
-        """
+        """Start the messenger server and open the pipe"""
         self.client_conn, self.server_conn = Pipe()
         self.lock = Lock()
-        self.server = Process(target=message_server, args=(self.lock,
-                                                           self.server_conn))
+        self.server = Process(target=message_server, args=(self.lock, self.server_conn))
         self.server.daemon = True
         self.server.start()
 
     def _check_restart_server(self):
-        """Restart the server if it was terminated
-        """
+        """Restart the server if it was terminated"""
         if self.server.is_alive() is True:
             return
         self.client_conn.close()
@@ -295,10 +292,13 @@ class Messenger(object):
         self.client_conn.send(["PERCENT", n, d, s])
 
     def stop(self):
-        """Stop the messenger server and close the pipe
-        """
+        """Stop the messenger server and close the pipe"""
         if self.server is not None and self.server.is_alive():
-            self.client_conn.send(["STOP", ])
+            self.client_conn.send(
+                [
+                    "STOP",
+                ]
+            )
             self.server.join(5)
             self.server.terminate()
         if self.client_conn is not None:
@@ -307,14 +307,14 @@ class Messenger(object):
     def set_raise_on_error(self, raise_on_error=True):
         """Set the fatal error behavior
 
-           :param raise_on_error: if True a FatalError exception will be
-                                  raised instead of calling sys.exit(1)
-           :type raise_on_error: bool
+        :param raise_on_error: if True a FatalError exception will be
+                               raised instead of calling sys.exit(1)
+        :type raise_on_error: bool
 
-           - If raise_on_error == True, a FatalError exception will be raised
-             if fatal() is called
-           - If raise_on_error == False, sys.exit(1) will be invoked if
-             fatal() is called
+        - If raise_on_error == True, a FatalError exception will be raised
+          if fatal() is called
+        - If raise_on_error == False, sys.exit(1) will be invoked if
+          fatal() is called
 
         """
         self.raise_on_error = raise_on_error
@@ -322,21 +322,27 @@ class Messenger(object):
     def get_raise_on_error(self):
         """Get the fatal error behavior
 
-           :returns: True if a FatalError exception will be raised or False if
-                     sys.exit(1) will be called in case of invoking fatal()
+        :returns: True if a FatalError exception will be raised or False if
+                  sys.exit(1) will be called in case of invoking fatal()
         """
         return self.raise_on_error
 
     def test_fatal_error(self, message):
-        """Force the messenger server to call G_fatal_error()
-        """
+        """Force the messenger server to call G_fatal_error()"""
         import time
+
         self._check_restart_server()
         self.client_conn.send(["FATAL", message])
         time.sleep(1)
 
 
-def get_msgr(_instance=[None, ], *args, **kwargs):
+def get_msgr(
+    _instance=[
+        None,
+    ],
+    *args,
+    **kwargs,
+):
     """Return a Messenger instance.
 
        :returns: the Messenger instance.
@@ -356,4 +362,5 @@ def get_msgr(_instance=[None, ], *args, **kwargs):
 
 if __name__ == "__main__":
     import doctest
+
     doctest.testmod()

+ 9 - 7
python/grass/pygrass/messages/testsuite/test_pygrass_messages_doctests.py

@@ -17,12 +17,14 @@ import grass.pygrass.messages as gmessages
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -35,5 +37,5 @@ def load_tests(loader, tests, ignore):
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 178 - 121
python/grass/pygrass/modules/grid/grid.py

@@ -1,6 +1,13 @@
 # -*- coding: utf-8 -*-
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 import os
 import sys
 import multiprocessing as mltp
@@ -90,8 +97,8 @@ def copy_mapset(mapset, path):
     >>> shutil.rmtree(path)
 
     """
-    per_old = os.path.join(mapset.gisdbase, mapset.location, 'PERMANENT')
-    per_new = os.path.join(path, 'PERMANENT')
+    per_old = os.path.join(mapset.gisdbase, mapset.location, "PERMANENT")
+    per_new = os.path.join(path, "PERMANENT")
     map_old = mapset.path()
     map_new = os.path.join(path, mapset.name)
     if not os.path.isdir(per_new):
@@ -120,10 +127,11 @@ def read_gisrc(gisrc):
     ...                                      genv['GISDBASE']))
     True
     """
-    with open(gisrc, 'r') as gfile:
-        gis = dict([(k.strip(), v.strip())
-                    for k, v in [row.split(':', 1) for row in gfile]])
-    return gis['MAPSET'], gis['LOCATION_NAME'], gis['GISDBASE']
+    with open(gisrc, "r") as gfile:
+        gis = dict(
+            [(k.strip(), v.strip()) for k, v in [row.split(":", 1) for row in gfile]]
+        )
+    return gis["MAPSET"], gis["LOCATION_NAME"], gis["GISDBASE"]
 
 
 def get_mapset(gisrc_src, gisrc_dst):
@@ -171,32 +179,33 @@ def copy_groups(groups, gisrc_src, gisrc_dst, region=None):
     """
 
     def rmloc(r):
-        return r.split('@')[0] if '@' in r else r
+        return r.split("@")[0] if "@" in r else r
 
     env = os.environ.copy()
     # instantiate modules
-    get_grp = Module('i.group', flags='lg', stdout_=sub.PIPE, run_=False)
-    set_grp = Module('i.group')
+    get_grp = Module("i.group", flags="lg", stdout_=sub.PIPE, run_=False)
+    set_grp = Module("i.group")
     get_grp.run_ = True
 
     src = read_gisrc(gisrc_src)
     dst = read_gisrc(gisrc_dst)
     rm = True if src[2] != dst[2] else False
-    all_rasts = [r[0]
-                 for r in findmaps('raster', location=dst[1], gisdbase=dst[2])]
+    all_rasts = [r[0] for r in findmaps("raster", location=dst[1], gisdbase=dst[2])]
     for grp in groups:
         # change gisdbase to src
-        env['GISRC'] = gisrc_src
+        env["GISRC"] = gisrc_src
         get_grp(group=grp, env_=env)
         rasts = [r for r in get_grp.outputs.stdout.split()]
         # change gisdbase to dst
-        env['GISRC'] = gisrc_dst
+        env["GISRC"] = gisrc_dst
         rast2cp = [r for r in rasts if rmloc(r) not in all_rasts]
         if rast2cp:
             copy_rasters(rast2cp, gisrc_src, gisrc_dst, region=region)
-        set_grp(group=grp,
-                input=[rmloc(r) for r in rasts] if rast2cp or rm else rasts,
-                env_=env)
+        set_grp(
+            group=grp,
+            input=[rmloc(r) for r in rasts] if rast2cp or rm else rasts,
+            env_=env,
+        )
 
 
 def set_region(region, gisrc_src, gisrc_dst, env):
@@ -214,13 +223,15 @@ def set_region(region, gisrc_src, gisrc_dst, env):
     :type env:
     :returns: None
     """
-    reg_str = "g.region n=%(north)r s=%(south)r " \
-              "e=%(east)r w=%(west)r " \
-              "nsres=%(nsres)r ewres=%(ewres)r"
+    reg_str = (
+        "g.region n=%(north)r s=%(south)r "
+        "e=%(east)r w=%(west)r "
+        "nsres=%(nsres)r ewres=%(ewres)r"
+    )
     reg_cmd = reg_str % dict(region.items())
-    env['GISRC'] = gisrc_src
+    env["GISRC"] = gisrc_src
     sub.Popen(reg_cmd, shell=True, env=env)
-    env['GISRC'] = gisrc_dst
+    env["GISRC"] = gisrc_dst
     sub.Popen(reg_cmd, shell=True, env=env)
 
 
@@ -245,25 +256,25 @@ def copy_rasters(rasters, gisrc_src, gisrc_dst, region=None):
         set_region(region, gisrc_src, gisrc_dst, env)
 
     path_dst = os.path.join(*read_gisrc(gisrc_dst)[::-1])
-    nam = "copy%d__%s" % (id(gisrc_dst), '%s')
+    nam = "copy%d__%s" % (id(gisrc_dst), "%s")
 
     # instantiate modules
-    mpclc = Module('r.mapcalc')
-    rpck = Module('r.pack')
-    rupck = Module('r.unpack')
-    remove = Module('g.remove')
+    mpclc = Module("r.mapcalc")
+    rpck = Module("r.pack")
+    rupck = Module("r.unpack")
+    remove = Module("g.remove")
 
     for rast in rasters:
-        rast_clean = rast.split('@')[0] if '@' in rast else rast
+        rast_clean = rast.split("@")[0] if "@" in rast else rast
         # change gisdbase to src
-        env['GISRC'] = gisrc_src
+        env["GISRC"] = gisrc_src
         name = nam % rast_clean
         mpclc(expression="%s=%s" % (name, rast), overwrite=True, env_=env)
         file_dst = "%s.pack" % os.path.join(path_dst, name)
         rpck(input=name, output=file_dst, overwrite=True, env_=env)
-        remove(flags='f', type='raster', name=name, env_=env)
+        remove(flags="f", type="raster", name=name, env_=env)
         # change gisdbase to dst
-        env['GISRC'] = gisrc_dst
+        env["GISRC"] = gisrc_dst
         rupck(input=file_dst, output=rast_clean, overwrite=True, env_=env)
         os.remove(file_dst)
 
@@ -282,22 +293,22 @@ def copy_vectors(vectors, gisrc_src, gisrc_dst):
     """
     env = os.environ.copy()
     path_dst = os.path.join(*read_gisrc(gisrc_dst))
-    nam = "copy%d__%s" % (id(gisrc_dst), '%s')
+    nam = "copy%d__%s" % (id(gisrc_dst), "%s")
 
     # instantiate modules
-    vpck = Module('v.pack')
-    vupck = Module('v.unpack')
-    remove = Module('g.remove')
+    vpck = Module("v.pack")
+    vupck = Module("v.unpack")
+    remove = Module("g.remove")
 
     for vect in vectors:
         # change gisdbase to src
-        env['GISRC'] = gisrc_src
+        env["GISRC"] = gisrc_src
         name = nam % vect
         file_dst = "%s.pack" % os.path.join(path_dst, name)
         vpck(input=name, output=file_dst, overwrite=True, env_=env)
-        remove(flags='f', type='vector', name=name, env_=env)
+        remove(flags="f", type="vector", name=name, env_=env)
         # change gisdbase to dst
-        env['GISRC'] = gisrc_dst
+        env["GISRC"] = gisrc_dst
         vupck(input=file_dst, output=vect, overwrite=True, env_=env)
         os.remove(file_dst)
 
@@ -316,19 +327,33 @@ def get_cmd(cmdd):
     >>> get_cmd(slp.get_dict())  # doctest: +ELLIPSIS
     ['r.slope.aspect', 'elevation=ele', 'format=degrees', ..., '--o']
     """
-    cmd = [cmdd['name'], ]
-    cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['inputs']
-                if not isinstance(v, list)))
-    cmd.extend(("%s=%s" % (k, ','.join(vals if isinstance(vals[0], str)
-                                       else [repr(v) for v in vals]))
-                for k, vals in cmdd['inputs']
-                if isinstance(vals, list)))
-    cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['outputs']
-                if not isinstance(v, list)))
-    cmd.extend(("%s=%s" % (k, ','.join([repr(v) for v in vals]))
-                for k, vals in cmdd['outputs'] if isinstance(vals, list)))
-    cmd.extend(("-%s" % (flg) for flg in cmdd['flags'] if len(flg) == 1))
-    cmd.extend(("--%s" % (flg[0]) for flg in cmdd['flags'] if len(flg) > 1))
+    cmd = [
+        cmdd["name"],
+    ]
+    cmd.extend(("%s=%s" % (k, v) for k, v in cmdd["inputs"] if not isinstance(v, list)))
+    cmd.extend(
+        (
+            "%s=%s"
+            % (
+                k,
+                ",".join(vals if isinstance(vals[0], str) else [repr(v) for v in vals]),
+            )
+            for k, vals in cmdd["inputs"]
+            if isinstance(vals, list)
+        )
+    )
+    cmd.extend(
+        ("%s=%s" % (k, v) for k, v in cmdd["outputs"] if not isinstance(v, list))
+    )
+    cmd.extend(
+        (
+            "%s=%s" % (k, ",".join([repr(v) for v in vals]))
+            for k, vals in cmdd["outputs"]
+            if isinstance(vals, list)
+        )
+    )
+    cmd.extend(("-%s" % (flg) for flg in cmdd["flags"] if len(flg) == 1))
+    cmd.extend(("--%s" % (flg[0]) for flg in cmdd["flags"] if len(flg) > 1))
     return cmd
 
 
@@ -356,19 +381,21 @@ def cmd_exe(args):
     bbox, mapnames, gisrc_src, gisrc_dst, cmd, groups = args
     src, dst = get_mapset(gisrc_src, gisrc_dst)
     env = os.environ.copy()
-    env['GISRC'] = gisrc_dst
-    shell = True if sys.platform == 'win32' else False
+    env["GISRC"] = gisrc_dst
+    shell = True if sys.platform == "win32" else False
     if mapnames:
-        inputs = dict(cmd['inputs'])
+        inputs = dict(cmd["inputs"])
         # reset the inputs to
         for key in mapnames:
             inputs[key] = mapnames[key]
-        cmd['inputs'] = inputs.items()
+        cmd["inputs"] = inputs.items()
         # set the region to the tile
-        sub.Popen(['g.region', 'raster=%s' % key], shell=shell, env=env).wait()
+        sub.Popen(["g.region", "raster=%s" % key], shell=shell, env=env).wait()
     else:
         # set the computational region
-        lcmd = ['g.region', ]
+        lcmd = [
+            "g.region",
+        ]
         lcmd.extend(["%s=%s" % (k, v) for k, v in bbox.items()])
         sub.Popen(lcmd, shell=shell, env=env).wait()
     if groups:
@@ -410,11 +437,26 @@ class GridModule(object):
     >>> grd.run()
     """
 
-    def __init__(self, cmd, width=None, height=None, overlap=0, processes=None,
-                 split=False, debug=False, region=None, move=None, log=False,
-                 start_row=0, start_col=0, out_prefix='', mapset_prefix=None,
-                 *args, **kargs):
-        kargs['run_'] = False
+    def __init__(
+        self,
+        cmd,
+        width=None,
+        height=None,
+        overlap=0,
+        processes=None,
+        split=False,
+        debug=False,
+        region=None,
+        move=None,
+        log=False,
+        start_row=0,
+        start_col=0,
+        out_prefix="",
+        mapset_prefix=None,
+        *args,
+        **kargs,
+    ):
+        kargs["run_"] = False
         self.mset = Mapset()
         self.module = Module(cmd, *args, **kargs)
         self.width = width
@@ -427,31 +469,31 @@ class GridModule(object):
         self.out_prefix = out_prefix
         self.log = log
         self.move = move
-        self.gisrc_src = os.environ['GISRC']
+        self.gisrc_src = os.environ["GISRC"]
         self.n_mset, self.gisrc_dst = None, None
         if self.move:
             self.n_mset = copy_mapset(self.mset, self.move)
-            self.gisrc_dst = write_gisrc(self.n_mset.gisdbase,
-                                         self.n_mset.location,
-                                         self.n_mset.name)
-            rasters = [r for r in select(self.module.inputs, 'raster')]
+            self.gisrc_dst = write_gisrc(
+                self.n_mset.gisdbase, self.n_mset.location, self.n_mset.name
+            )
+            rasters = [r for r in select(self.module.inputs, "raster")]
             if rasters:
-                copy_rasters(rasters, self.gisrc_src, self.gisrc_dst,
-                             region=self.region)
-            vectors = [v for v in select(self.module.inputs, 'vector')]
+                copy_rasters(
+                    rasters, self.gisrc_src, self.gisrc_dst, region=self.region
+                )
+            vectors = [v for v in select(self.module.inputs, "vector")]
             if vectors:
                 copy_vectors(vectors, self.gisrc_src, self.gisrc_dst)
-            groups = [g for g in select(self.module.inputs, 'group')]
+            groups = [g for g in select(self.module.inputs, "group")]
             if groups:
-                copy_groups(groups, self.gisrc_src, self.gisrc_dst,
-                            region=self.region)
-        self.bboxes = split_region_tiles(region=region,
-                                         width=width, height=height,
-                                         overlap=overlap)
+                copy_groups(groups, self.gisrc_src, self.gisrc_dst, region=self.region)
+        self.bboxes = split_region_tiles(
+            region=region, width=width, height=height, overlap=overlap
+        )
         if mapset_prefix:
             self.msetstr = mapset_prefix + "_%03d_%03d"
         else:
-            self.msetstr = cmd.replace('.', '') + "_%03d_%03d"
+            self.msetstr = cmd.replace(".", "") + "_%03d_%03d"
         self.inlist = None
         if split:
             self.split()
@@ -473,7 +515,7 @@ class GridModule(object):
                 self.n_mset.current()
             location = Location()
 
-        mapsets = location.mapsets(self.msetstr.split('_')[0] + '_*')
+        mapsets = location.mapsets(self.msetstr.split("_")[0] + "_*")
         for mset in mapsets:
             Mapset(mset).delete()
         if self.n_mset and self.n_mset.is_current():
@@ -481,15 +523,18 @@ class GridModule(object):
 
     def split(self):
         """Split all the raster inputs using r.tile"""
-        rtile = Module('r.tile')
+        rtile = Module("r.tile")
         inlist = {}
-        for inm in select(self.module.inputs, 'raster'):
-            rtile(input=inm.value, output=inm.value,
-                  width=self.width, height=self.height,
-                  overlap=self.overlap)
-            patt = '%s-*' % inm.value
-            inlist[inm.value] = sorted(self.mset.glist(type='raster',
-                                                       pattern=patt))
+        for inm in select(self.module.inputs, "raster"):
+            rtile(
+                input=inm.value,
+                output=inm.value,
+                width=self.width,
+                height=self.height,
+                overlap=self.overlap,
+            )
+            patt = "%s-*" % inm.value
+            inlist[inm.value] = sorted(self.mset.glist(type="raster", pattern=patt))
         self.inlist = inlist
 
     def get_works(self):
@@ -501,7 +546,7 @@ class GridModule(object):
         else:
             ldst, gdst = self.mset.location, self.mset.gisdbase
         cmd = self.module.get_dict()
-        groups = [g for g in select(self.module.inputs, 'group')]
+        groups = [g for g in select(self.module.inputs, "group")]
         for row, box_row in enumerate(self.bboxes):
             for col, box in enumerate(box_row):
                 inms = None
@@ -510,29 +555,34 @@ class GridModule(object):
                     cols = len(box_row)
                     for key in self.inlist:
                         indx = row * cols + col
-                        inms[key] = "%s@%s" % (self.inlist[key][indx],
-                                               self.mset.name)
+                        inms[key] = "%s@%s" % (self.inlist[key][indx], self.mset.name)
                 # set the computational region, prepare the region parameters
                 bbox = dict([(k[0], str(v)) for k, v in box.items()[:-2]])
-                bbox['nsres'] = '%f' % reg.nsres
-                bbox['ewres'] = '%f' % reg.ewres
-                new_mset = self.msetstr % (self.start_row + row,
-                                           self.start_col + col),
-                works.append((bbox, inms,
-                              self.gisrc_src,
-                              write_gisrc(gdst, ldst, new_mset),
-                              cmd, groups))
+                bbox["nsres"] = "%f" % reg.nsres
+                bbox["ewres"] = "%f" % reg.ewres
+                new_mset = (
+                    self.msetstr % (self.start_row + row, self.start_col + col),
+                )
+                works.append(
+                    (
+                        bbox,
+                        inms,
+                        self.gisrc_src,
+                        write_gisrc(gdst, ldst, new_mset),
+                        cmd,
+                        groups,
+                    )
+                )
         return works
 
     def define_mapset_inputs(self):
-        """Add the mapset information to the input maps
-        """
+        """Add the mapset information to the input maps"""
         for inmap in self.module.inputs:
             inm = self.module.inputs[inmap]
-            if inm.type in ('raster', 'vector') and inm.value:
-                if '@' not in inm.value:
+            if inm.type in ("raster", "vector") and inm.value:
+                if "@" not in inm.value:
                     mset = get_mapset_raster(inm.value)
-                    inm.value = inm.value + '@%s' % mset
+                    inm.value = inm.value + "@%s" % mset
 
     def run(self, patch=True, clean=True):
         """Run the GRASS command
@@ -559,14 +609,15 @@ class GridModule(object):
 
         if patch:
             if self.move:
-                os.environ['GISRC'] = self.gisrc_dst
+                os.environ["GISRC"] = self.gisrc_dst
                 self.n_mset.current()
                 self.patch()
-                os.environ['GISRC'] = self.gisrc_src
+                os.environ["GISRC"] = self.gisrc_src
                 self.mset.current()
                 # copy the outputs from dst => src
-                routputs = [self.out_prefix + o
-                            for o in select(self.module.outputs, 'raster')]
+                routputs = [
+                    self.out_prefix + o for o in select(self.module.outputs, "raster")
+                ]
                 copy_rasters(routputs, self.gisrc_dst, self.gisrc_src)
             else:
                 self.patch()
@@ -574,16 +625,16 @@ class GridModule(object):
         if self.log:
             # record in the temp directory
             from grass.lib.gis import G_tempfile
+
             tmp, dummy = os.path.split(G_tempfile())
             tmpdir = os.path.join(tmp, self.module.name)
             for k in self.module.outputs:
                 par = self.module.outputs[k]
-                if par.typedesc == 'raster' and par.value:
+                if par.typedesc == "raster" and par.value:
                     dirpath = os.path.join(tmpdir, par.name)
                     if not os.path.isdir(dirpath):
                         os.makedirs(dirpath)
-                    fil = open(os.path.join(dirpath,
-                                            self.out_prefix + par.value), 'w+')
+                    fil = open(os.path.join(dirpath, self.out_prefix + par.value), "w+")
                     fil.close()
 
         if clean:
@@ -595,7 +646,7 @@ class GridModule(object):
                 # rm temporary gis_rc
                 os.remove(self.gisrc_dst)
                 self.gisrc_dst = None
-                sht.rmtree(os.path.join(self.move, 'PERMANENT'))
+                sht.rmtree(os.path.join(self.move, "PERMANENT"))
                 sht.rmtree(os.path.join(self.move, self.mset.name))
 
     def patch(self):
@@ -607,22 +658,28 @@ class GridModule(object):
         noutputs = 0
         for otmap in self.module.outputs:
             otm = self.module.outputs[otmap]
-            if otm.typedesc == 'raster' and otm.value:
-                rpatch_map(otm.value,
-                           self.mset.name, self.msetstr, bboxes,
-                           self.module.flags.overwrite,
-                           self.start_row, self.start_col, self.out_prefix)
+            if otm.typedesc == "raster" and otm.value:
+                rpatch_map(
+                    otm.value,
+                    self.mset.name,
+                    self.msetstr,
+                    bboxes,
+                    self.module.flags.overwrite,
+                    self.start_row,
+                    self.start_col,
+                    self.out_prefix,
+                )
                 noutputs += 1
         if noutputs < 1:
-            msg = 'No raster output option defined for <{}>'.format(self.module.name)
-            if self.module.name == 'r.mapcalc':
-                msg += '. Use <{}.simple> instead'.format(self.module.name)
+            msg = "No raster output option defined for <{}>".format(self.module.name)
+            if self.module.name == "r.mapcalc":
+                msg += ". Use <{}.simple> instead".format(self.module.name)
             raise RuntimeError(msg)
 
     def rm_tiles(self):
         """Remove all the tiles."""
         # if split, remove tiles
         if self.inlist:
-            grm = Module('g.remove')
+            grm = Module("g.remove")
             for key in self.inlist:
-                grm(flags='f', type='raster', name=self.inlist[key])
+                grm(flags="f", type="raster", name=self.inlist[key])

+ 28 - 11
python/grass/pygrass/modules/grid/patch.py

@@ -4,8 +4,15 @@ Created on Tue Apr  2 18:57:42 2013
 
 @author: pietro
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 from grass.pygrass.gis.region import Region
 from grass.pygrass.raster import RasterRow
 from grass.pygrass.utils import coor2pixel
@@ -51,8 +58,16 @@ def rpatch_row(rast, rasts, bboxes):
         rast.put_row(rbuff)
 
 
-def rpatch_map(raster, mapset, mset_str, bbox_list, overwrite=False,
-               start_row=0, start_col=0, prefix=''):
+def rpatch_map(
+    raster,
+    mapset,
+    mset_str,
+    bbox_list,
+    overwrite=False,
+    start_row=0,
+    start_col=0,
+    prefix="",
+):
     # TODO is prefix useful??
     """Patch raster using a bounding box list to trim the raster.
 
@@ -76,22 +91,24 @@ def rpatch_map(raster, mapset, mset_str, bbox_list, overwrite=False,
     # Instantiate the RasterRow input objects
     rast = RasterRow(prefix + raster, mapset)
     rtype = RasterRow(name=raster, mapset=mset_str % (0, 0))
-    rtype.open('r')
-    rast.open('w', mtype=rtype.mtype, overwrite=overwrite)
+    rtype.open("r")
+    rast.open("w", mtype=rtype.mtype, overwrite=overwrite)
     rtype.close()
     rasts = []
     for row, rbbox in enumerate(bbox_list):
         rrasts = []
         for col in range(len(rbbox)):
-            rrasts.append(RasterRow(name=raster,
-                                    mapset=mset_str % (start_row + row,
-                                                       start_col + col)))
-            rrasts[-1].open('r')
+            rrasts.append(
+                RasterRow(
+                    name=raster, mapset=mset_str % (start_row + row, start_col + col)
+                )
+            )
+            rrasts[-1].open("r")
         rasts.append(rrasts)
         rpatch_row(rast, rrasts, rbbox)
 
         for rst in rrasts:
             rst.close()
-            del(rst)
+            del rst
 
     rast.close()

+ 19 - 10
python/grass/pygrass/modules/grid/split.py

@@ -4,8 +4,15 @@ Created on Tue Apr  2 19:00:15 2013
 
 @author: pietro
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 from grass.pygrass.gis.region import Region
 from grass.pygrass.vector.basic import Bbox
 
@@ -30,10 +37,12 @@ def get_bbox(reg, row, col, width, height, overlap):
     south = reg.north - ((row + 1) * height + overlap) * reg.nsres
     east = reg.west + ((col + 1) * width + overlap) * reg.ewres
     west = reg.west + (col * width - overlap) * reg.ewres
-    return Bbox(north=north if north <= reg.north else reg.north,
-                south=south if south >= reg.south else reg.south,
-                east=east if east <= reg.east else reg.east,
-                west=west if west >= reg.west else reg.west,)
+    return Bbox(
+        north=north if north <= reg.north else reg.north,
+        south=south if south >= reg.south else reg.south,
+        east=east if east <= reg.east else reg.east,
+        west=west if west >= reg.west else reg.west,
+    )
 
 
 def split_region_tiles(region=None, width=100, height=100, overlap=0):
@@ -70,11 +79,11 @@ def split_region_tiles(region=None, width=100, height=100, overlap=0):
     ncols = (reg.cols + width - 1) // width
     nrows = (reg.rows + height - 1) // height
     box_list = []
-    #print reg
+    # print reg
     for row in range(nrows):
         row_list = []
         for col in range(ncols):
-            #print 'c', c, 'r', r
+            # print 'c', c, 'r', r
             row_list.append(get_bbox(reg, row, col, width, height, overlap))
         box_list.append(row_list)
     return box_list
@@ -96,11 +105,11 @@ def get_overlap_region_tiles(region=None, width=100, height=100, overlap=0):
     ncols = (reg.cols + width - 1) // width
     nrows = (reg.rows + height - 1) // height
     box_list = []
-    #print reg
+    # print reg
     for row in range(nrows):
         row_list = []
         for col in range(ncols):
-            #print 'c', c, 'r', r
+            # print 'c', c, 'r', r
             row_list.append(get_bbox(reg, row, col, width, height, -overlap))
         box_list.append(row_list)
     return box_list

+ 10 - 8
python/grass/pygrass/modules/grid/testsuite/test_pygrass_modules_grid_doctests.py

@@ -17,12 +17,14 @@ import grass.pygrass.modules as gmodules
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -31,9 +33,9 @@ def load_tests(loader, tests, ignore):
     # for now it is the only place where it works
     grass.gunittest.utils.do_doctest_gettext_workaround()
 
-    #tests.addTests(doctest.DocTestSuite(gmodules.shortcuts))
+    # tests.addTests(doctest.DocTestSuite(gmodules.shortcuts))
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 5 - 1
python/grass/pygrass/modules/interface/__init__.py

@@ -10,4 +10,8 @@ from grass.pygrass.modules.interface import module
 from grass.pygrass.modules.interface import typedict
 from grass.pygrass.modules.interface import read
 
-from grass.pygrass.modules.interface.module import Module, MultiModule, ParallelModuleQueue
+from grass.pygrass.modules.interface.module import (
+    Module,
+    MultiModule,
+    ParallelModuleQueue,
+)

+ 2 - 0
python/grass/pygrass/modules/interface/docstring.py

@@ -20,8 +20,10 @@ def docstring_property(class_doc):
     >>> a.__doc__
     'My value of x is 10.'
     """
+
     def wrapper(fget):
         return DocstringProperty(class_doc, fget)
+
     return wrapper
 
 

+ 12 - 8
python/grass/pygrass/modules/interface/env.py

@@ -11,18 +11,22 @@ import sys
 
 def get_env():
     """Parse the GISRC file and return the GRASS variales"""
-    gisrc = os.environ.get('GISRC')
+    gisrc = os.environ.get("GISRC")
     if gisrc is None:
-        raise RuntimeError('You are not in a GRASS session, GISRC not found.')
-    with open(gisrc, mode='r') as grc:
-        env = dict([(k.strip(), v.strip())
-                    for k, v in [row.split(':',1) for row in grc if row]])
+        raise RuntimeError("You are not in a GRASS session, GISRC not found.")
+    with open(gisrc, mode="r") as grc:
+        env = dict(
+            [
+                (k.strip(), v.strip())
+                for k, v in [row.split(":", 1) for row in grc if row]
+            ]
+        )
     return env
 
 
 def get_debug_level():
     """Return the debug level"""
-    debug = get_env().get('DEBUG')
+    debug = get_env().get("DEBUG")
     return int(debug) if debug else 0
 
 
@@ -32,5 +36,5 @@ def G_debug(level, *msg):
     debug_level = get_debug_level()
     if debug_level >= level:
         dfile = os.environ.get("GRASS_DEBUG_FILE")
-        fd = sys.stderr if dfile is None else open(dfile, mode='a')
-        print("D%d/%d: " % (level, debug_level), *msg, end='\n', file=fd)
+        fd = sys.stderr if dfile is None else open(dfile, mode="a")
+        print("D%d/%d: " % (level, debug_level), *msg, end="\n", file=fd)

+ 28 - 20
python/grass/pygrass/modules/interface/flag.py

@@ -1,6 +1,13 @@
 # -*- coding: utf-8 -*-
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 from grass.pygrass.modules.interface.docstring import docstring_property
 from grass.pygrass.modules.interface import read
 
@@ -28,13 +35,14 @@ class Flag(object):
     def __init__(self, xflag=None, diz=None):
         self.value = False
         diz = read.element2dict(xflag) if xflag is not None else diz
-        self.name = diz['name']
-        self.special = True if self.name in (
-            'verbose', 'overwrite', 'quiet', 'run') else False
-        self.description = diz.get('description', None)
-        self.default = diz.get('default', None)
-        self.guisection = diz.get('guisection', None)
-        self.suppress_required = True if 'suppress_required' in diz else False
+        self.name = diz["name"]
+        self.special = (
+            True if self.name in ("verbose", "overwrite", "quiet", "run") else False
+        )
+        self.description = diz.get("description", None)
+        self.default = diz.get("default", None)
+        self.guisection = diz.get("guisection", None)
+        self.suppress_required = True if "suppress_required" in diz else False
 
     def get_bash(self):
         """Return the BASH representation of a flag.
@@ -55,11 +63,11 @@ class Flag(object):
         """
         if self.value:
             if self.special:
-                return '--%s' % self.name[0]
+                return "--%s" % self.name[0]
             else:
-                return '-%s' % self.name
+                return "-%s" % self.name
         else:
-            return ''
+            return ""
 
     def get_python(self):
         """Return the python representation of a flag.
@@ -79,8 +87,8 @@ class Flag(object):
         'overwrite=True'
         """
         if self.value:
-            return '%s=True' % self.name if self.special else self.name
-        return ''
+            return "%s=True" % self.name if self.special else self.name
+        return ""
 
     def __str__(self):
         """Return the BASH representation of the flag."""
@@ -116,9 +124,9 @@ class Flag(object):
             None
 
         """
-        return read.DOC['flag'].format(name=self.name,
-                                       default=repr(self.default),
-                                       description=self.description,
-                                       supress=('suppress required'
-                                                if self.suppress_required
-                                                else ''))
+        return read.DOC["flag"].format(
+            name=self.name,
+            default=repr(self.default),
+            description=self.description,
+            supress=("suppress required" if self.suppress_required else ""),
+        )

+ 128 - 78
python/grass/pygrass/modules/interface/module.py

@@ -1,6 +1,13 @@
 # -*- coding: utf-8 -*-
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 import sys
 from multiprocessing import cpu_count, Process, Queue
 import time
@@ -20,6 +27,7 @@ if sys.version_info[0] == 2:
     from itertools import izip_longest as zip_longest
 else:
     from itertools import zip_longest
+
     unicode = str
 
 
@@ -301,7 +309,11 @@ class ParallelModuleQueue(object):
         for proc in self._list:
             if proc:
                 if isinstance(proc, Module):
-                    self._finished_modules.extend([proc.wait(),])
+                    self._finished_modules.extend(
+                        [
+                            proc.wait(),
+                        ]
+                    )
                 else:
                     self._finished_modules.extend(proc.wait())
 
@@ -309,7 +321,6 @@ class ParallelModuleQueue(object):
         self._proc_count = 0
 
 
-
 class Module(object):
     """This class is design to wrap/run/interact with the GRASS modules.
 
@@ -538,7 +549,7 @@ class Module(object):
         tree = fromstring(self.xml)
 
         for e in tree:
-            if e.tag not in ('parameter', 'flag'):
+            if e.tag not in ("parameter", "flag"):
                 self.__setattr__(e.tag, GETFROMTAG[e.tag](e))
 
         #
@@ -577,18 +588,24 @@ class Module(object):
         self.stdin = None
         self.stdout_ = None
         self.stderr_ = None
-        diz = {'name': 'stdin', 'required': False,
-               'multiple': False, 'type': 'all',
-               'value': None}
-        self.inputs['stdin'] = Parameter(diz=diz)
-        diz['name'] = 'stdout'
-        self.outputs['stdout'] = Parameter(diz=diz)
-        diz['name'] = 'stderr'
-        self.outputs['stderr'] = Parameter(diz=diz)
+        diz = {
+            "name": "stdin",
+            "required": False,
+            "multiple": False,
+            "type": "all",
+            "value": None,
+        }
+        self.inputs["stdin"] = Parameter(diz=diz)
+        diz["name"] = "stdout"
+        self.outputs["stdout"] = Parameter(diz=diz)
+        diz["name"] = "stderr"
+        self.outputs["stderr"] = Parameter(diz=diz)
         self.popen = None
         self.time = None
-        self.start_time = None            # This variable will be set in the run() function
-        self._finished = False            # This variable is set True if wait() was successfully called
+        self.start_time = None  # This variable will be set in the run() function
+        self._finished = (
+            False  # This variable is set True if wait() was successfully called
+        )
 
         if args or kargs:
             self.__call__(*args, **kargs)
@@ -607,18 +624,18 @@ class Module(object):
         #
         # check for extra kargs, set attribute and remove from dictionary
         #
-        if 'flags' in kargs:
-            for flg in kargs['flags']:
+        if "flags" in kargs:
+            for flg in kargs["flags"]:
                 self.flags[flg].value = True
-            del(kargs['flags'])
+            del kargs["flags"]
 
         # set attributs
-        for key in ('run_', 'env_', 'finish_', 'stdout_', 'stderr_', 'check_'):
+        for key in ("run_", "env_", "finish_", "stdout_", "stderr_", "check_"):
             if key in kargs:
                 setattr(self, key, kargs.pop(key))
 
         # set inputs
-        for key in ('stdin_', ):
+        for key in ("stdin_",):
             if key in kargs:
                 self.inputs[key[:-1]].value = kargs.pop(key)
 
@@ -628,7 +645,7 @@ class Module(object):
         for param, arg in zip(self.params_list, args):
             param.value = arg
         for key, val in kargs.items():
-            key = key.strip('_')
+            key = key.strip("_")
             if key in self.inputs:
                 self.inputs[key].value = val
             elif key in self.outputs:
@@ -638,7 +655,7 @@ class Module(object):
                 # verbose and quiet) work like parameters
                 self.flags[key].value = val
             else:
-                raise ParameterError('%s is not a valid parameter.' % key)
+                raise ParameterError("%s is not a valid parameter." % key)
 
         #
         # check if execute
@@ -654,24 +671,32 @@ class Module(object):
 
     def get_bash(self):
         """Return a BASH representation of the Module."""
-        return ' '.join(self.make_cmd())
+        return " ".join(self.make_cmd())
 
     def get_python(self):
         """Return a Python representation of the Module."""
-        prefix = self.name.split('.')[0]
-        name = '_'.join(self.name.split('.')[1:])
-        params = ', '.join([par.get_python() for par in self.params_list
-                           if par.get_python() != ''])
-        flags = ''.join([flg.get_python()
-                         for flg in self.flags.values()
-                         if not flg.special and flg.get_python() != ''])
-        special = ', '.join([flg.get_python()
-                             for flg in self.flags.values()
-                             if flg.special and flg.get_python() != ''])
+        prefix = self.name.split(".")[0]
+        name = "_".join(self.name.split(".")[1:])
+        params = ", ".join(
+            [par.get_python() for par in self.params_list if par.get_python() != ""]
+        )
+        flags = "".join(
+            [
+                flg.get_python()
+                for flg in self.flags.values()
+                if not flg.special and flg.get_python() != ""
+            ]
+        )
+        special = ", ".join(
+            [
+                flg.get_python()
+                for flg in self.flags.values()
+                if flg.special and flg.get_python() != ""
+            ]
+        )
         #     pre name par flg special
         if flags and special:
-            return "%s.%s(%s, flags=%r, %s)" % (prefix, name, params,
-                                                flags, special)
+            return "%s.%s(%s, flags=%r, %s)" % (prefix, name, params, flags, special)
         elif flags:
             return "%s.%s(%s, flags=%r)" % (prefix, name, params, flags)
         elif special:
@@ -681,26 +706,35 @@ class Module(object):
 
     def __str__(self):
         """Return the command string that can be executed in a shell"""
-        return ' '.join(self.make_cmd())
+        return " ".join(self.make_cmd())
 
     def __repr__(self):
         return "Module(%r)" % self.name
 
     @docstring_property(__doc__)
     def __doc__(self):
-        """{cmd_name}({cmd_params})
-        """
-        head = DOC['head'].format(cmd_name=self.name,
-            cmd_params=('\n' +  # go to a new line
-             # give space under the function name
-             (' ' * (len(self.name) + 1))).join([', '.join(
-                 # transform each parameter in string
-                 [str(param) for param in line if param is not None])
-                 # make a list of parameters with only 3 param per line
-                 for line in zip_longest(*[iter(self.params_list)] * 3)]),)
-        params = '\n'.join([par.__doc__ for par in self.params_list])
+        """{cmd_name}({cmd_params})"""
+        head = DOC["head"].format(
+            cmd_name=self.name,
+            cmd_params=(
+                "\n"
+                +  # go to a new line
+                # give space under the function name
+                (" " * (len(self.name) + 1))
+            ).join(
+                [
+                    ", ".join(
+                        # transform each parameter in string
+                        [str(param) for param in line if param is not None]
+                    )
+                    # make a list of parameters with only 3 param per line
+                    for line in zip_longest(*[iter(self.params_list)] * 3)
+                ]
+            ),
+        )
+        params = "\n".join([par.__doc__ for par in self.params_list])
         flags = self.flags.__doc__
-        return '\n'.join([head, params, DOC['flag_head'], flags, DOC['foot']])
+        return "\n".join([head, params, DOC["flag_head"], flags, DOC["foot"]])
 
     def check(self):
         """Check the correctness of the provide parameters"""
@@ -710,8 +744,9 @@ class Module(object):
                 required = False
         if required:
             for k in self.required:
-                if ((k in self.inputs and self.inputs[k].value is None) or
-                        (k in self.outputs and self.outputs[k].value is None)):
+                if (k in self.inputs and self.inputs[k].value is None) or (
+                    k in self.outputs and self.outputs[k].value is None
+                ):
                     msg = "Required parameter <%s> not set."
                     raise ParameterError(msg % k)
 
@@ -720,12 +755,10 @@ class Module(object):
         inputs, outputs and flags
         """
         dic = {}
-        dic['name'] = self.name
-        dic['inputs'] = [(k, v.value) for k, v in self.inputs.items()
-                         if v.value]
-        dic['outputs'] = [(k, v.value) for k, v in self.outputs.items()
-                          if v.value]
-        dic['flags'] = [flg for flg in self.flags if self.flags[flg].value]
+        dic["name"] = self.name
+        dic["inputs"] = [(k, v.value) for k, v in self.inputs.items() if v.value]
+        dic["outputs"] = [(k, v.value) for k, v in self.outputs.items() if v.value]
+        dic["flags"] = [flg for flg in self.flags if self.flags[flg].value]
         return dic
 
     def make_cmd(self):
@@ -733,13 +766,23 @@ class Module(object):
 
         :returns: the command string
         """
-        skip = ['stdin', 'stdout', 'stderr']
-        args = [self.name, ]
+        skip = ["stdin", "stdout", "stderr"]
+        args = [
+            self.name,
+        ]
         for key in self.inputs:
-            if key not in skip and self.inputs[key].value is not None and self.inputs[key].value != '':
+            if (
+                key not in skip
+                and self.inputs[key].value is not None
+                and self.inputs[key].value != ""
+            ):
                 args.append(self.inputs[key].get_bash())
         for key in self.outputs:
-            if key not in skip and self.outputs[key].value is not None and self.outputs[key].value != '':
+            if (
+                key not in skip
+                and self.outputs[key].value is not None
+                and self.outputs[key].value != ""
+            ):
                 args.append(self.outputs[key].get_bash())
         for flg in self.flags:
             if self.flags[flg].value:
@@ -757,17 +800,19 @@ class Module(object):
         """
         G_debug(1, self.get_bash())
         self._finished = False
-        if self.inputs['stdin'].value:
-            self.stdin = self.inputs['stdin'].value
+        if self.inputs["stdin"].value:
+            self.stdin = self.inputs["stdin"].value
             self.stdin_ = PIPE
 
         cmd = self.make_cmd()
         self.start_time = time.time()
-        self.popen = Popen(cmd,
-                           stdin=self.stdin_,
-                           stdout=self.stdout_,
-                           stderr=self.stderr_,
-                           env=self.env_)
+        self.popen = Popen(
+            cmd,
+            stdin=self.stdin_,
+            stdout=self.stdout_,
+            stderr=self.stderr_,
+            env=self.env_,
+        )
 
         if self.finish_ is True:
             self.wait()
@@ -784,16 +829,19 @@ class Module(object):
             if self.stdin:
                 self.stdin = encode(self.stdin)
             stdout, stderr = self.popen.communicate(input=self.stdin)
-            self.outputs['stdout'].value = decode(stdout) if stdout else ''
-            self.outputs['stderr'].value = decode(stderr) if stderr else ''
+            self.outputs["stdout"].value = decode(stdout) if stdout else ""
+            self.outputs["stderr"].value = decode(stderr) if stderr else ""
             self.time = time.time() - self.start_time
 
             self._finished = True
 
             if self.popen.poll():
-                raise CalledModuleError(returncode=self.popen.returncode,
-                                        code=self.get_bash(),
-                                        module=self.name, errors=stderr)
+                raise CalledModuleError(
+                    returncode=self.popen.returncode,
+                    code=self.get_bash(),
+                    module=self.name,
+                    errors=stderr,
+                )
 
         return self
 
@@ -913,13 +961,13 @@ class MultiModule(object):
         """
         self.module_list = module_list
         self.set_temp_region = set_temp_region
-        self.finish_ = sync      # We use the same variable name a Module
+        self.finish_ = sync  # We use the same variable name a Module
         self.p = None
         self.q = Queue()
 
     def __str__(self):
         """Return the command string that can be executed in a shell"""
-        return ' ; '.join(str(string) for string in self.module_list)
+        return " ; ".join(str(string) for string in self.module_list)
 
     def get_modules(self):
         """Return the list of modules that have been run in synchronous mode
@@ -950,11 +998,11 @@ class MultiModule(object):
             return None
         else:
             if self.set_temp_region is True:
-                self.p = Process(target=run_modules_in_temp_region,
-                                 args=[self.module_list, self.q])
+                self.p = Process(
+                    target=run_modules_in_temp_region, args=[self.module_list, self.q]
+                )
             else:
-                self.p = Process(target=run_modules,
-                                 args=[self.module_list, self.q])
+                self.p = Process(target=run_modules, args=[self.module_list, self.q])
             self.p.start()
 
             return self.p
@@ -1011,8 +1059,10 @@ def run_modules(module_list, q):
     finally:
         q.put(module_list)
 
+
 ###############################################################################
 
 if __name__ == "__main__":
     import doctest
+
     doctest.testmod()

+ 106 - 67
python/grass/pygrass/modules/interface/parameter.py

@@ -4,8 +4,15 @@ Created on Tue Apr  2 18:31:47 2013
 
 @author: pietro
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 import re
 
 from grass.pygrass.modules.interface.docstring import docstring_property
@@ -16,9 +23,9 @@ def _check_value(param, value):
     """Function to check the correctness of a value and
     return the checked value and the original.
     """
-    must_val = 'The Parameter <%s>, must be one of the following values: %r'
-    req = 'The Parameter <%s>, require: %s, get: %s instead: %r\n%s'
-    string = (type(b''), type(u''))
+    must_val = "The Parameter <%s>, must be one of the following values: %r"
+    req = "The Parameter <%s>, require: %s, get: %s instead: %r\n%s"
+    string = (type(b""), type(""))
 
     def raiseexcpet(exc, param, ptype, value):
         """Function to modifa the error message"""
@@ -37,8 +44,9 @@ def _check_value(param, value):
             if type(value) in (int, float):
                 value = str(value)
             if type(value) not in string:
-                msg = ("The Parameter <%s> require a string,"
-                       " %s instead is provided: %r")
+                msg = (
+                    "The Parameter <%s> require a string," " %s instead is provided: %r"
+                )
                 raise ValueError(msg % (param.name, type(value), value))
         return value
 
@@ -49,8 +57,16 @@ def _check_value(param, value):
     # find errors with multiple parmeters
     if isinstance(value, (list, tuple)):
         if param.keydescvalues:
-            return (([value, ], value) if isinstance(value, tuple)
-                    else (value, value))
+            return (
+                (
+                    [
+                        value,
+                    ],
+                    value,
+                )
+                if isinstance(value, tuple)
+                else (value, value)
+            )
         if param.multiple:
             # everything looks fine, so check each value
             try:
@@ -58,14 +74,14 @@ def _check_value(param, value):
             except Exception as exc:
                 raiseexcpet(exc, param, param.type, value)
         else:
-            msg = 'The Parameter <%s> does not accept multiple inputs'
+            msg = "The Parameter <%s> does not accept multiple inputs"
             raise TypeError(msg % param.name)
 
     if param.keydescvalues:
-        msg = 'The Parameter <%s> require multiple inputs in the form: %s'
+        msg = "The Parameter <%s> require multiple inputs in the form: %s"
         raise TypeError(msg % (param.name, param.keydescvalues))
 
-    if param.typedesc == 'all':
+    if param.typedesc == "all":
         return value, value
 
     # check string before trying to convert value to the correct type
@@ -77,20 +93,30 @@ def _check_value(param, value):
         raiseexcpet(exc, param, type(value), value)
 
     # check values
-    if hasattr(param, 'values'):
+    if hasattr(param, "values"):
         if param.type in (float, int):
             # check for value in range
-            if ((param.min is not None and newvalue < param.min) or
-                    (param.max is not None and newvalue > param.max)):
-                err_str = ('The Parameter <%s>, must be between: '
-                           '%g<=value<=%g, %r is outside.')
-                raise ValueError(err_str % (param.name, param.min,
-                                            param.max, newvalue))
+            if (param.min is not None and newvalue < param.min) or (
+                param.max is not None and newvalue > param.max
+            ):
+                err_str = (
+                    "The Parameter <%s>, must be between: "
+                    "%g<=value<=%g, %r is outside."
+                )
+                raise ValueError(err_str % (param.name, param.min, param.max, newvalue))
         # check if value is in the list of valid values
         if param.values is not None and newvalue not in param.values:
             raise ValueError(must_val % (param.name, param.values))
-    return (([newvalue, ] if (param.multiple or param.keydescvalues)
-             else newvalue), value)
+    return (
+        (
+            [
+                newvalue,
+            ]
+            if (param.multiple or param.keydescvalues)
+            else newvalue
+        ),
+        value,
+    )
 
 
 # TODO add documentation
@@ -119,62 +145,62 @@ class Parameter(object):
         self.max = None
         diz = element2dict(xparameter) if xparameter is not None else diz
         if diz is None:
-            raise TypeError('Xparameter or diz are required')
-        self.name = diz['name']
-        self.required = True if diz['required'] == 'yes' else False
-        self.multiple = True if diz['multiple'] == 'yes' else False
+            raise TypeError("Xparameter or diz are required")
+        self.name = diz["name"]
+        self.required = True if diz["required"] == "yes" else False
+        self.multiple = True if diz["multiple"] == "yes" else False
         # check the type
-        if diz['type'] in GETTYPE:
-            self.type = GETTYPE[diz['type']]
-            self.typedesc = diz['type']
+        if diz["type"] in GETTYPE:
+            self.type = GETTYPE[diz["type"]]
+            self.typedesc = diz["type"]
         else:
-            raise TypeError('New type: %s, ignored' % diz['type'])
+            raise TypeError("New type: %s, ignored" % diz["type"])
 
-        self.description = diz.get('description', None)
-        self.keydesc, self.keydescvalues = diz.get('keydesc', (None, None))
+        self.description = diz.get("description", None)
+        self.keydesc, self.keydescvalues = diz.get("keydesc", (None, None))
 
         #
         # values
         #
-        if 'values' in diz:
+        if "values" in diz:
             try:
                 # Check for integer ranges: "3-30" or float ranges: "0.0-1.0"
-                isrange = re.match("(?P<min>-*\d+.*\d*)*-(?P<max>\d+.*\d*)*",
-                                   diz['values'][0])
+                isrange = re.match(
+                    "(?P<min>-*\d+.*\d*)*-(?P<max>\d+.*\d*)*", diz["values"][0]
+                )
                 if isrange:
                     mn, mx = isrange.groups()
                     self.min = None if mn is None else float(mn)
                     self.max = None if mx is None else float(mx)
                     self.values = None
-                    self.isrange = diz['values'][0]
+                    self.isrange = diz["values"][0]
                 # No range was found
                 else:
-                    self.values = [self.type(i) for i in diz['values']]
+                    self.values = [self.type(i) for i in diz["values"]]
                     self.isrange = False
             except TypeError:
-                self.values = [self.type(i) for i in diz['values']]
+                self.values = [self.type(i) for i in diz["values"]]
                 self.isrange = False
 
         #
         # default
         #
-        if 'default' in diz and diz['default']:
+        if "default" in diz and diz["default"]:
             if self.multiple or self.keydescvalues:
-                self.default = [self.type(v)
-                                for v in diz['default'].split(',')]
+                self.default = [self.type(v) for v in diz["default"].split(",")]
             else:
-                self.default = self.type(diz['default'])
+                self.default = self.type(diz["default"])
         else:
             self.default = None
         self._value, self._rawvalue = self.default, self.default
-        self.guisection = diz.get('guisection', None)
+        self.guisection = diz.get("guisection", None)
 
         #
         # gisprompt
         #
-        if 'gisprompt' in diz and diz['gisprompt']:
-            self.typedesc = diz['gisprompt'].get('prompt', '')
-            self.input = False if diz['gisprompt']['age'] == 'new' else True
+        if "gisprompt" in diz and diz["gisprompt"]:
+            self.typedesc = diz["gisprompt"].get("prompt", "")
+            self.input = False if diz["gisprompt"]["age"] == "new" else True
         else:
             self.input = True
 
@@ -186,8 +212,11 @@ class Parameter(object):
 
     # here the property function is used to transform value in an attribute
     # in this case we define which function must be use to get/set the value
-    value = property(fget=_get_value, fset=_set_value,
-                     doc="Parameter value transformed and validated.")
+    value = property(
+        fget=_get_value,
+        fset=_set_value,
+        doc="Parameter value transformed and validated.",
+    )
 
     @property
     def rawvalue(self):
@@ -205,11 +234,16 @@ class Parameter(object):
 
         ..
         """
-        sep = ','
+        sep = ","
         if isinstance(self.rawvalue, (list, tuple)):
-            value = sep.join([sep.join([str(v) for v in val])
-                              if isinstance(val, tuple) else str(val)
-                              for val in self.rawvalue])
+            value = sep.join(
+                [
+                    sep.join([str(v) for v in val])
+                    if isinstance(val, tuple)
+                    else str(val)
+                    for val in self.rawvalue
+                ]
+            )
         else:
             value = str(self.rawvalue)
         return "%s=%s" % (self.name, value)
@@ -226,7 +260,7 @@ class Parameter(object):
         ..
         """
         if self.value is None:
-            return ''
+            return ""
         return """%s=%r""" % (self.name, self.value)
 
     def __str__(self):
@@ -236,11 +270,13 @@ class Parameter(object):
     def __repr__(self):
         """Return the python representation of the GRASS module parameter."""
         str_repr = "Parameter <%s> (required:%s, type:%s, multiple:%s)"
-        mtype = ('raster', 'vector')  # map type
-        return str_repr % (self.name,
-                           "yes" if self.required else "no",
-                           self.type if self.type in mtype else self.typedesc,
-                           "yes" if self.multiple else "no")
+        mtype = ("raster", "vector")  # map type
+        return str_repr % (
+            self.name,
+            "yes" if self.required else "no",
+            self.type if self.type in mtype else self.typedesc,
+            "yes" if self.multiple else "no",
+        )
 
     @docstring_property(__doc__)
     def __doc__(self):
@@ -262,19 +298,22 @@ class Parameter(object):
                 Values: 2, 4, 6, 8
         ..
         """
-        if hasattr(self, 'values'):
+        if hasattr(self, "values"):
             if self.isrange:
                 vals = self.isrange
             else:
-                vals = ', '.join([repr(val) for val in self.values])
+                vals = ", ".join([repr(val) for val in self.values])
         else:
             vals = False
         if self.keydescvalues:
-            keydescvals = "\n    (%s)" % ', '.join(self.keydescvalues)
-        return DOC['param'].format(name=self.name,
-                default=repr(self.default) + ', ' if self.default else '',
-            required='required, ' if self.required else 'optional, ',
-            multi='multi' if self.multiple else '',
-            ptype=self.typedesc, description=self.description,
-            values='\n    Values: {0}'.format(vals)  if vals else '',
-            keydescvalues= keydescvals if self.keydescvalues else '')
+            keydescvals = "\n    (%s)" % ", ".join(self.keydescvalues)
+        return DOC["param"].format(
+            name=self.name,
+            default=repr(self.default) + ", " if self.default else "",
+            required="required, " if self.required else "optional, ",
+            multi="multi" if self.multiple else "",
+            ptype=self.typedesc,
+            description=self.description,
+            values="\n    Values: {0}".format(vals) if vals else "",
+            keydescvalues=keydescvals if self.keydescvalues else "",
+        )

+ 42 - 34
python/grass/pygrass/modules/interface/read.py

@@ -4,8 +4,15 @@ Created on Tue Apr  2 18:30:34 2013
 
 @author: pietro
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 
 
 def do_nothing(p):
@@ -21,7 +28,7 @@ def get_dict(p):
 
 
 def get_values(p):
-    return [e.text.strip() for e in p.findall('value/name')]
+    return [e.text.strip() for e in p.findall("value/name")]
 
 
 def read_text(p):
@@ -30,32 +37,32 @@ def read_text(p):
 
 def read_keydesc(par):
     name = par.text.strip()
-    items = [e.text.strip() for e in par.findall('item')]
+    items = [e.text.strip() for e in par.findall("item")]
     return name, tuple(items) if len(items) > 1 else None
 
 
 GETFROMTAG = {
-    'description': read_text,
-    'keydesc': read_keydesc,
-    'gisprompt': get_dict,
-    'default': read_text,
-    'values': get_values,
-    'value': get_None,
-    'guisection': read_text,
-    'label': read_text,
-    'suppress_required': get_None,
-    'keywords': read_text,
-    'guidependency': read_text,
-    'rules': get_None,
+    "description": read_text,
+    "keydesc": read_keydesc,
+    "gisprompt": get_dict,
+    "default": read_text,
+    "values": get_values,
+    "value": get_None,
+    "guisection": read_text,
+    "label": read_text,
+    "suppress_required": get_None,
+    "keywords": read_text,
+    "guidependency": read_text,
+    "rules": get_None,
 }
 
 GETTYPE = {
-    'string': str,
-    'integer': int,
-    'float': float,
-    'double': float,
-    'file': str,
-    'all': do_nothing,
+    "string": str,
+    "integer": int,
+    "float": float,
+    "double": float,
+    "file": str,
+    "all": do_nothing,
 }
 
 
@@ -65,37 +72,37 @@ def element2dict(xparameter):
         if p.tag in GETFROMTAG:
             diz[p.tag] = GETFROMTAG[p.tag](p)
         else:
-            print('New tag: %s, ignored' % p.tag)
+            print("New tag: %s, ignored" % p.tag)
     return diz
 
 
 # dictionary used to create docstring for the objects
 DOC = {
-    #------------------------------------------------------------
+    # ------------------------------------------------------------
     # head
-    'head': """{cmd_name}({cmd_params})
+    "head": """{cmd_name}({cmd_params})
 
 Parameters
 ----------
 
 """,
-    #------------------------------------------------------------
+    # ------------------------------------------------------------
     # param
-    'param': """{name}: {default}{required}{multi}{ptype}
+    "param": """{name}: {default}{required}{multi}{ptype}
     {description}{values}{keydescvalues}""",
-    #------------------------------------------------------------
+    # ------------------------------------------------------------
     # flag_head
-    'flag_head': """
+    "flag_head": """
 Flags
 ------
 """,
-    #------------------------------------------------------------
+    # ------------------------------------------------------------
     # flag
-    'flag': """{name}: {default}, {supress}
+    "flag": """{name}: {default}, {supress}
     {description}""",
-    #------------------------------------------------------------
+    # ------------------------------------------------------------
     # foot
-    'foot': """
+    "foot": """
 Special Parameters
 ------------------
 
@@ -111,4 +118,5 @@ stdin_: PIPE, optional
     Set the standard input.
 env_: dictionary, optional
     Set the environment variables.
-"""}
+""",
+}

+ 12 - 12
python/grass/pygrass/modules/interface/testsuite/test_flag.py

@@ -14,36 +14,36 @@ from grass.pygrass.modules.interface.flag import Flag
 class TestFlag(TestCase):
     def test_get_bash(self):
         """Test get_bash method"""
-        flag = Flag(diz=dict(name='a'))
+        flag = Flag(diz=dict(name="a"))
         self.assertFalse(flag.value)
-        self.assertEqual('', flag.get_bash())
+        self.assertEqual("", flag.get_bash())
         flag.special = True
-        self.assertEqual('', flag.get_bash())
+        self.assertEqual("", flag.get_bash())
         flag.value = True
-        self.assertEqual('--a', flag.get_bash())
+        self.assertEqual("--a", flag.get_bash())
         flag.special = False
-        self.assertEqual('-a', flag.get_bash())
+        self.assertEqual("-a", flag.get_bash())
 
     def test_get_python(self):
         """Test get_python method"""
-        flag = Flag(diz=dict(name='a'))
+        flag = Flag(diz=dict(name="a"))
         self.assertFalse(flag.value)
-        self.assertEqual('', flag.get_python())
+        self.assertEqual("", flag.get_python())
         flag.special = True
-        self.assertEqual('', flag.get_python())
+        self.assertEqual("", flag.get_python())
         flag.value = True
-        self.assertEqual('a=True', flag.get_python())
+        self.assertEqual("a=True", flag.get_python())
         flag.special = False
-        self.assertEqual('a', flag.get_python())
+        self.assertEqual("a", flag.get_python())
 
     def test_bool(self):
         """Test magic __bool__ method"""
-        flag = Flag(diz=dict(name='a'))
+        flag = Flag(diz=dict(name="a"))
         flag.value = True
         self.assertTrue(True if flag else False)
         flag.value = False
         self.assertFalse(True if flag else False)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 15 - 10
python/grass/pygrass/modules/interface/testsuite/test_modules.py

@@ -20,7 +20,9 @@ else:
     from io import BytesIO as StringIO
 
 
-SKIP = ["g.parser", ]
+SKIP = [
+    "g.parser",
+]
 
 
 # taken from six
@@ -30,24 +32,27 @@ def with_metaclass(meta, *bases):
     # metaclass for one level of class instantiation that replaces itself with
     # the actual metaclass.
     class metaclass(meta):
-
         def __new__(cls, name, this_bases, d):
             return meta(name, bases, d)
-    return type.__new__(metaclass, 'temporary_class', (), {})
+
+    return type.__new__(metaclass, "temporary_class", (), {})
 
 
 class ModulesMeta(type):
     def __new__(mcs, name, bases, dict):
-
         def gen_test(cmd):
             def test(self):
                 Module(cmd)
+
             return test
 
-        cmds = [c for c in sorted(list(get_commands()[0]))
-                if c not in SKIP and not fnmatch(c, "g.gui.*")]
+        cmds = [
+            c
+            for c in sorted(list(get_commands()[0]))
+            if c not in SKIP and not fnmatch(c, "g.gui.*")
+        ]
         for cmd in cmds:
-            test_name = "test__%s" % cmd.replace('.', '_')
+            test_name = "test__%s" % cmd.replace(".", "_")
             dict[test_name] = gen_test(cmd)
         return type.__new__(mcs, name, bases, dict)
 
@@ -62,14 +67,14 @@ class TestModulesPickability(TestCase):
         import pickle
 
         out = StringIO()
-        pickle.dump(Module('r.sun'), out)
+        pickle.dump(Module("r.sun"), out)
         out.close()
 
 
 class TestModulesCheck(TestCase):
     def test_flags_with_suppress_required(self):
         """Test if flags with suppress required are handle correctly"""
-        gextension = Module('g.extension')
+        gextension = Module("g.extension")
         # check if raise an error if required parameter are missing
         with self.assertRaises(ParameterError):
             gextension.check()
@@ -79,5 +84,5 @@ class TestModulesCheck(TestCase):
         self.assertIsNone(gextension.check())
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 227 - 114
python/grass/pygrass/modules/interface/testsuite/test_parameter.py

@@ -11,19 +11,20 @@ from grass.gunittest.main import test
 from grass.pygrass.modules.interface.parameter import Parameter, _check_value
 
 GETTYPE = {
-    'string': str,
-    'integer': int,
-    'float': float,
-    'double': float,
-    'file': str,
-    'all': lambda x: x,
+    "string": str,
+    "integer": int,
+    "float": float,
+    "double": float,
+    "file": str,
+    "all": lambda x: x,
 }
 
-class TestCheckValueFunction(TestCase):
 
+class TestCheckValueFunction(TestCase):
     def test_single_all(self):
-        param = Parameter(diz=dict(name='int_number', required='yes',
-                                   multiple='no', type='all'))
+        param = Parameter(
+            diz=dict(name="int_number", required="yes", multiple="no", type="all")
+        )
         value = 1
         self.assertTupleEqual((value, value), _check_value(param, value))
         value = 1.2
@@ -36,9 +37,10 @@ class TestCheckValueFunction(TestCase):
             _check_value(param, (1, 2))
 
     def test_single_float_double(self):
-        for ptype in ('float', 'double'):
-            param = Parameter(diz=dict(name='int_number', required='yes',
-                                       multiple='no', type=ptype))
+        for ptype in ("float", "double"):
+            param = Parameter(
+                diz=dict(name="int_number", required="yes", multiple="no", type=ptype)
+            )
             value = 1
             self.assertTupleEqual((float(value), value), _check_value(param, value))
             value = 1.2
@@ -52,30 +54,47 @@ class TestCheckValueFunction(TestCase):
             with self.assertRaises(ValueError):
                 _check_value(param, "elev")
             with self.assertRaises(TypeError):
-                _check_value(param, (1., 2.))
+                _check_value(param, (1.0, 2.0))
 
     def test_multiple_float_double(self):
-        for ptype in ('float', 'double'):
-            param = Parameter(diz=dict(name='number', required='yes',
-                                       multiple='yes', type=ptype))
+        for ptype in ("float", "double"):
+            param = Parameter(
+                diz=dict(name="number", required="yes", multiple="yes", type=ptype)
+            )
             value = (1.4, 2.3)
-            self.assertTupleEqual((list(value), value),
-                                  _check_value(param, value))
+            self.assertTupleEqual((list(value), value), _check_value(param, value))
             value = (1, 2)
-            self.assertTupleEqual(([float(v) for v in value], value),
-                                  _check_value(param, value))
+            self.assertTupleEqual(
+                ([float(v) for v in value], value), _check_value(param, value)
+            )
             value = ("1", "2")
-            self.assertTupleEqual(([float(v) for v in value], value),
-                                  _check_value(param, value))
+            self.assertTupleEqual(
+                ([float(v) for v in value], value), _check_value(param, value)
+            )
             value = ("1.4", "2.3")
-            self.assertTupleEqual(([float(v) for v in value], value),
-                                  _check_value(param, value))
-            value = 1.
-            self.assertTupleEqual(([value, ], value),
-                                  _check_value(param, value))
+            self.assertTupleEqual(
+                ([float(v) for v in value], value), _check_value(param, value)
+            )
+            value = 1.0
+            self.assertTupleEqual(
+                (
+                    [
+                        value,
+                    ],
+                    value,
+                ),
+                _check_value(param, value),
+            )
             value = 1
-            self.assertTupleEqual(([value, ], value),
-                                  _check_value(param, value))
+            self.assertTupleEqual(
+                (
+                    [
+                        value,
+                    ],
+                    value,
+                ),
+                _check_value(param, value),
+            )
 
             # test errors
             with self.assertRaises(ValueError):
@@ -84,10 +103,18 @@ class TestCheckValueFunction(TestCase):
                 _check_value(param, ("elev", "slope", "aspect"))
 
     def test_range_float_double(self):
-        for ptype in ('float', 'double'):
-            param = Parameter(diz=dict(name='int_number', required='yes',
-                                       multiple='no', type=ptype,
-                                       values=["0.0-2.5", ]))
+        for ptype in ("float", "double"):
+            param = Parameter(
+                diz=dict(
+                    name="int_number",
+                    required="yes",
+                    multiple="no",
+                    type=ptype,
+                    values=[
+                        "0.0-2.5",
+                    ],
+                )
+            )
             value = 1
             self.assertTupleEqual((float(value), value), _check_value(param, value))
             value = 1.2
@@ -101,15 +128,16 @@ class TestCheckValueFunction(TestCase):
             with self.assertRaises(ValueError):
                 _check_value(param, "elev")
             with self.assertRaises(TypeError):
-                _check_value(param, (1., 2.))
+                _check_value(param, (1.0, 2.0))
             with self.assertRaises(ValueError):
-                _check_value(param, -1.)
+                _check_value(param, -1.0)
             with self.assertRaises(ValueError):
                 _check_value(param, 2.6)
 
     def test_single_integer(self):
-        param = Parameter(diz=dict(name='int_number', required='yes',
-                                   multiple='no', type='integer'))
+        param = Parameter(
+            diz=dict(name="int_number", required="yes", multiple="no", type="integer")
+        )
         value = 1
         self.assertTupleEqual((value, value), _check_value(param, value))
         value = 1.2
@@ -126,25 +154,50 @@ class TestCheckValueFunction(TestCase):
             _check_value(param, (1, 2))
 
     def test_multiple_integer(self):
-        param = Parameter(diz=dict(name='int_number', required='yes',
-                                   multiple='yes', type='integer'))
+        param = Parameter(
+            diz=dict(name="int_number", required="yes", multiple="yes", type="integer")
+        )
         value = (1, 2)
-        #import ipdb; ipdb.set_trace()
+        # import ipdb; ipdb.set_trace()
         self.assertTupleEqual((list(value), value), _check_value(param, value))
         value = (1.2, 2.3)
-        self.assertTupleEqual(([int(v) for v in value], value),
-                              _check_value(param, value))
+        self.assertTupleEqual(
+            ([int(v) for v in value], value), _check_value(param, value)
+        )
         value = ("1", "2")
-        self.assertTupleEqual(([int(v) for v in value], value),
-                              _check_value(param, value))
+        self.assertTupleEqual(
+            ([int(v) for v in value], value), _check_value(param, value)
+        )
         value = 1
-        self.assertTupleEqual(([1, ], value), _check_value(param, value))
+        self.assertTupleEqual(
+            (
+                [
+                    1,
+                ],
+                value,
+            ),
+            _check_value(param, value),
+        )
         value = 1.2
-        self.assertTupleEqual(([int(value), ], value),
-                              _check_value(param, value))
+        self.assertTupleEqual(
+            (
+                [
+                    int(value),
+                ],
+                value,
+            ),
+            _check_value(param, value),
+        )
         value = "1"
-        self.assertTupleEqual(([int(value), ], value),
-                              _check_value(param, value))
+        self.assertTupleEqual(
+            (
+                [
+                    int(value),
+                ],
+                value,
+            ),
+            _check_value(param, value),
+        )
 
         # test errors
         with self.assertRaises(ValueError):
@@ -153,14 +206,26 @@ class TestCheckValueFunction(TestCase):
             _check_value(param, ("elev", "slope", "aspect"))
 
     def test_keydescvalues(self):
-        for ptype in ('integer', 'float'):
-            param = Parameter(diz=dict(name='int_number', required='yes',
-                                       multiple='yes',
-                                       keydesc=('range', '(min, max)'),
-                                       type='integer'))
+        for ptype in ("integer", "float"):
+            param = Parameter(
+                diz=dict(
+                    name="int_number",
+                    required="yes",
+                    multiple="yes",
+                    keydesc=("range", "(min, max)"),
+                    type="integer",
+                )
+            )
             value = (1, 2)
-            self.assertTupleEqual(([value, ], value),
-                                  _check_value(param, value))
+            self.assertTupleEqual(
+                (
+                    [
+                        value,
+                    ],
+                    value,
+                ),
+                _check_value(param, value),
+            )
             value = [(1, 2), (2, 3)]
             self.assertTupleEqual((value, value), _check_value(param, value))
 
@@ -168,9 +233,17 @@ class TestCheckValueFunction(TestCase):
                 _check_value(param, 1)
 
     def test_range_integer(self):
-        param = Parameter(diz=dict(name='int_number', required='yes',
-                                   multiple='no', type='integer',
-                                   values=["0-10", ]))
+        param = Parameter(
+            diz=dict(
+                name="int_number",
+                required="yes",
+                multiple="no",
+                type="integer",
+                values=[
+                    "0-10",
+                ],
+            )
+        )
         value = 1
         self.assertTupleEqual((value, value), _check_value(param, value))
         value = 0
@@ -195,9 +268,15 @@ class TestCheckValueFunction(TestCase):
             _check_value(param, 11)
 
     def test_choice_integer(self):
-        param = Parameter(diz=dict(name='int_number', required='yes',
-                                   multiple='no', type='integer',
-                                   values=[2, 4, 6, 8]))
+        param = Parameter(
+            diz=dict(
+                name="int_number",
+                required="yes",
+                multiple="no",
+                type="integer",
+                values=[2, 4, 6, 8],
+            )
+        )
         value = 4
         self.assertTupleEqual((value, value), _check_value(param, value))
         value = 2
@@ -216,39 +295,49 @@ class TestCheckValueFunction(TestCase):
             _check_value(param, 3)
 
     def test_single_string_file(self):
-        for ptype in ('string', 'file'):
-            param = Parameter(diz=dict(name='name', required='yes',
-                                       multiple='no', type=ptype))
-            value = u'elev'
+        for ptype in ("string", "file"):
+            param = Parameter(
+                diz=dict(name="name", required="yes", multiple="no", type=ptype)
+            )
+            value = "elev"
             self.assertTupleEqual((value, value), _check_value(param, value))
             value = 10
-            self.assertTupleEqual((str(value), value),
-                                  _check_value(param, value))
+            self.assertTupleEqual((str(value), value), _check_value(param, value))
             value = 12.5
-            self.assertTupleEqual((str(value), value),
-                                  _check_value(param, value))
+            self.assertTupleEqual((str(value), value), _check_value(param, value))
 
             # test errors
             with self.assertRaises(TypeError):
-                _check_value(param, ('abc', 'def'))
+                _check_value(param, ("abc", "def"))
 
     def test_multiple_strings(self):
-        param = Parameter(diz=dict(name='rastnames', required='yes',
-                                   multiple='yes', type='string'))
-        value = ['elev', 'slope', 'aspect']
+        param = Parameter(
+            diz=dict(name="rastnames", required="yes", multiple="yes", type="string")
+        )
+        value = ["elev", "slope", "aspect"]
         self.assertTupleEqual((value, value), _check_value(param, value))
-        value = ('elev', 'slope', 'aspect')
+        value = ("elev", "slope", "aspect")
         self.assertTupleEqual((list(value), value), _check_value(param, value))
-        value = ['1.3', '2.3', '4.5']
+        value = ["1.3", "2.3", "4.5"]
         self.assertTupleEqual((value, value), _check_value(param, value))
         value = [1.3, 2.3, 4.5]
-        self.assertTupleEqual(([str(v) for v in value], value),
-                              _check_value(param, value))
+        self.assertTupleEqual(
+            ([str(v) for v in value], value), _check_value(param, value)
+        )
         value = (1, 2, 3)
-        self.assertTupleEqual(([str(v) for v in value], value),
-                              _check_value(param, value))
-        value = 'elev'
-        self.assertTupleEqual(([value, ], value), _check_value(param, value))
+        self.assertTupleEqual(
+            ([str(v) for v in value], value), _check_value(param, value)
+        )
+        value = "elev"
+        self.assertTupleEqual(
+            (
+                [
+                    value,
+                ],
+                value,
+            ),
+            _check_value(param, value),
+        )
 
         # test errors
         with self.assertRaises(ValueError):
@@ -256,9 +345,15 @@ class TestCheckValueFunction(TestCase):
 
     def test_choice_string(self):
         values = ["elev", "asp", "slp"]
-        param = Parameter(diz=dict(name='rastname', required='yes',
-                                   multiple='no', type='string',
-                                   values=values))
+        param = Parameter(
+            diz=dict(
+                name="rastname",
+                required="yes",
+                multiple="no",
+                type="string",
+                values=values,
+            )
+        )
         value = "asp"
         self.assertTupleEqual((value, value), _check_value(param, value))
 
@@ -275,9 +370,10 @@ class TestCheckValueFunction(TestCase):
 
 class TestParameterGetBash(TestCase):
     def test_single_float_double(self):
-        for ptype in ('float', 'double'):
-            param = Parameter(diz=dict(name='number', required='yes',
-                                       multiple='no', type=ptype))
+        for ptype in ("float", "double"):
+            param = Parameter(
+                diz=dict(name="number", required="yes", multiple="no", type=ptype)
+            )
             # set private attributes to skip the check function
             param._value = 1.0
             param._rawvalue = 1.0
@@ -287,14 +383,19 @@ class TestParameterGetBash(TestCase):
             self.assertEqual("number=1.", param.get_bash())
 
     def test_multiple_float_double(self):
-        for ptype in ('float', 'double'):
-            param = Parameter(diz=dict(name='number', required='yes',
-                                       multiple='yes', type=ptype))
+        for ptype in ("float", "double"):
+            param = Parameter(
+                diz=dict(name="number", required="yes", multiple="yes", type=ptype)
+            )
             # set private attributes to skip the check function
-            param._value = [1.0, ]
+            param._value = [
+                1.0,
+            ]
             param._rawvalue = 1.0
             self.assertEqual("number=1.0", param.get_bash())
-            param._value = [1.0, ]
+            param._value = [
+                1.0,
+            ]
             param._rawvalue = "1."
             self.assertEqual("number=1.", param.get_bash())
             param._value = [1.0, 2.0, 3.0]
@@ -305,42 +406,54 @@ class TestParameterGetBash(TestCase):
             self.assertEqual("number=1.,2.,3.", param.get_bash())
 
     def test_single_string(self):
-        param = Parameter(diz=dict(name='rast', required='yes',
-                                   multiple='no', type='string'))
+        param = Parameter(
+            diz=dict(name="rast", required="yes", multiple="no", type="string")
+        )
         # set private attributes to skip the check function
-        param._value = 'elev'
-        param._rawvalue = 'elev'
+        param._value = "elev"
+        param._rawvalue = "elev"
         self.assertEqual("rast=elev", param.get_bash())
 
     def test_multiple_strings(self):
-        param = Parameter(diz=dict(name='rast', required='yes',
-                                   multiple='yes', type='string'))
+        param = Parameter(
+            diz=dict(name="rast", required="yes", multiple="yes", type="string")
+        )
         # set private attributes to skip the check function
-        param._value = ['elev', 'asp', 'slp']
-        param._rawvalue = ['elev', 'asp', 'slp']
+        param._value = ["elev", "asp", "slp"]
+        param._rawvalue = ["elev", "asp", "slp"]
         self.assertEqual("rast=elev,asp,slp", param.get_bash())
-        param._value = ['elev', ]
-        param._rawvalue = 'elev'
+        param._value = [
+            "elev",
+        ]
+        param._rawvalue = "elev"
         self.assertEqual("rast=elev", param.get_bash())
 
     def test_keydescvalues(self):
-        param = Parameter(diz=dict(name='range', required='yes',
-                                   multiple='yes',
-                                   keydesc=('range', '(min, max)'),
-                                   type='integer'))
+        param = Parameter(
+            diz=dict(
+                name="range",
+                required="yes",
+                multiple="yes",
+                keydesc=("range", "(min, max)"),
+                type="integer",
+            )
+        )
         # set private attributes to skip the check function
-        param._value = [(1., 2.), ]
-        param._rawvalue = (1., 2.)
+        param._value = [
+            (1.0, 2.0),
+        ]
+        param._rawvalue = (1.0, 2.0)
         self.assertEqual("range=1.0,2.0", param.get_bash())
-        param._value = [(1., 2.), (3., 4.)]
-        param._rawvalue = [(1., 2.), (3., 4.)]
+        param._value = [(1.0, 2.0), (3.0, 4.0)]
+        param._rawvalue = [(1.0, 2.0), (3.0, 4.0)]
         self.assertEqual("range=1.0,2.0,3.0,4.0", param.get_bash())
-        param._value = [(1., 2.), (3., 4.)]
-        param._rawvalue = [('1.0', '2.00'), ('3.000', '4.0000')]
+        param._value = [(1.0, 2.0), (3.0, 4.0)]
+        param._rawvalue = [("1.0", "2.00"), ("3.000", "4.0000")]
         self.assertEqual("range=1.0,2.00,3.000,4.0000", param.get_bash())
 
         with self.assertRaises(TypeError):
             _check_value(param, 1)
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     test()

+ 9 - 7
python/grass/pygrass/modules/interface/testsuite/test_pygrass_modules_interface_doctests.py

@@ -17,12 +17,14 @@ import grass.pygrass.modules as gmodules
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -38,5 +40,5 @@ def load_tests(loader, tests, ignore):
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 12 - 5
python/grass/pygrass/modules/interface/typedict.py

@@ -4,9 +4,17 @@ Created on Tue Apr  2 18:37:02 2013
 
 @author: pietro
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 from copy import deepcopy
+
 try:
     from collections import OrderedDict
 except ImportError:
@@ -38,13 +46,12 @@ class TypeDict(OrderedDict):
         if isinstance(value, self._type):
             super(TypeDict, self).__setitem__(key, value)
         else:
-            str_err = 'The value: %r is not a %s instance.'
+            str_err = "The value: %r is not a %s instance."
             raise TypeError(str_err % (value, self._type.__name__))
 
     @docstring_property(__doc__)
     def __doc__(self):
-        return '\n'.join([self.__getitem__(obj).__doc__
-                          for obj in self.__iter__()])
+        return "\n".join([self.__getitem__(obj).__doc__ for obj in self.__iter__()])
 
     def __call__(self):
         return [self.__getitem__(obj) for obj in self.__iter__()]

+ 56 - 48
python/grass/pygrass/modules/shortcuts.py

@@ -1,6 +1,13 @@
 # -*- coding: utf-8 -*-
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 import fnmatch
 
 
@@ -14,38 +21,38 @@ _CMDS.sort()
 class MetaModule(object):
     """Example how to use MetaModule
 
-       >>> g = MetaModule('g')
-       >>> g_list = g.list
-       >>> g_list.name
-       'g.list'
-       >>> g_list.required
-       ['type']
-       >>> g_list.inputs.type = 'raster'
-       >>> g_list.inputs.mapset = 'PERMANENT'
-       >>> g_list.stdout_ = -1
-       >>> g_list.run()
-       Module('g.list')
-       >>> g_list.outputs.stdout                         # doctest: +ELLIPSIS
-       '...basin...elevation...'
+    >>> g = MetaModule('g')
+    >>> g_list = g.list
+    >>> g_list.name
+    'g.list'
+    >>> g_list.required
+    ['type']
+    >>> g_list.inputs.type = 'raster'
+    >>> g_list.inputs.mapset = 'PERMANENT'
+    >>> g_list.stdout_ = -1
+    >>> g_list.run()
+    Module('g.list')
+    >>> g_list.outputs.stdout                         # doctest: +ELLIPSIS
+    '...basin...elevation...'
 
-       >>> r = MetaModule('r')
-       >>> what = r.what
-       >>> what.description
-       'Queries raster maps on their category values and category labels.'
-       >>> what.inputs.map = 'elevation'
-       >>> what.inputs.coordinates = [640000,220500]          # doctest: +SKIP
-       >>> what.run()                                         # doctest: +SKIP
-       >>> v = MetaModule('v')
-       >>> v.import                                      # doctest: +ELLIPSIS
-       Traceback (most recent call last):
-         File ".../doctest.py", line 1315, in __run
-          compileflags, 1) in test.globs
-         File "<doctest grass.pygrass.modules.shortcuts.MetaModule[16]>", line 1
-          v.import
-               ^
-       SyntaxError: invalid syntax
-       >>> v.import_
-       Module('v.import')
+    >>> r = MetaModule('r')
+    >>> what = r.what
+    >>> what.description
+    'Queries raster maps on their category values and category labels.'
+    >>> what.inputs.map = 'elevation'
+    >>> what.inputs.coordinates = [640000,220500]          # doctest: +SKIP
+    >>> what.run()                                         # doctest: +SKIP
+    >>> v = MetaModule('v')
+    >>> v.import                                      # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+      File ".../doctest.py", line 1315, in __run
+       compileflags, 1) in test.globs
+      File "<doctest grass.pygrass.modules.shortcuts.MetaModule[16]>", line 1
+       v.import
+            ^
+    SyntaxError: invalid syntax
+    >>> v.import_
+    Module('v.import')
     """
 
     def __init__(self, prefix, cls=None):
@@ -53,16 +60,17 @@ class MetaModule(object):
         self.cls = cls if cls else Module
 
     def __dir__(self):
-        return [mod[(len(self.prefix) + 1):].replace('.', '_')
-                for mod in fnmatch.filter(_CMDS, "%s.*" % self.prefix)]
+        return [
+            mod[(len(self.prefix) + 1) :].replace(".", "_")
+            for mod in fnmatch.filter(_CMDS, "%s.*" % self.prefix)
+        ]
 
     def __getattr__(self, name):
-        return self.cls('%s.%s' % (self.prefix,
-                                   name.strip('_').replace('_', '.')))
+        return self.cls("%s.%s" % (self.prefix, name.strip("_").replace("_", ".")))
 
 
 # https://grass.osgeo.org/grass79/manuals/full_index.html
-#[ d.* | db.* | g.* | i.* | m.* | ps.* | r.* | r3.* | t.* | v.* ]
+# [ d.* | db.* | g.* | i.* | m.* | ps.* | r.* | r3.* | t.* | v.* ]
 #
 #  d.*	display commands
 #  db.*	database commands
@@ -75,13 +83,13 @@ class MetaModule(object):
 #  t.*	temporal commands
 #  v.*	vector commands
 
-display = MetaModule('d')
-database = MetaModule('db')
-general = MetaModule('g')
-imagery = MetaModule('i')
-miscellaneous = MetaModule('m')
-postscript = MetaModule('ps')
-raster = MetaModule('r')
-raster3d = MetaModule('r3')
-temporal = MetaModule('t')
-vector = MetaModule('v')
+display = MetaModule("d")
+database = MetaModule("db")
+general = MetaModule("g")
+imagery = MetaModule("i")
+miscellaneous = MetaModule("m")
+postscript = MetaModule("ps")
+raster = MetaModule("r")
+raster3d = MetaModule("r3")
+temporal = MetaModule("t")
+vector = MetaModule("v")

+ 19 - 11
python/grass/pygrass/modules/testsuite/test_import_isolation.py

@@ -20,8 +20,7 @@ from grass.gunittest.main import test
 
 
 def check(*patterns):
-    """Return a set of the imported libraries that soddisfies several patterns.
-    """
+    """Return a set of the imported libraries that soddisfies several patterns."""
     result = []
     imports = sorted(sys.modules.keys())
     for pattern in patterns:
@@ -30,24 +29,33 @@ def check(*patterns):
 
 
 class TestImportIsolation(TestCase):
-    patterns = ['grass.lib*']
+    patterns = ["grass.lib*"]
 
     def test_import_isolation(self):
         """Check that modules  classes are not using ctypes"""
         isolate = set()
-        self.assertEqual(isolate, check(*self.patterns),
-                         msg="Test isolation before any import.")
+        self.assertEqual(
+            isolate, check(*self.patterns), msg="Test isolation before any import."
+        )
         # same import done in __init__ file
         from grass.pygrass.modules.interface import Module, ParallelModuleQueue
         from grass.pygrass.modules import shortcuts
-        self.assertEqual(isolate, check(*self.patterns),
-                         msg="Test isolation after import Module.")
+
+        self.assertEqual(
+            isolate, check(*self.patterns), msg="Test isolation after import Module."
+        )
         # test the other way round
         from grass.pygrass.vector import VectorTopo
-        self.assertNotEqual(isolate, check(*self.patterns),
-                            msg=("Test the isolation is broken, therefore "
-                                 "the defined patterns are correct"))
+
+        self.assertNotEqual(
+            isolate,
+            check(*self.patterns),
+            msg=(
+                "Test the isolation is broken, therefore "
+                "the defined patterns are correct"
+            ),
+        )
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 10 - 8
python/grass/pygrass/modules/testsuite/test_pygrass_modules_doctests.py

@@ -17,12 +17,14 @@ from grass.pygrass.modules import shortcuts, grid, interface
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -35,9 +37,9 @@ def load_tests(loader, tests, ignore):
     tests.addTests(doctest.DocTestSuite(grid.grid))
     tests.addTests(doctest.DocTestSuite(grid.patch))
     tests.addTests(doctest.DocTestSuite(grid.split))
-#    tests.addTests(doctest.DocTestSuite(shortcuts))
+    #    tests.addTests(doctest.DocTestSuite(shortcuts))
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 40 - 38
python/grass/pygrass/orderdict.py

@@ -14,7 +14,7 @@ except ImportError:
 
 
 class OrderedDict(dict):
-    'Dictionary that remembers insertion order'
+    "Dictionary that remembers insertion order"
     # An inherited dict maps keys to values.
     # The inherited dict provides __getitem__, __len__, __contains__, and get.
     # The remaining methods are order-aware.
@@ -26,23 +26,23 @@ class OrderedDict(dict):
     # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
 
     def __init__(self, *args, **kwds):
-        '''Initialize an ordered dictionary.  Signature is the same as for
+        """Initialize an ordered dictionary.  Signature is the same as for
         regular dictionaries, but keyword arguments are not recommended
         because their insertion order is arbitrary.
 
-        '''
+        """
         if len(args) > 1:
-            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+            raise TypeError("expected at most 1 arguments, got %d" % len(args))
         try:
             self.__root
         except AttributeError:
-            self.__root = root = []                     # sentinel node
+            self.__root = root = []  # sentinel node
             root[:] = [root, root, None]
             self.__map = {}
         self.__update(*args, **kwds)
 
     def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
-        'od.__setitem__(i, y) <==> od[i]=y'
+        "od.__setitem__(i, y) <==> od[i]=y"
         # Setting a new item creates a new link which goes at the end of the linked
         # list, and the inherited dictionary is updated with the new key/value pair.
         if key not in self:
@@ -52,7 +52,7 @@ class OrderedDict(dict):
         dict_setitem(self, key, value)
 
     def __delitem__(self, key, dict_delitem=dict.__delitem__):
-        'od.__delitem__(y) <==> del od[y]'
+        "od.__delitem__(y) <==> del od[y]"
         # Deleting an existing item uses self.__map to find the link which is
         # then removed by updating the links in the predecessor and successor nodes.
         dict_delitem(self, key)
@@ -61,7 +61,7 @@ class OrderedDict(dict):
         link_next[0] = link_prev
 
     def __iter__(self):
-        'od.__iter__() <==> iter(od)'
+        "od.__iter__() <==> iter(od)"
         root = self.__root
         curr = root[1]
         while curr is not root:
@@ -69,7 +69,7 @@ class OrderedDict(dict):
             curr = curr[1]
 
     def __reversed__(self):
-        'od.__reversed__() <==> reversed(od)'
+        "od.__reversed__() <==> reversed(od)"
         root = self.__root
         curr = root[0]
         while curr is not root:
@@ -77,7 +77,7 @@ class OrderedDict(dict):
             curr = curr[0]
 
     def clear(self):
-        'od.clear() -> None.  Remove all items from od.'
+        "od.clear() -> None.  Remove all items from od."
         try:
             for node in self.__map.itervalues():
                 del node[:]
@@ -89,12 +89,12 @@ class OrderedDict(dict):
         dict.clear(self)
 
     def popitem(self, last=True):
-        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        """od.popitem() -> (k, v), return and remove a (key, value) pair.
         Pairs are returned in LIFO order if last is true or FIFO order if false.
 
-        '''
+        """
         if not self:
-            raise KeyError('dictionary is empty')
+            raise KeyError("dictionary is empty")
         root = self.__root
         if last:
             link = root[0]
@@ -114,45 +114,47 @@ class OrderedDict(dict):
     # -- the following methods do not depend on the internal structure --
 
     def keys(self):
-        'od.keys() -> list of keys in od'
+        "od.keys() -> list of keys in od"
         return list(self)
 
     def values(self):
-        'od.values() -> list of values in od'
+        "od.values() -> list of values in od"
         return [self[key] for key in self]
 
     def items(self):
-        'od.items() -> list of (key, value) pairs in od'
+        "od.items() -> list of (key, value) pairs in od"
         return [(key, self[key]) for key in self]
 
     def iterkeys(self):
-        'od.iterkeys() -> an iterator over the keys in od'
+        "od.iterkeys() -> an iterator over the keys in od"
         return iter(self)
 
     def itervalues(self):
-        'od.itervalues -> an iterator over the values in od'
+        "od.itervalues -> an iterator over the values in od"
         for k in self:
             yield self[k]
 
     def iteritems(self):
-        'od.iteritems -> an iterator over the (key, value) items in od'
+        "od.iteritems -> an iterator over the (key, value) items in od"
         for k in self:
             yield (k, self[k])
 
     def update(*args, **kwds):
-        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
+        """od.update(E, **F) -> None.  Update od from dict/iterable E and F.
 
         If E is a dict instance, does:           for k in E: od[k] = E[k]
         If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
         Or if E is an iterable of items, does:   for k, v in E: od[k] = v
         In either case, this is followed by:     for k, v in F.items(): od[k] = v
 
-        '''
+        """
         if len(args) > 2:
-            raise TypeError('update() takes at most 2 positional '
-                            'arguments (%d given)' % (len(args),))
+            raise TypeError(
+                "update() takes at most 2 positional "
+                "arguments (%d given)" % (len(args),)
+            )
         elif not args:
-            raise TypeError('update() takes at least 1 argument (0 given)')
+            raise TypeError("update() takes at least 1 argument (0 given)")
         self = args[0]
         # Make progressively weaker assumptions about "other"
         other = ()
@@ -161,7 +163,7 @@ class OrderedDict(dict):
         if isinstance(other, dict):
             for key in other:
                 self[key] = other[key]
-        elif hasattr(other, 'keys'):
+        elif hasattr(other, "keys"):
             for key in other.keys():
                 self[key] = other[key]
         else:
@@ -175,10 +177,10 @@ class OrderedDict(dict):
     __marker = object()
 
     def pop(self, key, default=__marker):
-        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        """od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
         If key is not found, d is returned if given, otherwise KeyError is raised.
 
-        '''
+        """
         if key in self:
             result = self[key]
             del self[key]
@@ -188,27 +190,27 @@ class OrderedDict(dict):
         return default
 
     def setdefault(self, key, default=None):
-        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        "od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od"
         if key in self:
             return self[key]
         self[key] = default
         return default
 
     def __repr__(self, _repr_running={}):
-        'od.__repr__() <==> repr(od)'
+        "od.__repr__() <==> repr(od)"
         call_key = id(self), _get_ident()
         if call_key in _repr_running:
-            return '...'
+            return "..."
         _repr_running[call_key] = 1
         try:
             if not self:
-                return '%s()' % (self.__class__.__name__,)
-            return '%s(%r)' % (self.__class__.__name__, self.items())
+                return "%s()" % (self.__class__.__name__,)
+            return "%s(%r)" % (self.__class__.__name__, self.items())
         finally:
             del _repr_running[call_key]
 
     def __reduce__(self):
-        'Return state information for pickling'
+        "Return state information for pickling"
         items = [[k, self[k]] for k in self]
         inst_dict = vars(self).copy()
         for k in vars(OrderedDict()):
@@ -218,25 +220,25 @@ class OrderedDict(dict):
         return self.__class__, (items,)
 
     def copy(self):
-        'od.copy() -> a shallow copy of od'
+        "od.copy() -> a shallow copy of od"
         return self.__class__(self)
 
     @classmethod
     def fromkeys(cls, iterable, value=None):
-        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        """OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
         and values equal to v (which defaults to None).
 
-        '''
+        """
         d = cls()
         for key in iterable:
             d[key] = value
         return d
 
     def __eq__(self, other):
-        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        """od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
         while comparison to a regular mapping is order-insensitive.
 
-        '''
+        """
         if isinstance(other, OrderedDict):
             return len(self) == len(other) and self.items() == other.items()
         return dict.__eq__(self, other)

+ 130 - 107
python/grass/pygrass/raster/__init__.py

@@ -1,6 +1,13 @@
 # -*- coding: utf-8 -*-
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 import ctypes
 import numpy as np
 
@@ -14,7 +21,7 @@ import grass.lib.gis as libgis
 import grass.lib.raster as libraster
 import grass.lib.rowio as librowio
 
-libgis.G_gisinit('')
+libgis.G_gisinit("")
 
 #
 # import pygrass modules
@@ -137,26 +144,26 @@ class RasterRow(RasterAbstractBase):
 
     """
 
-    def __init__(self, name, mapset='', *args, **kargs):
+    def __init__(self, name, mapset="", *args, **kargs):
         super(RasterRow, self).__init__(name, mapset, *args, **kargs)
 
     # mode = "r", method = "row",
     @must_be_open
     def get_row(self, row, row_buffer=None):
         """Private method that return the row using the read mode
-            call the `Rast_get_row` C function.
+        call the `Rast_get_row` C function.
 
-            :param row: the number of row to obtain
-            :type row: int
-            :param row_buffer: Buffer object instance with the right dim and type
-            :type row_buffer: Buffer
+        :param row: the number of row to obtain
+        :type row: int
+        :param row_buffer: Buffer object instance with the right dim and type
+        :type row_buffer: Buffer
 
-            >>> elev = RasterRow(test_raster_name)
-            >>> elev.open()
-            >>> elev[0]
-            Buffer([11, 21, 31, 41], dtype=int32)
-            >>> elev.get_row(0)
-            Buffer([11, 21, 31, 41], dtype=int32)
+        >>> elev = RasterRow(test_raster_name)
+        >>> elev.open()
+        >>> elev[0]
+        Buffer([11, 21, 31, 41], dtype=int32)
+        >>> elev.get_row(0)
+        Buffer([11, 21, 31, 41], dtype=int32)
 
         """
         if row_buffer is None:
@@ -198,7 +205,7 @@ class RasterRow(RasterAbstractBase):
         self.mtype = mtype if mtype else self.mtype
         self.overwrite = overwrite if overwrite is not None else self.overwrite
 
-        if self.mode == 'r':
+        if self.mode == "r":
             if self.exist():
                 self.info.read()
                 self.cats.mtype = self.mtype
@@ -210,18 +217,18 @@ class RasterRow(RasterAbstractBase):
             else:
                 str_err = _("The map does not exist, I can't open in 'r' mode")
                 raise OpenError(str_err)
-        elif self.mode == 'w':
+        elif self.mode == "w":
             if self.exist():
                 if not self.overwrite:
-                    str_err = _("Raster map <{0}> already exists"
-                                " and will be not overwritten")
+                    str_err = _(
+                        "Raster map <{0}> already exists" " and will be not overwritten"
+                    )
                     raise OpenError(str_err.format(self))
             if self._gtype is None:
                 raise OpenError(_("Raster type not defined"))
             self._fd = libraster.Rast_open_new(self.name, self._gtype)
         else:
-            raise OpenError("Open mode: %r not supported,"
-                            " valid mode are: r, w")
+            raise OpenError("Open mode: %r not supported," " valid mode are: r, w")
         # read rows and cols from the active region
         self._rows = libraster.Rast_window_rows()
         self._cols = libraster.Rast_window_cols()
@@ -312,8 +319,7 @@ class RasterSegment(RasterAbstractBase):
 
     """
 
-    def __init__(self, name, srows=64, scols=64, maxmem=100,
-                 *args, **kargs):
+    def __init__(self, name, srows=64, scols=64, maxmem=100, *args, **kargs):
         self.segment = Segment(srows, scols, maxmem)
         super(RasterSegment, self).__init__(name, *args, **kargs)
 
@@ -321,20 +327,20 @@ class RasterSegment(RasterAbstractBase):
         return self._mode
 
     def _set_mode(self, mode):
-        if mode and mode.lower() not in ('r', 'w', 'rw'):
+        if mode and mode.lower() not in ("r", "w", "rw"):
             str_err = _("Mode type: {0} not supported ('r', 'w','rw')")
             raise ValueError(str_err.format(mode))
         self._mode = mode
 
-    mode = property(fget=_get_mode, fset=_set_mode,
-                    doc="Set or obtain the opening mode of raster")
+    mode = property(
+        fget=_get_mode, fset=_set_mode, doc="Set or obtain the opening mode of raster"
+    )
 
     def __setitem__(self, key, row):
         """Return the row of Raster object, slice allowed."""
         if isinstance(key, slice):
             # Get the start, stop, and step from the slice
-            return [self.put_row(ii, row)
-                    for ii in range(*key.indices(len(self)))]
+            return [self.put_row(ii, row) for ii in range(*key.indices(len(self)))]
         elif isinstance(key, tuple):
             x, y = key
             return self.put(x, y, row)
@@ -349,18 +355,15 @@ class RasterSegment(RasterAbstractBase):
 
     @must_be_open
     def map2segment(self):
-        """Transform an existing map to segment file.
-        """
+        """Transform an existing map to segment file."""
         row_buffer = Buffer((self._cols), self.mtype)
         for row in range(self._rows):
-            libraster.Rast_get_row(
-                self._fd, row_buffer.p, row, self._gtype)
+            libraster.Rast_get_row(self._fd, row_buffer.p, row, self._gtype)
             self.segment.put_row(row, row_buffer)
 
     @must_be_open
     def segment2map(self):
-        """Transform the segment file to a map.
-        """
+        """Transform the segment file to a map."""
         row_buffer = Buffer((self._cols), self.mtype)
         for row in range(self._rows):
             row_buffer = self.segment.get_row(row, row_buffer)
@@ -404,29 +407,29 @@ class RasterSegment(RasterAbstractBase):
     def put_row(self, row, row_buffer):
         """Write the row using the `segment.put_row` method
 
-            :param row: a Row object to insert into raster
-            :type row: Buffer object
+        :param row: a Row object to insert into raster
+        :type row: Buffer object
 
-            Input and output must have the same type in case of row copy
+        Input and output must have the same type in case of row copy
 
-            >>> map_a = RasterSegment(test_raster_name)
-            >>> map_b = RasterSegment(test_raster_name + "_segment")
-            >>> map_a.open('r')
-            >>> map_b.open('w', mtype="CELL", overwrite=True)
-            >>> for row in range(map_a.info.rows):
-            ...     map_b[row] = map_a[row] + 1000
-            >>> map_a.close()
-            >>> map_b.close()
+        >>> map_a = RasterSegment(test_raster_name)
+        >>> map_b = RasterSegment(test_raster_name + "_segment")
+        >>> map_a.open('r')
+        >>> map_b.open('w', mtype="CELL", overwrite=True)
+        >>> for row in range(map_a.info.rows):
+        ...     map_b[row] = map_a[row] + 1000
+        >>> map_a.close()
+        >>> map_b.close()
 
-            >>> map_b = RasterSegment(test_raster_name + "_segment")
-            >>> map_b.open("r")
-            >>> for row in map_b:
-            ...         row
-            Buffer([1011, 1021, 1031, 1041], dtype=int32)
-            Buffer([1012, 1022, 1032, 1042], dtype=int32)
-            Buffer([1013, 1023, 1033, 1043], dtype=int32)
-            Buffer([1014, 1024, 1034, 1044], dtype=int32)
-            >>> map_b.close()
+        >>> map_b = RasterSegment(test_raster_name + "_segment")
+        >>> map_b.open("r")
+        >>> for row in map_b:
+        ...         row
+        Buffer([1011, 1021, 1031, 1041], dtype=int32)
+        Buffer([1012, 1022, 1032, 1042], dtype=int32)
+        Buffer([1013, 1023, 1033, 1043], dtype=int32)
+        Buffer([1014, 1024, 1034, 1044], dtype=int32)
+        >>> map_b.close()
 
         """
         self.segment.put_row(row, row_buffer)
@@ -529,8 +532,7 @@ class RasterSegment(RasterAbstractBase):
             self.cats.mtype = self.mtype
             self.cats.read()
             self.hist.read()
-            if ((self.mode == "w" or self.mode == "rw") and
-                    self.overwrite is False):
+            if (self.mode == "w" or self.mode == "rw") and self.overwrite is False:
                 str_err = _("Raster map <{0}> already exists. Use overwrite.")
                 fatal(str_err.format(self))
 
@@ -552,12 +554,11 @@ class RasterSegment(RasterAbstractBase):
                     # warning(_(WARN_OVERWRITE.format(self)))
                     # Close the file descriptor and open it as new again
                     libraster.Rast_close(self._fd)
-                    self._fd = libraster.Rast_open_new(
-                        self.name, self._gtype)
+                    self._fd = libraster.Rast_open_new(self.name, self._gtype)
             # Here we simply overwrite the existing map without content copying
             elif self.mode == "w":
                 # warning(_(WARN_OVERWRITE.format(self)))
-                self._gtype = RTYPE[self.mtype]['grass type']
+                self._gtype = RTYPE[self.mtype]["grass type"]
                 self.segment.open(self)
                 self._fd = libraster.Rast_open_new(self.name, self._gtype)
         else:
@@ -565,7 +566,7 @@ class RasterSegment(RasterAbstractBase):
                 str_err = _("Raster map <{0}> does not exist")
                 raise OpenError(str_err.format(self.name))
 
-            self._gtype = RTYPE[self.mtype]['grass type']
+            self._gtype = RTYPE[self.mtype]["grass type"]
             self.segment.open(self)
             self._fd = libraster.Rast_open_new(self.name, self._gtype)
 
@@ -593,9 +594,17 @@ class RasterSegment(RasterAbstractBase):
 def random_map_only_columns(mapname, mtype, overwrite=True, factor=100):
     region = Region()
     random_map = RasterRow(mapname)
-    row_buf = Buffer((region.cols, ), mtype,
-                     buffer=(np.random.random(region.cols,) * factor).data)
-    random_map.open('w', mtype, overwrite)
+    row_buf = Buffer(
+        (region.cols,),
+        mtype,
+        buffer=(
+            np.random.random(
+                region.cols,
+            )
+            * factor
+        ).data,
+    )
+    random_map.open("w", mtype, overwrite)
     for _ in range(region.rows):
         random_map.put_row(row_buf)
     random_map.close()
@@ -605,62 +614,71 @@ def random_map_only_columns(mapname, mtype, overwrite=True, factor=100):
 def random_map(mapname, mtype, overwrite=True, factor=100):
     region = Region()
     random_map = RasterRow(mapname)
-    random_map.open('w', mtype, overwrite)
+    random_map.open("w", mtype, overwrite)
     for _ in range(region.rows):
-        row_buf = Buffer((region.cols, ), mtype,
-                         buffer=(np.random.random(region.cols,) * factor).data)
+        row_buf = Buffer(
+            (region.cols,),
+            mtype,
+            buffer=(
+                np.random.random(
+                    region.cols,
+                )
+                * factor
+            ).data,
+        )
         random_map.put_row(row_buf)
     random_map.close()
     return random_map
 
 
-def raster2numpy(rastname, mapset=''):
+def raster2numpy(rastname, mapset=""):
     """Return a numpy array from a raster map
 
     :param str rastname: the name of raster map
     :parar str mapset: the name of mapset containig raster map
     """
-    with RasterRow(rastname, mapset=mapset, mode='r') as rast:
+    with RasterRow(rastname, mapset=mapset, mode="r") as rast:
         return np.array(rast)
 
 
 def raster2numpy_img(rastname, region, color="ARGB", array=None):
     """Convert a raster map layer into a string with
-       32Bit ARGB, 24Bit RGB or 8Bit Gray little endian encoding.
+    32Bit ARGB, 24Bit RGB or 8Bit Gray little endian encoding.
 
-        Return a numpy array from a raster map of type uint8
-        that contains the colored map data as 32 bit ARGB, 32Bit RGB
-        or 8 bit image
+     Return a numpy array from a raster map of type uint8
+     that contains the colored map data as 32 bit ARGB, 32Bit RGB
+     or 8 bit image
 
-       :param rastname: The name of raster map
-       :type rastname: string
+    :param rastname: The name of raster map
+    :type rastname: string
 
-       :param region: The region to be used for raster map reading
-       :type region: grass.pygrass.gis.region.Region
+    :param region: The region to be used for raster map reading
+    :type region: grass.pygrass.gis.region.Region
 
-       :param color: "ARGB", "RGB", "GRAY1", "GRAY2"
-                     ARGB  -> 32Bit RGB with alpha channel (0xAARRGGBB)
-                     RGB   -> 32Bit RGB (0xffRRGGBB)
-                     GRAY1 -> grey scale formular: .33R+ .5G+ .17B
-                     GRAY2 -> grey scale formular: .30R+ .59G+ .11B
-       :type color: String
+    :param color: "ARGB", "RGB", "GRAY1", "GRAY2"
+                  ARGB  -> 32Bit RGB with alpha channel (0xAARRGGBB)
+                  RGB   -> 32Bit RGB (0xffRRGGBB)
+                  GRAY1 -> grey scale formular: .33R+ .5G+ .17B
+                  GRAY2 -> grey scale formular: .30R+ .59G+ .11B
+    :type color: String
 
-       :param array: A numpy array (optional) to store the image,
-                     the array needs to setup as follows:
+    :param array: A numpy array (optional) to store the image,
+                  the array needs to setup as follows:
 
-                     array = np.ndarray((region.rows*region.cols*scale), np.uint8)
+                  array = np.ndarray((region.rows*region.cols*scale), np.uint8)
 
-                     scale = 4 in case of ARGB and RGB or scale = 1
-                     in case of Gray scale
-       :type array: numpy.ndarray
+                  scale = 4 in case of ARGB and RGB or scale = 1
+                  in case of Gray scale
+    :type array: numpy.ndarray
 
-       :return: A numpy array of size rows*cols*4 in case of ARGB, RGB and
-                rows*cols*1 in case of gray scale
+    :return: A numpy array of size rows*cols*4 in case of ARGB, RGB and
+             rows*cols*1 in case of gray scale
 
-       Attention: This function will change the computational raster region
-       of the current process while running.
+    Attention: This function will change the computational raster region
+    of the current process while running.
     """
     from copy import deepcopy
+
     region_orig = deepcopy(region)
     # Set the raster region
     region.set_raster_region()
@@ -683,8 +701,9 @@ def raster2numpy_img(rastname, region, color="ARGB", array=None):
     if array is None:
         array = np.ndarray((region.rows * region.cols * scale), np.uint8)
 
-    libraster.Rast_map_to_img_str(rastname, color_mode,
-                                  array.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)))
+    libraster.Rast_map_to_img_str(
+        rastname, color_mode, array.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))
+    )
     # Restore the raster region
     region_orig.set_raster_region()
 
@@ -703,24 +722,31 @@ def numpy2raster(array, mtype, rastname, overwrite=False):
     if (reg.rows, reg.cols) != array.shape:
         msg = "Region and array are different: %r != %r"
         raise TypeError(msg % ((reg.rows, reg.cols), array.shape))
-    with RasterRow(rastname, mode='w', mtype=mtype, overwrite=overwrite) as new:
+    with RasterRow(rastname, mode="w", mtype=mtype, overwrite=overwrite) as new:
         newrow = Buffer((array.shape[1],), mtype=mtype)
         for row in array:
             newrow[:] = row[:]
             new.put_row(newrow)
 
+
 if __name__ == "__main__":
 
     import doctest
     from grass.pygrass.modules import Module
+
     Module("g.region", n=40, s=0, e=40, w=0, res=10)
-    Module("r.mapcalc",
+    Module(
+        "r.mapcalc",
         expression="%s = row() + (10 * col())" % (test_raster_name),
-        overwrite=True)
-    Module("r.support", map=test_raster_name,
+        overwrite=True,
+    )
+    Module(
+        "r.support",
+        map=test_raster_name,
         title="A test map",
         history="Generated by r.mapcalc",
-        description="This is a test map")
+        description="This is a test map",
+    )
     cats = """11:A
             12:B
             13:C
@@ -737,17 +763,14 @@ if __name__ == "__main__":
             42:n
             43:O
             44:P"""
-    Module("r.category", rules="-", map=test_raster_name,
-           stdin_=cats, separator=":")
+    Module("r.category", rules="-", map=test_raster_name, stdin_=cats, separator=":")
 
     doctest.testmod()
 
     """Remove the generated vector map, if exist"""
-    mset = utils.get_mapset_raster(test_raster_name, mapset='')
+    mset = utils.get_mapset_raster(test_raster_name, mapset="")
     if mset:
-        Module("g.remove", flags='f', type='raster', name=test_raster_name)
-    mset = utils.get_mapset_raster(test_raster_name + "_segment",
-                                   mapset='')
+        Module("g.remove", flags="f", type="raster", name=test_raster_name)
+    mset = utils.get_mapset_raster(test_raster_name + "_segment", mapset="")
     if mset:
-        Module("g.remove", flags='f', type='raster',
-               name=test_raster_name + "_segment")
+        Module("g.remove", flags="f", type="raster", name=test_raster_name + "_segment")

+ 105 - 65
python/grass/pygrass/raster/abstract.py

@@ -4,8 +4,15 @@ Created on Fri Aug 17 16:05:25 2012
 
 @author: pietro
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 import ctypes
 
 #
@@ -46,20 +53,20 @@ proj: {proj}
 
 
 class Info(object):
-    def __init__(self, name, mapset=''):
+    def __init__(self, name, mapset=""):
         """Read the information for a raster map. ::
 
-            >>> info = Info(test_raster_name)
-            >>> info.read()
-            >>> info          # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
-            abstract_test_map@
-            rows: 4
-            cols: 4
-            north: 40.0 south: 0.0 nsres:10.0
-            east:  40.0 west: 0.0 ewres:10.0
-            range: 11, 44
-            ...
-            <BLANKLINE>
+        >>> info = Info(test_raster_name)
+        >>> info.read()
+        >>> info          # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+        abstract_test_map@
+        rows: 4
+        cols: 4
+        north: 40.0 south: 0.0 nsres:10.0
+        east:  40.0 west: 0.0 ewres:10.0
+        range: 11, 44
+        ...
+        <BLANKLINE>
 
         """
         self.name = name
@@ -68,7 +75,7 @@ class Info(object):
         self.c_range = None
 
     def _get_range(self):
-        if self.mtype == 'CELL':
+        if self.mtype == "CELL":
             self.c_range = ctypes.pointer(libraster.Range())
             libraster.Rast_read_range(self.name, self.mapset, self.c_range)
         else:
@@ -164,9 +171,9 @@ class Info(object):
         band_ref = None
         p_filename = ctypes.c_char_p()
         p_band_ref = ctypes.c_char_p()
-        ret = libraster.Rast_read_band_reference(self.name, self.mapset,
-                                                 ctypes.byref(p_filename),
-                                                 ctypes.byref(p_band_ref))
+        ret = libraster.Rast_read_band_reference(
+            self.name, self.mapset, ctypes.byref(p_filename), ctypes.byref(p_band_ref)
+        )
         if ret:
             band_ref = utils.decode(p_band_ref.value)
             libgis.G_free(p_filename)
@@ -183,6 +190,7 @@ class Info(object):
         if band_reference:
             # assign
             from grass.bandref import BandReferenceReader, BandReferenceReaderError
+
             reader = BandReferenceReader()
             # determine filename (assuming that band_reference is unique!)
             try:
@@ -195,9 +203,7 @@ class Info(object):
                 raise
 
             # write band reference
-            libraster.Rast_write_band_reference(self.name,
-                                                filename,
-                                                band_reference)
+            libraster.Rast_write_band_reference(self.name, filename, band_reference)
         else:
             libraster.Rast_remove_band_reference(self.name)
 
@@ -220,19 +226,46 @@ class Info(object):
     vdatum = property(_get_vdatum, _set_vdatum)
 
     def __repr__(self):
-        return INFO.format(name=self.name, mapset=self.mapset,
-                           rows=self.rows, cols=self.cols,
-                           north=self.north, south=self.south,
-                           east=self.east, west=self.west,
-                           top=self.top, bottom=self.bottom,
-                           nsres=self.nsres, ewres=self.ewres,
-                           tbres=self.tbres, zone=self.zone,
-                           proj=self.proj, min=self.min, max=self.max)
+        return INFO.format(
+            name=self.name,
+            mapset=self.mapset,
+            rows=self.rows,
+            cols=self.cols,
+            north=self.north,
+            south=self.south,
+            east=self.east,
+            west=self.west,
+            top=self.top,
+            bottom=self.bottom,
+            nsres=self.nsres,
+            ewres=self.ewres,
+            tbres=self.tbres,
+            zone=self.zone,
+            proj=self.proj,
+            min=self.min,
+            max=self.max,
+        )
 
     def keys(self):
-        return ['name', 'mapset', 'rows', 'cols', 'north', 'south',
-                'east', 'west', 'top', 'bottom', 'nsres', 'ewres', 'tbres',
-                'zone', 'proj', 'min', 'max']
+        return [
+            "name",
+            "mapset",
+            "rows",
+            "cols",
+            "north",
+            "south",
+            "east",
+            "west",
+            "top",
+            "bottom",
+            "nsres",
+            "ewres",
+            "tbres",
+            "zone",
+            "proj",
+            "min",
+            "max",
+        ]
 
     def items(self):
         return [(k, self.__getattribute__(k)) for k in self.keys()]
@@ -241,8 +274,7 @@ class Info(object):
         return ((k, self.__getattribute__(k)) for k in self.keys())
 
     def _repr_html_(self):
-        return dict2html(dict(self.items()), keys=self.keys(),
-                         border='1', kdec='b')
+        return dict2html(dict(self.items()), keys=self.keys(), border="1", kdec="b")
 
 
 class RasterAbstractBase(object):
@@ -295,8 +327,8 @@ class RasterAbstractBase(object):
         self.info = Info(self.name, self.mapset)
         self._aopen = aopen
         self._kwopen = kwopen
-        self._mtype = 'CELL'
-        self._mode = 'r'
+        self._mtype = "CELL"
+        self._mode = "r"
         self._overwrite = False
 
     def __enter__(self):
@@ -312,11 +344,11 @@ class RasterAbstractBase(object):
 
     def _set_mtype(self, mtype):
         """Private method to change the Raster type"""
-        if mtype.upper() not in ('CELL', 'FCELL', 'DCELL'):
+        if mtype.upper() not in ("CELL", "FCELL", "DCELL"):
             str_err = "Raster type: {0} not supported ('CELL','FCELL','DCELL')"
             raise ValueError(_(str_err).format(mtype))
         self._mtype = mtype
-        self._gtype = RTYPE[self.mtype]['grass type']
+        self._gtype = RTYPE[self.mtype]["grass type"]
 
     mtype = property(fget=_get_mtype, fset=_set_mtype)
 
@@ -324,7 +356,7 @@ class RasterAbstractBase(object):
         return self._mode
 
     def _set_mode(self, mode):
-        if mode.upper() not in ('R', 'W'):
+        if mode.upper() not in ("R", "W"):
             str_err = _("Mode type: {0} not supported ('r', 'w')")
             raise ValueError(str_err.format(mode))
         self._mode = mode
@@ -391,7 +423,11 @@ class RasterAbstractBase(object):
             if key < 0:  # Handle negative indices
                 key += self._rows
             if key >= self._rows:
-                raise IndexError("The row index {0} is out of range [0, {1}).".format(key, self._rows))
+                raise IndexError(
+                    "The row index {0} is out of range [0, {1}).".format(
+                        key, self._rows
+                    )
+                )
             return self.get_row(key)
         else:
             fatal("Invalid argument type.")
@@ -414,9 +450,9 @@ class RasterAbstractBase(object):
         True
         """
         if self.name:
-            if self.mapset == '':
+            if self.mapset == "":
                 mapset = utils.get_mapset_raster(self.name, self.mapset)
-                self.mapset = mapset if mapset else ''
+                self.mapset = mapset if mapset else ""
                 return True if mapset else False
             return bool(utils.get_mapset_raster(self.name, self.mapset))
         else:
@@ -445,7 +481,7 @@ class RasterAbstractBase(object):
         """Remove the map"""
         if self.is_open():
             self.close()
-        utils.remove(self.name, 'rast')
+        utils.remove(self.name, "rast")
 
     def fullname(self):
         """Return the full name of a raster map: name@mapset"""
@@ -468,7 +504,7 @@ class RasterAbstractBase(object):
 
         gis_env = gisenv()
 
-        if mapset and mapset != gis_env['MAPSET']:
+        if mapset and mapset != gis_env["MAPSET"]:
             return "{name}@{mapset}".format(name=name, mapset=mapset)
         else:
             return name
@@ -476,39 +512,38 @@ class RasterAbstractBase(object):
     def rename(self, newname):
         """Rename the map"""
         if self.exist():
-            utils.rename(self.name, newname, 'rast')
+            utils.rename(self.name, newname, "rast")
         self._name = newname
 
-    def set_region_from_rast(self, rastname='', mapset=''):
+    def set_region_from_rast(self, rastname="", mapset=""):
         """Set the computational region from a map,
-           if rastername and mapset is not specify, use itself.
-           This region will be used by all
-           raster map layers that are opened in the same process.
+        if rastername and mapset is not specify, use itself.
+        This region will be used by all
+        raster map layers that are opened in the same process.
 
-           The GRASS region settings will not be modified.
+        The GRASS region settings will not be modified.
 
-           call C function `Rast_get_cellhd`, `Rast_set_window`
+        call C function `Rast_get_cellhd`, `Rast_set_window`
 
-           """
+        """
         if self.is_open():
             fatal("You cannot change the region if map is open")
             raise
         region = Region()
-        if rastname == '':
+        if rastname == "":
             rastname = self.name
-        if mapset == '':
+        if mapset == "":
             mapset = self.mapset
 
-        libraster.Rast_get_cellhd(rastname, mapset,
-                                  region.byref())
+        libraster.Rast_get_cellhd(rastname, mapset, region.byref())
         self._set_raster_window(region)
 
     def set_region(self, region):
         """Set the computational region that can be different from the
-           current region settings. This region will be used by all
-           raster map layers that are opened in the same process.
+        current region settings. This region will be used by all
+        raster map layers that are opened in the same process.
 
-           The GRASS region settings will not be modified.
+        The GRASS region settings will not be modified.
         """
         if self.is_open():
             fatal("You cannot change the region if map is open")
@@ -574,12 +609,12 @@ class RasterAbstractBase(object):
         self.cats.write(self)
 
     @must_be_open
-    def read_cats_rules(self, filename, sep=':'):
+    def read_cats_rules(self, filename, sep=":"):
         """Read category from the raster map file"""
         self.cats.read_rules(filename, sep)
 
     @must_be_open
-    def write_cats_rules(self, filename, sep=':'):
+    def write_cats_rules(self, filename, sep=":"):
         """Write category to the raster map file"""
         self.cats.write_rules(filename, sep)
 
@@ -605,17 +640,22 @@ class RasterAbstractBase(object):
         """Set or update a category"""
         self.cats.set_cat(index, (label, min_cat, max_cat))
 
+
 if __name__ == "__main__":
 
     import doctest
     from grass.pygrass.modules import Module
+
     Module("g.region", n=40, s=0, e=40, w=0, res=10)
-    Module("r.mapcalc", expression="%s = row() + (10 * col())" % (test_raster_name),
-        overwrite=True)
+    Module(
+        "r.mapcalc",
+        expression="%s = row() + (10 * col())" % (test_raster_name),
+        overwrite=True,
+    )
 
     doctest.testmod()
 
     """Remove the generated vector map, if exist"""
-    mset = utils.get_mapset_raster(test_raster_name, mapset='')
+    mset = utils.get_mapset_raster(test_raster_name, mapset="")
     if mset:
-        Module("g.remove", flags='f', type='raster', name=test_raster_name)
+        Module("g.remove", flags="f", type="raster", name=test_raster_name)

+ 15 - 12
python/grass/pygrass/raster/buffer.py

@@ -4,11 +4,11 @@ import ctypes
 import numpy as np
 
 
-_CELL = ('int', 'int0', 'int8', 'int16', 'int32', 'int64')
+_CELL = ("int", "int0", "int8", "int16", "int32", "int64")
 CELL = tuple([getattr(np, attr) for attr in _CELL if hasattr(np, attr)])
-_FCELL = 'float', 'float16', 'float32'
+_FCELL = "float", "float16", "float32"
 FCELL = tuple([getattr(np, attr) for attr in _FCELL if hasattr(np, attr)])
-_DCELL = 'float64', 'float128'
+_DCELL = "float64", "float128"
 DCELL = tuple([getattr(np, attr) for attr in _DCELL if hasattr(np, attr)])
 
 
@@ -16,31 +16,34 @@ class Buffer(np.ndarray):
     """shape, mtype='FCELL', buffer=None, offset=0,
     strides=None, order=None
     """
+
     @property
     def mtype(self):
         if self.dtype in CELL:
-            return 'CELL'
+            return "CELL"
         elif self.dtype in FCELL:
-            return 'FCELL'
+            return "FCELL"
         elif self.dtype in DCELL:
             return DCELL
         else:
             err = "Raster type: %r not supported by GRASS."
             raise TypeError(err % self.dtype)
 
-    def __new__(cls, shape, mtype='FCELL', buffer=None, offset=0,
-                strides=None, order=None):
-        obj = np.ndarray.__new__(cls, shape, RTYPE[mtype]['numpy'],
-                                 buffer, offset, strides, order)
-        obj.pointer_type = ctypes.POINTER(RTYPE[mtype]['ctypes'])
+    def __new__(
+        cls, shape, mtype="FCELL", buffer=None, offset=0, strides=None, order=None
+    ):
+        obj = np.ndarray.__new__(
+            cls, shape, RTYPE[mtype]["numpy"], buffer, offset, strides, order
+        )
+        obj.pointer_type = ctypes.POINTER(RTYPE[mtype]["ctypes"])
         obj.p = obj.ctypes.data_as(obj.pointer_type)
         return obj
 
     def __array_finalize__(self, obj):
         if obj is None:
             return
-        self.pointer_type = getattr(obj, 'pointer_type', None)
-        self.p = getattr(obj, 'p', None)
+        self.pointer_type = getattr(obj, "pointer_type", None)
+        self.p = getattr(obj, "p", None)
 
     def __array_wrap__(self, out_arr, context=None):
         """See:

+ 91 - 83
python/grass/pygrass/raster/history.py

@@ -12,15 +12,23 @@ from grass.pygrass.utils import decode
 
 
 class History(object):
-    """History class help to manage all the metadata of a raster map
-    """
-
-    def __init__(self, name, mapset='', mtype='',
-                 creator='', src1='', src2='', keyword='',
-                 date='', title=''):
+    """History class help to manage all the metadata of a raster map"""
+
+    def __init__(
+        self,
+        name,
+        mapset="",
+        mtype="",
+        creator="",
+        src1="",
+        src2="",
+        keyword="",
+        date="",
+        title="",
+    ):
         self.c_hist = ctypes.pointer(libraster.History())
         #                'Tue Nov  7 01:11:23 2006'
-        self.date_fmt = '%a %b  %d %H:%M:%S %Y'
+        self.date_fmt = "%a %b  %d %H:%M:%S %Y"
         self.name = name
         self.mapset = mapset
         self.mtype = mtype
@@ -30,12 +38,22 @@ class History(object):
         self.keyword = keyword
         self.date = date
         self.title = title
-        self.attrs = ['name', 'mapset', 'mtype', 'creator', 'src1', 'src2',
-                      'keyword', 'date', 'title']
+        self.attrs = [
+            "name",
+            "mapset",
+            "mtype",
+            "creator",
+            "src1",
+            "src2",
+            "keyword",
+            "date",
+            "title",
+        ]
 
     def __repr__(self):
-        return "History(%s)" % ', '.join(["%s=%r" % (self.attr, getattr(self, attr))
-                                          for attr in self.attrs])
+        return "History(%s)" % ", ".join(
+            ["%s=%r" % (self.attr, getattr(self, attr)) for attr in self.attrs]
+        )
 
     def __del__(self):
         """Rast_free_history"""
@@ -56,68 +74,67 @@ class History(object):
     # ----------------------------------------------------------------------
     # libraster.HIST_CREATOR
     def _get_creator(self):
-        return decode(libraster.Rast_get_history(self.c_hist,
-                                                 libraster.HIST_CREATOR))
+        return decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_CREATOR))
 
     def _set_creator(self, creator):
         creator = encode(creator)
-        return libraster.Rast_set_history(self.c_hist,
-                                          libraster.HIST_CREATOR,
-                                          ctypes.c_char_p(creator))
+        return libraster.Rast_set_history(
+            self.c_hist, libraster.HIST_CREATOR, ctypes.c_char_p(creator)
+        )
 
-    creator = property(fget=_get_creator, fset=_set_creator,
-                       doc="Set or obtain the creator of map")
+    creator = property(
+        fget=_get_creator, fset=_set_creator, doc="Set or obtain the creator of map"
+    )
 
     # ----------------------------------------------------------------------
     # libraster.HIST_DATSRC_1
     def _get_src1(self):
-        return decode(libraster.Rast_get_history(self.c_hist,
-                                                 libraster.HIST_DATSRC_1))
+        return decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_DATSRC_1))
 
     def _set_src1(self, src1):
         src1 = encode(src1)
-        return libraster.Rast_set_history(self.c_hist,
-                                          libraster.HIST_DATSRC_1,
-                                          ctypes.c_char_p(src1))
+        return libraster.Rast_set_history(
+            self.c_hist, libraster.HIST_DATSRC_1, ctypes.c_char_p(src1)
+        )
 
-    src1 = property(fget=_get_src1, fset=_set_src1,
-                    doc="Set or obtain the first source of map")
+    src1 = property(
+        fget=_get_src1, fset=_set_src1, doc="Set or obtain the first source of map"
+    )
 
     # ----------------------------------------------------------------------
     # libraster.HIST_DATSRC_2
     def _get_src2(self):
-        return decode(libraster.Rast_get_history(self.c_hist,
-                                                 libraster.HIST_DATSRC_2))
+        return decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_DATSRC_2))
 
     def _set_src2(self, src2):
         src2 = encode(src2)
-        return libraster.Rast_set_history(self.c_hist,
-                                          libraster.HIST_DATSRC_2,
-                                          ctypes.c_char_p(src2))
+        return libraster.Rast_set_history(
+            self.c_hist, libraster.HIST_DATSRC_2, ctypes.c_char_p(src2)
+        )
 
-    src2 = property(fget=_get_src2, fset=_set_src2,
-                    doc="Set or obtain the second source of map")
+    src2 = property(
+        fget=_get_src2, fset=_set_src2, doc="Set or obtain the second source of map"
+    )
 
     # ----------------------------------------------------------------------
     # libraster.HIST_KEYWORD
     def _get_keyword(self):
-        return decode(libraster.Rast_get_history(self.c_hist,
-                                                 libraster.HIST_KEYWRD))
+        return decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_KEYWRD))
 
     def _set_keyword(self, keyword):
         keyword = encode(keyword)
-        return libraster.Rast_set_history(self.c_hist,
-                                          libraster.HIST_KEYWRD,
-                                          ctypes.c_char_p(keyword))
+        return libraster.Rast_set_history(
+            self.c_hist, libraster.HIST_KEYWRD, ctypes.c_char_p(keyword)
+        )
 
-    keyword = property(fget=_get_keyword, fset=_set_keyword,
-                       doc="Set or obtain the keywords of map")
+    keyword = property(
+        fget=_get_keyword, fset=_set_keyword, doc="Set or obtain the keywords of map"
+    )
 
     # ----------------------------------------------------------------------
     # libraster.HIST_MAPID
     def _get_date(self):
-        date_str = decode(libraster.Rast_get_history(self.c_hist,
-                                                     libraster.HIST_MAPID))
+        date_str = decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_MAPID))
         if date_str:
             try:
                 return datetime.datetime.strptime(date_str, self.date_fmt)
@@ -128,42 +145,41 @@ class History(object):
         if datetimeobj:
             date_str = datetimeobj.strftime(self.date_fmt)
             date_str = encode(date_str)
-            return libraster.Rast_set_history(self.c_hist,
-                                              libraster.HIST_MAPID,
-                                              ctypes.c_char_p(date_str))
+            return libraster.Rast_set_history(
+                self.c_hist, libraster.HIST_MAPID, ctypes.c_char_p(date_str)
+            )
 
-    date = property(fget=_get_date, fset=_set_date,
-                    doc="Set or obtain the date of map")
+    date = property(fget=_get_date, fset=_set_date, doc="Set or obtain the date of map")
 
     # ----------------------------------------------------------------------
     # libraster.HIST_MAPSET
     def _get_mapset(self):
-        return decode(libraster.Rast_get_history(self.c_hist,
-                                                 libraster.HIST_MAPSET))
+        return decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_MAPSET))
 
     def _set_mapset(self, mapset):
         mapset = encode(mapset)
-        return libraster.Rast_set_history(self.c_hist,
-                                          libraster.HIST_MAPSET,
-                                          ctypes.c_char_p(mapset))
+        return libraster.Rast_set_history(
+            self.c_hist, libraster.HIST_MAPSET, ctypes.c_char_p(mapset)
+        )
 
-    mapset = property(fget=_get_mapset, fset=_set_mapset,
-                      doc="Set or obtain the mapset of map")
+    mapset = property(
+        fget=_get_mapset, fset=_set_mapset, doc="Set or obtain the mapset of map"
+    )
 
     # ----------------------------------------------------------------------
     # libraster.HIST_MAPTYPE
     def _get_maptype(self):
-        return decode(libraster.Rast_get_history(self.c_hist,
-                                                 libraster.HIST_MAPTYPE))
+        return decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_MAPTYPE))
 
     def _set_maptype(self, maptype):
         maptype = encode(maptype)
-        return libraster.Rast_set_history(self.c_hist,
-                                          libraster.HIST_MAPTYPE,
-                                          ctypes.c_char_p(maptype))
+        return libraster.Rast_set_history(
+            self.c_hist, libraster.HIST_MAPTYPE, ctypes.c_char_p(maptype)
+        )
 
-    maptype = property(fget=_get_maptype, fset=_set_maptype,
-                       doc="Set or obtain the type of map")
+    maptype = property(
+        fget=_get_maptype, fset=_set_maptype, doc="Set or obtain the type of map"
+    )
 
     # ----------------------------------------------------------------------
     # libraster.HIST_NUM_FIELDS
@@ -183,28 +199,25 @@ class History(object):
     # ----------------------------------------------------------------------
     # libraster.HIST_TITLE
     def _get_title(self):
-        return decode(libraster.Rast_get_history(self.c_hist,
-                                                 libraster.HIST_TITLE))
+        return decode(libraster.Rast_get_history(self.c_hist, libraster.HIST_TITLE))
 
     def _set_title(self, title):
         title = encode(title)
-        return libraster.Rast_set_history(self.c_hist,
-                                          libraster.HIST_TITLE,
-                                          ctypes.c_char_p(title))
+        return libraster.Rast_set_history(
+            self.c_hist, libraster.HIST_TITLE, ctypes.c_char_p(title)
+        )
 
-    title = property(fget=_get_title, fset=_set_title,
-                     doc="Set or obtain the title of map")
+    title = property(
+        fget=_get_title, fset=_set_title, doc="Set or obtain the title of map"
+    )
 
     def append(self, obj):
         """Rast_append_history"""
-        libraster.Rast_append_history(self.c_hist,
-                                      ctypes.c_char_p(str(obj)))
+        libraster.Rast_append_history(self.c_hist, ctypes.c_char_p(str(obj)))
 
     def append_fmt(self, fmt, *args):
         """Rast_append_format_history"""
-        libraster.Rast_append_format_history(self.c_hist,
-                                             ctypes.c_char_p(fmt),
-                                             *args)
+        libraster.Rast_append_format_history(self.c_hist, ctypes.c_char_p(fmt), *args)
 
     def clear(self):
         """Clear the history"""
@@ -216,10 +229,9 @@ class History(object):
 
     def format(self, field, fmt, *args):
         """Rast_format_history"""
-        libraster.Rast_format_history(self.c_hist,
-                                      ctypes.c_int(field),
-                                      ctypes.c_char_p(fmt),
-                                      *args)
+        libraster.Rast_format_history(
+            self.c_hist, ctypes.c_int(field), ctypes.c_char_p(fmt), *args
+        )
 
     def length(self):
         """Rast_history_length"""
@@ -227,8 +239,7 @@ class History(object):
 
     def line(self, line):
         """Rast_history_line"""
-        return libraster.Rast_history_line(self.c_hist,
-                                           ctypes.c_int(line))
+        return libraster.Rast_history_line(self.c_hist, ctypes.c_int(line))
 
     def read(self):
         """Read the history of map, users need to use this function to
@@ -245,11 +256,8 @@ class History(object):
 
     def write(self):
         """Rast_write_history"""
-        libraster.Rast_write_history(self.name,
-                                     self.c_hist)
+        libraster.Rast_write_history(self.name, self.c_hist)
 
     def short(self):
         """Rast_short_history"""
-        libraster.Rast_short_history(self.name,
-                                     'raster',
-                                     self.c_hist)
+        libraster.Rast_short_history(self.name, "raster", self.c_hist)

+ 25 - 15
python/grass/pygrass/raster/raster_type.py

@@ -9,20 +9,30 @@ import ctypes
 import numpy as np
 
 ## Private dictionary to convert RASTER_TYPE into type string.
-RTYPE_STR = {libraster.CELL_TYPE: 'CELL',
-             libraster.FCELL_TYPE: 'FCELL',
-             libraster.DCELL_TYPE: 'DCELL'}
+RTYPE_STR = {
+    libraster.CELL_TYPE: "CELL",
+    libraster.FCELL_TYPE: "FCELL",
+    libraster.DCELL_TYPE: "DCELL",
+}
 
 
-TYPE = {'CELL': {'grass type': libraster.CELL_TYPE,
-                  'grass def': libraster.CELL,
-                  'numpy': np.int32,
-                  'ctypes': ctypes.c_int},
-        'FCELL': {'grass type': libraster.FCELL_TYPE,
-                  'grass def': libraster.FCELL,
-                  'numpy': np.float32,
-                  'ctypes': ctypes.c_float},
-        'DCELL': {'grass type': libraster.DCELL_TYPE,
-                  'grass def': libraster.DCELL,
-                  'numpy': np.float64,
-                  'ctypes': ctypes.c_double}}
+TYPE = {
+    "CELL": {
+        "grass type": libraster.CELL_TYPE,
+        "grass def": libraster.CELL,
+        "numpy": np.int32,
+        "ctypes": ctypes.c_int,
+    },
+    "FCELL": {
+        "grass type": libraster.FCELL_TYPE,
+        "grass def": libraster.FCELL,
+        "numpy": np.float32,
+        "ctypes": ctypes.c_float,
+    },
+    "DCELL": {
+        "grass type": libraster.DCELL_TYPE,
+        "grass def": libraster.DCELL,
+        "numpy": np.float64,
+        "ctypes": ctypes.c_double,
+    },
+}

+ 23 - 20
python/grass/pygrass/raster/rowio.py

@@ -13,37 +13,34 @@ from grass.pygrass.errors import GrassError
 from grass.pygrass.raster.raster_type import TYPE as RTYPE
 
 
-CMPFUNC = ctypes.CFUNCTYPE(ctypes.c_int,
-                           ctypes.c_int, ctypes.c_void_p,
-                           ctypes.c_int, ctypes.c_int)
+CMPFUNC = ctypes.CFUNCTYPE(
+    ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_int
+)
 
 
 def getmaprow_CELL(fd, buf, row, l):
-    librast.Rast_get_c_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.CELL)),
-                           row)
+    librast.Rast_get_c_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.CELL)), row)
     return 1
 
 
 def getmaprow_FCELL(fd, buf, row, l):
-    librast.Rast_get_f_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.FCELL)),
-                           row)
+    librast.Rast_get_f_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.FCELL)), row)
     return 1
 
 
 def getmaprow_DCELL(fd, buf, row, l):
-    librast.Rast_get_d_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.DCELL)),
-                           row)
+    librast.Rast_get_d_row(fd, ctypes.cast(buf, ctypes.POINTER(librast.DCELL)), row)
     return 1
 
+
 get_row = {
-    'CELL': CMPFUNC(getmaprow_CELL),
-    'FCELL': CMPFUNC(getmaprow_FCELL),
-    'DCELL': CMPFUNC(getmaprow_DCELL),
+    "CELL": CMPFUNC(getmaprow_CELL),
+    "FCELL": CMPFUNC(getmaprow_FCELL),
+    "DCELL": CMPFUNC(getmaprow_DCELL),
 }
 
 
 class RowIO(object):
-
     def __init__(self):
         self.c_rowio = librowio.ROWIO()
         self.fd = None
@@ -57,13 +54,19 @@ class RowIO(object):
         self.rows = rows
         self.cols = cols
         self.mtype = mtype
-        self.row_size = ctypes.sizeof(RTYPE[mtype]['grass def'] * cols)
-        if (librowio.Rowio_setup(ctypes.byref(self.c_rowio), self.fd,
-                                 self.rows,
-                                 self.row_size,
-                                 get_row[self.mtype],
-                                 get_row[self.mtype]) == -1):
-            raise GrassError('Fatal error, Rowio not setup correctly.')
+        self.row_size = ctypes.sizeof(RTYPE[mtype]["grass def"] * cols)
+        if (
+            librowio.Rowio_setup(
+                ctypes.byref(self.c_rowio),
+                self.fd,
+                self.rows,
+                self.row_size,
+                get_row[self.mtype],
+                get_row[self.mtype],
+            )
+            == -1
+        ):
+            raise GrassError("Fatal error, Rowio not setup correctly.")
 
     def release(self):
         librowio.Rowio_release(ctypes.byref(self.c_rowio))

+ 48 - 40
python/grass/pygrass/raster/segment.py

@@ -27,58 +27,72 @@ class Segment(object):
     def nseg(self):
         rows = self.rows()
         cols = self.cols()
-        return int(((rows + self.srows - 1) / self.srows) *
-                   ((cols + self.scols - 1) / self.scols))
+        return int(
+            ((rows + self.srows - 1) / self.srows)
+            * ((cols + self.scols - 1) / self.scols)
+        )
 
     def segments_in_mem(self):
         if self.maxmem > 0 and self.maxmem < 100:
             seg_in_mem = (self.maxmem * self.nseg()) / 100
         else:
-            seg_in_mem = 4 * (self.rows() / self.srows +
-                              self.cols() / self.scols + 2)
+            seg_in_mem = 4 * (self.rows() / self.srows + self.cols() / self.scols + 2)
         if seg_in_mem == 0:
             seg_in_mem = 1
         return seg_in_mem
 
     def open(self, mapobj):
-        """Open a segment it is necessary to pass a RasterSegment object.
-
-        """
-        self.val = RTYPE[mapobj.mtype]['grass def']()
-        size = ctypes.sizeof(RTYPE[mapobj.mtype]['ctypes'])
+        """Open a segment it is necessary to pass a RasterSegment object."""
+        self.val = RTYPE[mapobj.mtype]["grass def"]()
+        size = ctypes.sizeof(RTYPE[mapobj.mtype]["ctypes"])
         file_name = libgis.G_tempfile()
-        libseg.Segment_open(self.c_seg, file_name,
-                            self.rows(), self.cols(),
-                            self.srows, self.scols,
-                            size,
-                            self.nseg())
+        libseg.Segment_open(
+            self.c_seg,
+            file_name,
+            self.rows(),
+            self.cols(),
+            self.srows,
+            self.scols,
+            size,
+            self.nseg(),
+        )
         self.flush()
 
-    def format(self, mapobj, file_name='', fill=True):
+    def format(self, mapobj, file_name="", fill=True):
         """The segmentation routines require a disk file to be used for paging
         segments in and out of memory. This routine formats the file open for
         write on file descriptor fd for use as a segment file.
         """
-        if file_name == '':
+        if file_name == "":
             file_name = libgis.G_tempfile()
-        mapobj.temp_file = open(file_name, 'w')
-        size = ctypes.sizeof(RTYPE[mapobj.mtype]['ctypes'])
+        mapobj.temp_file = open(file_name, "w")
+        size = ctypes.sizeof(RTYPE[mapobj.mtype]["ctypes"])
         if fill:
-            libseg.Segment_format(mapobj.temp_file.fileno(), self.rows(),
-                                  self.cols(), self.srows, self.scols, size)
+            libseg.Segment_format(
+                mapobj.temp_file.fileno(),
+                self.rows(),
+                self.cols(),
+                self.srows,
+                self.scols,
+                size,
+            )
         else:
-            libseg.Segment_format_nofill(mapobj.temp_file.fileno(),
-                                         self.rows(), self.cols(),
-                                         self.srows, self.scols, size)
+            libseg.Segment_format_nofill(
+                mapobj.temp_file.fileno(),
+                self.rows(),
+                self.cols(),
+                self.srows,
+                self.scols,
+                size,
+            )
         # TODO: why should I close and then re-open it?
         mapobj.temp_file.close()
 
-    def init(self, mapobj, file_name=''):
-        if file_name == '':
+    def init(self, mapobj, file_name=""):
+        if file_name == "":
             file_name = mapobj.temp_file.name
-        mapobj.temp_file = open(file_name, 'w')
-        libseg.Segment_init(self.c_seg, mapobj.temp_file.fileno(),
-                            self.segments_in_mem)
+        mapobj.temp_file = open(file_name, "w")
+        libseg.Segment_init(self.c_seg, mapobj.temp_file.fileno(), self.segments_in_mem)
 
     def get_row(self, row_index, buf):
         """Return the row using, the `segment` method"""
@@ -90,23 +104,17 @@ class Segment(object):
         libseg.Segment_put_row(self.c_seg, buf.p, row_index)
 
     def get(self, row_index, col_index):
-        """Return the value of the map
-        """
-        libseg.Segment_get(self.c_seg,
-                           ctypes.byref(self.val), row_index, col_index)
+        """Return the value of the map"""
+        libseg.Segment_get(self.c_seg, ctypes.byref(self.val), row_index, col_index)
         return self.val.value
 
     def put(self, row_index, col_index):
-        """Write the value to the map
-        """
-        libseg.Segment_put(self.c_seg,
-                           ctypes.byref(self.val), row_index, col_index)
+        """Write the value to the map"""
+        libseg.Segment_put(self.c_seg, ctypes.byref(self.val), row_index, col_index)
 
     def get_seg_number(self, row_index, col_index):
-        """Return the segment number
-        """
-        return row_index / self.srows * self.cols / self.scols + \
-            col_index / self.scols
+        """Return the segment number"""
+        return row_index / self.srows * self.cols / self.scols + col_index / self.scols
 
     def flush(self):
         """Flush pending updates to disk.

+ 16 - 13
python/grass/pygrass/raster/testsuite/test_category.py

@@ -22,13 +22,18 @@ class RasterCategoryTestCase(TestCase):
         """Create test raster map and region"""
         cls.use_temp_region()
         cls.runModule("g.region", n=40, s=0, e=40, w=0, res=10)
-        cls.runModule("r.mapcalc",
+        cls.runModule(
+            "r.mapcalc",
             expression="%s = row() + (10.0 * col())" % (cls.name),
-            overwrite=True)
-        cls.runModule("r.support", map=cls.name,
+            overwrite=True,
+        )
+        cls.runModule(
+            "r.support",
+            map=cls.name,
             title="A test map",
             history="Generated by r.mapcalc",
-            description="This is a test map")
+            description="This is a test map",
+        )
         cats = """11:A
                 12:B
                 13:C
@@ -46,14 +51,12 @@ class RasterCategoryTestCase(TestCase):
                 43:O
                 44:P"""
 
-        cls.runModule("r.category", rules="-", map=cls.name,
-            stdin_=cats, separator=":")
+        cls.runModule("r.category", rules="-", map=cls.name, stdin_=cats, separator=":")
 
     @classmethod
     def tearDownClass(cls):
         """Remove the generated vector map, if exist"""
-        cls.runModule("g.remove", flags='f', type='raster',
-                      name=cls.name)
+        cls.runModule("g.remove", flags="f", type="raster", name=cls.name)
         cls.del_temp_region()
 
     def testCategory(self):
@@ -66,9 +69,9 @@ class RasterCategoryTestCase(TestCase):
         r.close()
 
     def testFirstCat(self):
-        cat0 = ('A', 11, None)
-        cat7 = ('H', 24, None)
-        cat15 = ('P', 44, None)
+        cat0 = ("A", 11, None)
+        cat7 = ("H", 24, None)
+        cat15 = ("P", 44, None)
         cats = Category(self.name)
         cats.read()
         self.assertEqual(cats[0], cat0)
@@ -80,8 +83,8 @@ class RasterCategoryTestCase(TestCase):
         cats = Category(self.name)
         cats.read()
         cats.write_rules(tmpfile)
-        self.assertFilesEqualMd5(tmpfile, 'data/geology_cats')
+        self.assertFilesEqualMd5(tmpfile, "data/geology_cats")
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 12 - 7
python/grass/pygrass/raster/testsuite/test_history.py

@@ -22,19 +22,23 @@ class RasterHistoryTestCate(TestCase):
         """Create test raster map and region"""
         cls.use_temp_region()
         cls.runModule("g.region", n=40, s=0, e=40, w=0, res=10)
-        cls.runModule("r.mapcalc",
+        cls.runModule(
+            "r.mapcalc",
             expression="%s = row() + (10 * col())" % (cls.name),
-            overwrite=True)
-        cls.runModule("r.support", map=cls.name,
+            overwrite=True,
+        )
+        cls.runModule(
+            "r.support",
+            map=cls.name,
             title="A test map",
             history="Generated by r.mapcalc",
-            description="This is a test map")
+            description="This is a test map",
+        )
 
     @classmethod
     def tearDownClass(cls):
         """Remove the generated vector map, if exist"""
-        cls.runModule("g.remove", flags='f', type='raster',
-                      name=cls.name)
+        cls.runModule("g.remove", flags="f", type="raster", name=cls.name)
         cls.del_temp_region()
 
     def testHistory(self):
@@ -78,5 +82,6 @@ class RasterHistoryTestCate(TestCase):
         hist1.command()
         self.assertEqual(decode(hist1.line(0)), "test_history.py")
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     test()

+ 10 - 8
python/grass/pygrass/raster/testsuite/test_numpy.py

@@ -13,7 +13,7 @@ from grass.pygrass.raster import raster2numpy, numpy2raster, RasterRow
 def check_raster(name):
     r = RasterRow(name)
     try:
-        r.open(mode='r')
+        r.open(mode="r")
         r.close()
         return True
     except:
@@ -29,20 +29,21 @@ class NumpyTestCase(TestCase):
         """Create test raster map and region"""
         cls.use_temp_region()
         cls.runModule("g.region", n=40, s=0, e=60, w=0, res=1)
-        cls.runModule("r.mapcalc",
+        cls.runModule(
+            "r.mapcalc",
             expression="%s = float(row() + (10.0 * col()))" % (cls.name),
-            overwrite=True)
+            overwrite=True,
+        )
         cls.numpy_obj = raster2numpy(cls.name)
 
     @classmethod
     def tearDownClass(cls):
         """Remove the generated vector map, if exist"""
-        cls.runModule("g.remove", flags='f', type='raster',
-                      name=cls.name)
+        cls.runModule("g.remove", flags="f", type="raster", name=cls.name)
         cls.del_temp_region()
 
     def test_type(self):
-        self.assertTrue(str(self.numpy_obj.dtype), 'float32')
+        self.assertTrue(str(self.numpy_obj.dtype), "float32")
 
     def test_len(self):
         self.assertTrue(len(self.numpy_obj), 40)
@@ -50,8 +51,9 @@ class NumpyTestCase(TestCase):
 
     def test_write(self):
         ran = random([40, 60])
-        numpy2raster(ran, 'FCELL', self.name, True)
+        numpy2raster(ran, "FCELL", self.name, True)
         self.assertTrue(check_raster(self.name))
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     test()

+ 20 - 18
python/grass/pygrass/raster/testsuite/test_pygrass_raster.py

@@ -15,66 +15,68 @@ class RasterRowTestCase(TestCase):
         """Create test raster map and region"""
         cls.use_temp_region()
         cls.runModule("g.region", n=40, s=0, e=40, w=0, res=10)
-        cls.runModule("r.mapcalc", expression="%s = row() + (10.0 * col())" % (cls.name),
-            overwrite=True)
+        cls.runModule(
+            "r.mapcalc",
+            expression="%s = row() + (10.0 * col())" % (cls.name),
+            overwrite=True,
+        )
 
     @classmethod
     def tearDownClass(cls):
         """Remove the generated vector map, if exist"""
-        cls.runModule("g.remove", flags='f', type='raster',
-                      name=cls.name)
+        cls.runModule("g.remove", flags="f", type="raster", name=cls.name)
         cls.del_temp_region()
 
     def test_type(self):
         r = RasterRow(self.name)
-        r.open(mode='r')
-        self.assertTrue(r.mtype, 'DCELL')
+        r.open(mode="r")
+        self.assertTrue(r.mtype, "DCELL")
         r.close()
 
     def test_isopen(self):
         r = RasterRow(self.name)
         self.assertFalse(r.is_open())
-        r.open(mode='r')
+        r.open(mode="r")
         self.assertTrue(r.is_open())
         r.close()
         self.assertFalse(r.is_open())
 
     def test_name(self):
         r = RasterRow(self.name)
-        r.open(mode='r')
+        r.open(mode="r")
         self.assertEqual(r.name, self.name)
         fullname = "{name}@{mapset}".format(name=r.name, mapset=r.mapset)
         self.assertEqual(r.fullname(), fullname)
         r.close()
 
     def test_exist(self):
-        notexist = RasterRow(self.name + 'notexist')
+        notexist = RasterRow(self.name + "notexist")
         self.assertFalse(notexist.exist())
         exist = RasterRow(self.name)
         self.assertTrue(exist.exist())
 
     def test_open_r(self):
-        notexist = RasterRow(self.name + 'notexist')
+        notexist = RasterRow(self.name + "notexist")
         with self.assertRaises(OpenError):
             # raster does not exist
-            notexist.open(mode='r')
+            notexist.open(mode="r")
         r = RasterRow(self.name)
-        r.open(mode='r', mtype='FCELL')
+        r.open(mode="r", mtype="FCELL")
         # ignore the mtype if is open in read mode
-        self.assertEqual(r.mtype, 'DCELL')
+        self.assertEqual(r.mtype, "DCELL")
         r.close()
 
     def test_open_w(self):
         r = RasterRow(self.name)
         with self.assertRaises(OpenError):
             # raster type is not defined!
-            r.open(mode='w')
+            r.open(mode="w")
         with self.assertRaises(OpenError):
             # raster already exist
-            r.open(mode='w', mtype='DCELL')
+            r.open(mode="w", mtype="DCELL")
         # open in write mode and overwrite
-        r.open(mode='w', mtype='DCELL', overwrite=True)
-        self.assertTrue(r.mtype, 'DCELL')
+        r.open(mode="w", mtype="DCELL", overwrite=True)
+        self.assertTrue(r.mtype, "DCELL")
         r.close()
 
     def test_row_range(self):
@@ -89,5 +91,5 @@ class RasterRowTestCase(TestCase):
         r.close()
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     test()

+ 26 - 15
python/grass/pygrass/raster/testsuite/test_pygrass_raster_doctests.py

@@ -17,12 +17,14 @@ import grass.pygrass.raster as pgrass
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -33,14 +35,20 @@ def load_tests(loader, tests, ignore):
     # this should be called at some top level
 
     from grass.pygrass.modules import Module
+
     Module("g.region", n=40, s=0, e=40, w=0, res=10)
-    Module("r.mapcalc",
+    Module(
+        "r.mapcalc",
         expression="%s = row() + (10 * col())" % (pgrass.test_raster_name),
-        overwrite=True)
-    Module("r.support", map=pgrass.test_raster_name,
+        overwrite=True,
+    )
+    Module(
+        "r.support",
+        map=pgrass.test_raster_name,
         title="A test map",
         history="Generated by r.mapcalc",
-        description="This is a test map")
+        description="This is a test map",
+    )
     cats = """11:A
             12:B
             13:C
@@ -57,17 +65,20 @@ def load_tests(loader, tests, ignore):
             42:n
             43:O
             44:P"""
-    Module("r.category", rules="-", map=pgrass.test_raster_name,
-           stdin_=cats, separator=":")
+    Module(
+        "r.category", rules="-", map=pgrass.test_raster_name, stdin_=cats, separator=":"
+    )
 
-    Module("r.mapcalc",
+    Module(
+        "r.mapcalc",
         expression="%s = row() + (10 * col())" % (pgrass.abstract.test_raster_name),
-        overwrite=True)
+        overwrite=True,
+    )
 
     tests.addTests(doctest.DocTestSuite(pgrass))
     tests.addTests(doctest.DocTestSuite(pgrass.abstract))
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 19 - 24
python/grass/pygrass/raster/testsuite/test_raster_img.py

@@ -13,6 +13,7 @@ has_PyQt4 = False
 try:
     from PyQt4.QtCore import *
     from PyQt4.QtGui import *
+
     has_PyQt4 = True
 except:
     pass
@@ -27,16 +28,18 @@ class RasterRowImgTestCase(TestCase):
         """Create test raster map and region"""
         cls.use_temp_region()
         cls.runModule("g.region", n=60, s=0, e=40, w=0, res=0.1)
-        cls.runModule("r.mapcalc",
-            expression="%s = if(row() >= 10 && row() <= 60, null(), row()  + (10.0 * col()))" % (cls.name),
-            overwrite=True)
+        cls.runModule(
+            "r.mapcalc",
+            expression="%s = if(row() >= 10 && row() <= 60, null(), row()  + (10.0 * col()))"
+            % (cls.name),
+            overwrite=True,
+        )
         cls.runModule("r.colors", map=cls.name, color="elevation")
 
     @classmethod
     def tearDownClass(cls):
         """Remove the generated vector map, if exist"""
-        cls.runModule("g.remove", flags='f', type='raster',
-                      name=cls.name)
+        cls.runModule("g.remove", flags="f", type="raster", name=cls.name)
         cls.del_temp_region()
 
     @unittest.skipIf(has_PyQt4 is False, "Require PyQt4")
@@ -53,8 +56,7 @@ class RasterRowImgTestCase(TestCase):
 
         a = raster2numpy_img(self.name, region)
 
-        image = QImage(a.data, region.cols, region.rows,
-                       QImage.Format_ARGB32)
+        image = QImage(a.data, region.cols, region.rows, QImage.Format_ARGB32)
         # image.save("data/a.png")
         image.save(tmpfile)
         self.assertFilesEqualMd5(tmpfile, "data/a.png")
@@ -74,11 +76,9 @@ class RasterRowImgTestCase(TestCase):
         # With array as argument
         array = np.ndarray((region.rows * region.cols * 4), np.uint8)
 
-        raster2numpy_img(rastname=self.name, region=region,
-                         color="ARGB", array=array)
+        raster2numpy_img(rastname=self.name, region=region, color="ARGB", array=array)
 
-        image = QImage(array.data,
-                       region.cols, region.rows, QImage.Format_ARGB32)
+        image = QImage(array.data, region.cols, region.rows, QImage.Format_ARGB32)
         # image.save("data/b.png")
         image.save(tmpfile)
         self.assertFilesEqualMd5(tmpfile, "data/b.png")
@@ -98,11 +98,9 @@ class RasterRowImgTestCase(TestCase):
         # With array as argument
         array = np.ndarray((region.rows * region.cols * 4), np.uint8)
 
-        raster2numpy_img(rastname=self.name, region=region,
-                         color="ARGB", array=array)
+        raster2numpy_img(rastname=self.name, region=region, color="ARGB", array=array)
 
-        image = QImage(array.data,
-                       region.cols, region.rows, QImage.Format_ARGB32)
+        image = QImage(array.data, region.cols, region.rows, QImage.Format_ARGB32)
         # image.save("data/c.png")
         image.save(tmpfile)
         self.assertFilesEqualMd5(tmpfile, "data/c.png")
@@ -122,11 +120,9 @@ class RasterRowImgTestCase(TestCase):
         # With array as argument
         array = np.ndarray((region.rows * region.cols * 4), np.uint8)
 
-        raster2numpy_img(rastname=self.name, region=region,
-                         color="RGB", array=array)
+        raster2numpy_img(rastname=self.name, region=region, color="RGB", array=array)
 
-        image = QImage(array.data,
-                       region.cols, region.rows, QImage.Format_RGB32)
+        image = QImage(array.data, region.cols, region.rows, QImage.Format_RGB32)
         # image.save("data/d.png")
         image.save(tmpfile)
         self.assertFilesEqualMd5(tmpfile, "data/d.png")
@@ -143,11 +139,9 @@ class RasterRowImgTestCase(TestCase):
         tmpfile = tempfile(False)
         tmpfile = tmpfile + ".png"
 
-        array = raster2numpy_img(rastname=self.name, region=region,
-                                 color="RGB")
+        array = raster2numpy_img(rastname=self.name, region=region, color="RGB")
 
-        image = QImage(array.data,
-                       region.cols, region.rows, QImage.Format_RGB32)
+        image = QImage(array.data, region.cols, region.rows, QImage.Format_RGB32)
         # image.save("data/e.png")
         image.save(tmpfile)
         self.assertFilesEqualMd5(tmpfile, "data/e.png")
@@ -196,5 +190,6 @@ class RasterRowImgTestCase(TestCase):
 
         self.assertEqual(len(a), region.rows * region.cols * 1)
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     test()

+ 18 - 11
python/grass/pygrass/raster/testsuite/test_raster_region.py

@@ -17,14 +17,16 @@ class RasterRowRegionTestCase(TestCase):
         """Create test raster map and region"""
         cls.use_temp_region()
         cls.runModule("g.region", n=40, s=0, e=40, w=0, res=10)
-        cls.runModule("r.mapcalc", expression="%s = row() + (10.0 * col())" % (cls.name),
-            overwrite=True)
+        cls.runModule(
+            "r.mapcalc",
+            expression="%s = row() + (10.0 * col())" % (cls.name),
+            overwrite=True,
+        )
 
     @classmethod
     def tearDownClass(cls):
         """Remove the generated vector map, if exist"""
-        cls.runModule("g.remove", flags='f', type='raster',
-                      name=cls.name)
+        cls.runModule("g.remove", flags="f", type="raster", name=cls.name)
         cls.del_temp_region()
 
     def test_resampling_1(self):
@@ -41,10 +43,14 @@ class RasterRowRegionTestCase(TestCase):
 
         rast = RasterRow(self.name)
         rast.set_region(region)
-        rast.open(mode='r')
+        rast.open(mode="r")
 
-        six.assertCountEqual(self, rast[0].tolist(), [22, 22, 22, 22, 22, 32, 32, 32, 32, 32])
-        six.assertCountEqual(self, rast[5].tolist(), [23, 23, 23, 23, 23, 33, 33, 33, 33, 33])
+        six.assertCountEqual(
+            self, rast[0].tolist(), [22, 22, 22, 22, 22, 32, 32, 32, 32, 32]
+        )
+        six.assertCountEqual(
+            self, rast[5].tolist(), [23, 23, 23, 23, 23, 33, 33, 33, 33, 33]
+        )
 
         rast.close()
 
@@ -62,7 +68,7 @@ class RasterRowRegionTestCase(TestCase):
 
         rast = RasterRow(self.name)
         rast.set_region(region)
-        rast.open(mode='r')
+        rast.open(mode="r")
 
         """
         [nan, nan, nan, nan, nan, nan, nan, nan]
@@ -75,8 +81,8 @@ class RasterRowRegionTestCase(TestCase):
         [nan, nan, nan, nan, nan, nan, nan, nan]
         """
 
-        six.assertCountEqual(self, rast[2].tolist()[2:6], [11., 21., 31., 41.])
-        six.assertCountEqual(self, rast[5].tolist()[2:6], [14., 24., 34., 44.])
+        six.assertCountEqual(self, rast[2].tolist()[2:6], [11.0, 21.0, 31.0, 41.0])
+        six.assertCountEqual(self, rast[5].tolist()[2:6], [14.0, 24.0, 34.0, 44.0])
 
         rast.close()
 
@@ -110,5 +116,6 @@ class RasterRowRegionTestCase(TestCase):
 
         self.assertEqual(len(a), 8)
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     test()

+ 211 - 188
python/grass/pygrass/rpc/__init__.py

@@ -29,6 +29,7 @@ import logging
 ###############################################################################
 ###############################################################################
 
+
 class RPCDefs(object):
     # Function identifier and index
     STOP = 0
@@ -40,11 +41,11 @@ class RPCDefs(object):
 
 def _get_raster_image_as_np(lock, conn, data):
     """Convert a raster map into an image and return
-       a numpy array with RGB or Gray values.
+    a numpy array with RGB or Gray values.
 
-       :param lock: A multiprocessing.Lock instance
-       :param conn: A multiprocessing.Pipe instance used to send True or False
-       :param data: The list of data entries [function_id, raster_name, extent, color]
+    :param lock: A multiprocessing.Lock instance
+    :param conn: A multiprocessing.Pipe instance used to send True or False
+    :param data: The list of data entries [function_id, raster_name, extent, color]
     """
     array = None
     try:
@@ -86,12 +87,13 @@ def _get_raster_image_as_np(lock, conn, data):
     finally:
         conn.send(array)
 
+
 def _get_vector_table_as_dict(lock, conn, data):
     """Get the table of a vector map layer as dictionary
 
-       :param lock: A multiprocessing.Lock instance
-       :param conn: A multiprocessing.Pipe instance used to send True or False
-       :param data: The list of data entries [function_id, name, mapset, where]
+    :param lock: A multiprocessing.Lock instance
+    :param conn: A multiprocessing.Pipe instance used to send True or False
+    :param data: The list of data entries [function_id, name, mapset, where]
 
     """
     ret = None
@@ -124,16 +126,17 @@ def _get_vector_table_as_dict(lock, conn, data):
     finally:
         conn.send(ret)
 
+
 def _get_vector_features_as_wkb_list(lock, conn, data):
     """Return vector layer features as wkb list
 
-       supported feature types:
-       point, centroid, line, boundary, area
+    supported feature types:
+    point, centroid, line, boundary, area
 
-       :param lock: A multiprocessing.Lock instance
-       :param conn: A multiprocessing.Pipe instance used to send True or False
-       :param data: The list of data entries [function_id,name,mapset,extent,
-                                              feature_type, field]
+    :param lock: A multiprocessing.Lock instance
+    :param conn: A multiprocessing.Pipe instance used to send True or False
+    :param data: The list of data entries [function_id,name,mapset,extent,
+                                           feature_type, field]
 
     """
     wkb_list = None
@@ -154,26 +157,30 @@ def _get_vector_features_as_wkb_list(lock, conn, data):
 
         if layer.exist() is True:
             if extent is not None:
-                bbox = basic.Bbox(north=extent["north"],
-                                  south=extent["south"],
-                                  east=extent["east"],
-                                  west=extent["west"])
+                bbox = basic.Bbox(
+                    north=extent["north"],
+                    south=extent["south"],
+                    east=extent["east"],
+                    west=extent["west"],
+                )
 
             layer.open("r")
             if feature_type.lower() == "area":
                 wkb_list = layer.areas_to_wkb_list(bbox=bbox, field=field)
             else:
-                wkb_list = layer.features_to_wkb_list(bbox=bbox,
-                                                      feature_type=feature_type,
-                                                      field=field)
+                wkb_list = layer.features_to_wkb_list(
+                    bbox=bbox, feature_type=feature_type, field=field
+                )
             layer.close()
     except:
         raise
     finally:
         conn.send(wkb_list)
 
+
 ###############################################################################
 
+
 def _fatal_error(lock, conn, data):
     """Calls G_fatal_error()"""
     libgis.G_fatal_error("Fatal Error in C library server")
@@ -181,19 +188,22 @@ def _fatal_error(lock, conn, data):
 
 ###############################################################################
 
+
 def _stop(lock, conn, data):
     conn.close()
     lock.release()
     sys.exit()
 
+
 ###############################################################################
 
+
 def data_provider_server(lock, conn):
     """The PyGRASS data provider server designed to be a target for
-       multiprocessing.Process
+    multiprocessing.Process
 
-       :param lock: A multiprocessing.Lock
-       :param conn: A multiprocessing.Pipe
+    :param lock: A multiprocessing.Lock
+    :param conn: A multiprocessing.Pipe
     """
 
     def error_handler(data):
@@ -215,7 +225,7 @@ def data_provider_server(lock, conn):
     libgis.G_add_error_handler(cerror_handler, None)
 
     # Crerate the function array
-    functions = [0]*15
+    functions = [0] * 15
     functions[RPCDefs.GET_VECTOR_TABLE_AS_DICT] = _get_vector_table_as_dict
     functions[RPCDefs.GET_VECTOR_FEATURES_AS_WKB] = _get_vector_features_as_wkb_list
     functions[RPCDefs.GET_RASTER_IMAGE_AS_NP] = _get_raster_image_as_np
@@ -230,223 +240,236 @@ def data_provider_server(lock, conn):
         functions[data[0]](lock, conn, data)
         lock.release()
 
+
 test_vector_name = "data_provider_vector_map"
 test_raster_name = "data_provider_raster_map"
 
-class DataProvider(RPCServerBase):
-    """Fast and exit-safe interface to PyGRASS data delivery functions
 
-    """
+class DataProvider(RPCServerBase):
+    """Fast and exit-safe interface to PyGRASS data delivery functions"""
 
     def __init__(self):
         RPCServerBase.__init__(self)
 
     def start_server(self):
-        """This function must be re-implemented in the subclasses
-        """
+        """This function must be re-implemented in the subclasses"""
         self.client_conn, self.server_conn = Pipe(True)
         self.lock = Lock()
-        self.server = Process(target=data_provider_server, args=(self.lock,
-                                                             self.server_conn))
+        self.server = Process(
+            target=data_provider_server, args=(self.lock, self.server_conn)
+        )
         self.server.daemon = True
         self.server.start()
 
     def get_raster_image_as_np(self, name, mapset=None, extent=None, color="RGB"):
         """Return the attribute table of a vector map as dictionary.
 
-           See documentation of: pygrass.raster.raster2numpy_img
+        See documentation of: pygrass.raster.raster2numpy_img
 
-           Usage:
+        Usage:
 
-           .. code-block:: python
+        .. code-block:: python
 
-            >>> from grass.pygrass.rpc import DataProvider
-            >>> import time
-            >>> provider = DataProvider()
-            >>> ret = provider.get_raster_image_as_np(name=test_raster_name)
-            >>> len(ret)
-            64
+         >>> from grass.pygrass.rpc import DataProvider
+         >>> import time
+         >>> provider = DataProvider()
+         >>> ret = provider.get_raster_image_as_np(name=test_raster_name)
+         >>> len(ret)
+         64
 
-            >>> extent = {"north":30, "south":10, "east":30, "west":10,
-            ...           "rows":2, "cols":2}
-            >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
-            ...                                       extent=extent)
-            >>> len(ret)
-            16
+         >>> extent = {"north":30, "south":10, "east":30, "west":10,
+         ...           "rows":2, "cols":2}
+         >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
+         ...                                       extent=extent)
+         >>> len(ret)
+         16
 
-            >>> extent = {"rows":3, "cols":1}
-            >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
-            ...                                       extent=extent)
-            >>> len(ret)
-            12
+         >>> extent = {"rows":3, "cols":1}
+         >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
+         ...                                       extent=extent)
+         >>> len(ret)
+         12
 
-            >>> extent = {"north":100, "south":10, "east":30, "west":10,
-            ...           "rows":2, "cols":2}
-            >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
-            ...                                       extent=extent)
+         >>> extent = {"north":100, "south":10, "east":30, "west":10,
+         ...           "rows":2, "cols":2}
+         >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
+         ...                                       extent=extent)
 
-            >>> provider.stop()
-            >>> time.sleep(1)
+         >>> provider.stop()
+         >>> time.sleep(1)
 
-            >>> extent = {"rows":3, "cols":1}
-            >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
-            ...                                       extent=extent)
-            >>> len(ret)
-            12
+         >>> extent = {"rows":3, "cols":1}
+         >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
+         ...                                       extent=extent)
+         >>> len(ret)
+         12
 
 
-            ..
+         ..
         """
         self.check_server()
-        self.client_conn.send([RPCDefs.GET_RASTER_IMAGE_AS_NP,
-                               name, mapset, extent, color])
+        self.client_conn.send(
+            [RPCDefs.GET_RASTER_IMAGE_AS_NP, name, mapset, extent, color]
+        )
         return self.safe_receive("get_raster_image_as_np")
 
     def get_vector_table_as_dict(self, name, mapset=None, where=None):
         """Return the attribute table of a vector map as dictionary.
 
-           See documentation of: pygrass.vector.VectorTopo::table_to_dict
-
-           Usage:
-
-           .. code-block:: python
-
-            >>> from grass.pygrass.rpc import DataProvider
-            >>> provider = DataProvider()
-            >>> ret = provider.get_vector_table_as_dict(name=test_vector_name)
-            >>> ret["table"]
-            {1: [1, 'point', 1.0], 2: [2, 'line', 2.0], 3: [3, 'centroid', 3.0]}
-            >>> ret["columns"]
-            Columns([('cat', 'INTEGER'), ('name', 'varchar(50)'), ('value', 'double precision')])
-            >>> ret = provider.get_vector_table_as_dict(name=test_vector_name,
-            ...                                           where="value > 1")
-            >>> ret["table"]
-            {2: [2, 'line', 2.0], 3: [3, 'centroid', 3.0]}
-            >>> ret["columns"]
-            Columns([('cat', 'INTEGER'), ('name', 'varchar(50)'), ('value', 'double precision')])
-            >>> provider.get_vector_table_as_dict(name="no_map",
-            ...                                   where="value > 1")
-            >>> provider.stop()
-
-            ..
+        See documentation of: pygrass.vector.VectorTopo::table_to_dict
+
+        Usage:
+
+        .. code-block:: python
+
+         >>> from grass.pygrass.rpc import DataProvider
+         >>> provider = DataProvider()
+         >>> ret = provider.get_vector_table_as_dict(name=test_vector_name)
+         >>> ret["table"]
+         {1: [1, 'point', 1.0], 2: [2, 'line', 2.0], 3: [3, 'centroid', 3.0]}
+         >>> ret["columns"]
+         Columns([('cat', 'INTEGER'), ('name', 'varchar(50)'), ('value', 'double precision')])
+         >>> ret = provider.get_vector_table_as_dict(name=test_vector_name,
+         ...                                           where="value > 1")
+         >>> ret["table"]
+         {2: [2, 'line', 2.0], 3: [3, 'centroid', 3.0]}
+         >>> ret["columns"]
+         Columns([('cat', 'INTEGER'), ('name', 'varchar(50)'), ('value', 'double precision')])
+         >>> provider.get_vector_table_as_dict(name="no_map",
+         ...                                   where="value > 1")
+         >>> provider.stop()
+
+         ..
         """
         self.check_server()
-        self.client_conn.send([RPCDefs.GET_VECTOR_TABLE_AS_DICT,
-                               name, mapset, where])
+        self.client_conn.send([RPCDefs.GET_VECTOR_TABLE_AS_DICT, name, mapset, where])
         return self.safe_receive("get_vector_table_as_dict")
 
-    def get_vector_features_as_wkb_list(self, name, mapset=None, extent=None,
-                                        feature_type="point", field=1):
+    def get_vector_features_as_wkb_list(
+        self, name, mapset=None, extent=None, feature_type="point", field=1
+    ):
         """Return the features of a vector map as wkb list.
 
-           :param extent: A dictionary of {"north":double, "south":double,
-                                           "east":double, "west":double}
-           :param feature_type: point, centroid, line, boundary or area
-
-           See documentation: pygrass.vector.VectorTopo::features_to_wkb_list
-                              pygrass.vector.VectorTopo::areas_to_wkb_list
-
-
-           Usage:
-
-           .. code-block:: python
-
-            >>> from grass.pygrass.rpc import DataProvider
-            >>> provider = DataProvider()
-            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
-            ...                                                extent=None,
-            ...                                                feature_type="point")
-            >>> for entry in wkb:
-            ...     f_id, cat, string = entry
-            ...     print(f_id, cat, len(string))
-            1 1 21
-            2 1 21
-            3 1 21
-
-            >>> extent = {"north":6.6, "south":5.5, "east":14.5, "west":13.5}
-            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
-            ...                                                extent=extent,
-            ...                                                feature_type="point")
-            >>> for entry in wkb:
-            ...     f_id, cat, string = entry
-            ...     print(f_id, cat, len(string))
-            3 1 21
-
-            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
-            ...                                                extent=None,
-            ...                                                feature_type="line")
-            >>> for entry in wkb:
-            ...     f_id, cat, string = entry
-            ...     print(f_id, cat, len(string))
-            4 2 57
-            5 2 57
-            6 2 57
-
-
-            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
-            ...                                                extent=None,
-            ...                                                feature_type="centroid")
-            >>> for entry in wkb:
-            ...     f_id, cat, string = entry
-            ...     print(f_id, cat, len(string))
-            19 3 21
-            18 3 21
-            20 3 21
-            21 3 21
-
-            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
-            ...                                                extent=None,
-            ...                                                feature_type="area")
-            >>> for entry in wkb:
-            ...     f_id, cat, string = entry
-            ...     print(f_id, cat, len(string))
-            1 3 225
-            2 3 141
-            3 3 93
-            4 3 141
-
-            >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
-            ...                                                extent=None,
-            ...                                                feature_type="boundary")
-            >>> for entry in wkb:
-            ...     f_id, cat, string = entry
-            ...     print(f_id, cat, len(string))
-            10 None 41
-            7 None 41
-            8 None 41
-            9 None 41
-            11 None 89
-            12 None 41
-            14 None 41
-            13 None 41
-            17 None 41
-            15 None 41
-            16 None 41
-
-            >>> provider.stop()
-
-            ..
+        :param extent: A dictionary of {"north":double, "south":double,
+                                        "east":double, "west":double}
+        :param feature_type: point, centroid, line, boundary or area
+
+        See documentation: pygrass.vector.VectorTopo::features_to_wkb_list
+                           pygrass.vector.VectorTopo::areas_to_wkb_list
+
+
+        Usage:
+
+        .. code-block:: python
+
+         >>> from grass.pygrass.rpc import DataProvider
+         >>> provider = DataProvider()
+         >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+         ...                                                extent=None,
+         ...                                                feature_type="point")
+         >>> for entry in wkb:
+         ...     f_id, cat, string = entry
+         ...     print(f_id, cat, len(string))
+         1 1 21
+         2 1 21
+         3 1 21
+
+         >>> extent = {"north":6.6, "south":5.5, "east":14.5, "west":13.5}
+         >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+         ...                                                extent=extent,
+         ...                                                feature_type="point")
+         >>> for entry in wkb:
+         ...     f_id, cat, string = entry
+         ...     print(f_id, cat, len(string))
+         3 1 21
+
+         >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+         ...                                                extent=None,
+         ...                                                feature_type="line")
+         >>> for entry in wkb:
+         ...     f_id, cat, string = entry
+         ...     print(f_id, cat, len(string))
+         4 2 57
+         5 2 57
+         6 2 57
+
+
+         >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+         ...                                                extent=None,
+         ...                                                feature_type="centroid")
+         >>> for entry in wkb:
+         ...     f_id, cat, string = entry
+         ...     print(f_id, cat, len(string))
+         19 3 21
+         18 3 21
+         20 3 21
+         21 3 21
+
+         >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+         ...                                                extent=None,
+         ...                                                feature_type="area")
+         >>> for entry in wkb:
+         ...     f_id, cat, string = entry
+         ...     print(f_id, cat, len(string))
+         1 3 225
+         2 3 141
+         3 3 93
+         4 3 141
+
+         >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
+         ...                                                extent=None,
+         ...                                                feature_type="boundary")
+         >>> for entry in wkb:
+         ...     f_id, cat, string = entry
+         ...     print(f_id, cat, len(string))
+         10 None 41
+         7 None 41
+         8 None 41
+         9 None 41
+         11 None 89
+         12 None 41
+         14 None 41
+         13 None 41
+         17 None 41
+         15 None 41
+         16 None 41
+
+         >>> provider.stop()
+
+         ..
         """
         self.check_server()
-        self.client_conn.send([RPCDefs.GET_VECTOR_FEATURES_AS_WKB,
-                               name, mapset, extent, feature_type, field])
+        self.client_conn.send(
+            [
+                RPCDefs.GET_VECTOR_FEATURES_AS_WKB,
+                name,
+                mapset,
+                extent,
+                feature_type,
+                field,
+            ]
+        )
         return self.safe_receive("get_vector_features_as_wkb_list")
 
 
 if __name__ == "__main__":
     import doctest
     from grass.pygrass.modules import Module
+
     Module("g.region", n=40, s=0, e=40, w=0, res=10)
-    Module("r.mapcalc", expression="%s = row() + (10 * col())" % (test_raster_name),
-                             overwrite=True)
+    Module(
+        "r.mapcalc",
+        expression="%s = row() + (10 * col())" % (test_raster_name),
+        overwrite=True,
+    )
     utils.create_test_vector_map(test_vector_name)
 
     doctest.testmod()
 
     """Remove the generated maps, if exist"""
-    mset = utils.get_mapset_raster(test_raster_name, mapset='')
+    mset = utils.get_mapset_raster(test_raster_name, mapset="")
     if mset:
-        Module("g.remove", flags='f', type='raster', name=test_raster_name)
-    mset = utils.get_mapset_vector(test_vector_name, mapset='')
+        Module("g.remove", flags="f", type="raster", name=test_raster_name)
+    mset = utils.get_mapset_vector(test_vector_name, mapset="")
     if mset:
-        Module("g.remove", flags='f', type='vector', name=test_vector_name)
+        Module("g.remove", flags="f", type="vector", name=test_vector_name)

+ 44 - 38
python/grass/pygrass/rpc/base.py

@@ -20,11 +20,12 @@ import logging
 
 ###############################################################################
 
+
 def dummy_server(lock, conn):
     """Dummy server process
 
-       :param lock: A multiprocessing.Lock
-       :param conn: A multiprocessing.Pipe
+    :param lock: A multiprocessing.Lock
+    :param conn: A multiprocessing.Pipe
     """
 
     while True:
@@ -40,44 +41,45 @@ def dummy_server(lock, conn):
             raise Exception("Server process intentionally killed by exception")
         lock.release()
 
+
 class RPCServerBase(object):
     """This is the base class for send and receive RPC server
-       It uses a Pipe for IPC.
+    It uses a Pipe for IPC.
 
 
-        >>> import grass.script as gscript
-        >>> from grass.pygrass.rpc.base import RPCServerBase
-        >>> import time
-        >>> provider = RPCServerBase()
+     >>> import grass.script as gscript
+     >>> from grass.pygrass.rpc.base import RPCServerBase
+     >>> import time
+     >>> provider = RPCServerBase()
 
-        >>> provider.is_server_alive()
-        True
+     >>> provider.is_server_alive()
+     True
 
-        >>> provider.is_check_thread_alive()
-        True
+     >>> provider.is_check_thread_alive()
+     True
 
-        >>> provider.stop()
-        >>> time.sleep(1)
-        >>> provider.is_server_alive()
-        False
+     >>> provider.stop()
+     >>> time.sleep(1)
+     >>> provider.is_server_alive()
+     False
 
-        >>> provider.is_check_thread_alive()
-        False
+     >>> provider.is_check_thread_alive()
+     False
 
-        >>> provider = RPCServerBase()
-        >>> provider.is_server_alive()
-        True
-        >>> provider.is_check_thread_alive()
-        True
+     >>> provider = RPCServerBase()
+     >>> provider.is_server_alive()
+     True
+     >>> provider.is_check_thread_alive()
+     True
 
-        Kill the server process with an exception, it should restart
+     Kill the server process with an exception, it should restart
 
-        >>> provider.client_conn.send([1])
-        >>> provider.is_server_alive()
-        True
+     >>> provider.client_conn.send([1])
+     >>> provider.is_server_alive()
+     True
 
-        >>> provider.is_check_thread_alive()
-        True
+     >>> provider.is_check_thread_alive()
+     True
 
     """
 
@@ -127,14 +129,12 @@ class RPCServerBase(object):
             self.threadLock.release()
 
     def start_server(self):
-        """This function must be re-implemented in the subclasses
-        """
+        """This function must be re-implemented in the subclasses"""
         logging.debug("Start the libgis server")
 
         self.client_conn, self.server_conn = Pipe(True)
         self.lock = Lock()
-        self.server = Process(target=dummy_server, args=(self.lock,
-                                                         self.server_conn))
+        self.server = Process(target=dummy_server, args=(self.lock, self.server_conn))
         self.server.daemon = True
         self.server.start()
 
@@ -142,8 +142,7 @@ class RPCServerBase(object):
         self._check_restart_server()
 
     def _check_restart_server(self, caller="main thread"):
-        """Restart the server if it was terminated
-        """
+        """Restart the server if it was terminated"""
         logging.debug("Check libgis server restart")
 
         self.threadLock.acquire()
@@ -155,14 +154,16 @@ class RPCServerBase(object):
         self.start_server()
 
         if self.stopped is not True:
-            logging.warning("Needed to restart the libgis server, caller: %s" % (caller))
+            logging.warning(
+                "Needed to restart the libgis server, caller: %s" % (caller)
+            )
 
         self.threadLock.release()
         self.stopped = False
 
     def safe_receive(self, message):
         """Receive the data and throw a FatalError exception in case the server
-           process was killed and the pipe was closed by the checker thread"""
+        process was killed and the pipe was closed by the checker thread"""
         logging.debug("Receive message: {message}")
 
         try:
@@ -178,13 +179,17 @@ class RPCServerBase(object):
     def stop(self):
         """Stop the check thread, the libgis server and close the pipe
 
-           This method should be called at exit using the package atexit
+        This method should be called at exit using the package atexit
         """
         logging.debug("Stop libgis server")
 
         self.stop_checker_thread()
         if self.server is not None and self.server.is_alive():
-            self.client_conn.send([0, ])
+            self.client_conn.send(
+                [
+                    0,
+                ]
+            )
             self.server.terminate()
         if self.client_conn is not None:
             self.client_conn.close()
@@ -193,4 +198,5 @@ class RPCServerBase(object):
 
 if __name__ == "__main__":
     import doctest
+
     doctest.testmod()

+ 15 - 10
python/grass/pygrass/rpc/testsuite/test_pygrass_rpc_doctests.py

@@ -17,12 +17,14 @@ import grass.pygrass.rpc as pygrpc
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -34,16 +36,19 @@ def load_tests(loader, tests, ignore):
 
     from grass.pygrass import utils
     from grass.pygrass.modules import Module
+
     Module("g.region", n=40, s=0, e=40, w=0, res=10)
-    Module("r.mapcalc", expression="%s = row() + (10 * col())" % (pygrpc.test_raster_name),
-                             overwrite=True)
+    Module(
+        "r.mapcalc",
+        expression="%s = row() + (10 * col())" % (pygrpc.test_raster_name),
+        overwrite=True,
+    )
     utils.create_test_vector_map(pygrpc.test_vector_name)
 
-
     tests.addTests(doctest.DocTestSuite(pygrpc))
     tests.addTests(doctest.DocTestSuite(pygrpc.base))
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 22 - 6
python/grass/pygrass/shell/conversion.py

@@ -12,9 +12,17 @@ dcont = """    <tr>
     </tr>"""
 
 
-def dict2html(dic, keys=None, border='',
-              kfmt='%s', kdec='', kfun=None,
-              vfmt='%s', vdec='', vfun=None):
+def dict2html(
+    dic,
+    keys=None,
+    border="",
+    kfmt="%s",
+    kdec="",
+    kfun=None,
+    vfmt="%s",
+    vdec="",
+    vfun=None,
+):
     """Return a html repr of a dictionary.
 
     :param dict dic: dictionary or object with `keys` and `items` methods
@@ -73,6 +81,7 @@ def dict2html(dic, keys=None, border='',
         </tr>
     </table>
     """
+
     def fun(x):
         return x
 
@@ -82,6 +91,13 @@ def dict2html(dic, keys=None, border='',
     vd = "<%s>%s</%s>" % (vdec, vfmt, vdec) if vdec else vfmt
     kfun = kfun if kfun else fun
     vfun = vfun if vfun else fun
-    content = [dcont.format(key=kd % kfun(k), value=vd % vfun(dic[k]))
-               for k in keys]
-    return '\n'.join([header, ] + content + ['</table>', ])
+    content = [dcont.format(key=kd % kfun(k), value=vd % vfun(dic[k])) for k in keys]
+    return "\n".join(
+        [
+            header,
+        ]
+        + content
+        + [
+            "</table>",
+        ]
+    )

+ 1 - 1
python/grass/pygrass/shell/show.py

@@ -8,6 +8,6 @@ import io
 
 
 def raw_figure(figpath):
-    with io.OpenWrapper(figpath, mode='rb') as data:
+    with io.OpenWrapper(figpath, mode="rb") as data:
         res = data.read()
     return res

+ 9 - 7
python/grass/pygrass/shell/testsuite/test_pygrass_shell_doctests.py

@@ -17,12 +17,14 @@ from grass.pygrass.shell import conversion, show
 # and contains doctest's methods
 # the alternative is to copy 500 from doctest and change what is needed
 # (this might be necessary anyway because of the reports and stdout and stderr)
-doctest.DocFileCase = type('DocFileCase',
-                           (grass.gunittest.case.TestCase,),
-                           dict(doctest.DocFileCase.__dict__))
-doctest.SkipDocTestCase = type('SkipDocTestCase',
-                               (grass.gunittest.case.TestCase,),
-                               dict(doctest.SkipDocTestCase.__dict__))
+doctest.DocFileCase = type(
+    "DocFileCase", (grass.gunittest.case.TestCase,), dict(doctest.DocFileCase.__dict__)
+)
+doctest.SkipDocTestCase = type(
+    "SkipDocTestCase",
+    (grass.gunittest.case.TestCase,),
+    dict(doctest.SkipDocTestCase.__dict__),
+)
 
 
 def load_tests(loader, tests, ignore):
@@ -36,5 +38,5 @@ def load_tests(loader, tests, ignore):
     return tests
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     grass.gunittest.main.test()

+ 131 - 64
python/grass/pygrass/tests/benchmark.py

@@ -4,11 +4,19 @@ Created on Sat Jun 16 20:24:56 2012
 
 @author: soeren
 """
-from __future__ import (nested_scopes, generators, division, absolute_import,
-                        with_statement, print_function, unicode_literals)
+from __future__ import (
+    nested_scopes,
+    generators,
+    division,
+    absolute_import,
+    with_statement,
+    print_function,
+    unicode_literals,
+)
 
 import optparse
-#import numpy as np
+
+# import numpy as np
 import time
 import collections
 import copy
@@ -16,6 +24,7 @@ import cProfile
 import sys
 import os
 from jinja2 import Template
+
 sys.path.append(os.getcwd())
 sys.path.append("%s/.." % (os.getcwd()))
 
@@ -43,6 +52,7 @@ def test__RasterSegment_value_access__if():
     test_a.close()
     test_c.close()
 
+
 def test__RasterSegment_value_access__add():
     test_a = pygrass.RasterSegment(name="test_a")
     test_a.open(mode="r")
@@ -58,7 +68,7 @@ def test__RasterSegment_value_access__add():
 
     for row in range(test_a.rows):
         test_a.get_row(row, buff_a)
-        test_b.get_row(row,buff_b)
+        test_b.get_row(row, buff_b)
         for col in range(test_a.cols):
             test_c.put(row, col, buff_a[col] + buff_b[col])
 
@@ -66,6 +76,7 @@ def test__RasterSegment_value_access__add():
     test_b.close()
     test_c.close()
 
+
 def test__RasterSegment_row_access__if():
     test_a = pygrass.RasterSegment(name="test_a")
     test_a.open(mode="r")
@@ -82,6 +93,7 @@ def test__RasterSegment_row_access__if():
     test_a.close()
     test_c.close()
 
+
 def test__RasterSegment_row_access__add():
     test_a = pygrass.RasterSegment(name="test_a")
     test_a.open(mode="r")
@@ -97,13 +109,14 @@ def test__RasterSegment_row_access__add():
 
     for row in range(test_a.rows):
         test_a.get_row(row, buff_a)
-        test_b.get_row(row,buff_b)
+        test_b.get_row(row, buff_b)
         test_c.put_row(row, buff_a + buff_b)
 
     test_a.close()
     test_b.close()
     test_c.close()
 
+
 def test__RasterRow_value_access__add():
     test_a = pygrass.RasterRow(name="test_a")
     test_a.open(mode="r")
@@ -120,7 +133,7 @@ def test__RasterRow_value_access__add():
 
     for row in range(test_a.rows):
         test_a.get_row(row, buff_a)
-        test_b.get_row(row,buff_b)
+        test_b.get_row(row, buff_b)
 
         for col in range(test_a.cols):
             buff_c[col] = buff_a[col] + buff_b[col]
@@ -131,6 +144,7 @@ def test__RasterRow_value_access__add():
     test_b.close()
     test_c.close()
 
+
 def test__RasterRow_value_access__if():
     test_a = pygrass.RasterRow(name="test_a")
     test_a.open(mode="r")
@@ -152,6 +166,7 @@ def test__RasterRow_value_access__if():
     test_a.close()
     test_c.close()
 
+
 def test__RasterRowIO_row_access__add():
     test_a = pygrass.RasterRowIO(name="test_a")
     test_a.open(mode="r")
@@ -167,13 +182,14 @@ def test__RasterRowIO_row_access__add():
 
     for row in range(test_a.rows):
         test_a.get_row(row, buff_a)
-        test_b.get_row(row,buff_b)
+        test_b.get_row(row, buff_b)
         test_c.put_row(buff_a + buff_b)
 
     test_a.close()
     test_b.close()
     test_c.close()
 
+
 def test__RasterRowIO_row_access__if():
     test_a = pygrass.RasterRowIO(name="test_a")
     test_a.open(mode="r")
@@ -190,6 +206,7 @@ def test__RasterRowIO_row_access__if():
     test_a.close()
     test_c.close()
 
+
 def test__RasterRow_row_access__add():
     test_a = pygrass.RasterRow(name="test_a")
     test_a.open(mode="r")
@@ -205,13 +222,14 @@ def test__RasterRow_row_access__add():
 
     for row in range(test_a.rows):
         test_a.get_row(row, buff_a)
-        test_b.get_row(row,buff_b)
+        test_b.get_row(row, buff_b)
         test_c.put_row(buff_a + buff_b)
 
     test_a.close()
     test_b.close()
     test_c.close()
 
+
 def test__RasterRow_row_access__if():
     test_a = pygrass.RasterRow(name="test_a")
     test_a.open(mode="r")
@@ -228,12 +246,15 @@ def test__RasterRow_row_access__if():
     test_a.close()
     test_c.close()
 
+
 def test__mapcalc__add():
     core.mapcalc("test_c = test_a + test_b", quite=True, overwrite=True)
 
+
 def test__mapcalc__if():
     core.mapcalc("test_c = if(test_a > 50, 1, 0)", quite=True, overwrite=True)
 
+
 def mytimer(func, runs=1):
     times = []
     t = 0.0
@@ -244,15 +265,16 @@ def mytimer(func, runs=1):
         times.append(end - start)
         t = t + end - start
 
-    return t/runs, times
-
+    return t / runs, times
 
 
 def run_benchmark(resolution_list, runs, testdict, profile):
     regions = []
     for resolution in resolution_list:
         core.use_temp_region()
-        core.run_command('g.region', e=50, w=-50, n=50, s=-50, res=resolution, flags='p')
+        core.run_command(
+            "g.region", e=50, w=-50, n=50, s=-50, res=resolution, flags="p"
+        )
 
         # Adjust the computational region for this process
         region = libgis.Cell_head()
@@ -273,53 +295,61 @@ def run_benchmark(resolution_list, runs, testdict, profile):
         core.mapcalc("test_a = rand(0, 100)", quite=True, overwrite=True)
         core.mapcalc("test_b = rand(0.0, 1.0)", quite=True, overwrite=True)
         result = collections.OrderedDict()
-        result['res'] = resolution
-        result['cols'] = region.cols
-        result['rows'] = region.rows
-        result['cells'] = region.rows * region.cols
-        result['results'] = copy.deepcopy(testdict)
-        for execmode, operation in result['results'].items():
+        result["res"] = resolution
+        result["cols"] = region.cols
+        result["rows"] = region.rows
+        result["cells"] = region.rows * region.cols
+        result["results"] = copy.deepcopy(testdict)
+        for execmode, operation in result["results"].items():
             print(execmode)
             for oper, operdict in operation.items():
-                operdict['time'], operdict['times'] = mytimer(operdict['func'],runs)
+                operdict["time"], operdict["times"] = mytimer(operdict["func"], runs)
                 if profile:
-                    filename = '{0}_{1}_{2}'.format(execmode, oper, profile)
-                    cProfile.runctx(operdict['func'].__name__ + '()',
-                                    globals(), locals(), filename = filename)
-                print(('    {0}: {1: 40.6f}s'.format(oper, operdict['time'])))
-                del(operdict['func'])
+                    filename = "{0}_{1}_{2}".format(execmode, oper, profile)
+                    cProfile.runctx(
+                        operdict["func"].__name__ + "()",
+                        globals(),
+                        locals(),
+                        filename=filename,
+                    )
+                print(("    {0}: {1: 40.6f}s".format(oper, operdict["time"])))
+                del operdict["func"]
 
         regions.append(result)
         core.del_temp_region()
 
     return regions
 
+
 def get_testlist(loc):
-    testlist = [test for test in list(loc.keys()) if 'test' in test[:5]]
+    testlist = [test for test in list(loc.keys()) if "test" in test[:5]]
     testlist.sort()
     return testlist
 
+
 def get_testdict(testlist):
     testdict = collections.OrderedDict()
     for testfunc in testlist:
-        #import pdb; pdb.set_trace()
-        dummy, execmode, operation = testfunc.split('__')
+        # import pdb; pdb.set_trace()
+        dummy, execmode, operation = testfunc.split("__")
         if execmode in list(testdict.keys()):
             testdict[execmode][operation] = collections.OrderedDict()
-            testdict[execmode][operation]['func'] = loc[testfunc]
+            testdict[execmode][operation]["func"] = loc[testfunc]
         else:
             testdict[execmode] = collections.OrderedDict()
             testdict[execmode][operation] = collections.OrderedDict()
-            testdict[execmode][operation]['func'] = loc[testfunc]
+            testdict[execmode][operation]["func"] = loc[testfunc]
     return testdict
 
+
 def print_test(testdict):
     for execmode, operation in testdict.items():
         print(execmode)
         for oper, operdict in operation.items():
-            print('    ', oper)
+            print("    ", oper)
             for key, value in operdict.items():
-                print('        ', key)
+                print("        ", key)
+
 
 TXT = """
 {% for region in regions %}
@@ -346,29 +376,31 @@ CSV = """Class; Mode; Operation;
 
 RST = """
 """
-#>>> txt = Template(TxT)
-#>>> txt.render(name='John Doe')
+# >>> txt = Template(TxT)
+# >>> txt.render(name='John Doe')
 
 
 def get_txt(results):
     txt = Template(TXT)
-    return txt.render(regions = results)
+    return txt.render(regions=results)
 
 
-#classes for required options
-strREQUIRED = 'required'
+# classes for required options
+strREQUIRED = "required"
+
 
 class OptionWithDefault(optparse.Option):
     ATTRS = optparse.Option.ATTRS + [strREQUIRED]
 
     def __init__(self, *opts, **attrs):
         if attrs.get(strREQUIRED, False):
-            attrs['help'] = '(Required) ' + attrs.get('help', "")
+            attrs["help"] = "(Required) " + attrs.get("help", "")
         optparse.Option.__init__(self, *opts, **attrs)
 
+
 class OptionParser(optparse.OptionParser):
     def __init__(self, **kwargs):
-        kwargs['option_class'] = OptionWithDefault
+        kwargs["option_class"] = OptionWithDefault
         optparse.OptionParser.__init__(self, **kwargs)
 
     def check_values(self, values, args):
@@ -381,59 +413,94 @@ class OptionParser(optparse.OptionParser):
 
 def main(testdict):
     """Main function"""
-    #usage
+    # usage
     usage = "usage: %prog [options] raster_map"
     parser = OptionParser(usage=usage)
     # ntime
-    parser.add_option("-n", "--ntimes", dest="ntime",default=5, type="int",
-                      help="Number of run for each test.")
+    parser.add_option(
+        "-n",
+        "--ntimes",
+        dest="ntime",
+        default=5,
+        type="int",
+        help="Number of run for each test.",
+    )
     # res
-    parser.add_option("-r", "--resolution", action="store", type="string",
-                      dest="res", default = '1,0.25',
-                      help="Resolution list separate by comma.")
+    parser.add_option(
+        "-r",
+        "--resolution",
+        action="store",
+        type="string",
+        dest="res",
+        default="1,0.25",
+        help="Resolution list separate by comma.",
+    )
     # fmt
-    parser.add_option("-f", "--fmt", action="store", type="string",
-                      dest="fmt", default = 'txt',
-                      help="Choose the output format: 'txt', 'csv', 'rst'.")
+    parser.add_option(
+        "-f",
+        "--fmt",
+        action="store",
+        type="string",
+        dest="fmt",
+        default="txt",
+        help="Choose the output format: 'txt', 'csv', 'rst'.",
+    )
 
     # output
-    parser.add_option("-o", "--output", action="store", type="string",
-                      dest="output", help="The output filename.")
+    parser.add_option(
+        "-o",
+        "--output",
+        action="store",
+        type="string",
+        dest="output",
+        help="The output filename.",
+    )
 
     # store
-    parser.add_option("-s", "--store", action="store", type="string",
-                      dest="store", help="The filename of pickle obj.")
+    parser.add_option(
+        "-s",
+        "--store",
+        action="store",
+        type="string",
+        dest="store",
+        help="The filename of pickle obj.",
+    )
 
     # profile
-    parser.add_option("-p", "--profile", action="store", type="string",
-                      dest="profile", help="The filename of the profile results.")
-
-    #return options and argument
+    parser.add_option(
+        "-p",
+        "--profile",
+        action="store",
+        type="string",
+        dest="profile",
+        help="The filename of the profile results.",
+    )
+
+    # return options and argument
     options, args = parser.parse_args()
-    res = [float(r) for r in options.res.split(',')]
-    #res = [1, 0.25, 0.1, 0.05]
+    res = [float(r) for r in options.res.split(",")]
+    # res = [1, 0.25, 0.1, 0.05]
 
     results = run_benchmark(res, options.ntime, testdict, options.profile)
 
     if options.store:
         import pickle
-        output = open(options.store, 'wb')
+
+        output = open(options.store, "wb")
         pickle.dump(results, output)
         output.close()
-    #import pdb; pdb.set_trace()
+    # import pdb; pdb.set_trace()
     print(get_txt(results))
 
 
-#add options
+# add options
 if __name__ == "__main__":
-    #import pdb; pdb.set_trace()
+    # import pdb; pdb.set_trace()
     loc = locals()
     testlist = get_testlist(loc)
     testdict = get_testdict(testlist)
-    #print_test(testdict)
-
-
+    # print_test(testdict)
 
-    #import pdb; pdb.set_trace()
+    # import pdb; pdb.set_trace()
 
     main(testdict)

+ 0 - 0
python/grass/pygrass/tests/set_mapset.py


Some files were not shown because too many files changed in this diff