diff --git a/processinghistory/history.py b/processinghistory/history.py index dbb07bf..e7e8520 100644 --- a/processinghistory/history.py +++ b/processinghistory/history.py @@ -38,6 +38,11 @@ import time import zlib import base64 +try: + import importlib.metadata + HAVE_IMPLIB_METADATA = True +except ImportError: + HAVE_IMPLIB_METADATA = False from osgeo import gdal @@ -176,14 +181,57 @@ def makeAutomaticFields(): moduleVersionDict[toplevelModname] = "Unknown" if len(moduleVersionDict) > 0: - for modname in moduleVersionDict: + moduleVersionDictKeys = list(moduleVersionDict.keys()) + for modname in moduleVersionDictKeys: if hasattr(sys.modules[modname], '__version__'): moduleVersionDict[modname] = str(sys.modules[modname].__version__) + else: + (distributionName, verStr) = versionFromDistribution(modname) + if None not in (distributionName, verStr): + moduleVersionDict[distributionName] = verStr + # If distribution name is different, remove modname + if modname != distributionName: + moduleVersionDict.pop(modname) + dictn['package_version_dict'] = json.dumps(moduleVersionDict) return dictn +def versionFromDistribution(modname): + """ + If possible, deduce a package version number for the given + module/package name, using distribution metadata. + + If available, return a tuple of (distributionName, versionStr) + Note that the distribution name may not be the same as the + module or package name. + + If unavailable for any reason, return (None, None). + + """ + nullReturn = (None, None) + + # The importlib.metadata module was only introduced in Python 3.8 + if not HAVE_IMPLIB_METADATA: + return nullReturn + pkgs = importlib.metadata.packages_distributions() + distNameList = pkgs.get(modname) + if distNameList is None: + return nullReturn + if len(distNameList) == 0: + return nullReturn + distName = distNameList[0] + try: + verStr = importlib.metadata.version(distName) + except importlib.metadata.PackageNotFoundError: + verStr = None + if verStr is None: + return nullReturn + + return (distName, verStr) + + def writeHistoryToFile(userDict={}, parents=[], *, filename=None, gdalDS=None): """ Make the full processing history and save to the given file. diff --git a/processinghistory/tests.py b/processinghistory/tests.py index 546d689..5edb3bd 100755 --- a/processinghistory/tests.py +++ b/processinghistory/tests.py @@ -76,6 +76,14 @@ def test_singleFile(self): self.assertIn(k, metadict, msg=f"Automatic key {k} missing (driver={drvrName})") + pkgVerDict = metadict['package_version_dict'] + for pkg in ['processinghistory', 'osgeo', 'numpy']: + self.assertIn(pkg, pkgVerDict, + msg=f"Expected '{pkg}' in package_version_dict, not found") + self.assertNotIn('_distutils_hack', pkgVerDict, + msg=("Found _distutils_hack in package_version_dict, " + + "should be recorded as setuptools")) + self.deleteTempFiles(tmpfileList) def test_ancestry(self):