diff --git a/Python - Create a Virtual Environment/Python - Create a Virtual Environment.step b/Python - Create a Virtual Environment/Python - Create a Virtual Environment.step index 45919fd1..19710d1e 100644 --- a/Python - Create a Virtual Environment/Python - Create a Virtual Environment.step +++ b/Python - Create a Virtual Environment/Python - Create a Virtual Environment.step @@ -1,67 +1 @@ -{ - "name": "Python - Create a Virtual Environment.step", - "creationTimeStamp": "2022-10-15T16:09:21.746Z", - "modifiedTimeStamp": "2022-10-19T15:03:33.683Z", - "createdBy": "Sundaresh.Sankaran@sas.com", - "modifiedBy": "Sundaresh.Sankaran@sas.com", - "displayName": "Python - Create a Virtual Environment.step", - "localDisplayName": "Python - Create a Virtual Environment.step", - "properties": {}, - "links": [{ - "method": "GET", - "rel": "self", - "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "type": "application/vnd.sas.data.flow.step" - }, { - "method": "GET", - "rel": "alternate", - "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "type": "application/vnd.sas.data.flow.step.summary" - }, { - "method": "GET", - "rel": "up", - "href": "/dataFlows/steps", - "uri": "/dataFlows/steps", - "type": "application/vnd.sas.collection", - "itemType": "application/vnd.sas.data.flow.step.summary" - }, { - "method": "PUT", - "rel": "update", - "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "type": "application/vnd.sas.data.flow.step", - "responseType": "application/vnd.sas.data.flow.step" - }, { - "method": "DELETE", - "rel": "delete", - "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f" - }, { - "method": "GET", - "rel": "transferExport", - "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "responseType": "application/vnd.sas.transfer.object" - }, { - "method": "PUT", - "rel": "transferImportUpdate", - "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", - "type": "application/vnd.sas.transfer.object", - "responseType": "application/vnd.sas.summary" - } - ], - "metadataVersion": 0.0, - "version": 2, - "type": "code", - "flowMetadata": { - "inputPorts": [], - "outputPorts": [] - }, - "ui": "{\"showPageContentOnly\": true, \"pages\": [{\"id\": \"param\", \"type\": \"page\", \"label\": \"Parameters\", \"children\": [{\"id\": \"venv\", \"type\": \"path\", \"label\": \"Provide a path for your virtual environment (note: provide a path on the filesystem if you want to persist your virtual environment):\", \"pathtype\": \"folder\", \"placeholder\": \"\", \"required\": false, \"visible\": \"\"}, {\"id\": \"install_system_site_packages\", \"type\": \"checkbox\", \"label\": \"Install system site packages (inherited from global environment)\", \"visible\": \"\"}, {\"id\": \"req\", \"type\": \"textfield\", \"label\": \"Enter the path to your requirements.txt file (or simply list your packages with a space between):\", \"placeholder\": \"\", \"required\": false, \"visible\": \"\"}, {\"id\": \"text1\", \"type\": \"text\", \"text\": \"Notes: \\n1. Creation of a virtual environment also activates the same.\\n2. The virtual environment will be created under a folder named venv.\", \"visible\": \"\"}]}, {\"id\": \"page2\", \"type\": \"page\", \"label\": \"About\", \"children\": [{\"id\": \"text2\", \"type\": \"text\", \"text\": \"Python - Create a Virtual Environment\\n===============================\\n\\nThe \\\"Python - Create a Virtual Environment\\\" custom step helps users create a virtual Python environment, install a given set of packages if needed, and work within the confines of the new Python environment for project or program-specific purposes.\\n\\nUsers may choose to persist the virtual environment folder for later reuse, by simply providing a full path to a persistent location folder. Please provide a path on a shared file system, which you expect to access through another SAS session later on. Most SAS Viya deployments may contain a /mnt/... folder location which serves this purpose.\\n\\nUsers also have an option to provide a requirements file consisting of packages which they would like pip-installed. Either provide a path to a requirements.txt file which contains a list of python packages (and optionally, specific versions), or provide a list of packages as a string, separated by a space.\\n\\nFor more details, refer https://blogs.sas.com/content/subconsciousmusings/2022/05/16/python-a-la-carte/\", \"visible\": \"\"}, {\"id\": \"contact_details\", \"type\": \"section\", \"label\": \"Created / Contact\", \"open\": true, \"visible\": \"\", \"children\": [{\"id\": \"created_contact_text\", \"type\": \"text\", \"text\": \"Sundaresh Sankaran (sundaresh.sankaran@sas.com) and Ali Aiello (ali.aiello@sas.com) \", \"visible\": \"\"}]}, {\"id\": \"about_version\", \"type\": \"section\", \"label\": \"Version\", \"open\": true, \"visible\": \"\", \"children\": [{\"id\": \"version_text\", \"type\": \"text\", \"text\": \"Version : 2.0.0 (26AUG2025)\", \"visible\": \"\"}]}]}], \"syntaxversion\": \"1.3.0\", \"values\": {\"venv\": \"\", \"install_system_site_packages\": true, \"req\": \"\"}}", - "templates": { - "SAS": "/* SAS templated code goes here */\n\n/* -------------------------------------------------------------------------------------------* \n Python - Create a Virtual Environment\n\n v 2.0.0 (26AUG2025)\n\n This program helps you create a Python virtual environment from within a SAS session.\n It captures the current Python executable path and creates a virtual environment in the\n specified location (or current working directory if not specified). It also allows you to\n install packages either from a requirements.txt file or a space-separated list of packages.\n\n Sundaresh Sankaran (sundaresh.sankaran@sas.com|sundaresh.sankaran@gmail.com)\n*-------------------------------------------------------------------------------------------- */\n\n/*-----------------------------------------------------------------------------------------*\n Python Block Definition\n*------------------------------------------------------------------------------------------*/\n\n/*-----------------------------------------------------------------------------------------*\n The following block of code has been created for the purpose of allowing proc python \n to execute within a macro. Execution within a macro allows for other checks to be carried \n out through SAS prior to handing off to the Python step.\n\n In this example, a temporary file is created containing the requisite Python commands, which \n are then executed through infile reference.\n\n Note that Python code is pasted as-is and may be out of line with the SAS indentation followed.\n\n*------------------------------------------------------------------------------------------*/\n\nfilename cvirenv temp;\n\ndata _null_;\n\n length line $32767; * max SAS character size ;\n infile datalines4 truncover pad;\n input ; \n file cvirenv;\n line = strip(_infile_); * line without leading and trailing blanks ;\n l1 = length(trimn(_infile_)); * length of line without trailing blanks ;\n l2 = length(line); * length of line without leading and trailing blanks ;\n first_position=l1-l2+1; * position where the line should start (alignment) ;\n if (line eq ' ') then put @1; * empty line ;\n else put @first_position line; * line without leading and trailing blanks correctly aligned ;\n\n datalines4;\n\n# Import necessary libraries\nimport os\nimport subprocess\nfrom pathlib import Path\n\n# Capture current Python executable and save in a macro variable called ORIGINAL_PYPATH for reference\npyt=os.environ[\"PROC_PYPATH\"]\nSAS.symput(\"ORIGINAL_PYPATH\",str(pyt))\n\n# Create a virtual environment in the directory specified.\nvenv_input=SAS.symget(\"venv_input\")\npath = Path(venv_input)\nif path.exists():\n venv=os.path.join(venv_input,\"venv\")\nelse:\n venv=os.path.join(os.getcwd(),\"venv\")\n\ninstall_system_site_packages=int(SAS.symget(\"install_system_site_packages\"))\n\nif install_system_site_packages==1:\n command = f\"{pyt} -m venv {venv} --system-site-packages\"\nelse:\n command = f\"{pyt} -m venv {venv}\"\n\n\nret = subprocess.run(command, capture_output=True, shell=True)\n\nif ret.__dict__[\"returncode\"]==0: \n SAS.symput(\"TEMP_PYPATH\",str(os.path.join(venv,\"bin\",\"python3\")))\n SAS.logMessage(\"Virtual environment created successfully.\")\nelse:\n SAS.logMessage(\"Error creating virtual environment. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]),\"error\")\n SAS.symput(\"_cvirenv_error_flag\",1) # if error, retain original Python path ;\n SAS.symput(\"_cvirenv_error_desc\",\"Error creating virtual environment. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]))\n\n\n;;;;\nrun; \n\nfilename instpack temp;\n\ndata _null_;\n\n length line $32767; * max SAS character size ;\n infile datalines4 truncover pad;\n input ; \n file instpack;\n line = strip(_infile_); * line without leading and trailing blanks ;\n l1 = length(trimn(_infile_)); * length of line without trailing blanks ;\n l2 = length(line); * length of line without leading and trailing blanks ;\n first_position=l1-l2+1; * position where the line should start (alignment) ;\n if (line eq ' ') then put @1; * empty line ;\n else put @first_position line; * line without leading and trailing blanks correctly aligned ;\n\n datalines4;\n\n# Import necessary libraries\nimport os\nimport subprocess\nimport sys\n\nSAS.logMessage(f\"Current Python Path: {sys.executable}\")\npyt=SAS.symget(\"TEMP_PYPATH\")\nSAS.logMessage(\"Starting Requirements install\")\nreq=SAS.symget(\"req\")\n\n# Error prevention: check for empty requirements file or no package provided\nif not req or req.strip() == \"\":\n SAS.logMessage(\"No requirements file or package list provided. Skipping pip install.\")\nelse:\n if os.path.isfile(req):\n # Check if file is empty\n if os.path.getsize(req) == 0:\n SAS.logMessage(f\"Requirements file '{req}' is empty. Skipping pip install.\")\n else:\n print(\"File provided\")\n command = \"{pyt} -m pip install -r {req}\".format(pyt=pyt,req=req)\n ret = subprocess.run(command, capture_output=True, shell=True)\n if ret.__dict__[\"returncode\"]==0: \n SAS.logMessage(\"Requirements installed successfully.\")\n else:\n SAS.logMessage(\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]),\"error\")\n SAS.symput(\"_cvirenv_error_flag\",1) # if error, retain original Python path ;\n SAS.symput(\"_cvirenv_error_desc\",\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]))\n else:\n # Check if req is just whitespace\n if req.strip() == \"\":\n SAS.logMessage(\"No packages specified in the list. Skipping pip install.\")\n else:\n print(\"List provided\")\n command = \"{pyt} -m pip install {req}\".format(pyt=pyt,req=req)\n ret = subprocess.run(command, capture_output=True, shell=True)\n if ret.__dict__[\"returncode\"]==0: \n SAS.logMessage(\"Requirements installed successfully.\")\n else:\n SAS.logMessage(\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]),\"error\")\n SAS.symput(\"_cvirenv_error_flag\",1) # if error, retain original Python path ;\n SAS.symput(\"_cvirenv_error_desc\",\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]))\n\n\n;;;;\nrun; \n\n/*-----------------------------------------------------------------------------------------*\n MACROS\n*------------------------------------------------------------------------------------------*/\n/* -------------------------------------------------------------------------------------------* \n Macro to initialize a run-time trigger global macro variable to run SAS Studio Custom Steps. \n A value of 1 (the default) enables this custom step to run. A value of 0 (provided by \n upstream code) sets this to disabled.\n\n Input:\n 1. triggerName: The name of the runtime trigger you wish to create. Ensure you provide a \n unique value to this parameter since it will be declared as a global variable.\n\n Output:\n 2. &triggerName : A global variable which takes the name provided to triggerName.\n*-------------------------------------------------------------------------------------------- */\n\n%macro _create_runtime_trigger(triggerName);\n\n %global &triggerName.;\n\n %if %sysevalf(%superq(&triggerName.)=, boolean) %then %do;\n \n %put NOTE: Trigger macro variable &triggerName. does not exist. Creating it now.;\n %let &triggerName.=1;\n\n %end;\n\n%mend _create_runtime_trigger;\n\n/* -----------------------------------------------------------------------------------------* \n Macro to create an error flag for capture during code execution.\n\n Input:\n 1. errorFlagName: The name of the error flag you wish to create. Ensure you provide a \n unique value to this parameter since it will be declared as a global variable.\n 2. errorFlagDesc: A description to add to the error flag.\n\n Output:\n 1. &errorFlagName : A global variable which takes the name provided to errorFlagName.\n 2. &errorFlagDesc : A global variable which takes the name provided to errorFlagDesc.\n*------------------------------------------------------------------------------------------ */\n\n%macro _create_error_flag(errorFlagName, errorFlagDesc);\n\n %global &errorFlagName.;\n %let &errorFlagName.=0;\n %global &errorFlagDesc.;\n\n%mend _create_error_flag;\n\n/* -----------------------------------------------------------------------------------------* \n Macro to extract the path provided from a SAS Studio Custom Step file or folder selector.\n\n Input:\n 1. pathReference: A path reference provided by the file or folder selector control in \n a SAS Studio Custom step.\n\n Output:\n 1. _sas_folder_path: Set inside macro, a global variable containing the path.\n\n Also available at: https://raw.githubusercontent.com/SundareshSankaran/sas_utility_programs/main/code/Extract%20SAS%20Folder%20Path/macro_extract_sas_folder_path.sas\n\n*------------------------------------------------------------------------------------------ */\n\n%macro _extract_sas_folder_path(pathReference);\n\n %global _sas_folder_path;\n\n data _null_;\n call symput(\"_sas_folder_path\", scan(\"&pathReference.\",2,\":\",\"MO\"));\n run;\n\n%mend _extract_sas_folder_path;\n\n/*-----------------------------------------------------------------------------------------*\n EXECUTION CODE MACRO \n\n _cvirenv prefix stands for Create Virtual Environment\n*------------------------------------------------------------------------------------------*/\n%macro _cvirenv_execution_code;\n\n %_create_error_flag(_cvirenv_error_flag, _cvirenv_error_desc);\n\n/*-----------------------------------------------------------------------------------------*\n Extract value from folder selector\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n %_extract_sas_folder_path(&venv.);\n %let venv_input=&_sas_folder_path.;\n %end; \n\n/*-----------------------------------------------------------------------------------------*\n Create a virtual environment in the specified location\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n proc python infile=cvirenv;\n run;\n %end; \n/*-----------------------------------------------------------------------------------------*\n Change interpreter to new virtual environment\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n proc python terminate;\n quit;\n \n options set=PROC_PYPATH=\"&TEMP_PYPATH.\";\n \n %end; \n \n/*-----------------------------------------------------------------------------------------*\n Install Packages\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n proc python infile=instpack;\n run;\n %end; \n\n%mend _cvirenv_execution_code;\n\n/*-----------------------------------------------------------------------------------------*\n END MACROS\n*------------------------------------------------------------------------------------------*/\n\n/*-----------------------------------------------------------------------------------------*\n EXECUTION CODE\n*------------------------------------------------------------------------------------------*/\n \n/*-----------------------------------------------------------------------------------------*\n Create Runtime Trigger\n*------------------------------------------------------------------------------------------*/\n%_create_runtime_trigger(_cvirenv_run_trigger);\n\n/*-----------------------------------------------------------------------------------------*\n Execute \n*------------------------------------------------------------------------------------------*/\n\n%if &_cvirenv_run_trigger. = 1 %then %do;\n\n %_cvirenv_execution_code;\n\n%end;\n\n%if &_cvirenv_run_trigger. = 0 %then %do;\n\n %put NOTE: This step has been disabled. Nothing to do.;\n\n%end;\n\n\n%put NOTE: Final summary;\n%put NOTE: Status of error flag - &_cvirenv_error_flag. ;\n%put &_cvirenv_error_desc.;\n%put NOTE: Error desc - &_cvirenv_error_desc. ;\n\n/*-----------------------------------------------------------------------------------------*\n END EXECUTION CODE\n*------------------------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------------------*\n Clean up existing macro variables and macro definitions.\n*------------------------------------------------------------------------------------------*/\n\n%if %symexist(_cvirenv_run_trigger) %then %do;\n %symdel _cvirenv_run_trigger;\n%end;\n%if %symexist(_cvirenv_error_flag) %then %do;\n %symdel _cvirenv_error_flag;\n%end;\n%if %symexist(_cvirenv_error_desc) %then %do;\n %symdel _cvirenv_error_desc;\n%end;\n\n\n%sysmacdelete _create_runtime_trigger;\n%sysmacdelete _create_error_flag;\n%sysmacdelete _extract_sas_folder_path;\n%sysmacdelete _cvirenv_execution_code;\n\nfilename instpack clear;\nfilename cvirenv clear;\n\n\n\n" - } -} +{"name": "Python - Create a Virtual Environment.step", "creationTimeStamp": "2022-10-15T16:09:21.746Z", "modifiedTimeStamp": "2022-10-19T15:03:33.683Z", "createdBy": "Sundaresh.Sankaran@sas.com", "modifiedBy": "Sundaresh.Sankaran@sas.com", "displayName": "Python - Create a Virtual Environment.step", "localDisplayName": "Python - Create a Virtual Environment.step", "properties": {}, "links": [{"method": "GET", "rel": "self", "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "type": "application/vnd.sas.data.flow.step"}, {"method": "GET", "rel": "alternate", "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "type": "application/vnd.sas.data.flow.step.summary"}, {"method": "GET", "rel": "up", "href": "/dataFlows/steps", "uri": "/dataFlows/steps", "type": "application/vnd.sas.collection", "itemType": "application/vnd.sas.data.flow.step.summary"}, {"method": "PUT", "rel": "update", "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "type": "application/vnd.sas.data.flow.step", "responseType": "application/vnd.sas.data.flow.step"}, {"method": "DELETE", "rel": "delete", "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f"}, {"method": "GET", "rel": "transferExport", "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "responseType": "application/vnd.sas.transfer.object"}, {"method": "PUT", "rel": "transferImportUpdate", "href": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "uri": "/dataFlows/steps/2971fefb-bfb5-4bbe-9832-1235358c671f", "type": "application/vnd.sas.transfer.object", "responseType": "application/vnd.sas.summary"}], "metadataVersion": 0.0, "version": 2, "type": "code", "flowMetadata": {"inputPorts": [], "outputPorts": []}, "ui": "{\"showPageContentOnly\": true, \"pages\": [{\"id\": \"param\", \"type\": \"page\", \"label\": \"Parameters\", \"children\": [{\"id\": \"venv\", \"type\": \"path\", \"label\": \"Provide a path for your virtual environment (note: provide a path on the filesystem if you want to persist your virtual environment):\", \"pathtype\": \"folder\", \"placeholder\": \"\", \"required\": false, \"visible\": \"\"}, {\"id\": \"install_system_site_packages\", \"type\": \"checkbox\", \"label\": \"Install system site packages (inherited from global environment)\", \"visible\": \"\"}, {\"id\": \"req\", \"type\": \"textfield\", \"label\": \"Enter the path to your requirements.txt file (or simply list your packages with a space between)\", \"placeholder\": \"\", \"required\": false, \"visible\": \"\"}, {\"id\": \"text1\", \"type\": \"text\", \"text\": \"Notes: \\n1. Creation of a virtual environment also activates the same.\\n2. The virtual environment will be created under a folder named venv.\", \"visible\": \"\"}]}, {\"id\": \"page2\", \"type\": \"page\", \"label\": \"About\", \"children\": [{\"id\": \"text2\", \"type\": \"text\", \"text\": \"Python - Create a Virtual Environment\\n===============================\\n\\nThe \\\"Python - Create a Virtual Environment\\\" custom step helps users create a virtual Python environment, install a given set of packages if needed, and work within the confines of the new Python environment for project or program-specific purposes.\\n\\nUsers may choose to persist the virtual environment folder for later reuse, by simply providing a full path to a persistent location folder. Please provide a path on a shared file system, which you expect to access through another SAS session later on. Most SAS Viya deployments may contain a /mnt/... folder location which serves this purpose.\\n\\nUsers also have an option to provide a requirements file consisting of packages which they would like pip-installed. Either provide a path to a requirements.txt file which contains a list of python packages (and optionally, specific versions), or provide a list of packages as a string, separated by a space.\\n\\nFor more details, refer https://blogs.sas.com/content/subconsciousmusings/2022/05/16/python-a-la-carte/\", \"visible\": \"\"}, {\"id\": \"contact_details\", \"type\": \"section\", \"label\": \"Created / Contact\", \"open\": true, \"visible\": \"\", \"children\": [{\"id\": \"created_contact_text\", \"type\": \"text\", \"text\": \"Sundaresh Sankaran (sundaresh.sankaran@sas.com) and Ali Aiello (ali.aiello@sas.com) \", \"visible\": \"\"}]}, {\"id\": \"about_version\", \"type\": \"section\", \"label\": \"Version\", \"open\": true, \"visible\": \"\", \"children\": [{\"id\": \"version_text\", \"type\": \"text\", \"text\": \"Version : 2.1.0 (01SEP2025)\", \"visible\": \"\"}]}]}], \"syntaxversion\": \"1.3.0\", \"values\": {\"venv\": \"\", \"install_system_site_packages\": true, \"req\": \"\"}}", "templates": {"SAS": "/* SAS templated code goes here */\n\n/* -------------------------------------------------------------------------------------------* \n Python - Create a Virtual Environment\n\n v 2.1.0 (01SEP2025)\n\n This program helps you create a Python virtual environment from within a SAS session.\n It captures the current Python executable path and creates a virtual environment in the\n specified location (or current working directory if not specified). It also allows you to\n install packages either from a requirements.txt file or a space-separated list of packages.\n\n Sundaresh Sankaran (sundaresh.sankaran@sas.com|sundaresh.sankaran@gmail.com)\n*-------------------------------------------------------------------------------------------- */\n\n/*-----------------------------------------------------------------------------------------*\n Python Block Definition\n*------------------------------------------------------------------------------------------*/\n\n/*-----------------------------------------------------------------------------------------*\n The following block of code has been created for the purpose of allowing proc python \n to execute within a macro. Execution within a macro allows for other checks to be carried \n out through SAS prior to handing off to the Python step.\n\n In this example, a temporary file is created containing the requisite Python commands, which \n are then executed through infile reference.\n\n Note that Python code is pasted as-is and may be out of line with the SAS indentation followed.\n\n*------------------------------------------------------------------------------------------*/\n\nfilename cvirenv temp;\n\ndata _null_;\n\n length line $32767; * max SAS character size ;\n infile datalines4 truncover pad;\n input ; \n file cvirenv;\n line = strip(_infile_); * line without leading and trailing blanks ;\n l1 = length(trimn(_infile_)); * length of line without trailing blanks ;\n l2 = length(line); * length of line without leading and trailing blanks ;\n first_position=l1-l2+1; * position where the line should start (alignment) ;\n if (line eq ' ') then put @1; * empty line ;\n else put @first_position line; * line without leading and trailing blanks correctly aligned ;\n\n datalines4;\n\n# Import necessary libraries\nimport os\nimport subprocess\nfrom pathlib import Path\n\n# Capture current Python executable and save in a macro variable called ORIGINAL_PYPATH for reference\npyt=os.environ[\"PROC_PYPATH\"]\nSAS.logMessage(f\"Current Python Path: {pyt}\")\n\n# Create a virtual environment in the directory specified.\nvenv_input=SAS.symget(\"venv_input\")\npath = Path(venv_input)\nif path.exists():\n venv=os.path.join(venv_input,\"venv\")\nelse:\n venv=os.path.join(os.getcwd(),\"venv\")\n\ninstall_system_site_packages=int(SAS.symget(\"install_system_site_packages\"))\n\nif install_system_site_packages==1:\n command = f\"{pyt} -m venv {venv} --system-site-packages\"\nelse:\n command = f\"{pyt} -m venv {venv}\"\n\n\nret = subprocess.run(command, capture_output=True, shell=True)\n\nif ret.__dict__[\"returncode\"]==0: \n SAS.symput(\"TEMP_PYPATH\",str(os.path.join(venv,\"bin\",\"python3\")))\n SAS.logMessage(\"Virtual environment created successfully.\")\nelse:\n SAS.logMessage(\"Error creating virtual environment. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]),\"error\")\n SAS.symput(\"_cvirenv_error_flag\",1) # if error, retain original Python path ;\n SAS.symput(\"_cvirenv_error_desc\",\"Error creating virtual environment. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]))\n\n\n;;;;\nrun; \n\nfilename instpack temp;\n\ndata _null_;\n\n length line $32767; * max SAS character size ;\n infile datalines4 truncover pad;\n input ; \n file instpack;\n line = strip(_infile_); * line without leading and trailing blanks ;\n l1 = length(trimn(_infile_)); * length of line without trailing blanks ;\n l2 = length(line); * length of line without leading and trailing blanks ;\n first_position=l1-l2+1; * position where the line should start (alignment) ;\n if (line eq ' ') then put @1; * empty line ;\n else put @first_position line; * line without leading and trailing blanks correctly aligned ;\n\n datalines4;\n\n# Import necessary libraries\nimport os\nimport subprocess\nimport sys\n\nSAS.logMessage(f\"Current Python Path: {sys.executable}\")\npyt=SAS.symget(\"TEMP_PYPATH\")\nSAS.logMessage(\"Starting Requirements install\")\nreq=SAS.symget(\"req\")\n\n# Error prevention: check for empty requirements file or no package provided\nif not req or req.strip() == \"\":\n SAS.logMessage(\"No requirements file or package list provided. Skipping pip install.\")\nelse:\n if os.path.isfile(req):\n # Check if file is empty\n if os.path.getsize(req) == 0:\n SAS.logMessage(f\"Requirements file '{req}' is empty. Skipping pip install.\")\n else:\n print(\"File provided\")\n command = \"{pyt} -m pip install -r {req}\".format(pyt=pyt,req=req)\n ret = subprocess.run(command, capture_output=True, shell=True)\n if ret.__dict__[\"returncode\"]==0: \n SAS.logMessage(\"Requirements installed successfully.\")\n else:\n SAS.logMessage(\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]),\"error\")\n SAS.symput(\"_cvirenv_error_flag\",1) # if error, retain original Python path ;\n SAS.symput(\"_cvirenv_error_desc\",\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]))\n else:\n # Check if req is just whitespace\n if req.strip() == \"\":\n SAS.logMessage(\"No packages specified in the list. Skipping pip install.\")\n else:\n print(\"List provided\")\n command = \"{pyt} -m pip install {req}\".format(pyt=pyt,req=req)\n ret = subprocess.run(command, capture_output=True, shell=True)\n if ret.__dict__[\"returncode\"]==0: \n SAS.logMessage(\"Requirements installed successfully.\")\n else:\n SAS.logMessage(\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]),\"error\")\n SAS.symput(\"_cvirenv_error_flag\",1) # if error, retain original Python path ;\n SAS.symput(\"_cvirenv_error_desc\",\"Error installing requirements. Return code: \" + str(ret.__dict__[\"returncode\"]) + \". Error message: \" + str(ret.__dict__[\"stderr\"]))\n\n\n;;;;\nrun; \n\n/*-----------------------------------------------------------------------------------------*\n MACROS\n*------------------------------------------------------------------------------------------*/\n\n/*-----------------------------------------------------------------------------------------*\n The following two macro variable is meant to be global as it refers to a changed state \n of the global environment (SAS Studio session) it exists in.\n*------------------------------------------------------------------------------------------*/\n%global ORIGINAL_PYPATH;\n\n\n\n/* -------------------------------------------------------------------------------------------* \n Macro to initialize a run-time trigger global macro variable to run SAS Studio Custom Steps. \n A value of 1 (the default) enables this custom step to run. A value of 0 (provided by \n upstream code) sets this to disabled.\n\n Input:\n 1. triggerName: The name of the runtime trigger you wish to create. Ensure you provide a \n unique value to this parameter since it will be declared as a global variable.\n\n Output:\n 2. &triggerName : A global variable which takes the name provided to triggerName.\n*-------------------------------------------------------------------------------------------- */\n\n%macro _create_runtime_trigger(triggerName);\n\n %global &triggerName.;\n\n %if %sysevalf(%superq(&triggerName.)=, boolean) %then %do;\n \n %put NOTE: Trigger macro variable &triggerName. does not exist. Creating it now.;\n %let &triggerName.=1;\n\n %end;\n\n%mend _create_runtime_trigger;\n\n/* -----------------------------------------------------------------------------------------* \n Macro to create an error flag for capture during code execution.\n\n Input:\n 1. errorFlagName: The name of the error flag you wish to create. Ensure you provide a \n unique value to this parameter since it will be declared as a global variable.\n 2. errorFlagDesc: A description to add to the error flag.\n\n Output:\n 1. &errorFlagName : A global variable which takes the name provided to errorFlagName.\n 2. &errorFlagDesc : A global variable which takes the name provided to errorFlagDesc.\n*------------------------------------------------------------------------------------------ */\n\n%macro _create_error_flag(errorFlagName, errorFlagDesc);\n\n %global &errorFlagName.;\n %let &errorFlagName.=0;\n %global &errorFlagDesc.;\n\n%mend _create_error_flag;\n\n/* -----------------------------------------------------------------------------------------* \n Macro to extract the path provided from a SAS Studio Custom Step file or folder selector.\n\n Input:\n 1. pathReference: A path reference provided by the file or folder selector control in \n a SAS Studio Custom step.\n\n Output:\n 1. _sas_folder_path: Set inside macro, a global variable containing the path.\n\n Also available at: https://raw.githubusercontent.com/SundareshSankaran/sas_utility_programs/main/code/Extract%20SAS%20Folder%20Path/macro_extract_sas_folder_path.sas\n\n*------------------------------------------------------------------------------------------ */\n\n%macro _extract_sas_folder_path(pathReference);\n\n %global _sas_folder_path;\n\n data _null_;\n call symput(\"_sas_folder_path\", scan(\"&pathReference.\",2,\":\",\"MO\"));\n run;\n\n%mend _extract_sas_folder_path;\n\n/*-----------------------------------------------------------------------------------------*\n EXECUTION CODE MACRO \n\n _cvirenv prefix stands for Create Virtual Environment\n*------------------------------------------------------------------------------------------*/\n%macro _cvirenv_execution_code;\n\n %_create_error_flag(_cvirenv_error_flag, _cvirenv_error_desc);\n\n/*-----------------------------------------------------------------------------------------*\n Extract existing Python path\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n %let ORIGINAL_PYPATH=%sysget(PROC_PYPATH);\n %end; \n\n/*-----------------------------------------------------------------------------------------*\n Extract value from folder selector\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n %_extract_sas_folder_path(&venv.);\n %let venv_input=&_sas_folder_path.;\n %end; \n\n/*-----------------------------------------------------------------------------------------*\n Create a virtual environment in the specified location\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n proc python infile=cvirenv;\n run;\n %end; \n/*-----------------------------------------------------------------------------------------*\n Change interpreter to new virtual environment\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n proc python terminate;\n quit;\n \n options set=PROC_PYPATH=\"&TEMP_PYPATH.\";\n \n %end; \n \n/*-----------------------------------------------------------------------------------------*\n Install Packages\n*------------------------------------------------------------------------------------------*/\n %if &_cvirenv_error_flag. = 0 %then %do;\n proc python infile=instpack;\n run;\n %end; \n\n%mend _cvirenv_execution_code;\n\n/*-----------------------------------------------------------------------------------------*\n END MACROS\n*------------------------------------------------------------------------------------------*/\n\n/*-----------------------------------------------------------------------------------------*\n EXECUTION CODE\n*------------------------------------------------------------------------------------------*/\n \n/*-----------------------------------------------------------------------------------------*\n Create Runtime Trigger\n*------------------------------------------------------------------------------------------*/\n%_create_runtime_trigger(_cvirenv_run_trigger);\n\n/*-----------------------------------------------------------------------------------------*\n Execute \n*------------------------------------------------------------------------------------------*/\n\n%if &_cvirenv_run_trigger. = 1 %then %do;\n\n %_cvirenv_execution_code;\n\n%end;\n\n%if &_cvirenv_run_trigger. = 0 %then %do;\n\n %put NOTE: This step has been disabled. Nothing to do.;\n\n%end;\n\n\n%put NOTE: Final summary;\n%put NOTE: Status of error flag - &_cvirenv_error_flag. ;\n%put &_cvirenv_error_desc.;\n%put NOTE: Error desc - &_cvirenv_error_desc. ;\n\n/*-----------------------------------------------------------------------------------------*\n END EXECUTION CODE\n*------------------------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------------------*\n Clean up existing macro variables and macro definitions.\n*------------------------------------------------------------------------------------------*/\n\n%if %symexist(_cvirenv_run_trigger) %then %do;\n %symdel _cvirenv_run_trigger;\n%end;\n%if %symexist(_cvirenv_error_flag) %then %do;\n %symdel _cvirenv_error_flag;\n%end;\n%if %symexist(_cvirenv_error_desc) %then %do;\n %symdel _cvirenv_error_desc;\n%end;\n\n\n%sysmacdelete _create_runtime_trigger;\n%sysmacdelete _create_error_flag;\n%sysmacdelete _extract_sas_folder_path;\n%sysmacdelete _cvirenv_execution_code;\n\nfilename instpack clear;\nfilename cvirenv clear;\n\n\n\n"}} \ No newline at end of file diff --git a/Python - Create a Virtual Environment/README.md b/Python - Create a Virtual Environment/README.md index 2534ca21..91654a73 100644 --- a/Python - Create a Virtual Environment/README.md +++ b/Python - Create a Virtual Environment/README.md @@ -50,6 +50,8 @@ This repository contains 5 custom steps which are offered as examples of how you ## Change Log +* Version 2.1.0 (01SEP2025) + - Converted ORIGINAL_PYPATH to global macro variable to avoid breaking downstream steps * Version 2.0.0 (26AUG2025) - **Refactored code to leverage venv (*Goodbye, virtualenv!*)** - Separate folder in repository diff --git a/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment components.json b/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment components.json index b2d5fbdd..78e19b1b 100644 --- a/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment components.json +++ b/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment components.json @@ -73,7 +73,7 @@ { "id": "version_text", "type": "text", - "text": "Version : 2.0.0 (26AUG2025)", + "text": "Version : 2.1.0 (01SEP2025)", "visible": "" } ] diff --git a/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment.sas b/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment.sas index cdc68e27..096c7fc2 100644 --- a/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment.sas +++ b/Python - Create a Virtual Environment/extras/Python - Create a Virtual Environment.sas @@ -3,7 +3,7 @@ /* -------------------------------------------------------------------------------------------* Python - Create a Virtual Environment - v 2.0.0 (26AUG2025) + v 2.1.0 (01SEP2025) This program helps you create a Python virtual environment from within a SAS session. It captures the current Python executable path and creates a virtual environment in the @@ -53,7 +53,7 @@ from pathlib import Path # Capture current Python executable and save in a macro variable called ORIGINAL_PYPATH for reference pyt=os.environ["PROC_PYPATH"] -SAS.symput("ORIGINAL_PYPATH",str(pyt)) +SAS.logMessage(f"Current Python Path: {pyt}") # Create a virtual environment in the directory specified. venv_input=SAS.symget("venv_input") @@ -152,6 +152,15 @@ run; /*-----------------------------------------------------------------------------------------* MACROS *------------------------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------------------------* + The following two macro variable is meant to be global as it refers to a changed state + of the global environment (SAS Studio session) it exists in. +*------------------------------------------------------------------------------------------*/ +%global ORIGINAL_PYPATH; + + + /* -------------------------------------------------------------------------------------------* Macro to initialize a run-time trigger global macro variable to run SAS Studio Custom Steps. A value of 1 (the default) enables this custom step to run. A value of 0 (provided by @@ -232,6 +241,13 @@ run; %_create_error_flag(_cvirenv_error_flag, _cvirenv_error_desc); +/*-----------------------------------------------------------------------------------------* + Extract existing Python path +*------------------------------------------------------------------------------------------*/ + %if &_cvirenv_error_flag. = 0 %then %do; + %let ORIGINAL_PYPATH=%sysget(PROC_PYPATH); + %end; + /*-----------------------------------------------------------------------------------------* Extract value from folder selector *------------------------------------------------------------------------------------------*/ diff --git a/Python - Create a Virtual Environment/extras/Test_observations.md b/Python - Create a Virtual Environment/extras/Test_observations.md new file mode 100644 index 00000000..fdaf1eeb --- /dev/null +++ b/Python - Create a Virtual Environment/extras/Test_observations.md @@ -0,0 +1,15 @@ +# Testing Bugs encountered + +The following is more an internal focussed activity (and perhaps won't / needn't be included). To illustrate bugs or errors that came about during a test of this custom step (in the hope that it promotes awareness / learning) + +1. Testing of [Python - Switch Environments](../../Python%20-%20Switch%20Environments/) raised this error, which gives us the lesson that ORIGINAL_PYPATH should be made global. + +```sas +399 %put &_switchenv_error_desc.; +ERROR: ORIGINAL_PYPATH does not exist. Cannot revert to original environment. +400 %put NOTE: Error desc if any - &_switchenv_error_desc. ; +NOTE: Error desc if any - ERROR: ORIGINAL_PYPATH does not exist. Cannot revert to original environment. +401 + +``` + diff --git a/Python - Switch Environments/.gitignore b/Python - Switch Environments/.gitignore new file mode 100644 index 00000000..0ea49162 --- /dev/null +++ b/Python - Switch Environments/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +Test * \ No newline at end of file diff --git a/Python - Switch Environments/Python - Switch Environments.step b/Python - Switch Environments/Python - Switch Environments.step new file mode 100644 index 00000000..1c878c4b --- /dev/null +++ b/Python - Switch Environments/Python - Switch Environments.step @@ -0,0 +1 @@ +{"name": "Python - Activate a Virtual Environment.step", "creationTimeStamp": "2022-10-15T16:09:21.438Z", "modifiedTimeStamp": "2022-10-19T14:33:18.560Z", "createdBy": "Sundaresh.Sankaran@sas.com", "modifiedBy": "Sundaresh.Sankaran@sas.com", "displayName": "Python - Activate a Virtual Environment.step", "localDisplayName": "Python - Activate a Virtual Environment.step", "properties": {}, "links": [{"method": "GET", "rel": "self", "href": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "uri": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "type": "application/vnd.sas.data.flow.step"}, {"method": "GET", "rel": "alternate", "href": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "uri": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "type": "application/vnd.sas.data.flow.step.summary"}, {"method": "GET", "rel": "up", "href": "/dataFlows/steps", "uri": "/dataFlows/steps", "type": "application/vnd.sas.collection", "itemType": "application/vnd.sas.data.flow.step.summary"}, {"method": "PUT", "rel": "update", "href": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "uri": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "type": "application/vnd.sas.data.flow.step", "responseType": "application/vnd.sas.data.flow.step"}, {"method": "DELETE", "rel": "delete", "href": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "uri": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5"}, {"method": "GET", "rel": "transferExport", "href": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "uri": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "responseType": "application/vnd.sas.transfer.object"}, {"method": "PUT", "rel": "transferImportUpdate", "href": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "uri": "/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5", "type": "application/vnd.sas.transfer.object", "responseType": "application/vnd.sas.summary"}], "metadataVersion": 0.0, "version": 2, "type": "code", "flowMetadata": {"inputPorts": [], "outputPorts": []}, "ui": "{\"showPageContentOnly\": true, \"pages\": [{\"id\": \"param\", \"type\": \"page\", \"label\": \"Parameters\", \"children\": [{\"id\": \"revert_to_original\", \"type\": \"radiogroup\", \"label\": \"What would you like to do?\", \"items\": [{\"value\": \"1\", \"label\": \"Revert to original Python environment\"}, {\"value\": \"0\", \"label\": \"Switch to specified Python environment(venv)\"}], \"visible\": \"\"}, {\"id\": \"venv\", \"type\": \"path\", \"label\": \"Provide a path to your virtual environment (the 'venv' folder or the folder containing /bin/python3):\", \"pathtype\": \"folder\", \"placeholder\": \"\", \"required\": false, \"visible\": [\"$revert_to_original\", \"=\", \"0\"], \"enabled\": [\"$revert_to_original\", \"=\", \"0\"]}, {\"id\": \"text1\", \"type\": \"text\", \"text\": \"If you choose to revert, the Python environment will be set to the original or default environment used by SAS Studio. If you choose to switch, you must provide the full path to the virtual environment you wish to use.\", \"visible\": \"\"}]}, {\"id\": \"about\", \"type\": \"page\", \"label\": \"About\", \"children\": [{\"id\": \"about_text\", \"type\": \"text\", \"text\": \"Python - Switch Environments\\n===============================\\n\\nThis custom step allows you to switch between different Python environments from within a SAS session, or revert to the original environment.\\n\\nFor more details, refer to the documentation or contact the author.\", \"visible\": \"\"}, {\"id\": \"contact_details\", \"type\": \"section\", \"label\": \"Created / Contact\", \"open\": true, \"visible\": \"\", \"children\": [{\"id\": \"created_contact_text\", \"type\": \"text\", \"text\": \"Sundaresh Sankaran (sundaresh.sankaran@sas.com)\", \"visible\": \"\"}]}, {\"id\": \"about_version\", \"type\": \"section\", \"label\": \"Version\", \"open\": true, \"visible\": \"\", \"children\": [{\"id\": \"version_text\", \"type\": \"text\", \"text\": \"Version : 1.0.0 (29AUG2025)\", \"visible\": \"\"}]}]}], \"syntaxversion\": \"1.3.0\", \"values\": {\"revert_to_original\": {\"value\": \"1\", \"label\": \"Revert to original Python environment\"}, \"venv\": \"\"}}", "templates": {"SAS": "/* SAS templated code goes here */\n\n/* -------------------------------------------------------------------------------------------* \n Python - Switch Environments\n\n v 1.0.0 (29AUG2025)\n\n This program helps you switch between different Python environments from within a SAS session.\n It is also meant as a mechanism for a user to revert to the base environment from an active \n virtual environment.\n\n Sundaresh Sankaran (sundaresh.sankaran@sas.com|sundaresh.sankaran@gmail.com)\n*-------------------------------------------------------------------------------------------- */\n\n/*-----------------------------------------------------------------------------------------*\n Debug Section\n\n The following code block (to be commented out or deleted in production) is meant to \n help you debug the custom step. It sets values for the macro variables that would \n normally be set through the custom step interface.\n\n Uncomment and modify the values as needed to test the custom step outside of SAS Studio.\n \n Here's the requirement from the project:\n\n Receives input from user regarding folder location of venv (include venv)\n Changes options to point to same\n If user wants to revert to original, check for presence of ORIGINAL_PYPATH\n Revert to Original_PYPATH if so\n SAS log message to indicate success\n sys.executable to specify the interpreter and remove confusion\n All the useful macro and wiring\n\n*------------------------------------------------------------------------------------------*/\n\n/* === User Input Macro Variables (in logical order) === */\n\n\n/* 1. Option to revert to original Python environment (1 = Revert, 0 = Use specified venv)*/;\n* %let revert_to_original = 1; /* Set to 1 to revert to ORIGINAL_PYPATH, 0 to use a specified venv */\n\n/* 2. If not reverting, specify the folder location of the virtual environment (venv) */\n* %let venv = ; /* Provide the full path including 'venv' folder if revert_to_original=0 */\n\n*/; \n\n/*-----------------------------------------------------------------------------------------*\n Python Block Definition\n*------------------------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------------------*\n The following block of code has been created for the purpose of allowing proc python \n to execute within a macro. Execution within a macro allows for other checks to be carried \n out through SAS prior to handing off to the Python step.\n\n In this example, a temporary file is created containing the requisite Python commands, which \n are then executed through infile reference.\n\n Note that Python code is pasted as-is and may be out of line with the SAS indentation followed.\n\n*------------------------------------------------------------------------------------------*/\n\nfilename printint temp;\n\ndata _null_;\n\n length line $32767; * max SAS character size ;\n infile datalines4 truncover pad;\n input ; \n file printint;\n line = strip(_infile_); * line without leading and trailing blanks ;\n l1 = length(trimn(_infile_)); * length of line without trailing blanks ;\n l2 = length(line); * length of line without leading and trailing blanks ;\n first_position=l1-l2+1; * position where the line should start (alignment) ;\n if (line eq ' ') then put @1; * empty line ;\n else put @first_position line; * line without leading and trailing blanks correctly aligned ;\n\n datalines4;\n\n# Import necessary libraries\nimport sys\n\nSAS.logMessage(f\"Current Python Path: {sys.executable}\")\n\n\n;;;;\nrun; \n\n/*-----------------------------------------------------------------------------------------*\n MACROS\n*------------------------------------------------------------------------------------------*/\n\n/*-----------------------------------------------------------------------------------------*\n The following macro variable is defined as global because it will help in cases\n involving repeated use of this step. \n*------------------------------------------------------------------------------------------*/\n\n%global ORIGINAL_PYPATH;\n\n/* -------------------------------------------------------------------------------------------* \n Macro to initialize a run-time trigger global macro variable to run SAS Studio Custom Steps. \n A value of 1 (the default) enables this custom step to run. A value of 0 (provided by \n upstream code) sets this to disabled.\n\n Input:\n 1. triggerName: The name of the runtime trigger you wish to create. Ensure you provide a \n unique value to this parameter since it will be declared as a global variable.\n\n Output:\n 2. &triggerName : A global variable which takes the name provided to triggerName.\n*-------------------------------------------------------------------------------------------- */\n\n%macro _create_runtime_trigger(triggerName);\n\n %global &triggerName.;\n\n %if %sysevalf(%superq(&triggerName.)=, boolean) %then %do;\n \n %put NOTE: Trigger macro variable &triggerName. does not exist. Creating it now.;\n %let &triggerName.=1;\n\n %end;\n\n%mend _create_runtime_trigger;\n\n/* -----------------------------------------------------------------------------------------* \n Macro to create an error flag for capture during code execution.\n\n Input:\n 1. errorFlagName: The name of the error flag you wish to create. Ensure you provide a \n unique value to this parameter since it will be declared as a global variable.\n 2. errorFlagDesc: A description to add to the error flag.\n\n Output:\n 1. &errorFlagName : A global variable which takes the name provided to errorFlagName.\n 2. &errorFlagDesc : A global variable which takes the name provided to errorFlagDesc.\n*------------------------------------------------------------------------------------------ */\n\n%macro _create_error_flag(errorFlagName, errorFlagDesc);\n\n %global &errorFlagName.;\n %let &errorFlagName.=0;\n %global &errorFlagDesc.;\n\n%mend _create_error_flag;\n\n/* -----------------------------------------------------------------------------------------* \n Macro to extract the path provided from a SAS Studio Custom Step file or folder selector.\n\n Input:\n 1. pathReference: A path reference provided by the file or folder selector control in \n a SAS Studio Custom step.\n\n Output:\n 1. _sas_folder_path: Set inside macro, a global variable containing the path.\n\n Also available at: https://raw.githubusercontent.com/SundareshSankaran/sas_utility_programs/main/code/Extract%20SAS%20Folder%20Path/macro_extract_sas_folder_path.sas\n\n*------------------------------------------------------------------------------------------ */\n\n%macro _extract_sas_folder_path(pathReference);\n\n %global _sas_folder_path;\n\n data _null_;\n call symput(\"_sas_folder_path\", scan(\"&pathReference.\",2,\":\",\"MO\"));\n run;\n\n%mend _extract_sas_folder_path;\n\n\n/*-----------------------------------------------------------------------------------------*\n EXECUTION CODE MACRO \n\n _switchenv prefix stands for Switch Environments\n*------------------------------------------------------------------------------------------*/\n%macro _switchenv_execution_code;\n\n %let revertt=&REVERT_TO_ORIGINAL;\n\n %_create_error_flag(_switchenv_error_flag, _switchenv_error_desc);\n\n/*-----------------------------------------------------------------------------------------*\n Check if user wants to revert to original environment\n*------------------------------------------------------------------------------------------*/\n %if &_switchenv_error_flag. = 0 %then %do;\n %put NOTE: This is the current state of revertt - &revertt. ;\n %if \"&revertt.\" = \"1\" %then %do;\n %if %symexist(ORIGINAL_PYPATH) %then %do;\n options set=PROC_PYPATH=\"&ORIGINAL_PYPATH.\";\n %let _switchenv_error_flag=0;\n %let _switchenv_error_desc=NOTE: Set Python path to original Python environment at &ORIGINAL_PYPATH..;\n %end;\n %else %do;\n/*-----------------------------------------------------------------------------------------*\n Insert code to check if default path exists at /opt/sas/viya/home/sas-pyconfig\n*------------------------------------------------------------------------------------------*/\n %if \"%str(%sysfunc(fileexist(/opt/sas/viya/home/sas-pyconfig/default_py/bin/python3)))\" = \"1\" %then %do;\n options set=PROC_PYPATH=\"/opt/sas/viya/home/sas-pyconfig/default_py/bin/python3\";\n %let _switchenv_error_flag=0;\n %let _switchenv_error_desc=NOTE: Set Python path to default Python profile environment at /opt/sas/viya/home/sas-pyconfig/default_py/bin/python3.;\n /*-----------------------------------------------------------------------------------------*\n Retain ORIGINAL_PYPATH for any future repeated use\n*------------------------------------------------------------------------------------------*/\n %let ORIGINAL_PYPATH = %sysget(PROC_PYPATH); \n %end;\n %else %do;\n %let _switchenv_error_flag=1;\n %let _switchenv_error_desc=ERROR: Neither an ORIGINAL_PYPATH variable or default Python path exists. Cannot revert to original environment.;\n/*-----------------------------------------------------------------------------------------*\n Record current path as ORIGINAL_PYPATH for any future repeated use\n*------------------------------------------------------------------------------------------*/\n %let ORIGINAL_PYPATH = %sysget(PROC_PYPATH); \n %end;\n %end;\n %end;\n %else %if \"&revertt.\" = \"0\" %then %do;\n/*-----------------------------------------------------------------------------------------*\n Record ORIGINAL_PYPATH in order to roll back if needed\n Note: Original means the current path before the following code runs.\n*------------------------------------------------------------------------------------------*/\n %let ORIGINAL_PYPATH = %sysget(PROC_PYPATH); \n %_extract_sas_folder_path(&venv.);\n %let venv_input=&_sas_folder_path.;\n %if \"&venv_input.\" = \"\" %then %do;\n %let _switchenv_error_flag=1;\n %let _switchenv_error_desc=ERROR: No virtual environment path specified. Please provide a valid path.;\n %end;\n %else %do;\n %if %str(%sysfunc(fileexist(&venv_input./bin/python3))) = \"0\" %then %do;\n %let _switchenv_error_flag=1;\n %let _switchenv_error_desc=ERROR: The specified virtual environment path &venv_input. does not exist or is invalid. Please provide a valid path.;\n %end;\n %else %do;\n options set=PROC_PYPATH=\"&venv_input./bin/python3\";\n %let _switchenv_error_flag=0;\n %let _switchenv_error_desc=NOTE: Set Python path to virtual environment at &venv_input./bin/python3.;\n \n %end;\n %end;\n %end;\n %end;\n\n/*-----------------------------------------------------------------------------------------*\n Reset interpreter to new virtual environment\n*------------------------------------------------------------------------------------------*/\n %if &_SWITCHENV_ERROR_FLAG. = 0 %then %do;\n proc python terminate;\n quit;\n\n proc python infile=printint; \n run;\n\n %end; \n\n\n \n\n%mend _switchenv_execution_code;\n\n/*-----------------------------------------------------------------------------------------*\n END MACROS\n*------------------------------------------------------------------------------------------*/\n\n/*-----------------------------------------------------------------------------------------*\n EXECUTION CODE\n*------------------------------------------------------------------------------------------*/\n \n/*-----------------------------------------------------------------------------------------*\n Create Runtime Trigger\n*------------------------------------------------------------------------------------------*/\n%_create_runtime_trigger(_switchenv_run_trigger);\n\n/*-----------------------------------------------------------------------------------------*\n Execute \n*------------------------------------------------------------------------------------------*/\n\n%if &_switchenv_run_trigger. = 1 %then %do;\n\n %_switchenv_execution_code;\n\n%end;\n\n%if &_switchenv_run_trigger. = 0 %then %do;\n\n %put NOTE: This step has been disabled. Nothing to do.;\n\n%end;\n\n\n%put NOTE: Final summary;\n%put NOTE: Status of error flag - &_switchenv_error_flag. ;\n%put &_switchenv_error_desc.;\n%put NOTE: Error desc if any - &_switchenv_error_desc. ;\n\n/*-----------------------------------------------------------------------------------------*\n END EXECUTION CODE\n*------------------------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------------------*\n Clean up existing macro variables and macro definitions.\n*------------------------------------------------------------------------------------------*/\n\n%if %symexist(_switchenv_run_trigger) %then %do;\n %symdel _switchenv_run_trigger;\n%end;\n%if %symexist(_switchenv_error_flag) %then %do;\n %symdel _switchenv_error_flag;\n%end;\n%if %symexist(_switchenv_error_desc) %then %do;\n %symdel _switchenv_error_desc;\n%end;\n\n\n%sysmacdelete _create_runtime_trigger;\n%sysmacdelete _create_error_flag;\n%sysmacdelete _extract_sas_folder_path;\n%sysmacdelete _switchenv_execution_code;\n\n\nfilename printint clear;\n\n"}} \ No newline at end of file diff --git a/Python - Switch Environments/README.md b/Python - Switch Environments/README.md new file mode 100644 index 00000000..092b138a --- /dev/null +++ b/Python - Switch Environments/README.md @@ -0,0 +1,58 @@ + +# Python - Switch Environments + +## Description +Switch between different Python environments or revert to the original environment from within your SAS Viya session. This SAS Studio custom step enables you to seamlessly toggle between a specified virtual environment (venv) and the default/original Python environment, supporting reproducible and portable analytics workflows. + +A quick look: + +![Video](./img/Switch_Environments.gif) + +## User Interface + +Refer to the "About" tab on the step for more details. + +### Parameters +This step helps you switch between Python environments. Input arguments required: +1. **What would you like to do?** + - Radio Button + - (Default) Revert to original Python environment + - Switch to specified Python environment (venv) +2. **If switching, provide the path to your virtual environment** + - The full path to the 'venv' folder or the folder containing `/bin/python3`. + +## Requirements + +1. A SAS Viya 4 environment (last test on monthly release 2025.07) with SAS Studio Flows +2. Python configured with the above environment (preferably using the [SAS Configurator for Open Source](https://go.documentation.sas.com/doc/en/itopscdc/v_016/itopswn/p19hj5ipftk86un1axa51rzr5mxv.htm)) + +## Installation & Usage + +Refer to the [steps](../README.md#getting-started---making-a-custom-step-from-this-repository-available-in-sas-studio) listed in the main README.md + +## The WHY : Background information + +Refer this [blog](https://blogs.sas.com/content/subconsciousmusings/2022/05/16/python-a-la-carte) for background. The ability to create and use virtual Python environments for use within SAS Viya helps data scientists create portable solutions, maintain solution integrity, and exploit the integration between SAS and Python to the fullest extent. + +Watch this example! + +[SAS & Open Source (Python) Integration : Better Together](https://www.youtube.com/watch?v=YVaX-A-ZsQ0&list=PLpe69msCs2C8IcarG0aEs_iKy4gyRSFPN&index=3) + +[Creating virtual Python environments within SAS Studio.](https://youtu.be/UIYZf2bKcWw) + +This repository contains custom steps which are offered as examples of how you could create, activate, switch between, and package virtual Python environments from within SAS Viya applications and tools, such as SAS Studio. It makes use of [Custom Steps](https://go.documentation.sas.com/doc/en/webeditorcdc/v_006/webeditorug/n0b7ljqhka8lh5n12judc27x5gph.htm), a component within SAS Studio which helps users package repeatable steps in a user-friendly manner. + +## Change Log + +* Version 1.0.0 (29AUG2025) + - Initial release: Switch between original and specified Python environments + - Deprecates existing Activate a Virtual Environment and Revert to Original Environment steps under Python Virtual Environments. + - UI for selecting revert or switch, and specifying venv path + - Error handling for missing or invalid venv paths + - Retains and restores original Python path for session portability + +## Created / Contact + - Sundaresh Sankaran (sundaresh.sankaran@sas.com) + + + diff --git a/Python - Switch Environments/extras/Python - Switch Environments components.json b/Python - Switch Environments/extras/Python - Switch Environments components.json new file mode 100644 index 00000000..3b224d93 --- /dev/null +++ b/Python - Switch Environments/extras/Python - Switch Environments components.json @@ -0,0 +1,103 @@ +{ + "showPageContentOnly": true, + "pages": [ + { + "id": "param", + "type": "page", + "label": "Parameters", + "children": [ + { + "id": "revert_to_original", + "type": "radiogroup", + "label": "What would you like to do?", + "items": [ + { + "value": "1", + "label": "Revert to original Python environment" + }, + { + "value": "0", + "label": "Switch to specified Python environment(venv)" + } + ], + "visible": "" + }, + { + "id": "venv", + "type": "path", + "label": "Provide a path to your virtual environment (the 'venv' folder or the folder containing /bin/python3):", + "pathtype": "folder", + "placeholder": "", + "required": false, + "visible": [ + "$revert_to_original", + "=", + "0" + ], + "enabled": [ + "$revert_to_original", + "=", + "0" + ] + }, + { + "id": "text1", + "type": "text", + "text": "If you choose to revert, the Python environment will be set to the original or default environment used by SAS Studio. If you choose to switch, you must provide the full path to the virtual environment you wish to use.", + "visible": "" + } + ] + }, + { + "id": "about", + "type": "page", + "label": "About", + "children": [ + { + "id": "about_text", + "type": "text", + "text": "Python - Switch Environments\n===============================\n\nThis custom step allows you to switch between different Python environments from within a SAS session, or revert to the original environment.\n\nFor more details, refer to the documentation or contact the author.", + "visible": "" + }, + { + "id": "contact_details", + "type": "section", + "label": "Created / Contact", + "open": true, + "visible": "", + "children": [ + { + "id": "created_contact_text", + "type": "text", + "text": "Sundaresh Sankaran (sundaresh.sankaran@sas.com)", + "visible": "" + } + ] + }, + { + "id": "about_version", + "type": "section", + "label": "Version", + "open": true, + "visible": "", + "children": [ + { + "id": "version_text", + "type": "text", + "text": "Version : 1.0.0 (29AUG2025)", + "visible": "" + } + ] + } + ] + } + ], + "syntaxversion": "1.3.0", + "values": { + "revert_to_original": { + "value": "1", + "label": "Revert to original Python environment" + }, + "venv": "" + } +} \ No newline at end of file diff --git a/Python - Switch Environments/extras/Python - Switch Environments.sas b/Python - Switch Environments/extras/Python - Switch Environments.sas new file mode 100644 index 00000000..50fc1ebe --- /dev/null +++ b/Python - Switch Environments/extras/Python - Switch Environments.sas @@ -0,0 +1,320 @@ +/* SAS templated code goes here */ + +/* -------------------------------------------------------------------------------------------* + Python - Switch Environments + + v 1.0.0 (29AUG2025) + + This program helps you switch between different Python environments from within a SAS session. + It is also meant as a mechanism for a user to revert to the base environment from an active + virtual environment. + + Sundaresh Sankaran (sundaresh.sankaran@sas.com|sundaresh.sankaran@gmail.com) +*-------------------------------------------------------------------------------------------- */ + +/*-----------------------------------------------------------------------------------------* + Debug Section + + The following code block (to be commented out or deleted in production) is meant to + help you debug the custom step. It sets values for the macro variables that would + normally be set through the custom step interface. + + Uncomment and modify the values as needed to test the custom step outside of SAS Studio. + + Here's the requirement from the project: + + Receives input from user regarding folder location of venv (include venv) + Changes options to point to same + If user wants to revert to original, check for presence of ORIGINAL_PYPATH + Revert to Original_PYPATH if so + SAS log message to indicate success + sys.executable to specify the interpreter and remove confusion + All the useful macro and wiring + +*------------------------------------------------------------------------------------------*/ + +/* === User Input Macro Variables (in logical order) === */ + + +/* 1. Option to revert to original Python environment (1 = Revert, 0 = Use specified venv)*/; +* %let revert_to_original = 1; /* Set to 1 to revert to ORIGINAL_PYPATH, 0 to use a specified venv */ + +/* 2. If not reverting, specify the folder location of the virtual environment (venv) */ +* %let venv = ; /* Provide the full path including 'venv' folder if revert_to_original=0 */ + +*/; + +/*-----------------------------------------------------------------------------------------* + Python Block Definition +*------------------------------------------------------------------------------------------*/ +/*-----------------------------------------------------------------------------------------* + The following block of code has been created for the purpose of allowing proc python + to execute within a macro. Execution within a macro allows for other checks to be carried + out through SAS prior to handing off to the Python step. + + In this example, a temporary file is created containing the requisite Python commands, which + are then executed through infile reference. + + Note that Python code is pasted as-is and may be out of line with the SAS indentation followed. + +*------------------------------------------------------------------------------------------*/ + +filename printint temp; + +data _null_; + + length line $32767; * max SAS character size ; + infile datalines4 truncover pad; + input ; + file printint; + line = strip(_infile_); * line without leading and trailing blanks ; + l1 = length(trimn(_infile_)); * length of line without trailing blanks ; + l2 = length(line); * length of line without leading and trailing blanks ; + first_position=l1-l2+1; * position where the line should start (alignment) ; + if (line eq ' ') then put @1; * empty line ; + else put @first_position line; * line without leading and trailing blanks correctly aligned ; + + datalines4; + +# Import necessary libraries +import sys + +SAS.logMessage(f"Current Python Path: {sys.executable}") + + +;;;; +run; + +/*-----------------------------------------------------------------------------------------* + MACROS +*------------------------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------------------------* + The following macro variable is defined as global because it will help in cases + involving repeated use of this step. +*------------------------------------------------------------------------------------------*/ + +%global ORIGINAL_PYPATH; + +/* -------------------------------------------------------------------------------------------* + Macro to initialize a run-time trigger global macro variable to run SAS Studio Custom Steps. + A value of 1 (the default) enables this custom step to run. A value of 0 (provided by + upstream code) sets this to disabled. + + Input: + 1. triggerName: The name of the runtime trigger you wish to create. Ensure you provide a + unique value to this parameter since it will be declared as a global variable. + + Output: + 2. &triggerName : A global variable which takes the name provided to triggerName. +*-------------------------------------------------------------------------------------------- */ + +%macro _create_runtime_trigger(triggerName); + + %global &triggerName.; + + %if %sysevalf(%superq(&triggerName.)=, boolean) %then %do; + + %put NOTE: Trigger macro variable &triggerName. does not exist. Creating it now.; + %let &triggerName.=1; + + %end; + +%mend _create_runtime_trigger; + +/* -----------------------------------------------------------------------------------------* + Macro to create an error flag for capture during code execution. + + Input: + 1. errorFlagName: The name of the error flag you wish to create. Ensure you provide a + unique value to this parameter since it will be declared as a global variable. + 2. errorFlagDesc: A description to add to the error flag. + + Output: + 1. &errorFlagName : A global variable which takes the name provided to errorFlagName. + 2. &errorFlagDesc : A global variable which takes the name provided to errorFlagDesc. +*------------------------------------------------------------------------------------------ */ + +%macro _create_error_flag(errorFlagName, errorFlagDesc); + + %global &errorFlagName.; + %let &errorFlagName.=0; + %global &errorFlagDesc.; + +%mend _create_error_flag; + +/* -----------------------------------------------------------------------------------------* + Macro to extract the path provided from a SAS Studio Custom Step file or folder selector. + + Input: + 1. pathReference: A path reference provided by the file or folder selector control in + a SAS Studio Custom step. + + Output: + 1. _sas_folder_path: Set inside macro, a global variable containing the path. + + Also available at: https://raw.githubusercontent.com/SundareshSankaran/sas_utility_programs/main/code/Extract%20SAS%20Folder%20Path/macro_extract_sas_folder_path.sas + +*------------------------------------------------------------------------------------------ */ + +%macro _extract_sas_folder_path(pathReference); + + %global _sas_folder_path; + + data _null_; + call symput("_sas_folder_path", scan("&pathReference.",2,":","MO")); + run; + +%mend _extract_sas_folder_path; + + +/*-----------------------------------------------------------------------------------------* + EXECUTION CODE MACRO + + _switchenv prefix stands for Switch Environments +*------------------------------------------------------------------------------------------*/ +%macro _switchenv_execution_code; + + %let revertt=&REVERT_TO_ORIGINAL; + + %_create_error_flag(_switchenv_error_flag, _switchenv_error_desc); + +/*-----------------------------------------------------------------------------------------* + Check if user wants to revert to original environment +*------------------------------------------------------------------------------------------*/ + %if &_switchenv_error_flag. = 0 %then %do; + %put NOTE: This is the current state of revertt - &revertt. ; + %if "&revertt." = "1" %then %do; + %if %symexist(ORIGINAL_PYPATH) %then %do; + options set=PROC_PYPATH="&ORIGINAL_PYPATH."; + %let _switchenv_error_flag=0; + %let _switchenv_error_desc=NOTE: Set Python path to original Python environment at &ORIGINAL_PYPATH..; + %end; + %else %do; +/*-----------------------------------------------------------------------------------------* + Insert code to check if default path exists at /opt/sas/viya/home/sas-pyconfig +*------------------------------------------------------------------------------------------*/ + %if "%str(%sysfunc(fileexist(/opt/sas/viya/home/sas-pyconfig/default_py/bin/python3)))" = "1" %then %do; + options set=PROC_PYPATH="/opt/sas/viya/home/sas-pyconfig/default_py/bin/python3"; + %let _switchenv_error_flag=0; + %let _switchenv_error_desc=NOTE: Set Python path to default Python profile environment at /opt/sas/viya/home/sas-pyconfig/default_py/bin/python3.; + /*-----------------------------------------------------------------------------------------* + Retain ORIGINAL_PYPATH for any future repeated use +*------------------------------------------------------------------------------------------*/ + %let ORIGINAL_PYPATH = %sysget(PROC_PYPATH); + %end; + %else %do; + %let _switchenv_error_flag=1; + %let _switchenv_error_desc=ERROR: Neither an ORIGINAL_PYPATH variable or default Python path exists. Cannot revert to original environment.; +/*-----------------------------------------------------------------------------------------* + Record current path as ORIGINAL_PYPATH for any future repeated use +*------------------------------------------------------------------------------------------*/ + %let ORIGINAL_PYPATH = %sysget(PROC_PYPATH); + %end; + %end; + %end; + %else %if "&revertt." = "0" %then %do; +/*-----------------------------------------------------------------------------------------* + Record ORIGINAL_PYPATH in order to roll back if needed + Note: Original means the current path before the following code runs. +*------------------------------------------------------------------------------------------*/ + %let ORIGINAL_PYPATH = %sysget(PROC_PYPATH); + %_extract_sas_folder_path(&venv.); + %let venv_input=&_sas_folder_path.; + %if "&venv_input." = "" %then %do; + %let _switchenv_error_flag=1; + %let _switchenv_error_desc=ERROR: No virtual environment path specified. Please provide a valid path.; + %end; + %else %do; + %if %str(%sysfunc(fileexist(&venv_input./bin/python3))) = "0" %then %do; + %let _switchenv_error_flag=1; + %let _switchenv_error_desc=ERROR: The specified virtual environment path &venv_input. does not exist or is invalid. Please provide a valid path.; + %end; + %else %do; + options set=PROC_PYPATH="&venv_input./bin/python3"; + %let _switchenv_error_flag=0; + %let _switchenv_error_desc=NOTE: Set Python path to virtual environment at &venv_input./bin/python3.; + + %end; + %end; + %end; + %end; + +/*-----------------------------------------------------------------------------------------* + Reset interpreter to new virtual environment +*------------------------------------------------------------------------------------------*/ + %if &_SWITCHENV_ERROR_FLAG. = 0 %then %do; + proc python terminate; + quit; + + proc python infile=printint; + run; + + %end; + + + + +%mend _switchenv_execution_code; + +/*-----------------------------------------------------------------------------------------* + END MACROS +*------------------------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------------------------* + EXECUTION CODE +*------------------------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------------------------* + Create Runtime Trigger +*------------------------------------------------------------------------------------------*/ +%_create_runtime_trigger(_switchenv_run_trigger); + +/*-----------------------------------------------------------------------------------------* + Execute +*------------------------------------------------------------------------------------------*/ + +%if &_switchenv_run_trigger. = 1 %then %do; + + %_switchenv_execution_code; + +%end; + +%if &_switchenv_run_trigger. = 0 %then %do; + + %put NOTE: This step has been disabled. Nothing to do.; + +%end; + + +%put NOTE: Final summary; +%put NOTE: Status of error flag - &_switchenv_error_flag. ; +%put &_switchenv_error_desc.; +%put NOTE: Error desc if any - &_switchenv_error_desc. ; + +/*-----------------------------------------------------------------------------------------* + END EXECUTION CODE +*------------------------------------------------------------------------------------------*/ +/*-----------------------------------------------------------------------------------------* + Clean up existing macro variables and macro definitions. +*------------------------------------------------------------------------------------------*/ + +%if %symexist(_switchenv_run_trigger) %then %do; + %symdel _switchenv_run_trigger; +%end; +%if %symexist(_switchenv_error_flag) %then %do; + %symdel _switchenv_error_flag; +%end; +%if %symexist(_switchenv_error_desc) %then %do; + %symdel _switchenv_error_desc; +%end; + + +%sysmacdelete _create_runtime_trigger; +%sysmacdelete _create_error_flag; +%sysmacdelete _extract_sas_folder_path; +%sysmacdelete _switchenv_execution_code; + + +filename printint clear; + diff --git a/Python - Switch Environments/extras/Test_observations.md b/Python - Switch Environments/extras/Test_observations.md new file mode 100644 index 00000000..f74d56ce --- /dev/null +++ b/Python - Switch Environments/extras/Test_observations.md @@ -0,0 +1,14 @@ +# Testing Bugs encountered + +The following is more an internal focussed activity (and perhaps won't / needn't be included). To illustrate bugs or errors that came about during a test of this custom step (in the hope that it promotes awareness / learning) + +1. Check environment variables (also happens if you base your code on existing code) + +```sas +WARNING: Apparent symbolic reference _CVIRENV_ERROR_FLAG not resolved. +ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: + &_cvirenv_error_flag. = 0 +ERROR: The macro _SWITCHENV_EXECUTION_CODE will stop executing. +``` + +2. Debug section variables need to be commented out prior to any production code test. \ No newline at end of file diff --git a/Python - Switch Environments/img/Switch_Environments.gif b/Python - Switch Environments/img/Switch_Environments.gif new file mode 100644 index 00000000..5d0c3b53 Binary files /dev/null and b/Python - Switch Environments/img/Switch_Environments.gif differ diff --git a/Python Virtual environments/Python - Activate a Virtual Environment.step b/Python Virtual environments/Python - Activate a Virtual Environment.step deleted file mode 100644 index 5d0086ea..00000000 --- a/Python Virtual environments/Python - Activate a Virtual Environment.step +++ /dev/null @@ -1 +0,0 @@ -{"creationTimeStamp":"2022-10-15T16:09:21.438Z","modifiedTimeStamp":"2022-10-19T14:33:18.560Z","createdBy":"Sundaresh.Sankaran@sas.com","modifiedBy":"Sundaresh.Sankaran@sas.com","name":"Python - Activate a Virtual Environment.step","displayName":"Python - Activate a Virtual Environment.step","localDisplayName":"Python - Activate a Virtual Environment.step","properties":{},"links":[{"method":"GET","rel":"self","href":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","uri":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","type":"application/vnd.sas.data.flow.step"},{"method":"GET","rel":"alternate","href":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","uri":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","type":"application/vnd.sas.data.flow.step.summary"},{"method":"GET","rel":"up","href":"/dataFlows/steps","uri":"/dataFlows/steps","type":"application/vnd.sas.collection","itemType":"application/vnd.sas.data.flow.step.summary"},{"method":"PUT","rel":"update","href":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","uri":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","type":"application/vnd.sas.data.flow.step","responseType":"application/vnd.sas.data.flow.step"},{"method":"DELETE","rel":"delete","href":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","uri":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5"},{"method":"GET","rel":"transferExport","href":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","uri":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","responseType":"application/vnd.sas.transfer.object"},{"method":"PUT","rel":"transferImportUpdate","href":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","uri":"/dataFlows/steps/8f6e6e7d-54c6-4249-aba1-2c06bc6d73c5","type":"application/vnd.sas.transfer.object","responseType":"application/vnd.sas.summary"}],"metadataVersion":0.0,"version":2,"type":"code","flowMetadata":{"inputPorts":[],"outputPorts":[]},"ui":"{\n\t\"showPageContentOnly\": true,\n\t\"pages\": [\n\t\t{\n\t\t\t\"id\": \"page1\",\n\t\t\t\"type\": \"page\",\n\t\t\t\"label\": \"Parameters\",\n\t\t\t\"children\": [\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"venv\",\n\t\t\t\t\t\"type\": \"textfield\",\n\t\t\t\t\t\"label\": \"Provide a path to an existing virtual environment.\",\n\t\t\t\t\t\"placeholder\": \"e.g. /mnt/path/to/venv/folder\",\n\t\t\t\t\t\"required\": true,\n\t\t\t\t\t\"visible\": \"\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"id\": \"page2\",\n\t\t\t\"type\": \"page\",\n\t\t\t\"label\": \"About\",\n\t\t\t\"children\": [\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"text1\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"text\": \"Python - Activate a virtual environment\\n\\nThe \\\"Python - Activate a virtual environment\\\" custom step helps users activate a virtual Python environment, which had been created earlier using a virtualenv package. Activating a virtual Python environment leads to the current Python interpreter operating in the context of this new environment. \\n\\nProvide a path to the folder which contains a virtual environment created earlier, which would typically be on a shared file system which is mounted to the SAS Studio session. A common area would be the /mnt/ folder.\\n\\nCreated / contact : Sundaresh Sankaran (sundaresh.sankaran@sas.com) and Ali Aiello (ali.aiello@sas.com) \\n\\nVersion : 1.0. (20MAY2022)\\n\\nFor more details, refer https://blogs.sas.com/content/subconsciousmusings/2022/05/16/python-a-la-carte/\",\n\t\t\t\t\t\"visible\": \"\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"syntaxversion\": \"1.3.0\",\n\t\"values\": {\n\t\t\"venv\": \"\"\n\t},\n\t\"promptHierarchies\": []\n}","templates":{"SAS":"/* SAS templated code goes here */\n\n\nproc python;\n\nsubmit;\n\nimport os\nvenv=SAS.symget(\"venv\")\n\npyt=os.environ[\"PROC_PYPATH\"]\n\nmpt=SAS.symget(\"MAIN_PYTH\")\n\nif mpt==\"\":\n SAS.symput(\"MAIN_PYTH\",str(pyt))\n\nactivate_this_file = os.path.join(venv,\"bin\",\"activate_this.py\")\nexec(open(activate_this_file).read(), {'__file__': str(activate_this_file)})\nSAS.symput(\"TEMP_PYTH\",str(os.path.join(venv,\"bin\",\"python3\")))\n\nendsubmit;\n\nquit;\nproc python terminate;\nquit;\n\noptions set=PROC_PYPATH=\"&TEMP_PYTH.\";\n\n%put &TEMP_PYTH.;\n%put &MAIN_PYTH.;\n\nproc python;\nsubmit;\nimport os\npyt=SAS.symget(\"TEMP_PYTH\")\nprint(\"Environment activated: {pyt}\".format(pyt=pyt))\nendsubmit;\nquit;"}} \ No newline at end of file diff --git a/Python Virtual environments/Python - Revert to Original Environment.step b/Python Virtual environments/Python - Revert to Original Environment.step deleted file mode 100644 index a5519692..00000000 --- a/Python Virtual environments/Python - Revert to Original Environment.step +++ /dev/null @@ -1 +0,0 @@ -{"creationTimeStamp":"2022-10-15T16:09:21.669Z","modifiedTimeStamp":"2022-10-19T15:04:22.142Z","createdBy":"Sundaresh.Sankaran@sas.com","modifiedBy":"Sundaresh.Sankaran@sas.com","name":"Python - Revert to Original Environment.step","displayName":"Python - Revert to Original Environment.step","localDisplayName":"Python - Revert to Original Environment.step","properties":{},"links":[{"method":"GET","rel":"self","href":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","uri":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","type":"application/vnd.sas.data.flow.step"},{"method":"GET","rel":"alternate","href":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","uri":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","type":"application/vnd.sas.data.flow.step.summary"},{"method":"GET","rel":"up","href":"/dataFlows/steps","uri":"/dataFlows/steps","type":"application/vnd.sas.collection","itemType":"application/vnd.sas.data.flow.step.summary"},{"method":"PUT","rel":"update","href":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","uri":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","type":"application/vnd.sas.data.flow.step","responseType":"application/vnd.sas.data.flow.step"},{"method":"DELETE","rel":"delete","href":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","uri":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d"},{"method":"GET","rel":"transferExport","href":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","uri":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","responseType":"application/vnd.sas.transfer.object"},{"method":"PUT","rel":"transferImportUpdate","href":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","uri":"/dataFlows/steps/d0efe957-42f5-4499-8e37-00a0c0dfd83d","type":"application/vnd.sas.transfer.object","responseType":"application/vnd.sas.summary"}],"metadataVersion":0.0,"version":2,"type":"code","flowMetadata":{"inputPorts":[],"outputPorts":[]},"ui":"{\n\t\"showPageContentOnly\": true,\n\t\"pages\": [\n\t\t{\n\t\t\t\"id\": \"page1\",\n\t\t\t\"type\": \"page\",\n\t\t\t\"label\": \"About\",\n\t\t\t\"children\": [\n\t\t\t\t{\n\t\t\t\t\t\"id\": \"text1\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"text\": \"Python - Revert to original environment\\n\\nThe \\\"Python - Revert to original environment\\\" custom step helps users change their current Python executable from a virtual Python environment to the original environment. Another option is to simply reset the SAS Studio session. \\n\\nGiven that your current SAS Studio session is utilizing a virtual Python environment (created using the \\\"Create / Activate a virtual environment\\\" step), simply run this step within a flow at any time you wish to go back to the original environment. The environment will revert to the Python profile (as determined by the PROC_PYPATH environment variable) which was used at the start of the session. \\n\\nCreated / contact : Sundaresh Sankaran (sundaresh.sankaran@sas.com) and Ali Aiello (ali.aiello@sas.com) \\n\\nVersion : 1.0. (20MAY2022)\\n\\nFor more details, refer https://blogs.sas.com/content/subconsciousmusings/2022/05/16/python-a-la-carte/\",\n\t\t\t\t\t\"visible\": \"\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"syntaxversion\": \"1.3.0\",\n\t\"promptHierarchies\": []\n}","templates":{"SAS":"/* SAS templated code goes here */\n\nproc python;\n\nsubmit;\n\nimport os\n\ncurrent_venv=SAS.symget(\"TEMP_PYTH\")\npyt=SAS.symget(\"MAIN_PYTH\")\n\n\n\n\nendsubmit;\n\nquit;\n\noptions set=PROC_PYPATH=\"&MAIN_PYTH.\";\n\n%put &TEMP_PYTH.;\n%put &MAIN_PYTH.;\n\nproc python terminate;\nquit;\n\nproc python;\nsubmit;\nimport os\npyt=SAS.symget(\"MAIN_PYTH\")\nprint(\"Virtual environment deactivated; Python is now at {pyt}\".format(pyt=pyt))\nendsubmit;\nquit;"}} \ No newline at end of file diff --git a/Python Virtual environments/README.md b/Python Virtual environments/README.md index 59cc15db..01302d11 100644 --- a/Python Virtual environments/README.md +++ b/Python Virtual environments/README.md @@ -6,6 +6,9 @@ >[!TIP] >27AUG2025: Custom step **Python - Create a Virtual Environment** has a new version that now lives in a dedicated subfolder, see [subfolder Python - Create a Virtual Environment](../Python%20-%20Create%20a%20Virtual%20Environment). +>[!TIP] +> 29AUG2025: Custom steps **Python - Activate a Virtual Environment** and **Python - Revert to Original Environment** have been removed as they are now subsumed into a new step, see [subfolder Python - Switch Environments](../Python%20-%20Switch%20Environments/). + ## Description Package your Python-based analytics solutions in a portable, repeatable, and reusable manner. This repo contains **five** SAS Studio custom steps which help you create, activate, and switch between virtual Python environments for use within SAS Viya. @@ -28,6 +31,9 @@ This step helps you create a virtual environment. Input arguments required : ![Python - Create a virtual environment](./img/create-a-virtual-environment.png) ### Activate a virtual environment +>[!TIP] +> 29AUG2025: Custom steps **Python - Activate a Virtual Environment** and **Python - Revert to Original Environment** have been removed as they are now subsumed into a new step, see [subfolder Python - Switch Environments](../Python%20-%20Switch%20Environments/). + This step helps you activate an existing virtual environment. It requires a single argument to a folder path pointing to your virtual environment. ![Python - Activate a virtual environment](./img/activate-a-virtual-environment.png) @@ -38,6 +44,9 @@ This step helps you save details of all the packages currently installed in your ![Python - Freeze requirement details](./img/freeze-requirement-details.png) ### Revert to original environment +>[!TIP] +> 29AUG2025: Custom steps **Python - Activate a Virtual Environment** and **Python - Revert to Original Environment** have been removed as they are now subsumed into a new step, see [subfolder Python - Switch Environments](../Python%20-%20Switch%20Environments/). + This step switches a SAS Studio session, currently under a virtual Python environment, to the Python environment that was in operation earlier. This step does not require any arguments. ![Python - Revert to original Python environment](./img/revert-to-original-environment.png)