# HG changeset patch # User Jean-Francois Pieronne <jf.pieronne@laposte.net> # Date 1599716792 -34200 # Thu Sep 10 15:16:32 2020 +0930 # Node ID 94b50ed5112d3ddd883fdb94aed679a9b447d403 # Parent 5ad3e90b39d15b74301ca31dd1456409327b6e8c v3.0.0 update (no version id change) diff --git a/src/python/pyrte.c b/src/python/pyrte.c --- a/src/python/pyrte.c +++ b/src/python/pyrte.c @@ -353,7 +353,7 @@ VERSION HISTORY (update SOFTWAREVN as well!) --------------- -08-SEP-2020 MGD v3.0.0, Python 3 (via JFP Python 3.10.0a0) +10-SEP-2020 MGD v3.0.0, Python 3 (via JFP Python 3.10.0a0) v2.0.0, Python 2 equivalent Python 2.7.18 build kludge for VSI C V7.4-001 on OpenVMS IA64 V8.4-2L1 @@ -2246,12 +2246,9 @@ PyObject *args ) { - int DataLength; -#if PY_MAJOR_VERSION >= 3 - const char *DataPtr; -#else + int isuni, + DataLength; char *DataPtr; -#endif PyObject *pValue; /*********/ @@ -2274,14 +2271,18 @@ return (Py_None); } + DEBUGPYOBJ("pValue",pValue) #if PY_MAJOR_VERSION >= 3 - if (PyUnicode_Check (pValue)) + if ((isuni = PyUnicode_Check (pValue)) || PyBytes_Check (pValue)) #else if (PyString_Check (pValue)) #endif { #if PY_MAJOR_VERSION >= 3 - DataPtr = PyUnicode_AsUTF8AndSize (pValue, &DataLength); + if (isuni) + DataPtr = (char*)PyUnicode_AsUTF8AndSize (pValue, &DataLength); + else + PyBytes_AsStringAndSize (pValue, &DataPtr, &DataLength); #else PyString_AsStringAndSize (pValue, &DataPtr, &DataLength); #endif @@ -2300,7 +2301,11 @@ return (Py_None); } +#if PY_MAJOR_VERSION >= 3 + PyErr_SetString (PyExc_TypeError, "type must be Unicode, Bytes or None"); +#else PyErr_SetString (PyExc_TypeError, "type must be String or None"); +#endif return (NULL); } @@ -3016,7 +3021,7 @@ return (Py_None); } - if (!PyArg_ParseTuple (args, "SO|O:start_response", + if (!PyArg_ParseTuple (args, "OO|O:start_response", &pStatus, &pHeaders, &pWsgiExcInfo)) return (NULL); @@ -3079,7 +3084,11 @@ { int cnt, DataLength; +#if PY_MAJOR_VERSION >= 3 + const char *DataPtr; +#else char *DataPtr; +#endif PyObject *pItem, *pIter; @@ -3094,13 +3103,13 @@ DEBUGPYOBJ("pResponse",pResponse) #if PY_MAJOR_VERSION >= 3 - if (PyBytes_Check (pResponse)) + if (PyUnicode_Check (pResponse)) #else if (PyString_Check (pResponse)) #endif { #if PY_MAJOR_VERSION >= 3 - PyBytes_AsStringAndSize (pResponse, &DataPtr, &DataLength); + DataPtr = PyUnicode_AsUTF8AndSize (pResponse, &DataLength); #else PyString_AsStringAndSize (pResponse, &DataPtr, &DataLength); #endif @@ -3117,12 +3126,12 @@ { if (!WsgiHeadersSent) WsgiSendHeaders (); #if PY_MAJOR_VERSION >= 3 - if (PyBytes_Check (pItem)) + if (PyUnicode_Check (pItem)) #else if (PyString_Check (pItem)) #endif #if PY_MAJOR_VERSION >= 3 - if (!PyBytes_AsStringAndSize (pItem, &DataPtr, &DataLength)) + if (DataPtr = PyUnicode_AsUTF8AndSize (pItem, &DataLength)) #else if (!PyString_AsStringAndSize (pItem, &DataPtr, &DataLength)) #endif @@ -3152,10 +3161,15 @@ int WsgiSendHeaders () { - int StatusCode; + int StatusCode, + StatusLength; char *NamePtr, - *StatusPtr, *ValuePtr; +#if PY_MAJOR_VERSION >= 3 + const char *StatusPtr; +#else + char *StatusPtr; +#endif PyObject *pItem, *pIter; @@ -3170,7 +3184,7 @@ if (!pWsgiHeaders || !pWsgiStatus) return (0); #if PY_MAJOR_VERSION >= 3 - if (!PyBytes_Check (pWsgiStatus)) + if (!PyUnicode_Check (pWsgiStatus)) #else if (!PyString_Check (pWsgiStatus)) #endif @@ -3181,7 +3195,7 @@ WsgiHeadersSent = 1; #if PY_MAJOR_VERSION >= 3 - StatusPtr = PyBytes_AsString (pWsgiStatus); + StatusPtr = PyUnicode_AsUTF8AndSize (pWsgiStatus, &StatusLength); #else StatusPtr = PyString_AsString (pWsgiStatus); #endif diff --git a/src/python/readmore.html b/src/python/readmore.html --- a/src/python/readmore.html +++ b/src/python/readmore.html @@ -60,6 +60,12 @@ padding-left:1em; } +.indent +{ + margin-left:2em; + margin-right:5em; +} + .quote { border-style:dotted; border-color:#888888; border-width:1px; font-size:90%; @@ -89,8 +95,8 @@ <h1>Python Run-Time Environment</h1> <h3>~~~ PRE-RELEASE ~~~</h3> -<h3>Version 2.0.0, 8th September 2020 -<br>Version 3.0.0, 8th September 2020</h3> +<h3>Version 2.0.0, 10th September 2020 +<br>Version 3.0.0, 10th September 2020</h3> <p><b>Copyright © 2007-2020 Mark G. Daniel</b> <br>This program, comes with ABSOLUTELY NO WARRANTY. @@ -335,6 +341,70 @@ after changing mapping rules. +<h3>Supporting Multiple Python Versions</h3> + +<p> It is sometimes useful, sometimes necessary, to maintain multiple versions +of Python on a system, and also support multiple versions to web applications +(scripts). It is not difficult to have PyRTE activate a specific version by +wrapping the executable in a procedure that sets up the appropriate Python +environment in a process-local context. + +<pre class="code"> +$! CGI_BIN:PYRTE.COM +$! activate JFP Python 2.7 +$ @disk$jfplib0020i:[000000]lib_logicals.com "/process" +$ @disk$jfppy1400i:[000000]python_logicals.com "/process" +$ @python_root:[vms]setup +$ pyrte == "$cgi_exe:pyrte.exe" +$ pyrte +</pre> + +<pre class="code"> +$! CGI_BIN:PYRTE3.COM +$! activate JFP Python 3.10 +$ @disk$jfplib0020i:[000000]lib_logicals.com "/process" +$ @disk$jfppy3100i:[python3100.vms]logicals.com "/process" +$ @python3_root:[vms]setup +$ pyrte3 == "$cgi_exe:pyrte3.exe" +$ pyrte3 +</pre> + +<p> Each of these is mapped for independent activation based on the request +path. + +<pre class="code"> +# WASD_CONFIG_MAP +exec /py-bin/* (@cgi_bin:pyrte.com)/wasd_root/src/python/scripts/* \ + script=syntax=unix map=once script=query=none ods=5 +exec /py3-bin/* (@cgi_bin:pyrte3.com)/<i>volume<sup>**</sup></i>/wasd_root/src/python/scripts/* \ + script=syntax=unix map=once script=query=none ods=5 +</pre> + +<table style="margin-left:2em;font-size:95%;"> +<td style="vertical-align:top;"><sup>**</sup></td><td> +<b>Note</b> that the scripts activated are the same on-disk files, located in +the PyRTE source directory. +<br><b>Also note</b> that <tt>/py3-bin/</tt> mapping result is different to +that of the <tt>/py-bin/</tt> mapping. There is a /<i>volume</i>/ present. +<br> This is only required where the same on-disk files are being scripted. +<b> This is a kludge.</b> Due to the way WASD caches script information (on +the disk file path), <b>including any associated RTE</b>, it was necessary to +differentiate the two activations and the chosen solution was to include the +physical volume (on which <tt>[WASD_ROOT]</tt> resides) in the mapping. +Another solution might be to use an alternate concealed logical device name. +</td> +</table> + +<p> This is one approach. A little creativity would yield others. + +<h3>wasd.vsm.com.au</h3> + +<p> At the time of writing the demonstration site uses this approach to +default the examples below to (JFP) Python 2.7, while optionally allowing them +to be activated under 3.10 – just modify the browser location field to +contain <tt>/py3-bin/</tt> rather than the default <tt>/py-bin/</tt>. Several +of the scripts display the underlying Python version confirming the activations. + <a name="example"> <h2>Example Scripts</h2> </a> @@ -573,11 +643,11 @@ <dl> -<dt>v3.0.0 08-SEP-2020 ~~~ PRE-RELEASE ~~~</dt> +<dt>v3.0.0 10-SEP-2020 ~~~ PRE-RELEASE ~~~</dt> <dd>• Python version 3.10.0a0</dd> </dd> -<dt>v2.0.0 08-SEP-2020 ~~~ PRE-RELEASE ~~~</dt> +<dt>v2.0.0 10-SEP-2020 ~~~ PRE-RELEASE ~~~</dt> <dd>• Python version 2.7.18</dd> </dd> diff --git a/src/python/scripts/wsgi_test2.py b/src/python/scripts/wsgi_test2.py old mode 100755 new mode 100644 --- a/src/python/scripts/wsgi_test2.py +++ b/src/python/scripts/wsgi_test2.py @@ -1,15 +1,17 @@ def application(environ, start_response): import cgi + import html + import platform write_fn = start_response('200 OK', [('Content-Type', 'text/html')]) yield '<html><head><title>os.environment</title></head>\n' \ '<body bgcolor="#ffffff" color="#000000">\n' \ '<p><b><u>os.environment</b></u></p>\n' \ '<table cellspacing="0" cellpadding="2" border="1">' names = environ.keys() - names.sort() + sorted(names) for name in names: yield '<tr><td>%s</td><td>%s</td></tr>\n' % ( - name, cgi.escape(`environ[name]`)) + name, escape(str(environ[name]))) form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ, keep_blank_values=1) @@ -20,10 +22,22 @@ yield '<tr><td>%s</td><td>%s</td></tr>\n' % ( field.name, field.value) - write_fn('<tr><td>This has been write()n</td><td>:-)</td></tr>\n'); + write_fn('<tr><td>This has been write()n :-)</td><td>Python ' + + platform.sys.version + '</td></tr>\n') yield '</table>\n' \ '</body></html>\n' +def escape(s, quote=None): + '''Replace special characters "&", "<" and ">" to HTML-safe sequences. + If the optional flag quote is true, the quotation mark character (") +is also translated. (Python 2 and 3 neutral.)''' + s = s.replace("&", "&") # Must be done first! + s = s.replace("<", "<") + s = s.replace(">", ">") + if quote: + s = s.replace('"', """) + return s + import wasd wasd.wsgi_run(application) diff --git a/src/python/scripts/wsgi_test3.py b/src/python/scripts/wsgi_test3.py old mode 100755 new mode 100644 --- a/src/python/scripts/wsgi_test3.py +++ b/src/python/scripts/wsgi_test3.py @@ -1,7 +1,7 @@ def show_environ(environ, start_response): start_response('200 OK',[('Content-type','text/html')]) sorted_keys = environ.keys() - sorted_keys.sort() + sorted(sorted_keys) return [ '<html><body><h1>Keys in <tt>environ</tt></h1><p>', '<br />'.join(sorted_keys), diff --git a/src/python/scripts/wsgi_test4.py b/src/python/scripts/wsgi_test4.py old mode 100755 new mode 100644 --- a/src/python/scripts/wsgi_test4.py +++ b/src/python/scripts/wsgi_test4.py @@ -3,8 +3,13 @@ It can operate as a standard CGI or as a CGIplus script. It accepts a GIF, JPEG or PNG file and displays it in the browser. """ + # caters for Python 2 and 3 + if hasattr(importlib,'reload'): + importlib.reload(cgi) + else: + reload(cgi) if os.environ["REQUEST_METHOD"] == "POST": - display_uploaded_file (start_response, "image_file") + display_uploaded_file (start_response) else: print_html_form (start_response) return ('') @@ -29,13 +34,13 @@ wsgi_write = start_response('200 OK',[('Content-type','text/html')]) wsgi_write (HTML_TEMPLATE % {'SCRIPT_NAME':os.environ['SCRIPT_NAME']}) -def display_uploaded_file (start_response, form_field): +def display_uploaded_file (start_response): """This display an image file uploaded by an HTML form. """ form = cgi.FieldStorage() - if not form.has_key(form_field): return - fileitem = form[form_field] + if not 'image_file' in form: return + fileitem = form['image_file'] if not fileitem.file: return if not fileitem.filename: return filename = fileitem.filename.lower() @@ -47,7 +52,7 @@ elif filename.rfind(".png") != -1: response_headers = [('Content-type','image/png')] else: - wsgi_write = start_response('200 OK',[('Content-type','text/html')]) + wsgi_write = start_response(status,[('Content-type','text/html')]) wsgi_write ("Only GIFs, JPEGs and PNGs handled by this test.") return @@ -61,6 +66,7 @@ del fileitem import wasd; +import importlib import cgi import cgitb; cgitb.enable() import os, sys