Repository: RedHatInsights/insights-core

Issue:
Missing dependencies when inspect parsers using Python 3.10

~~~
# cat /root/insights-vagrant-host-20180406174409/etc/redhat-release 
Red Hat Enterprise Linux Server release 7.4 (Maipo)

# insights inspect insights.parsers.redhat_release.RedhatRelease /root/insights-vagrant-host-20180406174409/ -D
DEBUG:insights.core.dr:Importing insights.specs.default
DEBUG:insights.core.dr:Importing insights.specs.insights_archive
DEBUG:insights.core.dr:Importing insights.specs.core3_archive
DEBUG:insights.core.dr:Importing insights.specs.sos_archive
DEBUG:insights.core.dr:Importing insights.specs.jdr_archive
DEBUG:insights.core.dr:Importing insights.specs.must_gather_archive
INFO:insights.core.dr:Trying insights.specs.Specs.redhat_release
DEBUG:insights.core.dr:insights.specs.Specs.redhat_release missing requirements All: [] Any: []
INFO:insights.core.dr:Trying insights.parsers.redhat_release.RedhatRelease
DEBUG:insights.core.dr:insights.parsers.redhat_release.RedhatRelease missing requirements All: ['insights.specs.Specs.redhat_release'] Any: 
Missing Dependencies:
    Requires:
        insights.specs.Specs.redhat_release
~~~


Discussion:
It looks more like the next python version that needs to be supported by the framework should be Python `3.9`, but not `3.10`.

```
In RHEL 9, Python 3.9 is the default Python implementation. The unversioned python command points to the default Python 3.9 version.
```
- [Chapter 2. Installing and using Python Red Hat Enterprise Linux 9-beta](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9-beta/html/installing_and_using_dynamic_programming_languages/assembly_installing-and-using-python_installing-and-using-dynamic-programming-languages)

Anyway, it is a good point to discuss.
This bug is most probably caused by the `MutableMapping` class being moved from `collections` to `collections.abc`. I've seen some warnings about that in out tests, but don't see them now. Not sure what changed on our side or in Python.

`from insights.specs.default import DefaultSpecs` works in Python 3.9 and older but fails in Python 3.10 with

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./.venv-py3.10/lib64/python3.10/site-packages/insights/specs/default.py", line 34, in <module>
    from insights.specs.datasources import (
  File "./.venv-py3.10/lib64/python3.10/site-packages/insights/specs/datasources/ssl_certificate.py", line 6, in <module>
    from insights.combiners.nginx_conf import NginxConfTree
  File "./.venv-py3.10/lib64/python3.10/site-packages/insights/combiners/nginx_conf.py", line 7, in <module>
    from insights.parsers.nginx_conf import NginxConfPEG
  File "./.venv-py3.10/lib64/python3.10/site-packages/insights/parsers/nginx_conf.py", line 10, in <module>
    from insights.contrib.nginxparser import create_parser, UnspacedList
  File "./.venv-py3.10/lib64/python3.10/site-packages/insights/contrib/nginxparser.py", line 25, in <module>
    from insights.contrib.pyparsing import (
  File "./.venv-py3.10/lib64/python3.10/site-packages/insights/contrib/pyparsing.py", line 714, in <module>
    collections.MutableMapping.register(ParseResults)
AttributeError: module 'collections' has no attribute 'MutableMapping'
```
I suppose `insights.contrib.pyparsing` is kept for compatibility with Python 2.7. The module uses `collections.MutableMapping` and `collections.Sequence`, both of which have been moved to `collections.abc` quite some time ago and eventually removed from `collections` in Python 3.10.

A conditional import at https://github.com/RedHatInsights/insights-core/blob/master/insights/contrib/pyparsing.py#L72 **should** do the trick

```
if system_version[:2] >= (3,10):
    import collections.abc as collections
else:
   import collections
```

This is as far as I can go. I've been unable to run all insights-core tests successfully even with Python 3.6 so far and I have no interest in playing with Python 2.

Resolving patch:
diff --git a/insights/combiners/nfs_exports.py b/insights/combiners/nfs_exports.py
--- a/insights/combiners/nfs_exports.py
+++ b/insights/combiners/nfs_exports.py
@@ -29,11 +29,13 @@
     True
 
 """
-
 from insights.core.plugins import combiner
 from insights.parsers.nfs_exports import NFSExports, NFSExportsD
 
-import collections
+try:
+    from six.moves import collections_abc
+except ImportError:
+    import collections as collections_abc
 
 
 @combiner(NFSExports, optional=[NFSExportsD])
@@ -70,7 +72,7 @@ def __init__(self, nfsexports, nfsexportsd):
         sources = [nfsexports]
         # Make sure exports are stored in the order they're parsed -
         # alphabetically by file name.  Ignore it if nfsexportsd isn't valid.
-        if isinstance(nfsexportsd, collections.Iterable):
+        if isinstance(nfsexportsd, collections_abc.Iterable):
             sources.extend(sorted(nfsexportsd, key=lambda f: f.file_path))
 
         def add_paths_to_dict(src_path, src_dict, dest_dict):
diff --git a/insights/contrib/pyparsing.py b/insights/contrib/pyparsing.py
--- a/insights/contrib/pyparsing.py
+++ b/insights/contrib/pyparsing.py
@@ -62,18 +62,21 @@ class names, and the use of '+', '|' and '^' operators.
 __versionTime__ = "13 May 2016 18:25 UTC"
 __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
 
-import string
-from weakref import ref as wkref
 import copy
-import sys
-import warnings
+import pprint
 import re
 import sre_constants
-import collections
-import pprint
-import functools
-import itertools
+import string
+import sys
 import traceback
+import warnings
+
+from weakref import ref as wkref
+
+try:
+    from six.moves import collections_abc
+except ImportError:
+    import collections as collections_abc
 
 #~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
 
@@ -711,7 +714,7 @@ def __getnewargs__(self):
     def __dir__(self):
         return (dir(type(self)) + list(self.keys()))
 
-collections.MutableMapping.register(ParseResults)
+collections_abc.MutableMapping.register(ParseResults)
 
 def col (loc,strg):
     """Returns current column within a string, counting newlines as line separators.
@@ -2403,7 +2406,7 @@ def __init__( self, exprs, savelist = False ):
 
         if isinstance( exprs, basestring ):
             self.exprs = [ Literal( exprs ) ]
-        elif isinstance( exprs, collections.Sequence ):
+        elif isinstance(exprs, collections_abc.Sequence):
             # if sequence of strings provided, wrap with Literal
             if all(isinstance(expr, basestring) for expr in exprs):
                 exprs = map(Literal, exprs)
@@ -3437,7 +3440,7 @@ def oneOf( strs, caseless=False, useRegex=True ):
     symbols = []
     if isinstance(strs,basestring):
         symbols = strs.split()
-    elif isinstance(strs, collections.Sequence):
+    elif isinstance(strs, collections_abc.Sequence):
         symbols = list(strs[:])
     elif isinstance(strs, _generatorType):
         symbols = list(strs)


Test patch: