@@ -1004,50 +1004,105 @@ def createRequirementsJSON(cls, jPath=Path.cwd()):
1004
1004
packageList = picklePackages + codeDependencies
1005
1005
packageAndVersion = cls .getLocalPackageVersion (list (set (packageList )))
1006
1006
1007
- with open (Path (jPath ) / "requirements.json" ) as file :
1008
- for package , version in packageAndVersion :
1007
+ # Identify packages with missing versions
1008
+ missingPackageVersions = [item [0 ] for item in packageAndVersion if not item [1 ]]
1009
+
1010
+ with open (Path (jPath ) / "requirements.json" , "w" ) as file :
1011
+ if missingPackageVersions :
1009
1012
jsonStep = json .dumps (
1010
- [
1011
- {
1012
- "step" : "install " + package ,
1013
- "command" : "pip install " + package + "==" + version ,
1014
- }
1015
- ],
1016
- indent = 4 ,
1017
- )
1013
+ [
1014
+ {
1015
+ "Warning" : "The versions for the following packages could not be determined" ,
1016
+ "Packages" : ", " .join (missingPackageVersions )
1017
+ }
1018
+ ],
1019
+ indent = 4 ,
1020
+ )
1021
+ file .write (jsonStep )
1022
+ for package , version in packageAndVersion :
1023
+ if version :
1024
+ jsonStep = json .dumps (
1025
+ [
1026
+ {
1027
+ "step" : "install " + package ,
1028
+ "command" : "pip install " + package + "==" + version ,
1029
+ }
1030
+ ],
1031
+ indent = 4 ,
1032
+ )
1033
+ else :
1034
+ jsonStep = json .dumps (
1035
+ [
1036
+ {
1037
+ "step" : "install " + package ,
1038
+ "command" : "pip install " + package ,
1039
+ }
1040
+ ],
1041
+ indent = 4 ,
1042
+ )
1018
1043
file .write (jsonStep )
1019
1044
1020
1045
def getLocalPackageVersion (self , packageList ):
1046
+ '''Get package versions from the local environment. For Python versions
1047
+ < 3.8, if the package does not contain an attribute of "__version__",
1048
+ "version", or "VERSION", no package version will be found.
1021
1049
1022
- packageVersion = []
1050
+ Parameters
1051
+ ----------
1052
+ packageList : list
1053
+ List of Python packages.
1054
+
1055
+ Returns
1056
+ -------
1057
+ list
1058
+ Nested list of Python package names and found versions.
1059
+ '''
1060
+ packageAndVersion = []
1023
1061
if sys .version_info [1 ] >= 8 :
1024
1062
from importlib .metadata import version
1025
1063
for package in packageList :
1026
1064
try :
1027
- packageVersion .append (version (package ))
1065
+ packageAndVersion .append ([ package , version (package )] )
1028
1066
except PackageNotFoundError :
1029
1067
print ("Warning: Package {} was not found in the local environment, so a version could not be determined." .format (package ))
1030
- print ("The pip installation command will not include a version number." )
1031
- packageVersion .append (None )
1032
- return packageVersion
1068
+ print ("The pip installation command will not include a version number for {}." ). format ( package )
1069
+ packageAndVersion .append ([ package , None ] )
1070
+ return packageAndVersion
1033
1071
else :
1034
1072
import importlib
1035
1073
for package in packageList :
1036
1074
name = importlib .import_module (package )
1037
1075
try :
1038
- packageVersion .append (name .__version__ )
1039
- except :
1076
+ packageAndVersion .append ([package , name .__version__ ])
1077
+ except AttributeError :
1078
+ pass
1040
1079
try :
1041
- packageVersion .append (name .version )
1042
- except :
1080
+ packageAndVersion .append ([ package , name .version ] )
1081
+ except AttributeError :
1043
1082
try :
1044
- packageVersion .append (name .VERSION )
1045
- except :
1083
+ packageAndVersion .append ([ package , name .VERSION ] )
1084
+ except AttributeError :
1046
1085
print ("Warning: Package {} was not found in the local environment, so a version could not be determined." .format (package ))
1047
- print ("The pip installation command will not include a version number." )
1048
- return packageVersion
1086
+ print ("The pip installation command will not include a version number for {}." .format (package ))
1087
+ packageAndVersion .append ([package , None ])
1088
+ return packageAndVersion
1049
1089
1050
- def getCodeDependencies (self , jPath ):
1090
+ def getCodeDependencies (self , jPath = Path .cwd ()):
1091
+ '''Get the package dependencies for all Python scripts in the
1092
+ provided directory path. Note that currently this functionality
1093
+ only works for .py files.
1094
+
1095
+ Parameters
1096
+ ----------
1097
+ jPath : string, optional
1098
+ File location for the output JSON file. Default is the current
1099
+ working directory.
1100
+
1101
+ Returns
1102
+ -------
1103
+ list
1104
+ List of found package dependencies.
1105
+ '''
1051
1106
fileNames = []
1052
1107
fileNames .extend (sorted (Path (jPath ).glob ("*.py" )))
1053
1108
@@ -1059,14 +1114,31 @@ def getCodeDependencies(self, jPath):
1059
1114
return importInfo
1060
1115
1061
1116
def findImports (self , fPath ):
1062
- # modified from https://stackoverflow.com/questions/44988487/regex-to-parse-import-statements-in-python
1117
+ '''Find import calls in provided Python code path. Ignores
1118
+ built in Python modules.
1119
+
1120
+ Credit: modified from https://stackoverflow.com/questions/44988487/regex-to-parse-import-statements-in-python
1121
+
1122
+ Parameters
1123
+ ----------
1124
+ fPath : string
1125
+ File location for the Python file to be parsed.
1126
+
1127
+ Returns
1128
+ -------
1129
+ list
1130
+ List of found package dependencies.
1131
+ '''
1063
1132
import ast
1064
1133
1065
1134
fileText = open (fPath ).read ()
1066
- tree = ast .parse (fileText )
1135
+ # Parse the file to get the abstract syntax tree representation
1136
+ tree = ast .parse (fileText )
1067
1137
modules = []
1068
1138
1139
+ # Walk through each node in the ast to find import calls
1069
1140
for node in ast .walk (tree ):
1141
+ # Determine parent module for from * import * calls
1070
1142
if isinstance (node , ast .ImportFrom ):
1071
1143
for name in node .names :
1072
1144
if not name .asname :
@@ -1078,6 +1150,7 @@ def findImports(self, fPath):
1078
1150
1079
1151
modules = list (set (modules ))
1080
1152
try :
1153
+ # Remove 'settings' module that is auto generated for SAS Model Manager score code
1081
1154
modules .remove ('settings' )
1082
1155
return modules
1083
1156
except ValueError :
@@ -1133,7 +1206,7 @@ def getPackageNames(self, stream):
1133
1206
Generates (module, class_name) tuples from a pickle stream. Extracts all class names referenced
1134
1207
by GLOBAL and STACK_GLOBAL opcodes.
1135
1208
1136
- Credit: https://stackoverflow.com/questions/64850179/inspecting-a-pickle-dump-for-dependencies
1209
+ Credit: modified from https://stackoverflow.com/questions/64850179/inspecting-a-pickle-dump-for-dependencies
1137
1210
More information here: https://github.com/python/cpython/blob/main/Lib/pickletools.py
1138
1211
1139
1212
Parameters
0 commit comments