From 960f9df28a68b99004d2100bcee7910414824e64 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Mar 2023 14:44:02 +0100 Subject: [PATCH 01/11] Make test for local ip address more relaxed Since this does not work reliably anways, e.g. if WSL is installed. --- webware/MiscUtils/Tests/TestFuncs.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/webware/MiscUtils/Tests/TestFuncs.py b/webware/MiscUtils/Tests/TestFuncs.py index bf851ad..00eb34c 100644 --- a/webware/MiscUtils/Tests/TestFuncs.py +++ b/webware/MiscUtils/Tests/TestFuncs.py @@ -85,20 +85,21 @@ def testHostName(self): self.assertEqual(host, host.lower()) def testLocalIP(self): - ip = localIP() - self.assertTrue(ip) - self.assertFalse(ip.startswith('127.')) - self.assertEqual(localIP(), ip) # second invocation - self.assertEqual(localIP(useCache=None), ip) - # ignore if the following tests fetch the WSL address - ips = (ip, '192.168.80.1', '172.22.32.1', '172.25.112.1') - self.assertIn( - localIP(remote=None, useCache=None), ips, - 'See if this works: localIP(remote=None).' - ' If this fails, dont worry.') - self.assertIn( + ip_local = localIP() + self.assertTrue(ip_local) + self.assertFalse(ip_local.startswith('127.')) + self.assertEqual(localIP(), ip_local) # second invocation + self.assertEqual(localIP(useCache=None), ip_local) + + def assert_local(ip): + self.assertTrue( + ip == ip_local or ip.startswith(('127.', '192.168.')) + or '172.16.' <= ip <= '172.31.') + + assert_local(localIP(remote=None, useCache=None)) + assert_local( localIP(remote=('www.hostname.and.domain.are.invalid', 80), - useCache=None), ips) + useCache=None)) def testPositiveId(self): # About all we can do is invoke positiveId() From a534ba03e44996557f49548ea7872df437028c08 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Mar 2023 18:15:20 +0100 Subject: [PATCH 02/11] Prefer dict literals over function calls Though it adds single quotes, it is the style recommended by pylint now because it looks cleaner and a function call makes the code a bit slower. --- docs/conf.py | 3 +- docs/deploy.rst | 4 +- tox.ini | 2 +- webware/Admin/ServletCache.py | 2 +- webware/Application.py | 175 +++++++++--------- webware/Examples/FileUpload.py | 2 +- webware/Examples/ListBox.py | 8 +- webware/Examples/SecurePage.py | 2 +- webware/HTTPRequest.py | 13 +- webware/HTTPResponse.py | 2 +- webware/MiscUtils/Funcs.py | 11 +- webware/MiscUtils/Tests/TestDataTable.py | 2 +- webware/MiscUtils/Tests/TestPickleCache.py | 2 +- webware/MockApplication.py | 19 +- webware/PSP/ParseEventHandler.py | 16 +- webware/PickleRPCServlet.py | 2 +- webware/Testing/Main.py | 4 +- webware/Tests/TestEndToEnd/AppTest.py | 4 +- webware/Tests/TestEndToEnd/TestAdmin.py | 19 +- webware/Tests/TestEndToEnd/TestContextMap.py | 26 ++- webware/Tests/TestEndToEnd/TestExamples.py | 23 +-- webware/Tests/TestEndToEnd/TestPSPExamples.py | 5 +- webware/Tests/TestEndToEnd/TestTesting.py | 10 +- webware/Tests/TestSessions/Application.py | 17 +- webware/Tests/TestSessions/Session.py | 2 +- webware/UnknownFileTypeServlet.py | 9 +- webware/WebUtils/HTMLTag.py | 4 +- webware/WebUtils/HTTPStatusCodes.py | 6 +- .../Tests/TestFieldStorageModified.py | 55 +++--- .../Tests/TestFieldStorageStandard.py | 10 +- webware/WebUtils/Tests/TestFuncs.py | 20 +- 31 files changed, 230 insertions(+), 249 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ee5058a..0e47d93 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,8 +64,7 @@ # (references to some Python built-in types do not resolve correctly) nitpicky = True nitpick_ignore = [('py:class', t) for t in ( - 'cgi.FieldStorage', 'html.parser.HTMLParser', - 'threading.Thread', 'xmlrpc.client.ProtocolError')] + 'html.parser.HTMLParser', 'threading.Thread', 'xmlrpc.client.ProtocolError')] # -- Options for HTML output ------------------------------------------------- diff --git a/docs/deploy.rst b/docs/deploy.rst index 9aa5126..10724ed 100644 --- a/docs/deploy.rst +++ b/docs/deploy.rst @@ -486,7 +486,7 @@ In order to make use of Bjoern, you need to add the following at the end of the Since Bjoern does not support the WSGI ``write()`` callable, you must configure Webware to not use this mechanism, by using the following settings at the top of the ``Scripts\WSGIScript.py``:: - settings = dict(WSGIWrite=False) + settings = {'WSGIWrite': False} A systemd unit file at ``/etc/systemd/system/bjoern.service`` could look like this:: @@ -531,7 +531,7 @@ Add the following at the end of the ``Scripts\WSGIScript.py`` file in the applic Similarly to Bjoern, you need to also adapt the settings at the top of the ``Scripts\WSGIScript.py`` file:: - settings = dict(WSGIWrite=False) + settings = {'WSGIWrite': False} Using CherryPy as WSGI server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tox.ini b/tox.ini index bcc8a9b..ad40ef7 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ commands = [testenv:pylint] basepython = python3.10 -deps = pylint>=2.15,<3 +deps = pylint>=2.16,<3 commands = pylint webware diff --git a/webware/Admin/ServletCache.py b/webware/Admin/ServletCache.py index d449094..b238145 100644 --- a/webware/Admin/ServletCache.py +++ b/webware/Admin/ServletCache.py @@ -70,7 +70,7 @@ def htCache(factory): paths = [] for key in keys: head, tail = os.path.split(key) - path = dict(dir=head, base=tail, full=key, id=id(key)) + path = {'dir': head, 'base': tail, 'full': key, 'id': id(key)} paths.append(path) paths.sort(key=lambda p: (p['base'].lower(), p['dir'].lower())) for path in paths: diff --git a/webware/Application.py b/webware/Application.py index e6b4e6e..5f478b9 100644 --- a/webware/Application.py +++ b/webware/Application.py @@ -39,108 +39,104 @@ debug = False -defaultConfig = dict( - ActivityLogFilename='Activity.csv', - ActivityLogColumns=[ +defaultConfig = { + 'ActivityLogFilename': 'Activity.csv', + 'ActivityLogColumns': [ 'request.remoteAddress', 'request.method', 'request.uri', 'response.size', 'servlet.name', 'request.timeStamp', - 'transaction.duration', - 'transaction.errorOccurred' + 'transaction.duration', 'transaction.errorOccurred' ], - AlwaysSaveSessions=True, - AppLogFilename='Application.log', - CacheDir='Cache', - CacheServletClasses=True, - CacheServletInstances=True, - CheckInterval=None, - Contexts={ + 'AlwaysSaveSessions': True, + 'AppLogFilename': 'Application.log', + 'CacheDir': 'Cache', + 'CacheServletClasses': True, + 'CacheServletInstances': True, + 'CheckInterval': None, + 'Contexts': { 'default': 'Examples', 'Admin': 'Admin', 'Examples': 'Examples', 'Testing': 'Testing', }, - Debug=dict( - Sessions=False, - ), - DirectoryFile=['index', 'Index', 'main', 'Main'], - EnterDebuggerOnException=False, - EmailErrors=False, - EmailErrorReportAsAttachment=False, - ErrorEmailServer='localhost', - ErrorEmailHeaders={ + 'Debug': {'Sessions': False}, + 'DirectoryFile': ['index', 'Index', 'main', 'Main'], + 'EnterDebuggerOnException': False, + 'EmailErrors': False, + 'EmailErrorReportAsAttachment': False, + 'ErrorEmailServer': 'localhost', + 'ErrorEmailHeaders': { 'From': 'webware@mydomain', 'To': ['webware@mydomain'], 'Reply-To': 'webware@mydomain', 'Content-Type': 'text/html', 'Subject': 'Error' }, - ErrorLogFilename='Errors.csv', - ErrorMessagesDir='ErrorMsgs', - ErrorPage=None, - ExtensionCascadeOrder=['.py', '.psp', '.html'], - ExtensionsToIgnore={ + 'ErrorLogFilename': 'Errors.csv', + 'ErrorMessagesDir': 'ErrorMsgs', + 'ErrorPage': None, + 'ExtensionCascadeOrder': ['.py', '.psp', '.html'], + 'ExtensionsToIgnore': { '.pyc', '.pyo', '.tmpl', '.bak', '.py_bak', '.py~', '.psp~', '.html~', '.tmpl~' }, - ExtensionsToServe=[], - ExtraPathInfo=True, - FancyTracebackContext=5, - FilesToHide={ + 'ExtensionsToServe': [], + 'ExtraPathInfo': True, + 'FancyTracebackContext': 5, 'FilesToHide': { '.*', '*~', '*.bak', '*.py_bak', '*.tmpl', '*.pyc', '*.pyo', '__init__.*', '*.config' }, - FilesToServe=[], - IgnoreInvalidSession=True, - IncludeEditLink=True, - IncludeFancyTraceback=False, - LogActivity=True, - LogDir='Logs', - LogErrors=True, - MaxValueLengthInExceptionReport=500, - OutputEncoding='utf-8', - PlugIns=['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'], - PrintConfigAtStartUp=True, - PrintPlugIns=True, - RegisterSignalHandler=False, - ReloadServletClasses=False, - ReportRPCExceptionsInWebware=True, - ResponseBufferSize=8 * 1024, # 8 kBytes - RetainSessions=True, - RPCExceptionReturn='traceback', - RunTasks=True, - SaveErrorMessages=True, - SecureSessionCookie=True, - SessionCookiePath=None, - HttpOnlySessionCookie=True, - SameSiteSessionCookie='Strict', - SessionModule='Session', - SessionName='_SID_', - SessionPrefix='', - SessionStore='Dynamic', - SessionStoreDir='Sessions', - SessionTimeout=60, - ShowDebugInfoOnErrors=False, - SilentURIs=None, - UnknownFileTypes=dict( - ReuseServlets=True, - Technique='serveContent', # or redirectSansScript - CacheContent=False, - MaxCacheContentSize=128 * 1024, - ReadBufferSize=32 * 1024 - ), - UseAutomaticPathSessions=False, - UseCascadingExtensions=True, - UseCookieSessions=True, - UserErrorMessage=( + 'FilesToServe': [], + 'IgnoreInvalidSession': True, + 'IncludeEditLink': True, + 'IncludeFancyTraceback': False, + 'LogActivity': True, + 'LogDir': 'Logs', + 'LogErrors': True, + 'MaxValueLengthInExceptionReport': 500, + 'OutputEncoding': 'utf-8', + 'PlugIns': ['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'], + 'PrintConfigAtStartUp': True, + 'PrintPlugIns': True, + 'RegisterSignalHandler': False, + 'ReloadServletClasses': False, + 'ReportRPCExceptionsInWebware': True, + 'ResponseBufferSize': 8 * 1024, # 8 kBytes + 'RetainSessions': True, + 'RPCExceptionReturn': 'traceback', + 'RunTasks': True, + 'SaveErrorMessages': True, + 'SecureSessionCookie': True, + 'SessionCookiePath': None, + 'HttpOnlySessionCookie': True, + 'SameSiteSessionCookie': 'Strict', + 'SessionModule': 'Session', + 'SessionName': '_SID_', + 'SessionPrefix': '', + 'SessionStore': 'Dynamic', + 'SessionStoreDir': 'Sessions', + 'SessionTimeout': 60, + 'ShowDebugInfoOnErrors': False, + 'SilentURIs': None, + 'UnknownFileTypes': { + 'ReuseServlets': True, + 'Technique': 'serveContent', # or redirectSansScript + 'CacheContent': False, + 'MaxCacheContentSize': 128 * 1024, + 'ReadBufferSize': 32 * 1024 + }, + 'UseAutomaticPathSessions': False, + 'UseCascadingExtensions': True, + 'UseCookieSessions': True, + 'UserErrorMessage': ( 'The site is having technical difficulties with this page.' ' An error has been logged, and the problem will be fixed' ' as soon as possible. Sorry!' ), - UseSessionSweeper=True, - Verbose=True, - WSGIWrite=True, # use write callable with WSGI -) + 'UseSessionSweeper': True, + 'Verbose': True, + 'WSGIWrite': True # use write callable with WSGI +} class EndResponse(Exception): @@ -467,10 +463,11 @@ def configFilename(self): def configReplacementValues(self): """Get config values that need to be escaped.""" - return dict( - ServerSidePath=self._serverSidePath, - WebwarePath=self._webwarePath, - Development=self._development) + return { + 'ServerSidePath': self._serverSidePath, + 'WebwarePath': self._webwarePath, + 'Development': self._development + } def development(self): """Whether the application shall run in development mode""" @@ -645,12 +642,14 @@ def writeActivityLog(self, trans): if mode == 'w': f.write(','.join(self.setting('ActivityLogColumns')) + '\n') values = [] - objects = dict( - application=self, transaction=trans, - request=trans.request(), response=trans.response(), - servlet=trans.servlet(), + objects = { + 'application': self, 'transaction': trans, + 'request': trans.request(), + 'response': trans.response(), + 'servlet': trans.servlet(), # don't cause creation of session here: - session=trans._session) + 'session': trans._session + } for column in self.setting('ActivityLogColumns'): try: value = valueForName(objects, column) @@ -988,7 +987,7 @@ def handleExceptionInTransaction(self, excInfo, trans): editlink = f'{request.scriptName()}/Admin/EditFile' if self.setting( 'IncludeEditLink') else None self._exceptionHandlerClass( - self, trans, excInfo, dict(editlink=editlink)) + self, trans, excInfo, {'editlink': editlink}) def rootURLParser(self): """Accessor: the Root URL parser. @@ -1172,7 +1171,7 @@ def loadPlugIns(self): def __call__(self, environ, start_response): """The WSGI application callable""" verbose = self._verbose - requestDict = dict(environ=environ) + requestDict = {'environ': environ} requestID = self._requestID self._requestID = requestID + 1 diff --git a/webware/Examples/FileUpload.py b/webware/Examples/FileUpload.py index 963fc7f..c887587 100644 --- a/webware/Examples/FileUpload.py +++ b/webware/Examples/FileUpload.py @@ -4,7 +4,7 @@ class FileUpload(ExamplePage): """This servlet shows how to handle uploaded files. - The process is fairly self explanatory. You use a form like the one below + The process is fairly self-explanatory. You use a form like the one below in the writeContent method. When the form is uploaded, the request field with the name you gave to the file selector form item will be an instance of the FieldStorage class from the standard Python module "cgi". The key diff --git a/webware/Examples/ListBox.py b/webware/Examples/ListBox.py index 8981604..5b7e806 100644 --- a/webware/Examples/ListBox.py +++ b/webware/Examples/ListBox.py @@ -20,8 +20,10 @@ def awake(self, transaction): if session.hasValue('vars'): self._vars = session.value('vars') else: - self._vars = dict( - items=[], height=10, width=250, newCount=1, formCount=1) + self._vars = { + 'items': [], 'height': 10, 'width': 250, + 'newCount': 1, 'formCount': 1 + } session.setValue('vars', self._vars) self._error = None @@ -81,7 +83,7 @@ def widthChange(): def new(self): """Add a new item to the list box.""" newCount = self._vars['newCount'] - self._vars['items'].append(dict(name=f'New item {newCount}')) + self._vars['items'].append({'name': f'New item {newCount}'}) self._vars['newCount'] += 1 self.writeBody() diff --git a/webware/Examples/SecurePage.py b/webware/Examples/SecurePage.py index 43e1c23..4243d66 100644 --- a/webware/Examples/SecurePage.py +++ b/webware/Examples/SecurePage.py @@ -128,7 +128,7 @@ def loggedInUser(self): return self.session().value('authenticated_user', None) def defaultConfig(self): - return dict(RequireLogin=True) + return {'RequireLogin': True} def configFilename(self): return 'Configs/SecurePage.config' diff --git a/webware/HTTPRequest.py b/webware/HTTPRequest.py index 7f0c8a9..e59bbe8 100644 --- a/webware/HTTPRequest.py +++ b/webware/HTTPRequest.py @@ -1,6 +1,5 @@ """HTTP requests""" -import cgi import os import sys import traceback @@ -11,7 +10,7 @@ import HTTPResponse from MiscUtils import NoDefault -from WebUtils import FieldStorage +from WebUtils.FieldStorage import FieldStorage from Request import Request debug = False @@ -38,11 +37,11 @@ def __init__(self, requestDict=None): # because bad headers sometimes can break the field storage # (see also https://bugs.python.org/issue27777). try: - self._fields = FieldStorage.FieldStorage( + self._fields = FieldStorage( self._input, environ=self._environ, keep_blank_values=True, strict_parsing=False) except Exception: - self._fields = cgi.FieldStorage(keep_blank_values=True) + self._fields = FieldStorage(keep_blank_values=True) traceback.print_exc(file=sys.stderr) self._cookies = Cookie() if 'HTTP_COOKIE' in self._environ: @@ -64,7 +63,7 @@ def __init__(self, requestDict=None): self._time = time() self._environ = os.environ.copy() self._input = None - self._fields = cgi.FieldStorage(keep_blank_values=True) + self._fields = FieldStorage(keep_blank_values=True) self._cookies = Cookie() env = self._environ @@ -131,8 +130,8 @@ def __init__(self, requestDict=None): # We use Tim O'Malley's Cookie class to get the cookies, # but then change them into an ordinary dictionary of values - self._cookies = dict( - (key, self._cookies[key].value) for key in self._cookies) + self._cookies = { + key: self._cookies[key].value for key in self._cookies} self._contextName = None self._serverSidePath = self._serverSideContextPath = None diff --git a/webware/HTTPResponse.py b/webware/HTTPResponse.py index c55e6ca..bed574a 100644 --- a/webware/HTTPResponse.py +++ b/webware/HTTPResponse.py @@ -441,7 +441,7 @@ def rawResponse(self): headers.append((key, value)) for cookie in self._cookies.values(): headers.append(('Set-Cookie', cookie.headerValue())) - return dict(headers=headers, contents=self._strmOut.buffer()) + return {'headers': headers, 'contents': self._strmOut.buffer()} def size(self): """Return the size of the final contents of the response. diff --git a/webware/MiscUtils/Funcs.py b/webware/MiscUtils/Funcs.py index 98039c8..55b26d0 100644 --- a/webware/MiscUtils/Funcs.py +++ b/webware/MiscUtils/Funcs.py @@ -268,11 +268,12 @@ def timestamp(t=None): formats are generally more appropriate for filenames. """ t = time.localtime(t)[:6] - return dict( - tuple=t, - pretty='{:4d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(*t), - condensed='{:4d}{:02d}{:02d}{:02d}{:02d}{:02d}'.format(*t), - dashed='{:4d}-{:02d}-{:02d}-{:02d}-{:02d}-{:02d}'.format(*t)) + return { + 'tuple': t, + 'pretty': '{:4d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(*t), + 'condensed': '{:4d}{:02d}{:02d}{:02d}{:02d}{:02d}'.format(*t), + 'dashed': '{:4d}-{:02d}-{:02d}-{:02d}-{:02d}-{:02d}'.format(*t) + } def localTimeDelta(t=None): diff --git a/webware/MiscUtils/Tests/TestDataTable.py b/webware/MiscUtils/Tests/TestDataTable.py index 0c66287..ef14031 100644 --- a/webware/MiscUtils/Tests/TestDataTable.py +++ b/webware/MiscUtils/Tests/TestDataTable.py @@ -98,7 +98,7 @@ def _testBasic(self): data = [ ['John', '26', '7.25'], ['Mary', 32, 8.5], - dict(name='Fred', age=28, rating=9.0), + {'name': 'Fred', 'age': 28, 'rating': 9.0}, Record(name='Wilma', age=27, rating=9.5) ] for obj in data: diff --git a/webware/MiscUtils/Tests/TestPickleCache.py b/webware/MiscUtils/Tests/TestPickleCache.py index 52aef81..d040308 100644 --- a/webware/MiscUtils/Tests/TestPickleCache.py +++ b/webware/MiscUtils/Tests/TestPickleCache.py @@ -31,7 +31,7 @@ def testOneIteration(self): sourcePath = self._sourcePath = os.path.join(self._tempDir, 'foo.dict') picklePath = self._picklePath = pc.PickleCache().picklePath(sourcePath) self.remove(picklePath) # make sure we're clean - data = self._data = dict(x=1) + data = self._data = {'x': 1} self.writeSource() try: # test 1: no pickle cache yet diff --git a/webware/MockApplication.py b/webware/MockApplication.py index 767ce70..aaa974a 100644 --- a/webware/MockApplication.py +++ b/webware/MockApplication.py @@ -9,11 +9,11 @@ def recordFile(self, filename, isfile=None): pass -defaultConfig = dict( - CacheDir='Cache', - PlugIns=['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'], - PrintPlugIns=False -) +defaultConfig = { + 'CacheDir': 'Cache', + 'PlugIns': ['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'], + 'PrintPlugIns': False +} class MockApplication(ConfigurableForServerSidePath): @@ -51,10 +51,11 @@ def defaultConfig(self): def configReplacementValues(self): """Get config values that need to be escaped.""" - return dict( - ServerSidePath=self._serverSidePath, - WebwarePath=self._webwarePath, - Development=self._development) + return { + 'ServerSidePath': self._serverSidePath, + 'WebwarePath': self._webwarePath, + 'Development': self._development + } def configFilename(self): return self.serverSidePath('Configs/Application.config') diff --git a/webware/PSP/ParseEventHandler.py b/webware/PSP/ParseEventHandler.py index 71aa489..d47e31a 100644 --- a/webware/PSP/ParseEventHandler.py +++ b/webware/PSP/ParseEventHandler.py @@ -36,16 +36,12 @@ class ParseEventHandler: """ aspace = ' ' - defaults = dict( - BASE_CLASS='Page', - BASE_METHOD='writeHTML', - imports=dict(filename='classes'), - threadSafe='no', - instanceSafe='yes', - indent=4, - gobbleWhitespace=True, - formatter='str' - ) + defaults = { + 'BASE_CLASS': 'Page', 'BASE_METHOD': 'writeHTML', + 'imports': {'filename': 'classes'}, 'threadSafe': 'no', + 'instanceSafe': 'yes', 'indent': 4, + 'gobbleWhitespace': True, 'formatter': 'str' + } def __init__(self, ctxt, parser): self._ctxt = ctxt diff --git a/webware/PickleRPCServlet.py b/webware/PickleRPCServlet.py index 208f351..1d36ddd 100644 --- a/webware/PickleRPCServlet.py +++ b/webware/PickleRPCServlet.py @@ -80,7 +80,7 @@ def respondToPost(self, trans): try: request = trans.request() data = request.rawInput(rewind=1) - response = dict(timeReceived=trans.request().time()) + response = {'timeReceived': trans.request().time()} try: try: encoding = request.environ().get('HTTP_CONTENT_ENCODING') diff --git a/webware/Testing/Main.py b/webware/Testing/Main.py index c9d2e12..7ef4bb8 100644 --- a/webware/Testing/Main.py +++ b/webware/Testing/Main.py @@ -76,8 +76,8 @@ def readContent(self, content): if len(parts) != 2: self.error(f'Invalid line at {lineNum}.') urls.append(parts[0].strip()) - cases.append(dict( - URLs=urls, Expectation=parts[1].strip())) + cases.append( + {'URLs': urls, 'Expectation': parts[1].strip()}) urls = [] # reset list of URLs lineNum += 1 return cases diff --git a/webware/Tests/TestEndToEnd/AppTest.py b/webware/Tests/TestEndToEnd/AppTest.py index 0751d0c..52d44d5 100644 --- a/webware/Tests/TestEndToEnd/AppTest.py +++ b/webware/Tests/TestEndToEnd/AppTest.py @@ -14,9 +14,7 @@ class AppTest: - settings = dict( - PrintConfigAtStartUp=False - ) + settings = {'PrintConfigAtStartUp': False} catchOutput = True # set to False if you want to run pdb inside a test @classmethod diff --git a/webware/Tests/TestEndToEnd/TestAdmin.py b/webware/Tests/TestEndToEnd/TestAdmin.py index ca9801f..d408a14 100644 --- a/webware/Tests/TestEndToEnd/TestAdmin.py +++ b/webware/Tests/TestEndToEnd/TestAdmin.py @@ -7,10 +7,10 @@ class TestAdminLogin(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False, - AdminPassword='LoginTestPassword' - ) + settings = { + 'PrintConfigAtStartUp': False, + 'AdminPassword': 'LoginTestPassword' + } def testLogin(self): r = self.testApp.get('/') @@ -79,12 +79,11 @@ def testLogin(self): class TestAdmin(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False, - AdminPassword='AdminTestPassword', - CacheServletClasses=True, - CacheServletInstances=True - ) + settings = { + 'PrintConfigAtStartUp': False, + 'AdminPassword': 'AdminTestPassword', + 'CacheServletClasses': True, 'CacheServletInstances': True + } def setUp(self): AppTest.setUp(self) diff --git a/webware/Tests/TestEndToEnd/TestContextMap.py b/webware/Tests/TestEndToEnd/TestContextMap.py index 2b71944..4d935c0 100644 --- a/webware/Tests/TestEndToEnd/TestContextMap.py +++ b/webware/Tests/TestEndToEnd/TestContextMap.py @@ -7,11 +7,10 @@ class TestContextMap(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False, - ExtraPathInfo=False, - Contexts={'Testing': 'Testing', 'default': 'Testing'} - ) + settings = { + 'PrintConfigAtStartUp': False, 'ExtraPathInfo': False, + 'Contexts': {'Testing': 'Testing', 'default': 'Testing'} + } def testStartPage(self): r = self.testApp.get('/') @@ -34,11 +33,9 @@ def testBadContextPage(self): class TestAliasedContext(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False, - ExtraPathInfo=False, - Contexts={'TestAlias': 'Testing', 'default': 'TestAlias'} - ) + settings = { + 'PrintConfigAtStartUp': False, 'ExtraPathInfo': False, + 'Contexts': {'TestAlias': 'Testing', 'default': 'TestAlias'}} def testStartPage(self): r = self.testApp.get('/') @@ -61,11 +58,10 @@ def testBadContextPage(self): class TestOnlyDefaultContext(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False, - ExtraPathInfo=False, - Contexts={'default': 'Testing'} - ) + settings = { + 'PrintConfigAtStartUp': False, 'ExtraPathInfo': False, + 'Contexts': {'default': 'Testing'} + } def testStartPage(self): r = self.testApp.get('/') diff --git a/webware/Tests/TestEndToEnd/TestExamples.py b/webware/Tests/TestEndToEnd/TestExamples.py index 00bfa84..fae6fe3 100644 --- a/webware/Tests/TestEndToEnd/TestExamples.py +++ b/webware/Tests/TestEndToEnd/TestExamples.py @@ -9,9 +9,7 @@ class TestExamples(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False - ) + settings = {'PrintConfigAtStartUp': False} def setUp(self): AppTest.setUp(self) @@ -402,8 +400,7 @@ def testRequestInformation(self): '>cookies()<', 'TestCookieNameCookieValue', no=['Reload the page', 'add some test fields']) - r = r.goto('/RequestInformation', - extra_environ=dict(REMOTE_USER='bob')) + r = r.goto('/RequestInformation', extra_environ={'REMOTE_USER': 'bob'}) self.assertEqual(r.status, '200 OK') r.mustcontain( '

Request Variables

', @@ -626,17 +623,13 @@ def testJSONRPCClient(self): class TestExamplesWithoutWSGIWrite(TestExamples): - settings = dict( - PrintConfigAtStartUp=False, - WSGIWrite=False - ) + settings = {'PrintConfigAtStartUp': False, 'WSGIWrite': False} class TestExamplesWithoutCaching(TestExamples): - settings = dict( - PrintConfigAtStartUp=False, - CacheServletClasses=False, - CacheServletInstances=False, - ReloadServletClasses=False - ) + settings = { + 'PrintConfigAtStartUp': False, + 'CacheServletClasses': False, 'CacheServletInstances': False, + 'ReloadServletClasses': False + } diff --git a/webware/Tests/TestEndToEnd/TestPSPExamples.py b/webware/Tests/TestEndToEnd/TestPSPExamples.py index ad65884..e0f008c 100644 --- a/webware/Tests/TestEndToEnd/TestPSPExamples.py +++ b/webware/Tests/TestEndToEnd/TestPSPExamples.py @@ -7,10 +7,7 @@ class TestPSPExamples(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False, - ClearPSPCacheOnStart=True - ) + settings = {'PrintConfigAtStartUp': False, 'ClearPSPCacheOnStart': True} def testStartPage(self): r = self.testApp.get('/').click('^PSP$') diff --git a/webware/Tests/TestEndToEnd/TestTesting.py b/webware/Tests/TestEndToEnd/TestTesting.py index a6b8932..f4bc91f 100644 --- a/webware/Tests/TestEndToEnd/TestTesting.py +++ b/webware/Tests/TestEndToEnd/TestTesting.py @@ -8,10 +8,7 @@ class TestTesting(AppTest, unittest.TestCase): - settings = dict( - PrintConfigAtStartUp=False, - ExtraPathInfo=False - ) + settings = {'PrintConfigAtStartUp': False, 'ExtraPathInfo': False} def testStartPage(self): r = self.testApp.get('/').click('Testing') @@ -273,7 +270,4 @@ def testCase18(self): class TestTestingWithExtraPathInfo(TestTesting): - settings = dict( - PrintConfigAtStartUp=False, - ExtraPathInfo=True - ) + settings = {'PrintConfigAtStartUp': False, 'ExtraPathInfo': True} diff --git a/webware/Tests/TestSessions/Application.py b/webware/Tests/TestSessions/Application.py index d265ebb..dd96120 100644 --- a/webware/Tests/TestSessions/Application.py +++ b/webware/Tests/TestSessions/Application.py @@ -12,15 +12,14 @@ class Application: _sessionName = '_SID_' def setting(self, key, default=None): - return dict( - SessionTimeout=self._sessionTimeout // 60, - SessionPrefix=self._sessionPrefix, - SessionName=self._sessionName, - DynamicSessionTimeout=1, - MaxDynamicMemorySessions=3, - MemcachedOnIteration=None, - Debug=dict(Sessions=False), - ).get(key, default) + return { + 'SessionTimeout': self._sessionTimeout // 60, + 'SessionPrefix': self._sessionPrefix, + 'SessionName': self._sessionName, + 'DynamicSessionTimeout': 1, 'MaxDynamicMemorySessions': 3, + 'MemcachedOnIteration': None, + 'Debug': {'Sessions': False} + }.get(key, default) def handleException(self): raise Exception('Application Error') diff --git a/webware/Tests/TestSessions/Session.py b/webware/Tests/TestSessions/Session.py index 72bacfb..3938076 100644 --- a/webware/Tests/TestSessions/Session.py +++ b/webware/Tests/TestSessions/Session.py @@ -10,7 +10,7 @@ class Session: def __init__(self, identifier=7, value=None): self._identifier = f'foo-{identifier}' - self._data = dict(bar=value or identifier * 6) + self._data = {'bar': value or identifier * 6} self._expired = self._dirty = False self._timeout = 1800 self._lastAccessTime = time() - identifier * 400 diff --git a/webware/UnknownFileTypeServlet.py b/webware/UnknownFileTypeServlet.py index 1e59dbf..09acdf1 100644 --- a/webware/UnknownFileTypeServlet.py +++ b/webware/UnknownFileTypeServlet.py @@ -226,10 +226,11 @@ def serveContent(self, trans): and fileSize < maxCacheContentSize): if debug: print('>> caching') - fileDict = dict( - content=f.read(), - mimeType=mimeType, mimeEncoding=mimeEncoding, - mtime=mtime, size=fileSize, filename=filename) + fileDict = { + 'content': f.read(), + 'mimeType': mimeType, 'mimeEncoding': mimeEncoding, + 'mtime': mtime, 'size': fileSize, 'filename': filename + } fileCache[filename] = fileDict if fileDict is not None: if debug: diff --git a/webware/WebUtils/HTMLTag.py b/webware/WebUtils/HTMLTag.py index f0e7d60..3a35dd1 100644 --- a/webware/WebUtils/HTMLTag.py +++ b/webware/WebUtils/HTMLTag.py @@ -683,8 +683,8 @@ def encounteredTag(self, tag, lineNum): cannotHave=self.tags) -configClassForName = dict( - canOnlyHave=TagCanOnlyHaveConfig, cannotHave=TagCannotHaveConfig) +configClassForName = { + 'canOnlyHave': TagCanOnlyHaveConfig, 'cannotHave': TagCannotHaveConfig} if __name__ == '__main__': diff --git a/webware/WebUtils/HTTPStatusCodes.py b/webware/WebUtils/HTTPStatusCodes.py index 0d35659..21be35a 100644 --- a/webware/WebUtils/HTTPStatusCodes.py +++ b/webware/WebUtils/HTTPStatusCodes.py @@ -21,9 +21,9 @@ # Construct HTTPStatusCodes dictionary for code, identifier, description in HTTPStatusCodeList: - d = dict(code=code, identifier=identifier, description=description, - # the following tow exist for backward compatibility only: - htmlMsg=description, asciiMsg=description) + d = {'code': code, 'identifier': identifier, 'description': description, + # the following two exist for backward compatibility only: + 'htmlMsg': description, 'asciiMsg': description} HTTPStatusCodes[code] = d HTTPStatusCodes[identifier] = d diff --git a/webware/WebUtils/Tests/TestFieldStorageModified.py b/webware/WebUtils/Tests/TestFieldStorageModified.py index 510f956..a3a25e5 100644 --- a/webware/WebUtils/Tests/TestFieldStorageModified.py +++ b/webware/WebUtils/Tests/TestFieldStorageModified.py @@ -10,8 +10,8 @@ class TestFieldStorage(unittest.TestCase): def testGetRequest(self): - fs = FieldStorage(environ=dict( - REQUEST_METHOD='GET', QUERY_STRING='a=1&b=2&b=3&c=3')) + fs = FieldStorage(environ={ + 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'a=1&b=2&b=3&c=3'}) self.assertEqual(fs.getfirst('a'), '1') self.assertEqual(fs.getfirst('b'), '2') self.assertEqual(fs.getfirst('c'), '3') @@ -20,8 +20,8 @@ def testGetRequest(self): self.assertEqual(fs.getlist('c'), ['3']) def testPostRequestWithQuery(self): - fs = FieldStorage(fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1&b=2&b=3&c=3')) + fs = FieldStorage(fp=BytesIO(), environ={ + 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'a=1&b=2&b=3&c=3'}) self.assertEqual(fs.getfirst('a'), '1') self.assertEqual(fs.getfirst('b'), '2') self.assertEqual(fs.getfirst('c'), '3') @@ -30,8 +30,8 @@ def testPostRequestWithQuery(self): self.assertEqual(fs.getlist('c'), ['3']) def testPostRequestWithBody(self): - fs = FieldStorage(fp=BytesIO(b'd=4&e=5&e=6&f=6'), environ=dict( - REQUEST_METHOD='POST')) + fs = FieldStorage(fp=BytesIO(b'd=4&e=5&e=6&f=6'), environ={ + 'REQUEST_METHOD': 'POST'}) self.assertEqual(fs.getfirst('d'), '4') self.assertEqual(fs.getfirst('e'), '5') self.assertEqual(fs.getfirst('f'), '6') @@ -40,16 +40,16 @@ def testPostRequestWithBody(self): self.assertEqual(fs.getlist('f'), ['6']) def testPostRequestWithSpacesInValues(self): - fs = FieldStorage(fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=b%20c+d')) + fs = FieldStorage(fp=BytesIO(), environ={ + 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'a=b%20c+d'}) self.assertEqual(fs.getfirst('a'), 'b c d') - fs = FieldStorage(fp=BytesIO(b'a=b%20c+d'), environ=dict( - REQUEST_METHOD='POST')) + fs = FieldStorage(fp=BytesIO(b'a=b%20c+d'), environ={ + 'REQUEST_METHOD': 'POST'}) self.assertEqual(fs.getfirst('a'), 'b c d') def testPostRequestOverrides(self): - fs = FieldStorage(fp=BytesIO(b'b=8&c=9&d=4&e=5&e=6&f=6'), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1&b=2&b=3&c=3')) + fs = FieldStorage(fp=BytesIO(b'b=8&c=9&d=4&e=5&e=6&f=6'), environ={ + 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'a=1&b=2&b=3&c=3'}) self.assertEqual(fs.getfirst('a'), '1') self.assertEqual(fs.getfirst('b'), '8') self.assertEqual(fs.getfirst('c'), '9') @@ -64,20 +64,23 @@ def testPostRequestOverrides(self): self.assertEqual(fs.getlist('f'), ['6']) def testPostRequestWithTooManyFields(self): - fs = FieldStorage(fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1&a=2&a=3&a=4'), + fs = FieldStorage( + fp=BytesIO(), + environ={'REQUEST_METHOD': 'POST', + 'QUERY_STRING': 'a=1&a=2&a=3&a=4'}, max_num_fields=4) self.assertEqual(fs.getlist('a'), ['1', '2', '3', '4']) if hasattr(fs, 'max_num_fields'): # only test if this is supported self.assertRaises( ValueError, FieldStorage, - fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1&a=2&a=3&a=4'), + fp=BytesIO(), + environ={'REQUEST_METHOD': 'POST', + 'QUERY_STRING': 'a=1&a=2&a=3&a=4'}, max_num_fields=3) def testPostRequestWithQueryWithSemicolon1(self): - fs = FieldStorage(fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1&b=2;b=3&c=3')) + fs = FieldStorage(fp=BytesIO(), environ={ + 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'a=1&b=2;b=3&c=3'}) self.assertEqual(fs.getfirst('a'), '1') self.assertEqual(fs.getfirst('c'), '3') self.assertEqual(fs.getlist('a'), ['1']) @@ -85,8 +88,10 @@ def testPostRequestWithQueryWithSemicolon1(self): if hasSeparator(): # new Python version, splits only & self.assertEqual(fs.getfirst('b'), '2;b=3') self.assertEqual(fs.getlist('b'), ['2;b=3']) - fs = FieldStorage(fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1&b=2&b=3&c=3'), + fs = FieldStorage( + fp=BytesIO(), + environ={'REQUEST_METHOD': 'POST', + 'QUERY_STRING': 'a=1&b=2&b=3&c=3'}, separator='&') self.assertEqual(fs.getfirst('a'), '1') self.assertEqual(fs.getfirst('b'), '2') @@ -99,8 +104,8 @@ def testPostRequestWithQueryWithSemicolon1(self): self.assertEqual(fs.getlist('b'), ['2', '3']) def testPostRequestWithQueryWithSemicolon2(self): - fs = FieldStorage(fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1;b=2&b=3;c=3')) + fs = FieldStorage(fp=BytesIO(), environ={ + 'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'a=1;b=2&b=3;c=3'}) if hasSeparator(): # new Python version, splits only & self.assertEqual(fs.getfirst('a'), '1;b=2') self.assertEqual(fs.getfirst('b'), '3;c=3') @@ -108,8 +113,10 @@ def testPostRequestWithQueryWithSemicolon2(self): self.assertEqual(fs.getlist('a'), ['1;b=2']) self.assertEqual(fs.getlist('b'), ['3;c=3']) self.assertEqual(fs.getlist('c'), []) - fs = FieldStorage(fp=BytesIO(), environ=dict( - REQUEST_METHOD='POST', QUERY_STRING='a=1;b=2;b=3;c=3'), + fs = FieldStorage( + fp=BytesIO(), + environ={'REQUEST_METHOD': 'POST', + 'QUERY_STRING': 'a=1;b=2;b=3;c=3'}, separator=';') self.assertEqual(fs.getfirst('a'), '1') self.assertEqual(fs.getfirst('b'), '2') diff --git a/webware/WebUtils/Tests/TestFieldStorageStandard.py b/webware/WebUtils/Tests/TestFieldStorageStandard.py index 229e0d6..22f5ee2 100644 --- a/webware/WebUtils/Tests/TestFieldStorageStandard.py +++ b/webware/WebUtils/Tests/TestFieldStorageStandard.py @@ -17,7 +17,7 @@ class HackedSysModule: cgi.sys = HackedSysModule() -def genTesult(data, environ): +def genResult(data, environ): encoding = 'latin-1' fakeStdin = BytesIO(data.encode(encoding)) fakeStdin.seek(0) @@ -172,7 +172,7 @@ def check(content): 'CONTENT_TYPE': 'multipart/form-data; boundary=-123', 'REQUEST_METHOD': 'POST', } - self.assertEqual(genTesult(data, environ), + self.assertEqual(genResult(data, environ), {'upload': content.encode('latin1')}) check('x' * (maxline - 1)) check('x' * (maxline - 1) + '\r') @@ -271,7 +271,7 @@ def testQSAndUrlEncode(self): 'QUERY_STRING': 'key1=value1&key2=value2y', 'REQUEST_METHOD': 'POST', } - v = genTesult(data, environ) + v = genResult(data, environ) self.assertNotEqual(self._qsResult, v) self.assertEqual(self._qsResultModified, v) @@ -344,7 +344,7 @@ def testQSAndFormData(self): 'QUERY_STRING': 'key1=value1&key2=value2x', 'REQUEST_METHOD': 'POST', } - v = genTesult(data, environ) + v = genResult(data, environ) self.assertEqual(self._qsResult, v) def testQSAndFormDataFile(self): @@ -376,7 +376,7 @@ def testQSAndFormDataFile(self): } result = self._qsResult.copy() result['upload'] = b'this is the content of the fake file\n' - v = genTesult(data, environ) + v = genResult(data, environ) self.assertEqual(result, v) def testParseHeader(self): diff --git a/webware/WebUtils/Tests/TestFuncs.py b/webware/WebUtils/Tests/TestFuncs.py index cd5629e..dfe1ef4 100644 --- a/webware/WebUtils/Tests/TestFuncs.py +++ b/webware/WebUtils/Tests/TestFuncs.py @@ -61,33 +61,33 @@ def testUrlRoundTrip(self): def testHtmlDorDict(self): f = Funcs.htmlForDict self.assertEqual( - f(dict(foo='bar', answer=42)), + f({'foo': 'bar', 'answer': 42}), '\n' '\n' '\n
answer42
foobar
') self.assertEqual( - f(dict(foo='ba,zong', bar='ka;woom'), - addSpace=dict(foo=',', bar=';')), + f({'foo': 'ba,zong', 'bar': 'ka;woom'}, + addSpace={'foo': ',', 'bar': ';'}), '\n' '\n' '\n
barka; woom
fooba, zong
') self.assertEqual( - f(dict(foo='barbarabarbarabarbarabarbara'), maxValueLength=12), + f({'foo': 'barbarabarbarabarbarabarbara'}, maxValueLength=12), '\n' '\n
foobarbaraba...
') self.assertEqual( - f(dict(foo='zing', bar='zang'), + f({'foo': 'zing', 'bar': 'zang'}, filterValueCallBack=lambda v, k, d: 'zung' if k == 'bar' else v), '\n' '\n' '\n
barzung
foozing
') self.assertEqual( - f(dict(foo='bar'), topHeading='twinkle'), + f({'foo': 'bar'}, topHeading='twinkle'), '\n' '\n' '\n
twinkle
foobar
') self.assertEqual( - f(dict(foo='bar'), topHeading=('key', 'value')), + f({'foo': 'bar'}, topHeading=('key', 'value')), '\n' '\n' '\n
keyvalue
foobar
') @@ -103,12 +103,12 @@ def testHtmlDorDict(self): def testRequestURI(self): f = Funcs.requestURI self.assertEqual( - f(dict(REQUEST_URI='http://w4py.org')), 'http://w4py.org') + f({'REQUEST_URI': 'http://w4py.org'}), 'http://w4py.org') self.assertEqual( - f(dict(SCRIPT_URL='http://w4py.org', QUERY_STRING='foo=bar')), + f({'SCRIPT_URL': 'http://w4py.org', 'QUERY_STRING': 'foo=bar'}), 'http://w4py.org?foo=bar') self.assertEqual( - f(dict(SCRIPT_NAME='/test', QUERY_STRING='foo=bar')), + f({'SCRIPT_NAME': '/test', 'QUERY_STRING': 'foo=bar'}), '/test?foo=bar') def testNormURL(self): From 0ecd9f587c33e933d0c2b305a7283ec5419b03f2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Mar 2023 18:34:50 +0100 Subject: [PATCH 03/11] Remove unneeded else statements as pylint suggests --- webware/Examples/DBUtilsDemo.py | 6 ++---- webware/Examples/ExamplePage.py | 3 +-- webware/MiscUtils/DataTable.py | 3 +-- webware/Servlet.py | 8 +++----- webware/SessionMemcachedStore.py | 5 ++--- webware/SessionRedisStore.py | 5 ++--- 6 files changed, 11 insertions(+), 19 deletions(-) diff --git a/webware/Examples/DBUtilsDemo.py b/webware/Examples/DBUtilsDemo.py index 05a00cf..2752d0f 100644 --- a/webware/Examples/DBUtilsDemo.py +++ b/webware/Examples/DBUtilsDemo.py @@ -200,8 +200,7 @@ def listSeminars(self): pass self.outputMsg(error, True) return - else: - self.outputMsg(f'Entries deleted: {len(ids)}') + self.outputMsg(f'Entries deleted: {len(ids)}') db = self.connection() if not db: return @@ -271,8 +270,7 @@ def listAttendees(self): db.rollback() self.outputMsg(error, True) return - else: - self.outputMsg(f'Entries deleted: {len(ids)}') + self.outputMsg(f'Entries deleted: {len(ids)}') db = self.connection() if not db: return diff --git a/webware/Examples/ExamplePage.py b/webware/Examples/ExamplePage.py index b437a41..fce2a8f 100644 --- a/webware/Examples/ExamplePage.py +++ b/webware/Examples/ExamplePage.py @@ -57,8 +57,7 @@ def examplePages(self, plugInName=None): plugIn = self.application().plugIn(plugInName) except KeyError: return [] - else: - return plugIn.examplePages() + return plugIn.examplePages() def writeSidebar(self): self.writeExamplesMenu() diff --git a/webware/MiscUtils/DataTable.py b/webware/MiscUtils/DataTable.py index 9a5aac8..b56d499 100644 --- a/webware/MiscUtils/DataTable.py +++ b/webware/MiscUtils/DataTable.py @@ -253,8 +253,7 @@ def canReadExcel(): Dispatch("Excel.Application") except Exception: return False - else: - return True + return True # endregion Functions diff --git a/webware/Servlet.py b/webware/Servlet.py index 7189ab9..239ba67 100644 --- a/webware/Servlet.py +++ b/webware/Servlet.py @@ -81,11 +81,9 @@ def runTransaction(transaction): # just a result of the first. Then you're stuck scratching your # head wondering what the first might have been. raise second from first - else: - # no problems with sleep() so raise the one and only exception - raise - else: - transaction.sleep() + # no problems with sleep() so raise the one and only exception + raise + transaction.sleep() def runMethodForTransaction(self, transaction, method, *args, **kw): self.awake(transaction) diff --git a/webware/SessionMemcachedStore.py b/webware/SessionMemcachedStore.py index 39109f2..6dc5d48 100644 --- a/webware/SessionMemcachedStore.py +++ b/webware/SessionMemcachedStore.py @@ -212,9 +212,8 @@ def pop(self, key, default=NoDefault): value = self[key] except KeyError: return default - else: - del self[key] - return value + del self[key] + return value # endregion Access diff --git a/webware/SessionRedisStore.py b/webware/SessionRedisStore.py index 81b817f..9c80fc3 100644 --- a/webware/SessionRedisStore.py +++ b/webware/SessionRedisStore.py @@ -188,9 +188,8 @@ def pop(self, key, default=NoDefault): value = self[key] except KeyError: return default - else: - del self[key] - return value + del self[key] + return value # endregion Access From 34469fd02bf242d03baee26b968827a75fef8bc3 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Mar 2023 19:02:22 +0100 Subject: [PATCH 04/11] Minor simplifications --- webware/Examples/AjaxPage.py | 7 +--- webware/MiscUtils/DictForArgs.py | 40 ++++++++--------------- webware/MiscUtils/Tests/TestCSVParser.py | 19 ++++------- webware/MiscUtils/Tests/TestDataTable.py | 9 ++--- webware/PSP/BraceConverter.py | 2 +- webware/Testing/Main.py | 2 +- webware/Tests/TestSessions/Application.py | 2 +- 7 files changed, 27 insertions(+), 54 deletions(-) diff --git a/webware/Examples/AjaxPage.py b/webware/Examples/AjaxPage.py index d5242f1..ca8a902 100644 --- a/webware/Examples/AjaxPage.py +++ b/webware/Examples/AjaxPage.py @@ -41,12 +41,7 @@ def __str__(self): def __call__(self, *args, **kw): args = ','.join(map(quoteJs, args)) kwArgs = ','.join(f'{k}={quoteJs(v)}' for k, v in kw.items()) - if args and kwArgs: - allArgs = f'{args},{kwArgs}' - elif not kwArgs: - allArgs = args - elif not args: - allArgs = kwArgs + allArgs = f'{args},{kwArgs}' if args and kwArgs else args or kwArgs return self.__class__(f'{self}({allArgs}') def __getitem__(self, index): diff --git a/webware/MiscUtils/DictForArgs.py b/webware/MiscUtils/DictForArgs.py index 99dfd37..c660e73 100644 --- a/webware/MiscUtils/DictForArgs.py +++ b/webware/MiscUtils/DictForArgs.py @@ -7,6 +7,16 @@ import re +verbose = False + +_nameRE = re.compile(r'\w+') +_equalsRE = re.compile(r'\=') +_stringRE = re.compile(r'''"[^"]+"|'[^']+'|\S+''') +_whiteRE = re.compile(r'\s+') + +_REs = {_nameRE: 'name', _equalsRE: 'equals', + _stringRE: 'string', _whiteRE: 'white'} + class DictForArgsError(Exception): """Error when building dictionary from arguments.""" @@ -16,14 +26,6 @@ def _SyntaxError(s): raise DictForArgsError(f'Syntax error: {s!r}') -_nameRE = re.compile(r'\w+') -_equalsRE = re.compile(r'\=') -_stringRE = re.compile(r'''"[^"]+"|'[^']+'|\S+''') -_whiteRE = re.compile(r'\s+') - -_REs = [_nameRE, _equalsRE, _stringRE, _whiteRE] - - def dictForArgs(s): """Build dictionary from arguments. @@ -58,7 +60,6 @@ def dictForArgs(s): # Tokenize - verbose = False matches = [] start = 0 sLen = len(s) @@ -84,17 +85,7 @@ def dictForArgs(s): _SyntaxError(s) if verbose: - names = [] - for match in matches: - if match.re is _nameRE: - name = 'name' - elif match.re is _equalsRE: - name = 'equals' - elif match.re is _stringRE: - name = 'string' - elif match.re is _whiteRE: - name = 'white' - names.append(name) + names = ', '.join(_REs[match.re] for match in matches) print('>> names =', names) # Process tokens @@ -107,10 +98,7 @@ def dictForArgs(s): i = 0 while i < matchesLen: match = matches[i] - if i + 1 < matchesLen: - peekMatch = matches[i+1] - else: - peekMatch = None + peekMatch = matches[i+1] if i + 1 < matchesLen else None if match.re is _nameRE: if peekMatch is not None: if peekMatch.re is _nameRE: @@ -121,9 +109,9 @@ def dictForArgs(s): if peekMatch.re is _equalsRE: if i + 2 < matchesLen: target = matches[i+2] - if target.re is _nameRE or target.re is _stringRE: + if target.re in (_nameRE, _stringRE): value = target.group() - if value[0] == "'" or value[0] == '"': + if value[0] in ("'", '"'): value = value[1:-1] d[match.group()] = value i += 3 diff --git a/webware/MiscUtils/Tests/TestCSVParser.py b/webware/MiscUtils/Tests/TestCSVParser.py index 6f89ae2..da7c5ee 100644 --- a/webware/MiscUtils/Tests/TestCSVParser.py +++ b/webware/MiscUtils/Tests/TestCSVParser.py @@ -21,14 +21,10 @@ def testNegatives(self): 'a\n,b' ] for inp in inputs: - try: + with self.assertRaises( + ParseError, msg=f'Did not get an exception for: {inp!r}'): results = self.parse(inp) - except ParseError: - pass - else: - print() - print('results:', repr(results)) - raise Exception(f'Did not get an exception for: {inp!r}') + print(f'\nresults: {results!r}') def testPositives(self): tests = [ @@ -86,19 +82,16 @@ def testPositives(self): self.assertEqual( res, out, f'\ninput={inp!r}\nresult={res!r}\noutput={out!r}') - res = self.parse(inp+'\n') + res = self.parse(inp + '\n') self.assertEqual( res, out, f'\ninput={inp!r}\nresult={res!r}\noutput={out!r}') else: # multiple lines - gotFields = False + res = None for line in inp.splitlines(): - self.assertFalse(gotFields) + self.assertIsNone(res) res = self.parse(line) - if res is not None: - gotFields = True - self.assertTrue(gotFields) self.assertEqual( res, out, f'\ninput={inp!r}\nresult={res!r}\noutput={out!r}') diff --git a/webware/MiscUtils/Tests/TestDataTable.py b/webware/MiscUtils/Tests/TestDataTable.py index ef14031..ad728cc 100644 --- a/webware/MiscUtils/Tests/TestDataTable.py +++ b/webware/MiscUtils/Tests/TestDataTable.py @@ -212,13 +212,10 @@ def testBasics(self): self._testSource('MK enums', src, headings, data) # Unfinished multiline record - try: + with self.assertRaises( + DataTableError, + msg='Did not raise error for unfinished multiline record'): DataTable().readString('a\n"1\n') - except DataTableError: - pass # just what we were expecting - else: - raise Exception( - 'Failed to raise exception for unfinished multiline record') def testDefaultUsePickleCache(self): t = DataTable() diff --git a/webware/PSP/BraceConverter.py b/webware/PSP/BraceConverter.py index 5712165..da2f645 100644 --- a/webware/PSP/BraceConverter.py +++ b/webware/PSP/BraceConverter.py @@ -77,7 +77,7 @@ def parseLine(self, line, writer): self.line = '' else: # should never get here - raise Exception() + raise ValueError(f'Invalid character: {c!r}') writer.printChars('\n') def openBlock(self, writer): diff --git a/webware/Testing/Main.py b/webware/Testing/Main.py index 7ef4bb8..f1f9cc2 100644 --- a/webware/Testing/Main.py +++ b/webware/Testing/Main.py @@ -92,4 +92,4 @@ def writeNotes(self): ''') def error(self, msg): - raise Exception(msg) + raise RuntimeError(msg) diff --git a/webware/Tests/TestSessions/Application.py b/webware/Tests/TestSessions/Application.py index dd96120..0eb391a 100644 --- a/webware/Tests/TestSessions/Application.py +++ b/webware/Tests/TestSessions/Application.py @@ -22,7 +22,7 @@ def setting(self, key, default=None): }.get(key, default) def handleException(self): - raise Exception('Application Error') + raise RuntimeError('Application Error') def sessionTimeout(self, _trans=None): return self._sessionTimeout From 9c0cfb02bc181a8ddfc65ef7356c96a2bf3a0c7f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Mar 2023 19:29:56 +0100 Subject: [PATCH 05/11] Consistently use old variable naming conventions --- webware/MiscUtils/Tests/TestFuncs.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/webware/MiscUtils/Tests/TestFuncs.py b/webware/MiscUtils/Tests/TestFuncs.py index 00eb34c..d49a21c 100644 --- a/webware/MiscUtils/Tests/TestFuncs.py +++ b/webware/MiscUtils/Tests/TestFuncs.py @@ -85,20 +85,19 @@ def testHostName(self): self.assertEqual(host, host.lower()) def testLocalIP(self): - ip_local = localIP() - self.assertTrue(ip_local) - self.assertFalse(ip_local.startswith('127.')) - self.assertEqual(localIP(), ip_local) # second invocation - self.assertEqual(localIP(useCache=None), ip_local) + thisIp = localIP() + self.assertTrue(thisIp) + self.assertFalse(thisIp.startswith('127.')) + self.assertEqual(localIP(), thisIp) # second invocation + self.assertEqual(localIP(useCache=None), thisIp) - def assert_local(ip): + def assertLocal(ip): self.assertTrue( - ip == ip_local or ip.startswith(('127.', '192.168.')) + ip == thisIp or ip.startswith(('127.', '192.168.')) or '172.16.' <= ip <= '172.31.') - assert_local(localIP(remote=None, useCache=None)) - assert_local( - localIP(remote=('www.hostname.and.domain.are.invalid', 80), + assertLocal(localIP(remote=None, useCache=None)) + assertLocal(localIP(remote=('www.hostname.and.domain.are.invalid', 80), useCache=None)) def testPositiveId(self): From 526b8e36d46f484eedf4eeaf7f3064ec8b09c7cb Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 4 Mar 2023 19:42:45 +0100 Subject: [PATCH 06/11] Update docstring --- webware/Examples/FileUpload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webware/Examples/FileUpload.py b/webware/Examples/FileUpload.py index c887587..6b91d7f 100644 --- a/webware/Examples/FileUpload.py +++ b/webware/Examples/FileUpload.py @@ -7,7 +7,7 @@ class FileUpload(ExamplePage): The process is fairly self-explanatory. You use a form like the one below in the writeContent method. When the form is uploaded, the request field with the name you gave to the file selector form item will be an instance - of the FieldStorage class from the standard Python module "cgi". The key + of the FieldStorage class from the WebUtils.FieldStorage module. The key attributes of this class are shown in the example below. The most important things are filename, which gives the name of the file that was uploaded, and file, which is an open file handle to the uploaded file. The uploaded From 25179e5bb5a4cc929781c877ee07e284795a5c35 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Mar 2023 01:02:38 +0100 Subject: [PATCH 07/11] Fix processing binary data in FieldStorage (#14) --- webware/HTTPRequest.py | 13 +-- webware/WebUtils/FieldStorage.py | 13 ++- .../Tests/TestFieldStorageModified.py | 93 +++++++++++++++++++ 3 files changed, 108 insertions(+), 11 deletions(-) diff --git a/webware/HTTPRequest.py b/webware/HTTPRequest.py index e59bbe8..f9d50cc 100644 --- a/webware/HTTPRequest.py +++ b/webware/HTTPRequest.py @@ -35,7 +35,7 @@ def __init__(self, requestDict=None): self._requestID = requestDict['requestID'] # Protect the loading of fields with an exception handler, # because bad headers sometimes can break the field storage - # (see also https://bugs.python.org/issue27777). + # (see also https://github.com/python/cpython/issues/71964). try: self._fields = FieldStorage( self._input, environ=self._environ, @@ -47,7 +47,7 @@ def __init__(self, requestDict=None): if 'HTTP_COOKIE' in self._environ: # If there are duplicate cookies, always use the first one # because it is the most relevant one according to RFC 2965 - # (workaround for https://bugs.python.org/issue1375011). + # (see also https://github.com/python/cpython/issues/42664). # noinspection PyTypeChecker cookies = dict(cookie.split('=', 1) for cookie in reversed( self._environ['HTTP_COOKIE'].split('; '))) @@ -321,7 +321,7 @@ def urlPath(self): This is actually the same as pathInfo(). - For example, http://host/Webware/Context/Servlet?x=1 + For example, https://host/Webware/Context/Servlet?x=1 yields '/Context/Servlet'. """ return self._pathInfo @@ -329,7 +329,7 @@ def urlPath(self): def urlPathDir(self): """Same as urlPath, but only gives the directory. - For example, http://host/Webware/Context/Servlet?x=1 + For example, https://host/Webware/Context/Servlet?x=1 yields '/Context'. """ return os.path.dirname(self.urlPath()) @@ -434,7 +434,7 @@ def serverURL(self, canonical=False): then the canonical hostname of the server is used if possible. The path is returned without any extra path info or query strings, - i.e. http://www.my.own.host.com:8080/Webware/TestPage.py + i.e. https://www.my.own.host.com:8080/Webware/TestPage.py """ if canonical and 'SCRIPT_URI' in self._environ: return self._environ['SCRIPT_URI'] @@ -801,7 +801,8 @@ def info(self): ] for method in _infoMethods: try: - info.append((method.__name__, method(self))) + # noinspection PyArgumentList + info.append((method.__name__, method(self),)) except Exception: info.append((method.__name__, None)) return info diff --git a/webware/WebUtils/FieldStorage.py b/webware/WebUtils/FieldStorage.py index af9f6aa..9ab322e 100644 --- a/webware/WebUtils/FieldStorage.py +++ b/webware/WebUtils/FieldStorage.py @@ -416,7 +416,6 @@ def read_single(self): def read_binary(self): """Internal: read binary data.""" - self.file = self.make_file() todo = self.length while todo > 0: data = self.fp.read(min(todo, self.bufsize)) @@ -428,10 +427,14 @@ def read_binary(self): if not data: self.done = -1 break - if self._binary_file: - self.file.write(data) - else: # fix for issue 27777 - self.file.write(data.decode()) + if not self._binary_file: + # fix for https://github.com/python/cpython/pull/7804 + try: + data = data.decode() + except UnicodeDecodeError: + self._binary_file = True + self.file = self.make_file() + self.file.write(data) todo -= len(data) def read_lines(self): diff --git a/webware/WebUtils/Tests/TestFieldStorageModified.py b/webware/WebUtils/Tests/TestFieldStorageModified.py index a3a25e5..45c356f 100644 --- a/webware/WebUtils/Tests/TestFieldStorageModified.py +++ b/webware/WebUtils/Tests/TestFieldStorageModified.py @@ -131,3 +131,96 @@ def testPostRequestWithQueryWithSemicolon2(self): self.assertEqual(fs.getlist('a'), ['1']) self.assertEqual(fs.getlist('b'), ['2', '3']) self.assertEqual(fs.getlist('c'), ['3']) + + def testPostRequestWithoutContentLength(self): + # see https://github.com/python/cpython/issues/71964 + fs = FieldStorage( + fp=BytesIO(b'{"test":123}'), + environ={'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'application/json'}) + self.assertEqual(fs.headers, { + 'content-type': 'application/json'}) + self.assertEqual(fs.type, 'application/json') + self.assertEqual(fs.length, -1) + self.assertEqual(fs.bytes_read, 12) + assert fs.file.read() == '{"test":123}' + + def testPostRequestWithContentLengthAndContentDispositionInline(self): + # see https://github.com/python/cpython/issues/71964 + fs = FieldStorage( + fp=BytesIO(b'{"test":123}'), + headers={'content-length': 12, 'content-disposition': 'inline', + 'content-type': 'application/json'}, + environ={'REQUEST_METHOD': 'POST'}) + self.assertEqual(fs.headers, { + 'content-type': 'application/json', 'content-length': 12, + 'content-disposition': 'inline'}) + self.assertEqual(fs.disposition, 'inline') + self.assertIsNone(fs.filename) + self.assertEqual(fs.type, 'application/json') + self.assertEqual(fs.length, 12) + self.assertEqual(fs.bytes_read, 12) + self.assertEqual(fs.file.read(), '{"test":123}') + + def testPostRequestWithContentLengthAndContentDispositionAttachment(self): + # not affected by https://github.com/python/cpython/issues/71964 + fs = FieldStorage( + fp=BytesIO(b'{"test":123}'), + headers={'content-length': 12, + 'content-disposition': 'attachment; filename="foo.json"', + 'content-type': 'application/json'}, + environ={'REQUEST_METHOD': 'POST'}) + self.assertEqual(fs.headers, { + 'content-type': 'application/json', 'content-length': 12, + 'content-disposition': 'attachment; filename="foo.json"'}) + self.assertEqual(fs.disposition, 'attachment') + self.assertEqual(fs.filename, 'foo.json') + self.assertEqual(fs.type, 'application/json') + self.assertEqual(fs.length, 12) + self.assertEqual(fs.bytes_read, 12) + self.assertEqual(fs.file.read(), b'{"test":123}') + + def testPostRequestWithContentLengthButWithoutContentDisposition(self): + # see https://github.com/python/cpython/issues/71964 + fs = FieldStorage(fp=BytesIO(b'{"test":123}'), environ={ + 'CONTENT_LENGTH': 12, 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'application/json'}) + self.assertEqual(fs.headers, { + 'content-type': 'application/json', 'content-length': 12}) + self.assertEqual(fs.disposition, '') + self.assertEqual(fs.type, 'application/json') + self.assertEqual(fs.length, 12) + self.assertEqual(fs.bytes_read, 12) + self.assertEqual(fs.file.read(), '{"test":123}') + + def testPostRequestWithUtf8BinaryData(self): + text = 'The \u2603 by Raymond Briggs' + content = text.encode('utf-8') + length = len(content) + fs = FieldStorage(fp=BytesIO(content), environ={ + 'CONTENT_LENGTH': length, 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'application/octet-stream'}) + self.assertEqual(fs.headers, { + 'content-type': 'application/octet-stream', + 'content-length': length}) + self.assertEqual(fs.type, 'application/octet-stream') + self.assertEqual(fs.length, length) + self.assertEqual(fs.bytes_read, length) + self.assertEqual(fs.file.read(), text) + + def testPostRequestWithNonUtf8BinaryData(self): + # see https://github.com/WebwareForPython/w4py3/issues/14 + content = b'\xfe\xff\xc0' + with self.assertRaises(UnicodeDecodeError): + content.decode('utf-8') + length = len(content) + fs = FieldStorage(fp=BytesIO(content), environ={ + 'CONTENT_LENGTH': length, 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'application/octet-stream'}) + self.assertEqual(fs.headers, { + 'content-type': 'application/octet-stream', + 'content-length': length}) + self.assertEqual(fs.type, 'application/octet-stream') + self.assertEqual(fs.length, length) + self.assertEqual(fs.bytes_read, length) + self.assertEqual(fs.file.read(), content) From 91b38983724d75b5e987fc9ae72147767974fc18 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Mar 2023 01:03:25 +0100 Subject: [PATCH 08/11] Make reading Excel sheets in DataTable more robust --- webware/MiscUtils/DataTable.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/webware/MiscUtils/DataTable.py b/webware/MiscUtils/DataTable.py index b56d499..1e79826 100644 --- a/webware/MiscUtils/DataTable.py +++ b/webware/MiscUtils/DataTable.py @@ -13,7 +13,7 @@ John 893-5901 This data often comes from delimited text files which typically -have well defined columns or fields with several rows each of which can +have well-defined columns or fields with several rows each of which can be thought of as a record. Using a DataTable can be as easy as using lists and dictionaries:: @@ -202,6 +202,7 @@ from datetime import date, datetime, time, timedelta, tzinfo from decimal import Decimal +from time import sleep from warnings import warn from MiscUtils import NoDefault @@ -249,8 +250,8 @@ def canReadExcel(): try: - from win32com.client import Dispatch # pylint: disable=import-error - Dispatch("Excel.Application") + from win32com import client # pylint: disable=import-error + client.gencache.EnsureDispatch("Excel.Application") except Exception: return False return True @@ -440,9 +441,19 @@ def canReadExcel(): def readExcel(self, worksheet=1, row=1, column=1): maxBlankRows = 10 numRowsToReadPerCall = 20 - from win32com.client import Dispatch # pylint: disable=import-error - excel = Dispatch("Excel.Application") - workbook = excel.Workbooks.Open(os.path.abspath(self._filename)) + from win32com import client # pylint: disable=import-error + for _retry in range(40): + try: + excel = client.gencache.EnsureDispatch("Excel.Application") + workbooks = excel.Workbooks + except Exception: + # Excel may be busy, wait a bit and try again + sleep(0.125) + else: + break + else: + raise DataTableError('Cannot read Excel sheet') + workbook = workbooks.Open(os.path.abspath(self._filename)) try: worksheet = workbook.Worksheets(worksheet) worksheet.Cells(row, column) From 509a7c3304130207d13253be80a70421d732fc00 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Mar 2023 01:15:06 +0100 Subject: [PATCH 09/11] Fix link in comment --- webware/WebUtils/FieldStorage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webware/WebUtils/FieldStorage.py b/webware/WebUtils/FieldStorage.py index 9ab322e..a62899f 100644 --- a/webware/WebUtils/FieldStorage.py +++ b/webware/WebUtils/FieldStorage.py @@ -428,7 +428,7 @@ def read_binary(self): self.done = -1 break if not self._binary_file: - # fix for https://github.com/python/cpython/pull/7804 + # fix for https://github.com/python/cpython/issues/71964 try: data = data.decode() except UnicodeDecodeError: From 49dee4f9ceebece4416c6415ca510aa73aaabec3 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 16 Mar 2023 11:03:24 +0100 Subject: [PATCH 10/11] Use content type in FieldStorage (#14) --- webware/WebUtils/FieldStorage.py | 12 ++++ .../Tests/TestFieldStorageModified.py | 58 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/webware/WebUtils/FieldStorage.py b/webware/WebUtils/FieldStorage.py index a62899f..9f6eb06 100644 --- a/webware/WebUtils/FieldStorage.py +++ b/webware/WebUtils/FieldStorage.py @@ -187,6 +187,8 @@ def __init__(self, fp=None, headers=None, outerboundary=b'', ctype, pdict = 'application/x-www-form-urlencoded', {} self.type = ctype self.type_options = pdict + if not self._binary_file and isBinaryType(ctype, pdict): + self._binary_file = True self.innerboundary = pdict['boundary'].encode( self.encoding, self.errors) if 'boundary' in pdict else b'' @@ -637,3 +639,13 @@ def hasSeparator(): except TypeError: # Python < 3.9.2 return False return True + + +def isBinaryType(ctype, pdict=None): + """"Check whether the given MIME type uses binary data.""" + if pdict and pdict.get('charset') == 'binary': + return True + return not ( + ctype.startswith('text/') or ctype.endswith(('+json', '+xml')) or + (ctype.startswith('application') and + ctype.endswith(('/json', '/xml', '/ecmascript', '/javascript')))) diff --git a/webware/WebUtils/Tests/TestFieldStorageModified.py b/webware/WebUtils/Tests/TestFieldStorageModified.py index 45c356f..d55551f 100644 --- a/webware/WebUtils/Tests/TestFieldStorageModified.py +++ b/webware/WebUtils/Tests/TestFieldStorageModified.py @@ -4,7 +4,7 @@ from io import BytesIO -from WebUtils.FieldStorage import FieldStorage, hasSeparator +from WebUtils.FieldStorage import FieldStorage, hasSeparator, isBinaryType class TestFieldStorage(unittest.TestCase): @@ -206,7 +206,7 @@ def testPostRequestWithUtf8BinaryData(self): self.assertEqual(fs.type, 'application/octet-stream') self.assertEqual(fs.length, length) self.assertEqual(fs.bytes_read, length) - self.assertEqual(fs.file.read(), text) + self.assertEqual(fs.file.read(), content) def testPostRequestWithNonUtf8BinaryData(self): # see https://github.com/WebwareForPython/w4py3/issues/14 @@ -224,3 +224,57 @@ def testPostRequestWithNonUtf8BinaryData(self): self.assertEqual(fs.length, length) self.assertEqual(fs.bytes_read, length) self.assertEqual(fs.file.read(), content) + + def testPostRequestWithUtf8TextData(self): + text = 'The \u2603 by Raymond Briggs' + content = text.encode('utf-8') + length = len(content) + fs = FieldStorage(fp=BytesIO(content), environ={ + 'CONTENT_LENGTH': length, 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/plain'}) + self.assertEqual(fs.headers, { + 'content-type': 'text/plain', + 'content-length': length}) + self.assertEqual(fs.type, 'text/plain') + self.assertEqual(fs.length, length) + self.assertEqual(fs.bytes_read, length) + self.assertEqual(fs.file.read(), text) + + def testPostRequestWithNonUtf8TextData(self): + # see https://github.com/WebwareForPython/w4py3/issues/14 + content = b'\xfe\xff\xc0' + with self.assertRaises(UnicodeDecodeError): + content.decode('utf-8') + length = len(content) + fs = FieldStorage(fp=BytesIO(content), environ={ + 'CONTENT_LENGTH': length, 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/plain'}) + self.assertEqual(fs.headers, { + 'content-type': 'text/plain', + 'content-length': length}) + self.assertEqual(fs.type, 'text/plain') + self.assertEqual(fs.length, length) + self.assertEqual(fs.bytes_read, length) + self.assertEqual(fs.file.read(), content) + + def testIsBinaryType(self): + self.assertIs(isBinaryType('application/json'), False) + self.assertIs(isBinaryType('application/xml'), False) + self.assertIs(isBinaryType('application/calendar+json'), False) + self.assertIs(isBinaryType('application/calendar+xml'), False) + self.assertIs(isBinaryType('model/x3d+xml'), False) + self.assertIs(isBinaryType('text/csv'), False) + self.assertIs(isBinaryType('text/html'), False) + self.assertIs(isBinaryType('text/plain'), False) + self.assertIs(isBinaryType('x3d+xml'), False) + self.assertIs(isBinaryType('application/octet-stream'), True) + self.assertIs(isBinaryType('application/pdf'), True) + self.assertIs(isBinaryType('application/zip'), True) + self.assertIs(isBinaryType('audio/ogg'), True) + self.assertIs(isBinaryType('font/otf'), True) + self.assertIs(isBinaryType('image/png'), True) + self.assertIs(isBinaryType('video/mp4'), True) + self.assertIs(isBinaryType('application/json', + {'charset': 'utf8'}), False) + self.assertIs(isBinaryType('text/csv', + {'charset': 'binary'}), True) From 3b573aab40fb4ff3daedf74147bbbb34e7b212c0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 16 Mar 2023 12:37:22 +0100 Subject: [PATCH 11/11] Bump version number for patch release --- docs/conf.py | 2 +- webware/Properties.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0e47d93..723fb5e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '3.0' # The full version, including alpha/beta/rc tags -release = '3.0.7' +release = '3.0.8' # -- General configuration --------------------------------------------------- diff --git a/webware/Properties.py b/webware/Properties.py index 491aea0..ca1f447 100644 --- a/webware/Properties.py +++ b/webware/Properties.py @@ -1,6 +1,6 @@ name = 'Webware for Python' -version = (3, 0, 7) +version = (3, 0, 8) status = 'stable'