├── .flake8 ├── .gitattributes ├── .github └── workflows │ ├── release-docs.yml │ └── test-package.yml ├── .gitignore ├── .pylintrc ├── LICENSE ├── MANIFEST.in ├── README.md ├── bin ├── editfile.bat └── editfile.py ├── docs ├── Makefile ├── appdev.rst ├── changes.rst ├── conf.py ├── config.rst ├── copyright.rst ├── css │ ├── custom.css │ └── logo.png ├── deploy.rst ├── index.rst ├── install.rst ├── make.bat ├── migrate.rst ├── miscutils.rst ├── overview.rst ├── plugins.rst ├── psp.rst ├── quickstart.rst ├── ref │ ├── core │ │ ├── application.rst │ │ ├── configurableforserversidepath.rst │ │ ├── cookie.rst │ │ ├── exceptionhandler.rst │ │ ├── httpcontent.rst │ │ ├── httpexceptions.rst │ │ ├── httprequest.rst │ │ ├── httpresponse.rst │ │ ├── httpservlet.rst │ │ ├── importmanager.rst │ │ ├── index.rst │ │ ├── jsonrpcservlet.rst │ │ ├── page.rst │ │ ├── picklerpcservlet.rst │ │ ├── plugin.rst │ │ ├── properties.rst │ │ ├── request.rst │ │ ├── response.rst │ │ ├── rpcservlet.rst │ │ ├── servlet.rst │ │ ├── servletfactory.rst │ │ ├── session.rst │ │ ├── sessiondynamicstore.rst │ │ ├── sessionfilestore.rst │ │ ├── sessionmemcachedstore.rst │ │ ├── sessionmemorystore.rst │ │ ├── sessionredisstore.rst │ │ ├── sessionshelvestore.rst │ │ ├── sessionstore.rst │ │ ├── sidebarpage.rst │ │ ├── transaction.rst │ │ ├── unknownfiletypeservlet.rst │ │ ├── urlparser.rst │ │ ├── wsgistreamout.rst │ │ └── xmlrpcservlet.rst │ ├── index.rst │ ├── miscutils │ │ ├── configurable.rst │ │ ├── csvjoiner.rst │ │ ├── csvparser.rst │ │ ├── datatable.rst │ │ ├── dateinterval.rst │ │ ├── dateparser.rst │ │ ├── dbpool.rst │ │ ├── dictforargs.rst │ │ ├── error.rst │ │ ├── funcs.rst │ │ ├── index.rst │ │ ├── mixin.rst │ │ ├── namedvalueaccess.rst │ │ ├── paramfactory.rst │ │ ├── picklecache.rst │ │ └── picklerpc.rst │ ├── psp │ │ ├── braceconverter.rst │ │ ├── context.rst │ │ ├── generators.rst │ │ ├── index.rst │ │ ├── parseeventhandler.rst │ │ ├── pspcompiler.rst │ │ ├── psppage.rst │ │ ├── pspparser.rst │ │ ├── pspservletfactory.rst │ │ ├── psputils.rst │ │ ├── servletwriter.rst │ │ └── streamreader.rst │ ├── taskkit │ │ ├── index.rst │ │ ├── scheduler.rst │ │ ├── task.rst │ │ └── taskhandler.rst │ ├── userkit │ │ ├── hierrole.rst │ │ ├── index.rst │ │ ├── role.rst │ │ ├── roleuser.rst │ │ ├── roleusermanager.rst │ │ ├── roleusermanagermixin.rst │ │ ├── roleusermanagertofile.rst │ │ ├── user.rst │ │ ├── usermanager.rst │ │ └── usermanagertofile.rst │ └── webutils │ │ ├── expansivehtmlforexception.rst │ │ ├── fieldstorage.rst │ │ ├── funcs.rst │ │ ├── htmlforexception.rst │ │ ├── htmltag.rst │ │ ├── httpstatuscodes.rst │ │ └── index.rst ├── requirements.txt ├── style.rst ├── taskkit.rst ├── testing.rst ├── tutorial.rst ├── userkit.rst └── webutils.rst ├── setup.py ├── tox.ini └── webware ├── Admin ├── Access.py ├── AdminPage.py ├── AdminSecurity.py ├── AppControl.py ├── Config.py ├── DumpCSV.py ├── EditFile.py ├── Errors.py ├── LoginPage.py ├── Main.py ├── PlugIns.py ├── ServletCache.py ├── View.py └── __init__.py ├── Application.py ├── Configs └── Application.config ├── ConfigurableForServerSidePath.py ├── Cookie.py ├── Examples ├── AjaxPage.py ├── AjaxSuggest.py ├── Colorize.py ├── Colors.py ├── CountVisits.py ├── DBUtilsDemo.py ├── DominateDemo.py ├── Error.py ├── ExamplePage.py ├── FileUpload.py ├── Forward.py ├── ImageDemo.py ├── IncludeMe.py ├── Introspect.py ├── JSONRPCClient.py ├── JSONRPCExample.py ├── ListBox.py ├── LoginPage.py ├── OtherFileTypes.py ├── PickleRPCExample.py ├── PlugInInspector.py ├── PushServlet.py ├── RequestInformation.py ├── SecureCountVisits.py ├── SecurePage.py ├── ShowTime.py ├── Simple.py ├── View.py ├── Welcome.py ├── XMLRPCExample.py ├── YattagDemo.py ├── __init__.py ├── ajaxcall.js ├── ajaxpoll.js ├── ajaxsuggest.css ├── ajaxsuggest.js ├── favicon.ico ├── index.py └── jsonrpc.js ├── ExceptionHandler.py ├── HTTPContent.py ├── HTTPExceptions.py ├── HTTPRequest.py ├── HTTPResponse.py ├── HTTPServlet.py ├── ImportManager.py ├── JSONRPCServlet.py ├── MiscUtils ├── CSVJoiner.py ├── CSVParser.py ├── Configurable.py ├── DBPool.py ├── DataTable.py ├── DateInterval.py ├── DateParser.py ├── DictForArgs.py ├── Error.py ├── Funcs.py ├── M2PickleRPC.py ├── MixIn.py ├── NamedValueAccess.py ├── ParamFactory.py ├── PickleCache.py ├── PickleRPC.py ├── Properties.py ├── PropertiesObject.py ├── Tests │ ├── BenchCSVParser.py │ ├── BenchDataTable.py │ ├── Sample.csv │ ├── Sample.xls │ ├── TestCSVParser.py │ ├── TestDBPool.py │ ├── TestDataTable.py │ ├── TestDateInterval.py │ ├── TestDateParser.py │ ├── TestDictForArgs.py │ ├── TestError.py │ ├── TestFuncs.py │ ├── TestMixIn.py │ ├── TestNamedValueAccess.py │ ├── TestPickleCache.py │ └── __init__.py └── __init__.py ├── MockApplication.py ├── PSP ├── BraceConverter.py ├── CompilePSP.py ├── Context.py ├── Examples │ ├── Braces.psp │ ├── Hello.psp │ ├── PSPExamplePage.py │ ├── PSPTests-Braces.psp │ ├── PSPTests.psp │ ├── PSPinclude.psp │ ├── View.py │ ├── __init__.py │ ├── index.psp │ ├── my_include.html │ ├── my_include.psp │ └── psplogo.png ├── Generators.py ├── PSPCompiler.py ├── PSPPage.py ├── PSPParser.py ├── PSPServletFactory.py ├── PSPUtils.py ├── ParseEventHandler.py ├── Properties.py ├── ServletWriter.py ├── StreamReader.py ├── Tests │ ├── TestBraceConverter.py │ ├── TestCompiler.py │ ├── TestContext.py │ ├── TestUtils.py │ └── __init__.py └── __init__.py ├── Page.py ├── PickleRPCServlet.py ├── PlugIn.py ├── PlugInLoader.py ├── Properties.py ├── RPCServlet.py ├── Request.py ├── Response.py ├── Scripts ├── MakeAppWorkDir.py ├── WSGIScript.py ├── WaitressServer.py ├── WebwareCLI.py └── __init__.py ├── Servlet.py ├── ServletFactory.py ├── Session.py ├── SessionDynamicStore.py ├── SessionFileStore.py ├── SessionMemcachedStore.py ├── SessionMemoryStore.py ├── SessionRedisStore.py ├── SessionShelveStore.py ├── SessionStore.py ├── SidebarPage.py ├── TaskKit ├── Properties.py ├── Scheduler.py ├── Task.py ├── TaskHandler.py ├── Tests │ ├── SchedulerTest.py │ ├── TestScheduler.py │ └── __init__.py └── __init__.py ├── Tasks ├── SessionTask.py └── __init__.py ├── Testing ├── DebugPage.py ├── Dir │ ├── File.html │ ├── Forward2Target.py │ ├── Forward3.py │ ├── Forward3Target.py │ ├── IncludeURLTest2.py │ ├── __init__.py │ └── index.html ├── EmbeddedServlet.py ├── FieldStorage.py ├── Forward1.py ├── Forward1Target.py ├── Forward2.py ├── Forward3Target.py ├── IncludeURLTest.py ├── Main.py ├── Servlet.py ├── ServletImport.py ├── SetCookie.py ├── TestCases.data ├── TestIMS.py ├── URL │ ├── __init__.py │ ├── index.html │ ├── simple.html │ ├── test1 │ │ ├── Main.py │ │ └── __init__.py │ ├── test2 │ │ ├── Main.py │ │ └── __init__.py │ ├── test3 │ │ ├── Main.py │ │ └── __init__.py │ ├── test4 │ │ ├── Main.py │ │ └── __init__.py │ ├── test5 │ │ ├── __init__.py │ │ └── url3.py │ ├── test5join1 │ │ ├── __init__.py │ │ └── url1.py │ ├── test5join2 │ │ ├── __init__.py │ │ ├── url1.py │ │ └── url2.py │ ├── util.py │ └── vhosts │ │ ├── Main.py │ │ └── __init__.py ├── __init__.py ├── test.html └── test.text ├── Tests ├── TestEndToEnd │ ├── AppTest.py │ ├── TestAdmin.py │ ├── TestContextMap.py │ ├── TestExamples.py │ ├── TestMakeApp.py │ ├── TestPSPExamples.py │ ├── TestServer.py │ ├── TestTesting.py │ └── __init__.py ├── TestMocking.py ├── TestSessions │ ├── Application.py │ ├── Session.py │ ├── TestSession.py │ ├── TestSessionDynamicStore.py │ ├── TestSessionFileStore.py │ ├── TestSessionMemcachedStore.py │ ├── TestSessionMemoryStore.py │ ├── TestSessionRedisStore.py │ ├── TestSessionShelveStore.py │ ├── TestSessionStore.py │ ├── Transaction.py │ ├── __init__.py │ ├── memcache.py │ └── redis.py └── __init__.py ├── Transaction.py ├── URLParser.py ├── UnknownFileTypeServlet.py ├── UserKit ├── HierRole.py ├── Properties.py ├── Role.py ├── RoleUser.py ├── RoleUserManager.py ├── RoleUserManagerMixIn.py ├── RoleUserManagerToFile.py ├── Tests │ ├── TestExample.py │ ├── TestRole.py │ ├── TestUserManager.py │ └── __init__.py ├── User.py ├── UserManager.py ├── UserManagerToFile.py └── __init__.py ├── WSGIStreamOut.py ├── WebUtils ├── CGITraceback.py ├── ExpansiveHTMLForException.py ├── FieldStorage.py ├── Funcs.py ├── HTMLForException.py ├── HTMLTag.py ├── HTTPStatusCodes.py ├── Properties.py ├── Tests │ ├── TestFieldStorageModified.py │ ├── TestFieldStorageStandard.py │ ├── TestFuncs.py │ ├── TestHTMLStatusCodes.py │ ├── TestHTMLTag.py │ └── __init__.py └── __init__.py ├── XMLRPCServlet.py ├── __init__.py └── error404.html /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | .git,.tox,.venv,__pycache__, 4 | build,dist,docs, 5 | Cache,Configs,ErrorMsgs,Logs,Sessions, 6 | WSGIScript.py 7 | max-line-length = 79 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.bat text eol=crlf 4 | *.config text eol=lf 5 | *.css text eol=lf 6 | *.html text eol=lf 7 | *.js text eol=lf 8 | *.prefs text 9 | *.py text eol=lf 10 | *.rst text eol=lf 11 | *.sh text eol=lf 12 | *.txt text eol=lf 13 | *.po text eol=lf 14 | *.pot text eol=lf 15 | *.styl text eol=lf 16 | *.xml text 17 | 18 | *.gif binary 19 | *.ico binary 20 | *.jpg binary 21 | *.lnk binary 22 | *.mo binary 23 | *.png binary 24 | *.exe binary 25 | *.so binary 26 | *.ppt binary 27 | *.pdf binary 28 | *.gz binary 29 | *.zip binary 30 | -------------------------------------------------------------------------------- /.github/workflows/release-docs.yml: -------------------------------------------------------------------------------- 1 | name: Release Webware for Python 3 documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python 3.11 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: "3.11" 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install .[docs] 23 | - name: Create docs with Sphinx 24 | run: | 25 | cd docs 26 | make html 27 | touch _build/html/.nojekyll 28 | - name: Deploy docs to GitHub pages 29 | uses: peaceiris/actions-gh-pages@v3 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | publish_branch: gh-pages 33 | publish_dir: docs/_build/html 34 | enable_jekyll: false 35 | force_orphan: true 36 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Test Webware for Python 3 package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 5 10 | strategy: 11 | max-parallel: 6 12 | matrix: 13 | python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.9'] 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v3 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install .[tests] --use-pep517 25 | - name: Lint with flake8 26 | if: matrix.python-version == '3.11' 27 | run: | 28 | flake8 webware setup.py --count --exit-zero --statistics 29 | - name: Lint with pylint 30 | if: matrix.python-version == '3.11' 31 | run: | 32 | pylint webware 33 | - name: Run all unit tests 34 | run: | 35 | cd webware 36 | python -m unittest discover -fv -p Test*.py 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.address 3 | *.bak 4 | *.cache 5 | *.default 6 | *.db 7 | *.egg-info 8 | *.log 9 | *.patch 10 | *.pid 11 | *.pstats 12 | *.pyc 13 | *.pyo 14 | *.ses 15 | *.swp 16 | 17 | __pycache__ 18 | 19 | build/ 20 | dist/ 21 | _build/ 22 | 23 | .DS_Store 24 | 25 | Cache/ 26 | Logs/ 27 | ErrorMsgs/ 28 | Sessions/ 29 | 30 | Webware-for-Python-* 31 | 32 | .idea/ 33 | .tox/ 34 | .venv/ 35 | .venv.*/ 36 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | ignore = 4 | .git, .tox, .venv, .idea, 5 | build, dist, 6 | Cache, ErrorMsgs, Logs, Sessions, 7 | CGITraceback.py, FieldStorage.py, WSGIScript.py 8 | 9 | init-hook = sys.path.insert(0, 'webware') 10 | 11 | [MESSAGES CONTROL] 12 | 13 | disable = 14 | attribute-defined-outside-init, 15 | broad-except, 16 | consider-using-dict-items, 17 | consider-using-f-string, 18 | cyclic-import, 19 | similarities, 20 | eval-used, 21 | exec-used, 22 | fixme, 23 | global-statement, 24 | import-outside-toplevel, 25 | inconsistent-return-statements, 26 | missing-docstring, 27 | protected-access, 28 | redefined-argument-from-local, 29 | redefined-outer-name 30 | 31 | [REFACTORING] 32 | 33 | max-nested-blocks = 7 34 | 35 | [BASIC] 36 | 37 | attr-naming-style = camelCase 38 | argument-naming-style = camelCase 39 | class-attribute-naming-style = camelCase 40 | class-naming-style = PascalCase 41 | const-naming-style = any 42 | function-naming-style = camelCase 43 | inlinevar-naming-style = camelCase 44 | method-naming-style = camelCase 45 | module-naming-style = PascalCase 46 | variable-naming-style = camelCase 47 | 48 | good-names = 49 | b, c, d, e, f, g, h, i, j, k, m, n, p, q, r, s, t, ,v, w, x, y, 50 | db, dt, fd, fp, fs, ip, ok, tm, ts, wr, 51 | dir_, id_, input_, type_, 52 | allow_none, entry_point, has_key, start_response, ssl_context, 53 | memcache, redis, 54 | index, webware, MixIn, 55 | simple, test1, test2, test3, test4, test5, test5join1, test5join2, 56 | url1, url2, url3, util, vhosts 57 | 58 | bad-names = l, o 59 | 60 | [FORMAT] 61 | 62 | indent-after-paren = 4 63 | 64 | [DESIGN] 65 | 66 | max-attributes = 40 67 | max-args = 10 68 | max-branches = 40 69 | max-line-length = 79 70 | max-locals = 30 71 | max-module-lines = 1500 72 | max-parents = 10 73 | max-public-methods = 100 74 | max-returns = 10 75 | max-statements = 150 76 | min-public-methods = 0 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 1999 Chuck Esterbrook (Webware for Python) 4 | Copyright © 2019 Christoph Zwerschke (Webware for Python 3) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | 3 | include LICENSE 4 | include README.md 5 | 6 | include .flake8 7 | include .pylintrc 8 | 9 | include tox.ini 10 | 11 | graft bin 12 | 13 | graft webware 14 | recursive-include webware/PSP *.config *.psp *.html *.png 15 | 16 | prune webware/Cache 17 | prune webware/ErrorMsgs 18 | prune webware/Logs 19 | prune webware/Sessions 20 | 21 | graft docs 22 | recursive-include docs *.rst conf.py Makefile make.bat *.png 23 | prune docs/_build 24 | 25 | global-exclude *.py[co] *.db *.ses __pycache__ 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webware for Python 3 2 | 3 | Webware for Python is a well-proven and platform-independent suite of Python packages and tools for developing object-oriented, web-based applications. The suite uses well known design patterns and includes Servlets, Python Server Pages (PSP), Task Scheduling, Session Management, and many other features. Webware is very modular and can be easily extended. 4 | 5 | Webware for Python 3 is the latest version of the suite, which has been adapted to use Python 3 instead of Python 2 and the WSGI standard instead of the custom Webware threaded application server and various adapters, but otherwise kept compatible with the legacy version. 6 | 7 | The current project **homepage** can be found here: 8 | 9 | The current **documentation** is published at: 10 | 11 | You can still find the **legacy version** of Webware for Python at: 12 | -------------------------------------------------------------------------------- /bin/editfile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Helper script for running editfile.py on Windows 3 | pushd %~dp0 4 | python editfile.py "%1" 5 | popd 6 | rem pause 7 | -------------------------------------------------------------------------------- /bin/editfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Helper script for the feature provided by the IncludeEditLink setting.""" 4 | 5 | import os 6 | import sys 7 | from email import message_from_file 8 | from subprocess import run 9 | 10 | editor = 'Vim' # your favorite editor 11 | 12 | defaultArgs = [editor, ' +{line}', '{filename}'] 13 | 14 | # add an entry for your favorite editor here if it does not already exist 15 | editorArgs = { 16 | 'Emacs': 17 | ['gnuclient', '+{line}', '{filename}'], 18 | 'Geany': 19 | ['geany', '-l', '{line}', '{filename}'], 20 | 'Geany (Windows)': 21 | ['C:/Program Files (x86)/Geany/bin/geany.exe', 22 | '-l', '{line}', '{filename}'], 23 | 'gedit': 24 | ['gedit', '+{line}', '{filename}'], 25 | 'jEdit': 26 | ['jedit', '{filename}', '+line:{line}'], 27 | 'jEdit (Windows)': 28 | ['C:/Program Files/jEdit/jedit.exe', '{filename}', '+line:{line}'], 29 | 'Kate': 30 | ['kate', '-u', '-l', '{line}', '{filename}'], 31 | 'Komodo': 32 | ['komodo', '-l', '{line}', '{filename}'], 33 | 'KWrite': 34 | ['kwrite', '--line', '{line}', '{filename}'], 35 | 'Notepad++ (Windows)': 36 | ['C:/Program Files/Notepad++/notepad++.exe', '-n{line}', '{filename}'], 37 | 'PSPad (Windows)': 38 | ['C:/Program Files (x86)/PSPad editor/PSPad.exe', 39 | '-{line}', '{filename}'], 40 | 'SciTE': 41 | ['scite', '{filename}', '-goto:{line}'], 42 | 'SciTE (Windows)': 43 | ['C:/wscite/SciTE.exe', '{filename}', '-goto:{line}'], 44 | 'Sublime Text (Windows)': 45 | ['C:/Program Files/Sublime Text 3/subl.exe', '{filename}:{line}'], 46 | 'Vim': 47 | ['gvim', '+{line}', '{filename}'], 48 | } 49 | 50 | 51 | def transform(params): 52 | """Transform EditFile parameters. 53 | 54 | As an example, if you are under Windows and your edit file 55 | has a Unix filename, then it is transformed to a UNC path. 56 | """ 57 | filename = params['filename'] 58 | if os.sep == '\\' and filename.startswith('/'): 59 | filename = os.path.normpath(filename[1:]) 60 | hostname = params['hostname'].split(':', 1)[0] 61 | smbPath = fr'\\{hostname}\root' 62 | filename = os.path.join(smbPath, filename) 63 | params['filename'] = filename 64 | return params 65 | 66 | 67 | def openFile(params): 68 | """Open editor with file specified in parameters.""" 69 | params = {key.lower(): value for key, value in params.items()} 70 | params = transform(params) 71 | args = editorArgs.get(editor, defaultArgs) 72 | args[1:] = [arg.format(**params) for arg in args[1:]] 73 | print(' '.join(args)) 74 | run(args) 75 | 76 | 77 | def parseFile(filename): 78 | """Parse the Webware EditFile.""" 79 | openFile(message_from_file(open(filename))) 80 | 81 | 82 | if __name__ == '__main__': 83 | parseFile(sys.argv[1]) 84 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../webware')) 16 | 17 | # import some requirements as dummy modules 18 | # in order to avoid import errors when auto generating reference docs 19 | from Tests.TestSessions import redis, memcache 20 | 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = 'Webware for Python 3' 25 | copyright = '1999-2023, Christoph Zwerschke et al' 26 | author = 'Christoph Zwerschke et al.' 27 | 28 | # The short X.Y version 29 | version = '3.0' 30 | # The full version, including alpha/beta/rc tags 31 | release = '3.0.10' 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be 37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 38 | # ones. 39 | extensions = [ 40 | 'sphinx.ext.autodoc', 41 | 'sphinx.ext.autosummary', 42 | ] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # List of patterns, relative to source directory, that match files and 48 | # directories to ignore when looking for source files. 49 | # This pattern also affects html_static_path and html_extra_path. 50 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 51 | 52 | # AutoDoc configuration 53 | autoclass_content = "class" 54 | autodoc_default_options = { 55 | 'members': True, 56 | 'inherited-members': True, 57 | 'special-members': '__init__', 58 | 'undoc-members': True, 59 | 'show-inheritance': True 60 | } 61 | autosummary_generate = True 62 | 63 | # ignore certain warnings 64 | # (references to some Python built-in types do not resolve correctly) 65 | nitpicky = True 66 | nitpick_ignore = [('py:class', t) for t in ( 67 | 'html.parser.HTMLParser', 'threading.Thread', 'xmlrpc.client.ProtocolError')] 68 | 69 | # -- Options for HTML output ------------------------------------------------- 70 | 71 | # The theme to use for HTML and HTML Help pages. See the documentation for 72 | # a list of builtin themes. 73 | # 74 | html_theme = 'alabaster' 75 | 76 | # Add any paths that contain custom static files (such as style sheets) here, 77 | # relative to this directory. They are copied after the builtin static files, 78 | # so a file named "default.css" will overwrite the builtin "default.css". 79 | html_static_path = ['css'] 80 | -------------------------------------------------------------------------------- /docs/copyright.rst: -------------------------------------------------------------------------------- 1 | .. _copyright: 2 | 3 | Copyright and License 4 | ===================== 5 | 6 | The Gist 7 | -------- 8 | 9 | Webware for Python is open source, but there is no requirement that products developed with or derivative to Webware become open source. 10 | 11 | Webware for Python is copyrighted, but you can freely use and copy it as long as you don't change or remove this copyright notice. The license is a clone of the MIT license. 12 | 13 | There is no warranty of any kind. Use at your own risk. 14 | 15 | Read this entire document for complete, legal details. 16 | 17 | Copyright 18 | --------- 19 | 20 | Copyright © 1999 Chuck Esterbrook (Webware for Python) 21 | 22 | Copyright © 2019 Christoph Zwerschke (Webware for Python 3) 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | -------------------------------------------------------------------------------- /docs/css/custom.css: -------------------------------------------------------------------------------- 1 | .sphinxsidebar { 2 | background: no-repeat center top url(logo.png); 3 | padding-top: 128px; 4 | } 5 | -------------------------------------------------------------------------------- /docs/css/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebwareForPython/w4py3/7f75921926f58182cd3f28d5f974d13807df4ef6/docs/css/logo.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Webware for Python 3 2 | ==================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | overview 9 | install 10 | changes 11 | migrate 12 | copyright 13 | quickstart 14 | tutorial 15 | appdev 16 | config 17 | deploy 18 | plugins 19 | style 20 | psp 21 | userkit 22 | taskkit 23 | webutils 24 | miscutils 25 | testing 26 | ref/index 27 | 28 | 29 | Indices and tables 30 | ================== 31 | 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/miscutils.rst: -------------------------------------------------------------------------------- 1 | .. module:: MiscUtils 2 | 3 | .. _miscutils: 4 | 5 | MiscUtils 6 | ========= 7 | 8 | The MiscUtils package provides support classes and functions to Webware that aren't necessarily web-related and that don't fit into one of the other frameworks. There is plenty of useful reusable code here. 9 | 10 | See the :ref:`reference documentation ` for an overview of the available functions. 11 | -------------------------------------------------------------------------------- /docs/plugins.rst: -------------------------------------------------------------------------------- 1 | .. _plug-ins: 2 | 3 | Plug-ins 4 | ======== 5 | 6 | Webware for Python supports "plug-ins" to extend the framework and provide additional capabilities. 7 | 8 | In Webware for Python 3, plug-ins are implemented as packages with metadata ("entry points") through which they can be automatically discovered, even if they have been installed independetly of Webware. You only need to specify which plug-ins shall be loaded in the ``PlugIns`` configuration setting, and Webware will automatically load them if they are installed. 9 | 10 | Every Webware plug-in is a Python package, i.e. a directory that contains a ``__init__.py`` file and optionally other files. As a Webware plugin, it must also contain a special ``Properties.py`` file. You can disable a specific plug-in by placing a ``dontload`` file in its package directory. 11 | 12 | If you want to distribute a Webware plug-in, you should advertize it as an entry point using the ``webware.plugins`` identifier in the ``setup.py`` file used to install the plug-in. 13 | 14 | The ``__init.py__`` file of the plug-in must contain at least a function like this:: 15 | 16 | def installInWebware(application): 17 | pass 18 | 19 | The function doesn't need to do anything, but this gives it the opportunity to do something with the global Webware ``Application`` object. For instance, the PSP plugin uses ``addServletFactory.addServletFactory`` to add a handler for ``.psp`` files. 20 | 21 | The ``Properties.py`` file should contain a number of assignments:: 22 | 23 | name = "Plugin name" 24 | version = (1, 0, 0) 25 | status = 'beta' 26 | requiredPyVersion = (3, 6) 27 | requiredOpSys = 'posix' 28 | synopsis = """A paragraph-long description of the plugin""" 29 | webwareConfig = { 30 | 'examplePages': [ 31 | 'Example1', 32 | 'ComplexExample', 33 | ] 34 | } 35 | def willRunFunc(): 36 | if softwareNotInstalled: 37 | return "some message to that effect" 38 | else: 39 | return None 40 | 41 | If you want to provide some examples for using your plug-in, they should be put in an ``Examples/`` subdirectory. 42 | 43 | A plugin who's ``requiredPyVersion`` or ``requiredOpSys`` aren't satisfied will simply be ignored. ``requiredOpSys`` should be something returned by ``os.name``, like ``posix`` or ``nt``. Or you can define a function ``willRunFunc`` to test. If there aren't requirements you can leave these variables and functions out. 44 | 45 | If you plan to write your own Webware plug-in, also have a look at our :ref:`style-guidelines` and the source code of the built-in plug-ins (PSP, TaskKit, UserKit, WebUtils, MiscUtils) which can serve as examples. We also recommend to add some tests to your plug-in, see the section on :ref:`testing`. 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/ref/core/application.rst: -------------------------------------------------------------------------------- 1 | Application 2 | ----------- 3 | 4 | .. automodule:: Application 5 | -------------------------------------------------------------------------------- /docs/ref/core/configurableforserversidepath.rst: -------------------------------------------------------------------------------- 1 | ConfigurableForServerSidePath 2 | ----------------------------- 3 | 4 | .. automodule:: ConfigurableForServerSidePath 5 | -------------------------------------------------------------------------------- /docs/ref/core/cookie.rst: -------------------------------------------------------------------------------- 1 | Cookie 2 | ------ 3 | 4 | .. automodule:: Cookie 5 | -------------------------------------------------------------------------------- /docs/ref/core/exceptionhandler.rst: -------------------------------------------------------------------------------- 1 | ExceptionHandler 2 | ---------------- 3 | 4 | .. automodule:: ExceptionHandler 5 | -------------------------------------------------------------------------------- /docs/ref/core/httpcontent.rst: -------------------------------------------------------------------------------- 1 | HTTPContent 2 | ----------- 3 | 4 | .. automodule:: HTTPContent 5 | -------------------------------------------------------------------------------- /docs/ref/core/httpexceptions.rst: -------------------------------------------------------------------------------- 1 | HTTPExceptions 2 | -------------- 3 | 4 | .. automodule:: HTTPExceptions 5 | -------------------------------------------------------------------------------- /docs/ref/core/httprequest.rst: -------------------------------------------------------------------------------- 1 | HTTPRequest 2 | ----------- 3 | 4 | .. automodule:: HTTPRequest 5 | -------------------------------------------------------------------------------- /docs/ref/core/httpresponse.rst: -------------------------------------------------------------------------------- 1 | HTTPResponse 2 | ------------ 3 | 4 | .. automodule:: HTTPResponse 5 | -------------------------------------------------------------------------------- /docs/ref/core/httpservlet.rst: -------------------------------------------------------------------------------- 1 | HTTPServlet 2 | ----------- 3 | 4 | .. automodule:: HTTPServlet 5 | -------------------------------------------------------------------------------- /docs/ref/core/importmanager.rst: -------------------------------------------------------------------------------- 1 | ImportManager 2 | ------------- 3 | 4 | .. automodule:: ImportManager 5 | -------------------------------------------------------------------------------- /docs/ref/core/index.rst: -------------------------------------------------------------------------------- 1 | .. _ref-core: 2 | 3 | Core Classes 4 | ============ 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: The core classes of Webware for Python 3 are: 9 | 10 | application 11 | configurableforserversidepath 12 | cookie 13 | exceptionhandler 14 | httpcontent 15 | httpexceptions 16 | httprequest 17 | httpresponse 18 | httpservlet 19 | importmanager 20 | jsonrpcservlet 21 | page 22 | picklerpcservlet 23 | plugin 24 | properties 25 | request 26 | response 27 | rpcservlet 28 | servlet 29 | servletfactory 30 | session 31 | sessiondynamicstore 32 | sessionfilestore 33 | sessionmemcachedstore 34 | sessionmemorystore 35 | sessionredisstore 36 | sessionshelvestore 37 | sessionstore 38 | sidebarpage 39 | transaction 40 | unknownfiletypeservlet 41 | urlparser 42 | wsgistreamout 43 | xmlrpcservlet 44 | -------------------------------------------------------------------------------- /docs/ref/core/jsonrpcservlet.rst: -------------------------------------------------------------------------------- 1 | JSONRPCServlet 2 | -------------- 3 | 4 | .. automodule:: JSONRPCServlet 5 | -------------------------------------------------------------------------------- /docs/ref/core/page.rst: -------------------------------------------------------------------------------- 1 | Page 2 | ---- 3 | 4 | .. automodule:: Page 5 | -------------------------------------------------------------------------------- /docs/ref/core/picklerpcservlet.rst: -------------------------------------------------------------------------------- 1 | PickleRPCServlet 2 | ---------------- 3 | 4 | .. automodule:: PickleRPCServlet 5 | -------------------------------------------------------------------------------- /docs/ref/core/plugin.rst: -------------------------------------------------------------------------------- 1 | PlugIn 2 | ------ 3 | 4 | .. automodule:: PlugIn 5 | -------------------------------------------------------------------------------- /docs/ref/core/properties.rst: -------------------------------------------------------------------------------- 1 | Properties 2 | ---------- 3 | 4 | .. automodule:: Properties 5 | -------------------------------------------------------------------------------- /docs/ref/core/request.rst: -------------------------------------------------------------------------------- 1 | Request 2 | ------- 3 | 4 | .. automodule:: Request 5 | -------------------------------------------------------------------------------- /docs/ref/core/response.rst: -------------------------------------------------------------------------------- 1 | Response 2 | -------- 3 | 4 | .. automodule:: Response 5 | -------------------------------------------------------------------------------- /docs/ref/core/rpcservlet.rst: -------------------------------------------------------------------------------- 1 | RPCServlet 2 | ---------- 3 | 4 | .. automodule:: RPCServlet 5 | -------------------------------------------------------------------------------- /docs/ref/core/servlet.rst: -------------------------------------------------------------------------------- 1 | Servlet 2 | ------- 3 | 4 | .. automodule:: Servlet 5 | -------------------------------------------------------------------------------- /docs/ref/core/servletfactory.rst: -------------------------------------------------------------------------------- 1 | ServletFactory 2 | -------------- 3 | 4 | .. automodule:: ServletFactory 5 | -------------------------------------------------------------------------------- /docs/ref/core/session.rst: -------------------------------------------------------------------------------- 1 | Session 2 | ------- 3 | 4 | .. automodule:: Session 5 | -------------------------------------------------------------------------------- /docs/ref/core/sessiondynamicstore.rst: -------------------------------------------------------------------------------- 1 | SessionDynamicStore 2 | ------------------- 3 | 4 | .. automodule:: SessionDynamicStore 5 | -------------------------------------------------------------------------------- /docs/ref/core/sessionfilestore.rst: -------------------------------------------------------------------------------- 1 | SessionFileStore 2 | ---------------- 3 | 4 | .. automodule:: SessionFileStore 5 | -------------------------------------------------------------------------------- /docs/ref/core/sessionmemcachedstore.rst: -------------------------------------------------------------------------------- 1 | SessionMemcachedStore 2 | --------------------- 3 | 4 | .. automodule:: SessionMemcachedStore 5 | -------------------------------------------------------------------------------- /docs/ref/core/sessionmemorystore.rst: -------------------------------------------------------------------------------- 1 | SessionMemoryStore 2 | ------------------ 3 | 4 | .. automodule:: SessionMemoryStore 5 | -------------------------------------------------------------------------------- /docs/ref/core/sessionredisstore.rst: -------------------------------------------------------------------------------- 1 | SessionRedisStore 2 | ----------------- 3 | 4 | .. automodule:: SessionRedisStore 5 | -------------------------------------------------------------------------------- /docs/ref/core/sessionshelvestore.rst: -------------------------------------------------------------------------------- 1 | SessionShelveStore 2 | ------------------ 3 | 4 | .. automodule:: SessionShelveStore 5 | -------------------------------------------------------------------------------- /docs/ref/core/sessionstore.rst: -------------------------------------------------------------------------------- 1 | SessionStore 2 | ------------ 3 | 4 | .. automodule:: SessionStore 5 | -------------------------------------------------------------------------------- /docs/ref/core/sidebarpage.rst: -------------------------------------------------------------------------------- 1 | SidebarPage 2 | ----------- 3 | 4 | .. automodule:: SidebarPage 5 | -------------------------------------------------------------------------------- /docs/ref/core/transaction.rst: -------------------------------------------------------------------------------- 1 | Transaction 2 | ----------- 3 | 4 | .. automodule:: Transaction 5 | -------------------------------------------------------------------------------- /docs/ref/core/unknownfiletypeservlet.rst: -------------------------------------------------------------------------------- 1 | UnknownFileTypeServlet 2 | ---------------------- 3 | 4 | .. automodule:: UnknownFileTypeServlet 5 | -------------------------------------------------------------------------------- /docs/ref/core/urlparser.rst: -------------------------------------------------------------------------------- 1 | URLParser 2 | --------- 3 | 4 | .. automodule:: URLParser 5 | -------------------------------------------------------------------------------- /docs/ref/core/wsgistreamout.rst: -------------------------------------------------------------------------------- 1 | WSGIStreamOut 2 | ------------- 3 | 4 | .. automodule:: WSGIStreamOut 5 | -------------------------------------------------------------------------------- /docs/ref/core/xmlrpcservlet.rst: -------------------------------------------------------------------------------- 1 | XMLRPCServlet 2 | ------------- 3 | 4 | .. automodule:: XMLRPCServlet 5 | -------------------------------------------------------------------------------- /docs/ref/index.rst: -------------------------------------------------------------------------------- 1 | .. _api-reference: 2 | 3 | API Reference 4 | ============= 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Webware for Python 3 API reference: 9 | 10 | core/index 11 | psp/index 12 | userkit/index 13 | taskkit/index 14 | webutils/index 15 | miscutils/index 16 | -------------------------------------------------------------------------------- /docs/ref/miscutils/configurable.rst: -------------------------------------------------------------------------------- 1 | Configurable 2 | ------------ 3 | 4 | .. automodule:: MiscUtils.Configurable 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/csvjoiner.rst: -------------------------------------------------------------------------------- 1 | CSVJoiner 2 | --------- 3 | 4 | .. automodule:: MiscUtils.CSVJoiner 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/csvparser.rst: -------------------------------------------------------------------------------- 1 | CSVParser 2 | --------- 3 | 4 | .. automodule:: MiscUtils.CSVParser 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/datatable.rst: -------------------------------------------------------------------------------- 1 | DataTable 2 | --------- 3 | 4 | .. automodule:: MiscUtils.DataTable 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/dateinterval.rst: -------------------------------------------------------------------------------- 1 | DateInterval 2 | ------------ 3 | 4 | .. automodule:: MiscUtils.DateInterval 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/dateparser.rst: -------------------------------------------------------------------------------- 1 | DateParser 2 | ---------- 3 | 4 | .. automodule:: MiscUtils.DateParser 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/dbpool.rst: -------------------------------------------------------------------------------- 1 | DBPool 2 | ------ 3 | 4 | .. automodule:: MiscUtils.DBPool 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/dictforargs.rst: -------------------------------------------------------------------------------- 1 | DictForArgs 2 | ----------- 3 | 4 | .. automodule:: MiscUtils.DictForArgs 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/error.rst: -------------------------------------------------------------------------------- 1 | Error 2 | ----- 3 | 4 | .. automodule:: MiscUtils.Error 5 | :no-inherited-members: 6 | -------------------------------------------------------------------------------- /docs/ref/miscutils/funcs.rst: -------------------------------------------------------------------------------- 1 | Funcs 2 | ----- 3 | 4 | .. automodule:: MiscUtils.Funcs 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/index.rst: -------------------------------------------------------------------------------- 1 | .. _ref-miscutils: 2 | 3 | MiscUtils 4 | ========= 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: The MiscUtils package contains: 9 | 10 | configurable 11 | csvjoiner 12 | csvparser 13 | datatable 14 | dateinterval 15 | dateparser 16 | dbpool 17 | dictforargs 18 | error 19 | funcs 20 | mixin 21 | namedvalueaccess 22 | paramfactory 23 | picklecache 24 | picklerpc 25 | -------------------------------------------------------------------------------- /docs/ref/miscutils/mixin.rst: -------------------------------------------------------------------------------- 1 | MixIn 2 | ----- 3 | 4 | .. automodule:: MiscUtils.MixIn 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/namedvalueaccess.rst: -------------------------------------------------------------------------------- 1 | NamedValueAccess 2 | ---------------- 3 | 4 | .. automodule:: MiscUtils.NamedValueAccess 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/paramfactory.rst: -------------------------------------------------------------------------------- 1 | ParamFactory 2 | ------------ 3 | 4 | .. automodule:: MiscUtils.ParamFactory 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/picklecache.rst: -------------------------------------------------------------------------------- 1 | PickleCache 2 | ----------- 3 | 4 | .. automodule:: MiscUtils.PickleCache 5 | -------------------------------------------------------------------------------- /docs/ref/miscutils/picklerpc.rst: -------------------------------------------------------------------------------- 1 | PickleRPC 2 | --------- 3 | 4 | .. automodule:: MiscUtils.PickleRPC 5 | -------------------------------------------------------------------------------- /docs/ref/psp/braceconverter.rst: -------------------------------------------------------------------------------- 1 | BraceConverter 2 | -------------- 3 | 4 | .. automodule:: PSP.BraceConverter 5 | -------------------------------------------------------------------------------- /docs/ref/psp/context.rst: -------------------------------------------------------------------------------- 1 | Context 2 | ------- 3 | 4 | .. automodule:: PSP.Context 5 | -------------------------------------------------------------------------------- /docs/ref/psp/generators.rst: -------------------------------------------------------------------------------- 1 | Generators 2 | ---------- 3 | 4 | .. automodule:: PSP.Generators 5 | -------------------------------------------------------------------------------- /docs/ref/psp/index.rst: -------------------------------------------------------------------------------- 1 | .. _ref-psp: 2 | 3 | PSP 4 | === 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: The PSP plug-in contains the following internal classes: 9 | 10 | braceconverter 11 | context 12 | generators 13 | parseeventhandler 14 | pspcompiler 15 | psppage 16 | pspparser 17 | pspservletfactory 18 | psputils 19 | servletwriter 20 | streamreader 21 | -------------------------------------------------------------------------------- /docs/ref/psp/parseeventhandler.rst: -------------------------------------------------------------------------------- 1 | ParseEventHandler 2 | ----------------- 3 | 4 | .. automodule:: PSP.ParseEventHandler 5 | -------------------------------------------------------------------------------- /docs/ref/psp/pspcompiler.rst: -------------------------------------------------------------------------------- 1 | PSPCompiler 2 | ----------- 3 | 4 | .. automodule:: PSP.PSPCompiler 5 | -------------------------------------------------------------------------------- /docs/ref/psp/psppage.rst: -------------------------------------------------------------------------------- 1 | PSPPage 2 | ------- 3 | 4 | .. automodule:: PSP.PSPPage 5 | -------------------------------------------------------------------------------- /docs/ref/psp/pspparser.rst: -------------------------------------------------------------------------------- 1 | PSPParser 2 | --------- 3 | 4 | .. automodule:: PSP.PSPParser 5 | -------------------------------------------------------------------------------- /docs/ref/psp/pspservletfactory.rst: -------------------------------------------------------------------------------- 1 | PSPServletFactory 2 | ----------------- 3 | 4 | .. automodule:: PSP.PSPServletFactory 5 | -------------------------------------------------------------------------------- /docs/ref/psp/psputils.rst: -------------------------------------------------------------------------------- 1 | PSPUtils 2 | -------- 3 | 4 | .. automodule:: PSP.PSPUtils 5 | -------------------------------------------------------------------------------- /docs/ref/psp/servletwriter.rst: -------------------------------------------------------------------------------- 1 | ServletWriter 2 | ------------- 3 | 4 | .. automodule:: PSP.ServletWriter 5 | -------------------------------------------------------------------------------- /docs/ref/psp/streamreader.rst: -------------------------------------------------------------------------------- 1 | StreamReader 2 | ------------ 3 | 4 | .. automodule:: PSP.StreamReader 5 | -------------------------------------------------------------------------------- /docs/ref/taskkit/index.rst: -------------------------------------------------------------------------------- 1 | .. _ref-taskkit: 2 | 3 | TaskKit 4 | ======= 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: The TaskKit plug-in contains the following classes: 9 | 10 | scheduler 11 | task 12 | taskhandler 13 | -------------------------------------------------------------------------------- /docs/ref/taskkit/scheduler.rst: -------------------------------------------------------------------------------- 1 | Scheduler 2 | --------- 3 | 4 | .. automodule:: TaskKit.Scheduler 5 | -------------------------------------------------------------------------------- /docs/ref/taskkit/task.rst: -------------------------------------------------------------------------------- 1 | Task 2 | ---- 3 | 4 | .. automodule:: TaskKit.Task 5 | -------------------------------------------------------------------------------- /docs/ref/taskkit/taskhandler.rst: -------------------------------------------------------------------------------- 1 | TaskHandler 2 | ----------- 3 | 4 | .. automodule:: TaskKit.TaskHandler 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/hierrole.rst: -------------------------------------------------------------------------------- 1 | HierRole 2 | -------- 3 | 4 | .. automodule:: UserKit.HierRole 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/index.rst: -------------------------------------------------------------------------------- 1 | .. _ref-userkit: 2 | 3 | UserKit 4 | ======= 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: The UserKit plug-in contains the following classes: 9 | 10 | hierrole 11 | role 12 | roleuser 13 | roleusermanager 14 | roleusermanagermixin 15 | roleusermanagertofile 16 | user 17 | usermanager 18 | usermanagertofile 19 | -------------------------------------------------------------------------------- /docs/ref/userkit/role.rst: -------------------------------------------------------------------------------- 1 | Role 2 | ---- 3 | 4 | .. automodule:: UserKit.Role 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/roleuser.rst: -------------------------------------------------------------------------------- 1 | RoleUser 2 | -------- 3 | 4 | .. automodule:: UserKit.RoleUser 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/roleusermanager.rst: -------------------------------------------------------------------------------- 1 | RoleUserManager 2 | --------------- 3 | 4 | .. automodule:: UserKit.RoleUserManager 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/roleusermanagermixin.rst: -------------------------------------------------------------------------------- 1 | RoleUserManagerMixIn 2 | -------------------- 3 | 4 | .. automodule:: UserKit.RoleUserManagerMixIn 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/roleusermanagertofile.rst: -------------------------------------------------------------------------------- 1 | RoleUserManagerToFile 2 | --------------------- 3 | 4 | .. automodule:: UserKit.RoleUserManagerToFile 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/user.rst: -------------------------------------------------------------------------------- 1 | User 2 | ---- 3 | 4 | .. automodule:: UserKit.User 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/usermanager.rst: -------------------------------------------------------------------------------- 1 | UserManager 2 | ----------- 3 | 4 | .. automodule:: UserKit.UserManager 5 | -------------------------------------------------------------------------------- /docs/ref/userkit/usermanagertofile.rst: -------------------------------------------------------------------------------- 1 | UserManagerToFile 2 | ----------------- 3 | 4 | .. automodule:: UserKit.UserManagerToFile 5 | -------------------------------------------------------------------------------- /docs/ref/webutils/expansivehtmlforexception.rst: -------------------------------------------------------------------------------- 1 | ExpansiveHTMLForException 2 | ------------------------- 3 | 4 | .. automodule:: WebUtils.ExpansiveHTMLForException 5 | -------------------------------------------------------------------------------- /docs/ref/webutils/fieldstorage.rst: -------------------------------------------------------------------------------- 1 | FieldStorage 2 | ------------ 3 | 4 | .. automodule:: WebUtils.FieldStorage 5 | -------------------------------------------------------------------------------- /docs/ref/webutils/funcs.rst: -------------------------------------------------------------------------------- 1 | Funcs 2 | ----- 3 | 4 | .. automodule:: WebUtils.Funcs 5 | -------------------------------------------------------------------------------- /docs/ref/webutils/htmlforexception.rst: -------------------------------------------------------------------------------- 1 | HTMLForException 2 | ---------------- 3 | 4 | .. automodule:: WebUtils.HTMLForException 5 | -------------------------------------------------------------------------------- /docs/ref/webutils/htmltag.rst: -------------------------------------------------------------------------------- 1 | HTMLTag 2 | ------- 3 | 4 | .. automodule:: WebUtils.HTMLTag 5 | -------------------------------------------------------------------------------- /docs/ref/webutils/httpstatuscodes.rst: -------------------------------------------------------------------------------- 1 | HTTPStatusCodes 2 | --------------- 3 | 4 | .. automodule:: WebUtils.HTTPStatusCodes 5 | -------------------------------------------------------------------------------- /docs/ref/webutils/index.rst: -------------------------------------------------------------------------------- 1 | .. _ref-webutils: 2 | 3 | WebUtils 4 | ======== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: The WebUtils package contains: 9 | 10 | expansivehtmlforexception 11 | fieldstorage 12 | funcs 13 | htmlforexception 14 | htmltag 15 | httpstatuscodes 16 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=5,<6 2 | sphinx_rtd_theme>=1 3 | -------------------------------------------------------------------------------- /docs/userkit.rst: -------------------------------------------------------------------------------- 1 | .. module:: UserKit 2 | 3 | .. _userkit: 4 | 5 | UserKit 6 | ======= 7 | 8 | UserKit provides for the management of users including passwords, user data, server-side archiving and caching. Users can be persisted on the server side via files or the external MiddleKit plug-in. 9 | 10 | Introduction 11 | ------------ 12 | 13 | UserKit is a self contained library and is generally not dependent on the rest of Webware. It does use a few functions in MiscUtils. The objects of interest in UserKit are Users, UserMangers, and Roles. 14 | 15 | :class:`User` -- This represents a particular user and has a name, password, and various flags like ``user.isActive()``. 16 | 17 | :class:`UserManager` -- Your application will create one instance of a UserManager and use it to create and retrieve Users by name. The UserManager comes in several flavors depending on support for Roles, and where user data is stored. For storage, UserManagers can save the user records to either a flat file or a MiddleKit store. Also user managers may support Roles or not. If you don't need any roles and want the simplest UserManager, choose the UserManagerToFile which saves its data to a file. If you want hierarchical roles and persistence to MiddleKit, choose RoleUserManagerToMiddleKit. 18 | 19 | :class:`Role` -- A role represents a permission that users may be granted. A user may belong to several roles, and this is queried using the method ``roleUser.playsRole(role)``. Roles can be hierarchical. For example a customers role may indicate permissions that customers have. A staff role may include the customers role, meaning that members of staff may also do anything that customers can do. 20 | 21 | Examples and More Details 22 | ------------------------- 23 | 24 | The docstrings in :class:`UserManager` is the first place to start. It describes all the methods in :class:`UserManager`. Then go to the file ``Tests/TestExample.py`` which demonstrates how to create users, log them in, and see if they are members of a particular role. 25 | 26 | Once you get the idea, the docstrings in the various files may be perused for more details. See also the :ref:`reference documentation ` for an overview of the available classes and methods. 27 | 28 | Encryption of Passwords 29 | ----------------------- 30 | 31 | Generally one should never save users' passwords anywhere in plain text. However UserKit intentionally does no support encryption of passwords. That is left to how you use UserKit in your application. See ``TestExample.py``, for a demonstration of how easy this is using SHA digests to encrypt passwords. Basically you encrypt your password before you give it to UserKit. It is as simple as this:: 32 | 33 | usermanager.createUser('johndoe', sha('buster').hexdigest()) 34 | 35 | This design decision is to decouple UserKit from your particular encryption requirements, and allows you to use more advanced algorithms as they become available. 36 | 37 | Credit 38 | ------ 39 | 40 | Author: Chuck Esterbrook, and a cast of dozens of volunteers. 41 | Thanks to Tom Schwaller for design help. 42 | 43 | 44 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{36,37,38,39,310,311,312}, pypy3, flake8, pylint, docs, manifest 3 | 4 | [testenv:flake8] 5 | basepython = python3.11 6 | deps = flake8>=6,<7 7 | commands = 8 | flake8 webware setup.py 9 | 10 | [testenv:pylint] 11 | basepython = python3.11 12 | deps = pylint>=2.16,<3 13 | commands = 14 | pylint webware 15 | 16 | [testenv:docs] 17 | basepython = python3.11 18 | extras = 19 | docs 20 | commands = 21 | sphinx-build -b html -nEW docs docs/_build/html 22 | 23 | [testenv:manifest] 24 | basepython = python3.11 25 | deps = check-manifest>=0.49 26 | commands = 27 | check-manifest -v 28 | 29 | [testenv] 30 | setenv = 31 | PYTHONPATH = {toxinidir}/webware 32 | extras = 33 | tests 34 | commands = 35 | python -m unittest discover -fv -p Test*.py 36 | -------------------------------------------------------------------------------- /webware/Admin/Access.py: -------------------------------------------------------------------------------- 1 | from .DumpCSV import DumpCSV 2 | 3 | 4 | class Access(DumpCSV): 5 | 6 | def filename(self): 7 | return self.application().setting('ActivityLogFilename') 8 | -------------------------------------------------------------------------------- /webware/Admin/AdminPage.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from SidebarPage import SidebarPage 4 | 5 | 6 | class AdminPage(SidebarPage): 7 | """AdminPage 8 | 9 | This is the abstract superclass of all Webware administration pages. 10 | 11 | Subclasses typically override title() and writeContent(), but may 12 | customize other methods. 13 | """ 14 | 15 | def cornerTitle(self): 16 | return 'Webware Admin' 17 | 18 | def writeSidebar(self): 19 | self.writeAdminMenu() 20 | self.writeWebwareSidebarSections() 21 | 22 | def writeAdminMenu(self): 23 | self.menuHeading('Admin') 24 | self.menuItem('Home', 'Main') 25 | self.menuItem( 26 | 'Activity log', 'Access', self.fileSize('ActivityLog')) 27 | self.menuItem( 28 | 'Error log', 'Errors', self.fileSize('ErrorLog')) 29 | self.menuItem('Config', 'Config') 30 | self.menuItem('Plug-ins', 'PlugIns') 31 | self.menuItem('Servlet Cache', 'ServletCache') 32 | self.menuItem('Application Control', 'AppControl') 33 | self.menuItem('Logout', 'Main?logout=yes') 34 | 35 | def fileSize(self, log): 36 | """Utility method for writeMenu() to get the size of a log file. 37 | 38 | Returns an HTML string. 39 | """ 40 | filename = self.application().setting(log + 'Filename') 41 | if os.path.exists(filename): 42 | size = '{:0.0f} KB'.format(os.path.getsize(filename) / 1024) 43 | else: 44 | size = 'not existent' 45 | return f'({size})' 46 | 47 | def loginDisabled(self): 48 | """Return None if login is enabled, else a message about why not.""" 49 | if self.application().setting('AdminPassword'): 50 | return None 51 | return ( 52 | '

Logins to admin pages are disabled until' 53 | ' you supply an AdminPassword in Application.config.

') 54 | -------------------------------------------------------------------------------- /webware/Admin/AdminSecurity.py: -------------------------------------------------------------------------------- 1 | from .AdminPage import AdminPage 2 | 3 | 4 | class AdminSecurity(AdminPage): 5 | 6 | # Set this to False if you want to allow everyone to access secure pages 7 | # with no login required. This should instead come from a config file. 8 | _requireLogin = True 9 | 10 | def writeHTML(self): 11 | session = self.session() 12 | request = self.request() 13 | trans = self.transaction() 14 | app = self.application() 15 | # Are they logging in? 16 | if (request.hasField('login') 17 | and request.hasField('username') 18 | and request.hasField('password')): 19 | # They are logging in. Get login id and clear session: 20 | loginId = session.value('loginId', None) 21 | session.values().clear() 22 | # Check if this is a valid user/password 23 | username = request.field('username') 24 | password = request.field('password') 25 | if (self.isValidUserAndPassword(username, password) 26 | and request.field('loginId', 'noLogin') == loginId): 27 | # Success; log them in and send the page: 28 | session.setValue('authenticated_user_admin', username) 29 | AdminPage.writeHTML(self) 30 | else: 31 | # Failed login attempt; have them try again: 32 | request.fields()['extra'] = ( 33 | 'Login failed. Please try again.' 34 | ' (And make sure cookies are enabled.)') 35 | app.forward(trans, 'LoginPage') 36 | # Are they logging out? 37 | elif request.hasField('logout'): 38 | # They are logging out. Clear all session variables: 39 | session.values().clear() 40 | request.fields()['extra'] = 'You have been logged out.' 41 | app.forward(trans, 'LoginPage') 42 | # Are they already logged in? 43 | elif not self._requireLogin or session.value( 44 | 'authenticated_user_admin', None): 45 | # They are already logged in; write the HTML for this page: 46 | AdminPage.writeHTML(self) 47 | else: 48 | # They need to log in. 49 | app.forward(trans, 'LoginPage') 50 | 51 | def isValidUserAndPassword(self, username, password): 52 | # Replace this with a database lookup, or whatever you're using 53 | # for authentication... 54 | adminPassword = self.application().setting('AdminPassword') 55 | return (username == 'admin' and adminPassword and 56 | password == adminPassword) 57 | -------------------------------------------------------------------------------- /webware/Admin/Config.py: -------------------------------------------------------------------------------- 1 | from WebUtils.Funcs import htmlForDict 2 | 3 | from .AdminSecurity import AdminSecurity 4 | 5 | 6 | class Config(AdminSecurity): 7 | 8 | def title(self): 9 | return 'Config' 10 | 11 | def writeContent(self): 12 | self.writeln(htmlForDict( 13 | self.application().config(), topHeading='Application')) 14 | -------------------------------------------------------------------------------- /webware/Admin/DumpCSV.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from MiscUtils.DataTable import DataTable 4 | 5 | from .AdminSecurity import AdminSecurity 6 | 7 | 8 | class DumpCSV(AdminSecurity): 9 | 10 | def filename(self): 11 | """Overridden by subclasses to specify what filename to show.""" 12 | raise NotImplementedError 13 | 14 | def awake(self, transaction): 15 | AdminSecurity.awake(self, transaction) 16 | self._filename = self.filename() 17 | 18 | def shortFilename(self): 19 | return os.path.splitext(os.path.split(self._filename)[1])[0] 20 | 21 | def title(self): 22 | return 'View ' + self.shortFilename() 23 | 24 | def writeContent(self): 25 | if not os.path.exists(self._filename): 26 | self.writeln('

File does not exist.

') 27 | return 28 | table = DataTable(self._filename) 29 | plural = '' if len(table) == 1 else 's' 30 | self.writeln(f'

{len(table)} row{plural}

') 31 | self.writeln('') 32 | # Head row gets special formatting 33 | self._headings = [col.name().strip() for col in table.headings()] 34 | self._numCols = len(self._headings) 35 | self.writeln('') 36 | for value in self._headings: 37 | self.writeln('') 38 | self.writeln('') 39 | # Data rows 40 | for rowIndex, row in enumerate(table, 1): 41 | self.writeln('') 42 | for colIndex, value in enumerate(row): 43 | if colIndex >= self._numCols: 44 | break # skip surplus columns 45 | self.writeln('') 48 | self.writeln('') 49 | self.writeln('
', value, '
', 46 | self.cellContents(rowIndex, colIndex, value), 47 | '
') 50 | 51 | def cellContents(self, _rowIndex, _colIndex, value): 52 | """Hook for subclasses to customize the contents of a cell. 53 | 54 | Based on any criteria (including location). 55 | """ 56 | return value 57 | -------------------------------------------------------------------------------- /webware/Admin/EditFile.py: -------------------------------------------------------------------------------- 1 | from Page import Page 2 | 3 | # Set this to True if you want to pass additional information. 4 | # For security reasons, this has been disabled by default. 5 | appInfo = False 6 | 7 | 8 | class EditFile(Page): 9 | """Helper for the feature provided by the IncludeEditLink setting.""" 10 | 11 | def writeInfo(self, key, value): 12 | self.writeln(f'{key}: {value}') 13 | 14 | def writeHTML(self): 15 | res = self.response() 16 | header = res.setHeader 17 | info = self.writeInfo 18 | req = self.request() 19 | env = req.environ() 20 | field = req.field 21 | 22 | header('Content-Type', 'application/x-webware-edit-file') 23 | header('Content-Disposition', 'inline; filename="Webware.EditFile"') 24 | 25 | # Basic information for editing the file: 26 | info('Filename', field('filename')) 27 | info('Line', field('line')) 28 | 29 | # Additional information about the hostname: 30 | info('Hostname', env.get('HTTP_HOST', 'localhost')) 31 | 32 | if appInfo: 33 | # Additional information about this Webware installation: 34 | app = self.application() 35 | info('ServerSidePath', app.serverSidePath()) 36 | info('WebwarePath', app.webwarePath()) 37 | -------------------------------------------------------------------------------- /webware/Admin/Errors.py: -------------------------------------------------------------------------------- 1 | from os import sep 2 | 3 | from WebUtils.Funcs import urlEncode 4 | 5 | from .DumpCSV import DumpCSV 6 | 7 | 8 | class Errors(DumpCSV): 9 | 10 | def filename(self): 11 | return self.application().setting('ErrorLogFilename') 12 | 13 | def cellContents(self, _rowIndex, colIndex, value): 14 | """Hook for subclasses to customize the contents of a cell. 15 | 16 | Based on any criteria (including location). 17 | """ 18 | if self._headings[colIndex] in ('pathname', 'error report filename'): 19 | path = self.application().serverSidePath() 20 | if value.startswith(path): 21 | value = value[len(path):] 22 | if value.startswith(sep): 23 | value = value[len(sep):] 24 | link = f'View?filename={urlEncode(value)}' 25 | value = value.replace(sep, sep + '') 26 | value = f'{value}' 27 | else: 28 | value = value.replace(sep, sep + '') 29 | return value 30 | if self._headings[colIndex] == 'time': 31 | return f'{value}' 32 | return self.htmlEncode(value) 33 | -------------------------------------------------------------------------------- /webware/Admin/LoginPage.py: -------------------------------------------------------------------------------- 1 | from MiscUtils.Funcs import uniqueId 2 | 3 | from .AdminPage import AdminPage 4 | 5 | 6 | class LoginPage(AdminPage): 7 | """The log-in screen for the admin pages.""" 8 | 9 | def writeContent(self): 10 | if self.loginDisabled(): 11 | self.write(self.loginDisabled()) 12 | return 13 | self.writeln( 14 | '
' 15 | '

 

') 16 | extra = self.request().field('extra', None) 17 | if extra: 18 | self.writeln(f'

{self.htmlEncode(extra)}

') 19 | self.writeln('''

Please log in to view Administration Pages. 20 | The username is admin. The password can be set in the 21 | Application.config file in the Configs directory.

22 |
23 | 24 | 25 | 26 | 27 | 28 | 30 |
29 |
''') 31 | enc = self.htmlEncode 32 | for name, value in self.request().fields().items(): 33 | if name not in ('login', 'logout', 'loginId', 34 | 'username', 'password', 'extra'): 35 | if not isinstance(value, list): 36 | value = [value] 37 | for v in value: 38 | self.writeln(f'') 40 | if self.session().hasValue('loginId'): 41 | loginId = self.session().value('loginId') 42 | else: 43 | # Create a "unique" login id and put it in the form as well as 44 | # in the session. Login will only be allowed if they match. 45 | loginId = uniqueId(self) 46 | self.session().setValue('loginId', loginId) 47 | self.writeln(f'') 48 | self.writeln('
\n

 

') 49 | -------------------------------------------------------------------------------- /webware/Admin/Main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import time, localtime, gmtime, asctime 3 | 4 | from .AdminSecurity import AdminSecurity 5 | 6 | 7 | class Main(AdminSecurity): 8 | 9 | def title(self): 10 | return 'Admin' 11 | 12 | def writeContent(self): 13 | self.curTime = time() 14 | self.writeGeneralInfo() 15 | self.writeSignature() 16 | 17 | def writeGeneralInfo(self): 18 | app = self.application() 19 | info = ( 20 | ('Webware Version', app.webwareVersionString()), 21 | ('Local Time', asctime(localtime(self.curTime))), 22 | ('Up Since', asctime(localtime(app.startTime()))), 23 | ('Num Requests', app.numRequests()), 24 | ('Working Dir', os.getcwd()), 25 | ('Active Sessions', len(app.sessions())) 26 | ) 27 | self.writeln(''' 28 |

Webware Administration Pages

29 | 30 | ''') 31 | for label, value in info: 32 | self.writeln( 33 | f'' 34 | f'') 35 | self.writeln('
Application Info
{label}:{value}
') 36 | 37 | def writeSignature(self): 38 | self.writeln(f''' 39 | ''') 48 | -------------------------------------------------------------------------------- /webware/Admin/PlugIns.py: -------------------------------------------------------------------------------- 1 | from .AdminSecurity import AdminSecurity 2 | 3 | 4 | class PlugIns(AdminSecurity): 5 | 6 | def writeContent(self): 7 | wr = self.writeln 8 | plugIns = self.application().plugIns() 9 | if plugIns: 10 | wr('

' 11 | 'The following Plug-ins were found:

') 12 | wr('') 14 | wr('') 15 | wr('' 16 | '') 17 | for plugIn in plugIns.values(): 18 | name, path = plugIn.name(), plugIn.path() 19 | ver = plugIn.properties()['versionString'] 20 | wr(f'' 21 | f'' 22 | f'') 23 | wr('
Plug-ins
NameVersionDirectory
{name}{ver}{path}
') 24 | else: 25 | wr('

No Plug-ins found.

') 26 | -------------------------------------------------------------------------------- /webware/Admin/View.py: -------------------------------------------------------------------------------- 1 | from os.path import exists, splitext 2 | 3 | from .AdminSecurity import AdminSecurity 4 | 5 | 6 | class View(AdminSecurity): 7 | """View a text or html file. 8 | 9 | The Admin View servlet loads any text or html file 10 | in your application working directory on the Webware server 11 | and displays it in the browser for your viewing pleasure. 12 | """ 13 | 14 | def defaultAction(self): 15 | self._data = self._type = None 16 | self.writeHTML() 17 | if self._data and self._type: 18 | try: 19 | response = self.response() 20 | response.reset() 21 | response.setHeader('Content-Type', self._type) 22 | self.write(self._data) 23 | except Exception: 24 | self.writeError('File cannot be viewed!') 25 | 26 | def writeError(self, message): 27 | self.writeln(f'

Error

{message}

') 28 | 29 | def writeContent(self): 30 | filename = self.request().field('filename', None) 31 | if not filename: 32 | self.writeError('No filename given to view!') 33 | return 34 | filename = self.application().serverSidePath(filename) 35 | if not exists(filename): 36 | self.writeError( 37 | f'The requested file {filename!r} does not exist' 38 | ' in the server side directory.') 39 | return 40 | self._type = 'text/' + ( 41 | 'html' if splitext(filename)[1] in ('.htm', '.html') else 'plain') 42 | try: 43 | with open(filename, encoding='utf-8') as f: 44 | self._data = f.read() 45 | except Exception: 46 | self.writeError(f'The requested file {filename!r} cannot be read.') 47 | -------------------------------------------------------------------------------- /webware/Admin/__init__.py: -------------------------------------------------------------------------------- 1 | """Admin context""" 2 | -------------------------------------------------------------------------------- /webware/ConfigurableForServerSidePath.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from MiscUtils.Configurable import Configurable, NoDefault 4 | 5 | 6 | class ConfigurableForServerSidePath(Configurable): 7 | """Configuration file functionality incorporating a server side path. 8 | 9 | This is a version of `MiscUtils.Configurable.Configurable` that provides 10 | a customized `setting` method for classes which have a `serverSidePath` 11 | method. If a setting's name ends with ``Filename`` or ``Dir``, its value 12 | is passed through `serverSidePath` before being returned. 13 | 14 | In other words, relative filenames and directory names are expanded with 15 | the location of the object, *not* the current directory. 16 | 17 | Application is a prominent class that uses this mix-in. Any class that 18 | has a `serverSidePath` method and a `Configurable` base class, should 19 | inherit this class instead. 20 | 21 | This is used for `MakeAppWorkDir`, which changes the `serverSidePath`. 22 | """ 23 | 24 | def setting(self, name, default=NoDefault): 25 | """Return setting, using the server side path when indicated. 26 | 27 | Returns the setting, filtered by `self.serverSidePath()`, 28 | if the name ends with ``Filename`` or ``Dir``. 29 | """ 30 | value = Configurable.setting(self, name, default) 31 | if name.endswith(('Dir', 'Filename')) and ( 32 | value or name.endswith('Dir')): 33 | if name.endswith('LogFilename') and '/' not in value: 34 | value = os.path.join( 35 | Configurable.setting(self, 'LogDir'), value) 36 | value = self.serverSidePath(value) # pylint: disable=no-member 37 | return value 38 | -------------------------------------------------------------------------------- /webware/Examples/AjaxSuggest.py: -------------------------------------------------------------------------------- 1 | # 2 | # Ajax "Suggest" Example 3 | # 4 | # Written by Robert Forkel based on 5 | # https://webwareforpython.github.io/w4py-olde-wiki/ajaxinwebware.html 6 | # and http://www.dynamicajax.com/fr/AJAX_Suggest_Tutorial-271_290_312.html, 7 | # with minor changes made by Christoph Zwerschke. 8 | # 9 | 10 | from random import randint 11 | 12 | from .AjaxPage import AjaxPage 13 | 14 | maxSuggestions = 10 15 | 16 | # Create some random "magic words": 17 | maxLetters, maxWords = 5, 5000 18 | magicWords = [''.join(chr(randint(97, 122)) for j in range(maxLetters)) 19 | for i in range(maxWords)] 20 | 21 | 22 | class AjaxSuggest(AjaxPage): 23 | 24 | _clientPolling = None # we have no long-running queries 25 | 26 | def writeJavaScript(self): 27 | AjaxPage.writeJavaScript(self) 28 | self.writeln( 29 | '') 30 | 31 | def writeStyleSheet(self): 32 | AjaxPage.writeStyleSheet(self) 33 | self.writeln( 34 | '') 35 | 36 | def htBodyArgs(self): 37 | return AjaxPage.htBodyArgs(self) + ' onload="initPage();"' 38 | 39 | def writeContent(self): 40 | self.writeln('

Ajax "Suggest" Example

') 41 | if self.request().hasField('query'): 42 | query = self.htmlEncode(self.request().field('query')) 43 | self.writeln(f''' 44 |

You have just entered the word "{query}".

45 |

If you like, you can try again:

''') 46 | else: 47 | self.writeln(''' 48 |

This example uses Ajax techniques to make suggestions 49 | based on your input as you type.

50 |

Of course, you need a modern web browser with 51 | JavaScript enabled in order for this to work.

52 |

Start typing in some lowercase letters, 53 | and get random words starting with these characters suggested:

''') 54 | self.writeln('''
55 | 57 |
58 |
''') 59 | 60 | def exposedMethods(self): 61 | """Register the suggest method for use with Ajax.""" 62 | return ['suggest'] 63 | 64 | def suggest(self, prefix): 65 | """We return a JavaScript function call as string. 66 | 67 | The JavaScript function we want called is `handleSuggestions` 68 | and we pass an array of strings starting with prefix. 69 | 70 | Note: to pass more general Python objects to the client side, 71 | use Python's JSON encoder. 72 | """ 73 | words = [w for w in magicWords if w.startswith(prefix)] or ['none'] 74 | wordList = ",".join(map(repr, words[:maxSuggestions])) 75 | return f"handleSuggestions([{wordList}]);" 76 | -------------------------------------------------------------------------------- /webware/Examples/Colorize.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from Page import Page 4 | 5 | try: 6 | from pygments import highlight 7 | from pygments.lexers import PythonLexer 8 | from pygments.formatters import HtmlFormatter 9 | except ImportError: 10 | highlight = None 11 | 12 | errorPage = """\ 13 | 14 | 15 | 16 | 17 | Error 18 | 21 | 22 | 23 |

Error

24 |

{}

25 | 26 | 27 | """ 28 | 29 | viewPage = """\ 30 | 31 | 32 | 33 | 34 | {} 35 | 36 | 37 | 38 | {} 39 | 40 | 41 | """ 42 | 43 | 44 | class Colorize(Page): 45 | """Syntax highlights Python source files.""" 46 | 47 | def respond(self, transaction): 48 | """Write out a syntax highlighted version of the file.""" 49 | res = transaction._response 50 | req = self.request() 51 | if not req.hasField('filename'): 52 | res.write(errorPage.format('No filename given to syntax color!')) 53 | return 54 | filename = req.field('filename') 55 | filename = self.request().serverSidePath(os.path.basename(filename)) 56 | try: 57 | with open(filename, encoding='utf-8') as f: 58 | source = f.read() 59 | except IOError: 60 | source = None 61 | if source is None: 62 | res.write(errorPage.format( 63 | f'The requested file {os.path.basename(filename)!r}' 64 | ' does not exist in the proper directory.')) 65 | return 66 | if highlight: 67 | formatter = HtmlFormatter() 68 | lexer = PythonLexer() 69 | output = highlight(source, lexer, formatter) 70 | style = formatter.get_style_defs('.highlight') 71 | output = viewPage.format(filename, style, output) 72 | else: 73 | print("Pygments highlighter not found - showing plain source") 74 | res.setHeader('Content-Type', 'text/plain') 75 | output = source 76 | res.write(output) 77 | -------------------------------------------------------------------------------- /webware/Examples/CountVisits.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class CountVisits(ExamplePage): 5 | """Counting visits example.""" 6 | 7 | def writeContent(self): 8 | if self.request().hasField('expire'): 9 | self.session().expiring() 10 | self.sendRedirectAndEnd('CountVisits') 11 | self.writeln('

Counting Visits

') 12 | if self.request().isSessionExpired(): 13 | self.writeln('

Your session has expired.

') 14 | count = self.session().value('count', 0) + 1 15 | self.session().setValue('count', count) 16 | self.writeln(f"""

You've been here 17 |  {count:d}  18 | time{'' if count == 1 else 's'}.

19 |

This page records your visits using a session object. 20 | Every time you reload 21 | or revisit this page, the counter will increase. 22 | If you close your browser or force the session to 23 | expire, then your session will end 24 | and you will see the counter go back to 1 on your next visit.

25 |

Try hitting reload now.

""") 26 | -------------------------------------------------------------------------------- /webware/Examples/DominateDemo.py: -------------------------------------------------------------------------------- 1 | try: 2 | import dominate 3 | except ImportError: 4 | dominate = None 5 | else: 6 | from dominate.tags import a, div, h2, p, table, tbody, td, th, tr 7 | from dominate.util import text 8 | 9 | from .ExamplePage import ExamplePage 10 | 11 | 12 | class DominateDemo(ExamplePage): 13 | """Demo for using the Dominate library.""" 14 | 15 | homepage = 'https://github.com/Knio/dominate' 16 | 17 | def writeContent(self): 18 | self.writeln('

Using Webware with Dominate

') 19 | self.writeln( 20 | '

Dominate is a Python library that can be used in Webware' 21 | ' applications to generate HTML programmatically.

') 22 | if not dominate: 23 | self.writeln( 24 | f'

Please install Dominate' 25 | ' in order to view this demo.

') 26 | return 27 | 28 | content = div(id='content') 29 | with content: 30 | h2('Hello World!') 31 | with table(cls="NiceTable").add(tbody()): 32 | tr(th('Demo table', colspan=3)) 33 | r = tr() 34 | r += td('One') 35 | r.add(td('Two')) 36 | with r: 37 | td('Three') 38 | para = p(__pretty=False) 39 | with para: 40 | text('This content has been produced with ') 41 | a('Dominate', href=self.homepage) 42 | text(' programmatically.') 43 | self.write(content) 44 | -------------------------------------------------------------------------------- /webware/Examples/Error.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class CustomError(Exception): 5 | pass 6 | 7 | 8 | class Error(ExamplePage): 9 | 10 | def title(self): 11 | return 'Error raising Example' 12 | 13 | def writeContent(self): 14 | error = self.request().field('error', None) 15 | if error: 16 | msg = 'You clicked that button!' 17 | if error.startswith('String'): 18 | error = msg 19 | elif error.startswith('Custom'): 20 | error = CustomError(msg) 21 | elif error.startswith('System'): 22 | error = SystemError(msg) 23 | else: 24 | error = RuntimeError(msg) 25 | self.writeln('

About to raise an error...

') 26 | raise error 27 | self.writeln('''

Error Test

28 |
29 |

34 |

35 |
''') 36 | -------------------------------------------------------------------------------- /webware/Examples/FileUpload.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class FileUpload(ExamplePage): 5 | """This servlet shows how to handle uploaded files. 6 | 7 | The process is fairly self-explanatory. You use a form like the one below 8 | in the writeContent method. When the form is uploaded, the request field 9 | with the name you gave to the file selector form item will be an instance 10 | of the FieldStorage class from the WebUtils.FieldStorage module. The key 11 | attributes of this class are shown in the example below. The most important 12 | things are filename, which gives the name of the file that was uploaded, 13 | and file, which is an open file handle to the uploaded file. The uploaded 14 | file is temporarily stored in a temp file created by the standard module. 15 | You'll need to do something with the data in this file. The temp file will 16 | be automatically deleted. If you want to save the data in the uploaded file 17 | read it out and write it to a new file, database, whatever. 18 | """ 19 | 20 | def title(self): 21 | return "File Upload Example" 22 | 23 | def writeContent(self): 24 | self.writeln("

Upload Test

") 25 | try: 26 | f = self.request().field('filename') 27 | contents = f.file.read() 28 | except Exception: 29 | output = f'''

{self.htmlEncode(self.__doc__)}

30 |
31 | 32 | 33 |
''' 34 | else: 35 | try: 36 | contentString = contents.decode('utf-8') 37 | except UnicodeDecodeError: 38 | try: 39 | contentString = contents.decode('latin-1') 40 | except UnicodeDecodeError: 41 | contentString = contents.decode('ascii', 'replace') 42 | contentString = self.htmlEncode(contentString.strip()) 43 | output = f'''

Here's the file you submitted:

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
name{f.filename}
type{f.type}
type_options{f.type_options}
disposition{f.disposition}
disposition_options{f.disposition_options}
headers{f.headers}
size{len(contents)} bytes
contents
{contentString}
''' 55 | self.writeln(output) 56 | -------------------------------------------------------------------------------- /webware/Examples/Forward.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class Forward(ExamplePage): 5 | 6 | def writeContent(self): 7 | trans = self.transaction() 8 | resp = self.response() 9 | resp.write( 10 | "

This is the Forward servlet speaking. I am now" 11 | " going to include the output of the IncludeMe servlet" 12 | " via Application's includeURL() method:

") 13 | trans.application().includeURL(trans, 'IncludeMe') 14 | -------------------------------------------------------------------------------- /webware/Examples/IncludeMe.py: -------------------------------------------------------------------------------- 1 | from Page import Page 2 | 3 | 4 | class IncludeMe(Page): 5 | 6 | def writeHTML(self): 7 | self.writeln('

Hello from IncludeMe

') 8 | self.writeln('

I like to be included. This is my content.

') 9 | -------------------------------------------------------------------------------- /webware/Examples/Introspect.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class Introspect(ExamplePage): 5 | 6 | def writeContent(self): 7 | self.writeln('

Introspection

') 8 | self.writeln( 9 | "

The following table shows the values for various Python" 10 | " expressions, all of which are related to introspection." 11 | " That is to say, all the expressions examine the environment such" 12 | " as the object, the object's class, the module and so on.

") 13 | self.writeln('') 14 | self.list('list(locals())') 15 | self.list('list(globals())') 16 | self.list('dir(self)') 17 | self.list('dir(self.__class__)') 18 | self.list('self.__class__.__bases__') 19 | self.list('dir(self.__class__.__bases__[0])') 20 | self.writeln('
') 21 | 22 | def pair(self, key, value): 23 | if isinstance(value, (list, tuple)): 24 | value = ', '.join(map(str, value)) 25 | value = self.htmlEncode(str(value)) 26 | self.writeln( 27 | '' 28 | f'{key}{value}') 29 | 30 | def list(self, codeString): 31 | value = eval(codeString) 32 | if not isinstance(value, (list, tuple)): 33 | value = 'not a list or a tuple' 34 | self.pair(codeString, value) 35 | -------------------------------------------------------------------------------- /webware/Examples/JSONRPCClient.py: -------------------------------------------------------------------------------- 1 | """JSON-RPC demo client contributed by Christoph Zwerschke""" 2 | 3 | from .ExamplePage import ExamplePage 4 | 5 | 6 | class JSONRPCClient(ExamplePage): 7 | """Demo client for using the JSON-RPC example.""" 8 | 9 | def writeJavaScript(self): 10 | ExamplePage.writeJavaScript(self) 11 | self.write('''\ 12 | 13 | 23 | ''') 24 | 25 | def writeContent(self): 26 | self.write('''\ 27 |

JSON-RPC Example

28 |

This example shows how you can call methods 29 | of a JSON-RPC servlet 30 | built with Webware for Python from your web browser 31 | via JavaScript (which has to be activated to run this demo). 32 |

35 |

The example uses a JSON-RPC JavaScript client 36 | based on Jan-Klaas' "JavaScript o lait" library.

37 |

Type in any example text to be used as input parameter, 38 | choose one of the available methods to be invoked by the 39 | example servlet and press the button to display the result.

40 | 41 | 42 | 43 | 44 | 45 | 46 | 58 | 59 | 63 | 64 |
Input parameterRemote methodResult
47 | 48 | 49 | 55 | 56 | 57 |
60 | 62 |
65 | ''') 66 | -------------------------------------------------------------------------------- /webware/Examples/JSONRPCExample.py: -------------------------------------------------------------------------------- 1 | # 2 | # JSON-RPC example servlet contributed by Jean-Francois Pieronne 3 | # 4 | 5 | from JSONRPCServlet import JSONRPCServlet 6 | 7 | 8 | class JSONRPCExample(JSONRPCServlet): 9 | """Example JSON-RPC servlet. 10 | 11 | To try it out, use the JSONRPCClient servlet. 12 | """ 13 | 14 | def __init__(self): 15 | JSONRPCServlet.__init__(self) 16 | 17 | @staticmethod 18 | def echo(msg): 19 | return msg 20 | 21 | @staticmethod 22 | def reverse(msg): 23 | return msg[::-1] 24 | 25 | @staticmethod 26 | def uppercase(msg): 27 | return msg.upper() 28 | 29 | @staticmethod 30 | def lowercase(msg): 31 | return msg.lower() 32 | 33 | def exposedMethods(self): 34 | return ['echo', 'reverse', 'uppercase', 'lowercase'] 35 | -------------------------------------------------------------------------------- /webware/Examples/LoginPage.py: -------------------------------------------------------------------------------- 1 | from MiscUtils.Funcs import uniqueId 2 | from .ExamplePage import ExamplePage 3 | 4 | 5 | class LoginPage(ExamplePage): 6 | """A log-in screen for the example pages.""" 7 | 8 | def title(self): 9 | return 'Log In' 10 | 11 | def htBodyArgs(self): 12 | return super().htBodyArgs() + ( 13 | ' onload="document.loginForm.username.focus();"') 14 | 15 | def writeContent(self): 16 | self.writeln( 17 | '
' 18 | '

 

') 19 | request = self.request() 20 | extra = request.field('extra', None) 21 | if (not extra and request.isSessionExpired() 22 | and not request.hasField('logout')): 23 | extra = 'You have been automatically logged out due to inactivity.' 24 | if extra: 25 | self.writeln( 26 | f'

{self.htmlEncode(extra)}

') 27 | if self.session().hasValue('loginId'): 28 | loginId = self.session().value('loginId') 29 | else: 30 | # Create a "unique" login id and put it in the form as well as 31 | # in the session. Login will only be allowed if they match. 32 | loginId = uniqueId(self) 33 | self.session().setValue('loginId', loginId) 34 | action = request.field('action', '') 35 | if action: 36 | action = f' action="{action}"' 37 | self.writeln(f'''

Please log in to view the example. 38 | The username and password is alice or bob.

39 |
40 | 41 | 42 | 43 | 44 | 45 | 47 |
46 |
48 | ''') 49 | # Forward any passed in values to the user's intended page 50 | # after successful login, except for the special values 51 | # used by the login mechanism itself: 52 | enc = self.htmlEncode 53 | for name, value in request.fields().items(): 54 | if name not in ('login', 'logout', 'loginId', 55 | 'username', 'password', 'extra'): 56 | if not isinstance(value, list): 57 | value = [value] 58 | for v in value: 59 | self.writeln('') 61 | self.writeln('
\n

 

') 62 | -------------------------------------------------------------------------------- /webware/Examples/OtherFileTypes.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class OtherFileTypes(ExamplePage): 5 | 6 | def writeContent(self): 7 | self.writeln('

Test for other file types:

') 8 | self.writeLink('test.text') 9 | self.writeLink('test.html') 10 | 11 | def writeLink(self, link): 12 | self.writeln(f'

{link}

') 13 | -------------------------------------------------------------------------------- /webware/Examples/PickleRPCExample.py: -------------------------------------------------------------------------------- 1 | from operator import truediv 2 | from functools import reduce 3 | 4 | from PickleRPCServlet import PickleRPCServlet 5 | 6 | 7 | class PickleRPCExample(PickleRPCServlet): 8 | """Example XML-RPC servlet. 9 | 10 | To try it out, use something like the following: 11 | 12 | >>> from MiscUtils.PickleRPC import Server 13 | >>> server = Server('http://localhost:8080/Examples/PickleRPCExample') 14 | >>> server.multiply(10,20) 15 | 200 16 | >>> server.add(10,20) 17 | 30 18 | 19 | You'll get an exception if you try to call divide, because that 20 | method is not listed in exposedMethods. 21 | """ 22 | 23 | def exposedMethods(self): 24 | return ['multiply', 'add'] 25 | 26 | def multiply(self, x, y): 27 | return x * y 28 | 29 | def add(self, x, y): 30 | return x + y 31 | 32 | def divide(self, *args): 33 | return reduce(truediv, args) 34 | -------------------------------------------------------------------------------- /webware/Examples/PlugInInspector.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class PlugInInspector(ExamplePage): 5 | """Plug-in Inspector. 6 | 7 | Show all the public names of all registered Plug-ins. 8 | """ 9 | 10 | def writeContent(self): 11 | wr = self.writeln 12 | for plugIn in self.application().plugIns(): 13 | wr(f'

{self.htmlEncode(plugIn)}

') 14 | wr('
    ') 15 | for name in dir(plugIn): 16 | if name.startswith('_'): 17 | continue 18 | value = self.htmlEncode(str(getattr(plugIn, name))) 19 | wr(f'
  • {name} = {value}
  • ') 20 | self.writeln('
') 21 | -------------------------------------------------------------------------------- /webware/Examples/RequestInformation.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class RequestInformation(ExamplePage): 5 | """Request information demo.""" 6 | 7 | def writeContent(self): 8 | self.writeln('

Request Variables

') 9 | request = self.request() 10 | self.writeln( 11 | '

The following table shows the values' 12 | ' for three important request variables.

') 13 | p = 'We added some cookies for you.' 14 | if not self.request().hasCookie('TestCookieName'): 15 | p += (' Reload the page' 16 | ' to see them.') 17 | if not request.fields(): 18 | p += (' ' 20 | 'You can also add some test fields.') 21 | self.writeln(f'

{p}

') 22 | self.writeln('') 23 | if request.fields(): 24 | self.showDict('fields()', request.fields()) 25 | self.showDict('environ()', request.environ()) 26 | if request.cookies(): 27 | self.showDict('cookies()', request.cookies()) 28 | self.writeln('
') 29 | setCookie = self.response().setCookie 30 | setCookie('TestCookieName', 'CookieValue') 31 | setCookie('TestAnotherCookie', 'AnotherCookieValue') 32 | setCookie('TestExpire1', 'expires in 1 minute', expires='+1m') 33 | setCookie('TestExpire2', 'expires in 2 minute', expires='+2m') 34 | setCookie('TestExpire3', 'expires in 3 minute', expires='+3m') 35 | 36 | def showDict(self, name, d): 37 | self.writeln( 38 | '' 39 | f'{name}' 40 | '') 41 | for key in sorted(d): 42 | html = self.htmlEncode(str(d[key])).replace('\n', '
').replace( 43 | ',', ',').replace(';', ';').replace(':/', ':/') 44 | self.writeln( 45 | '' 46 | f'{key}{html}') 47 | -------------------------------------------------------------------------------- /webware/Examples/SecureCountVisits.py: -------------------------------------------------------------------------------- 1 | from .SecurePage import SecurePage 2 | 3 | 4 | class SecureCountVisits(SecurePage): 5 | """Secured version of counting visits example.""" 6 | 7 | def writeContent(self): 8 | count = self.session().value('secure_count', 0) + 1 9 | self.session().setValue('secure_count', count) 10 | self.writeln('

Counting Visits on a Secured Page

') 11 | if self.request().isSessionExpired(): 12 | self.writeln('

Your session has expired.

') 13 | self.writeln( 14 | "

You've been here" 15 | ' ' 16 | f' {count:d} ' 17 | f" time{'' if count == 1 else 's'}.

") 18 | reload = 'reload' 19 | revisit = 'revisit' 20 | self.writeln( 21 | '

This page records your visits using a session object.' 22 | f' Every time you {reload} or {revisit} this page,' 23 | ' the counter will increase. If you close your browser,' 24 | ' then your session will end and you will see the counter' 25 | ' go back to 1 on your next visit.

') 26 | self.writeln(f'

Try hitting {reload} now.

') 27 | user = self.loggedInUser() 28 | if user: 29 | self.writeln( 30 | f'

Authenticated user is {user}.

') 31 | revisit = 'Revisit this page' 32 | logout = 'Logout' 33 | self.writeln(f'

{revisit} | {logout}

') 34 | -------------------------------------------------------------------------------- /webware/Examples/ShowTime.py: -------------------------------------------------------------------------------- 1 | from MiscUtils.Funcs import asclocaltime 2 | 3 | from .ExamplePage import ExamplePage 4 | 5 | 6 | class ShowTime(ExamplePage): 7 | 8 | def writeContent(self): 9 | self.write('

The current time is:

') 10 | self.write('
', asclocaltime(), '
') 11 | -------------------------------------------------------------------------------- /webware/Examples/Simple.py: -------------------------------------------------------------------------------- 1 | from Page import Page 2 | 3 | 4 | class Simple(Page): 5 | 6 | def writeContent(self): 7 | self.writeln('

Simple Servlet

') 8 | self.writeln('

This is a very simple servlet.

') 9 | -------------------------------------------------------------------------------- /webware/Examples/View.py: -------------------------------------------------------------------------------- 1 | from os import sep 2 | 3 | from .ExamplePage import ExamplePage 4 | 5 | 6 | class View(ExamplePage): 7 | """View the source of a Webware servlet. 8 | 9 | For each Webware example, you will see a sidebar with various menu items, 10 | one of which is "View source of example". This link points to the 11 | View servlet and passes the filename of the current servlet. The View 12 | servlet then loads that file's source code and displays it in the browser 13 | for your viewing pleasure. 14 | 15 | Note that if the View servlet isn't passed a filename, 16 | it prints the View's docstring which you are reading right now. 17 | """ 18 | 19 | def writeContent(self): 20 | req = self.request() 21 | if req.hasField('filename'): 22 | trans = self.transaction() 23 | filename = req.field('filename') 24 | if sep in filename: 25 | self.write( 26 | '

Error

Cannot request a file' 27 | f' outside of this directory {filename!r}

') 28 | return 29 | filename = self.request().serverSidePath(filename) 30 | self.request().fields()['filename'] = filename 31 | trans.application().forward(trans, 'Colorize.py') 32 | else: 33 | doc = self.__class__.__doc__.split('\n', 1) 34 | doc[1] = '

\n

'.join(doc[1].split('\n\n')) 35 | self.writeln('

{}

\n

{}

'.format(*doc)) 36 | -------------------------------------------------------------------------------- /webware/Examples/Welcome.py: -------------------------------------------------------------------------------- 1 | from .ExamplePage import ExamplePage 2 | 3 | 4 | class Welcome(ExamplePage): 5 | 6 | def writeContent(self): 7 | wr = self.writeln 8 | version = self.application().webwareVersionString() 9 | wr(f'

Welcome to Webware for Python {version}!

') 10 | path = self.request().servletPath() 11 | wr(f''' 12 |

Along the side of this page you will see various links 13 | that will take you to:

14 |
    15 |
  • The different Webware examples.
  • 16 |
  • The source code of the current example.
  • 17 |
  • Whatever contexts have been configured. 18 | Each context represents a distinct set of web pages, 19 | usually given a descriptive name.
  • 20 |
  • External sites, such as the Webware home page.
  • 21 |
22 |

The Admin context is particularly 23 | interesting because it takes you to the administrative pages 24 | for the Webware application where you can review logs, 25 | configuration, plug-ins, etc.

26 | ''') 27 | req = self.request() 28 | extraURLPath = req.extraURLPath() 29 | if extraURLPath and extraURLPath != '/': 30 | wr(''' 31 |

Note: 32 | extraURLPath information was found on the URL, 33 | and a servlet was not found to process it. 34 | Processing has been delegated to this servlet.

35 | ''') 36 | wr('
    ') 37 | wr(f'
  • serverSidePath: {req.serverSidePath()}
  • ') 38 | wr(f'
  • extraURLPath: {extraURLPath}
  • ') 39 | wr('
') 40 | -------------------------------------------------------------------------------- /webware/Examples/XMLRPCExample.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from operator import truediv 3 | 4 | from webware.XMLRPCServlet import XMLRPCServlet 5 | 6 | 7 | class XMLRPCExample(XMLRPCServlet): 8 | """Example XML-RPC servlet. 9 | 10 | To try it out, use something like the following: 11 | 12 | >>> from xmlrpc.client import ServerProxy as Server 13 | >>> server = Server('http://localhost:8080/Examples/XMLRPCExample') 14 | >>> server.multiply(10,20) 15 | 200 16 | >>> server.add(10,20) 17 | 30 18 | 19 | You'll get an exception if you try to call divide, because that 20 | method is not listed in exposedMethods. 21 | """ 22 | 23 | def exposedMethods(self): 24 | return ['multiply', 'add'] 25 | 26 | def multiply(self, x, y): 27 | return x * y 28 | 29 | def add(self, x, y): 30 | return x + y 31 | 32 | def divide(self, *args): 33 | return reduce(truediv, args) 34 | -------------------------------------------------------------------------------- /webware/Examples/__init__.py: -------------------------------------------------------------------------------- 1 | """Main Examples context""" 2 | -------------------------------------------------------------------------------- /webware/Examples/ajaxpoll.js: -------------------------------------------------------------------------------- 1 | /* 2 | Extended Ajax JavaScript functions used by AjaxPage. 3 | 4 | Implements a periodic polling mechanism to prevent server timeouts 5 | and to allow pushing commands from the server to the client. 6 | 7 | Written by John Dickinson based on ideas from 8 | Apple Developer Connection and DivMod Nevow. 9 | Some changes made by Christoph Zwerschke. 10 | */ 11 | 12 | var dying = false; 13 | var poll_requester = getRequester(); 14 | 15 | function openPollConnection() 16 | { 17 | if (poll_requester) 18 | { 19 | var req = poll_requester; 20 | req.onreadystatechange = function() { 21 | var wait = 3 + Math.random() * 5; // 3 - 8 seconds 22 | if (req.readyState == 4) { 23 | if (req.status == 200) { 24 | try { 25 | eval(req.responseText); 26 | req.abort(); 27 | } catch(e) { } // ignore errors 28 | if (!dying) { 29 | // reopen the response connection after a wait period 30 | setTimeout("openPollConnection()", wait*1000); 31 | } 32 | } 33 | } 34 | } 35 | var url = request_url + 'Poll&_req_=' + ++request_id; 36 | req.open("GET", url, true); 37 | req.send(null); 38 | } 39 | } 40 | 41 | function shutdown() 42 | { 43 | if (poll_requester) { 44 | poll_requester.abort(); 45 | } 46 | dying = true; 47 | } 48 | 49 | if (window.addEventListener) 50 | window.addEventListener("beforeunload", shutdown, false); 51 | else if (window.attachEvent) // MSIE 52 | window.attachEvent("onbeforeunload", shutdown); 53 | 54 | // Open initial connection back to server: 55 | openPollConnection(); 56 | -------------------------------------------------------------------------------- /webware/Examples/ajaxsuggest.css: -------------------------------------------------------------------------------- 1 | /* Style sheet for the AjaxSuggest example. */ 2 | 3 | .in_red { 4 | color: #a33; 5 | } 6 | 7 | .suggest_button_normal { 8 | color: #000; 9 | background-color: #ddd; 10 | text-align: center; 11 | font-size: smaller; 12 | padding: 1px 4px; 13 | border: 2px outset #666; 14 | } 15 | 16 | .suggest_button_over { 17 | color: #ddd; 18 | background-color: #36c; 19 | text-align: center; 20 | font-size: smaller; 21 | padding: 1px 4px; 22 | border: 2px outset #666; 23 | } 24 | 25 | .suggest_link_normal { 26 | color: #000; 27 | background-color: #fff; 28 | padding: 2px 4px; 29 | } 30 | 31 | .suggest_link_over { 32 | color: #fff; 33 | background-color: #36c; 34 | padding: 2px 4px; 35 | } 36 | 37 | #query { 38 | padding: 2px 4px; 39 | border: 2px solid #000; 40 | } 41 | 42 | #suggestions { 43 | cursor: pointer; 44 | position: absolute; 45 | text-align: left; 46 | border: 1px solid #666; 47 | } 48 | 49 | .show { 50 | display: block; 51 | } 52 | 53 | .hide { 54 | display: none; 55 | } 56 | -------------------------------------------------------------------------------- /webware/Examples/ajaxsuggest.js: -------------------------------------------------------------------------------- 1 | /* 2 | JavaScript for the AjaxSuggest example. 3 | 4 | Provides all the client-side heavy lifting required to get Ajax functionality into a web page. 5 | 6 | Coded by Ryan Smith (www.dynamicajax.com/fr/AJAX_Suggest_Tutorial-271_290_312.html). 7 | Adapted for Webware by Robert Forkel. 8 | */ 9 | 10 | // Function that is called after the page has been loaded: 11 | function initPage() { 12 | document.getElementById('query').focus(); 13 | } 14 | 15 | // Function to be associated with input control (initiates the Ajax request): 16 | function getSuggestions() { 17 | ajax_call(false, 'suggest', escape(document.getElementById('query').value)); 18 | } 19 | 20 | // Function handling the Ajax response: 21 | function handleSuggestions(res) { 22 | if (res.length > 0) { 23 | var e = document.getElementById('suggestions'); 24 | e.innerHTML = '
close
'; 25 | for (var i=0; i'; 27 | } 28 | e.className = 'show'; 29 | } else { 30 | clearSuggestions(); 31 | } 32 | } 33 | 34 | function suggestOver(div_node) { 35 | div_node.className = div_node.className.replace('_normal', '_over'); 36 | } 37 | 38 | function suggestOut(div_node) { 39 | div_node.className = div_node.className.replace('_over', '_normal'); 40 | } 41 | 42 | function clearSuggestions() { 43 | var e = document.getElementById('suggestions'); 44 | e.innerHTML = ''; 45 | e.className = 'hide'; 46 | } 47 | 48 | function setQuery(value) { 49 | document.getElementById('query').value = value; 50 | clearSuggestions(); 51 | } 52 | -------------------------------------------------------------------------------- /webware/Examples/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebwareForPython/w4py3/7f75921926f58182cd3f28d5f974d13807df4ef6/webware/Examples/favicon.ico -------------------------------------------------------------------------------- /webware/Examples/index.py: -------------------------------------------------------------------------------- 1 | from HTTPServlet import HTTPServlet 2 | 3 | 4 | class index(HTTPServlet): 5 | 6 | def respond(self, transaction): 7 | extraPath = transaction.request().extraURLPath() 8 | path = transaction.request().urlPath() 9 | if extraPath and path.endswith(extraPath): 10 | path = path[:-len(extraPath)] 11 | if not path.endswith('Welcome'): 12 | path = path.rpartition('/')[0] + '/Welcome' + extraPath 13 | # redirection via the server: 14 | transaction.application().forward(transaction, path) 15 | # redirection via the client: 16 | # trans.response().sendRedirect(path) 17 | -------------------------------------------------------------------------------- /webware/MiscUtils/CSVJoiner.py: -------------------------------------------------------------------------------- 1 | """CSVJoiner.py 2 | 3 | A helper function for joining CSV fields. 4 | """ 5 | 6 | 7 | def joinCSVFields(fields): 8 | """Create a CSV record by joining its fields. 9 | 10 | Returns a CSV record (e.g. a string) from a sequence of fields. 11 | Fields containing commands (,) or double quotes (") are quoted, 12 | and double quotes are escaped (""). 13 | The terminating newline is *not* included. 14 | """ 15 | newFields = [] 16 | for field in fields: 17 | if not isinstance(field, str): 18 | raise UnicodeDecodeError('CSV fields should be strings') 19 | if '"' in field: 20 | field = field.replace('"', '""') 21 | newField = f'"{field}"' 22 | elif ',' in field or '\n' in field or '\r' in field: 23 | newField = f'"{field}"' 24 | else: 25 | newField = field 26 | newFields.append(newField) 27 | return ','.join(newFields) 28 | -------------------------------------------------------------------------------- /webware/MiscUtils/DateInterval.py: -------------------------------------------------------------------------------- 1 | """DateInterval.py 2 | 3 | Convert interval strings (in the form of 1w2d, etc) to seconds, and back again. 4 | Is not exactly about months or years (leap years in particular). 5 | 6 | Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd. 7 | 8 | Exports only timeEncode and timeDecode functions. 9 | """ 10 | 11 | __all__ = ['timeEncode', 'timeDecode'] 12 | 13 | import re 14 | 15 | from operator import itemgetter 16 | 17 | second = 1 18 | minute = second*60 19 | hour = minute*60 20 | day = hour*24 21 | week = day*7 22 | month = day*30 23 | year = day*365 24 | timeValues = { 25 | 'y': year, 26 | 'b': month, 27 | 'w': week, 28 | 'd': day, 29 | 'h': hour, 30 | 'm': minute, 31 | 's': second, 32 | } 33 | timeOrdered = sorted(timeValues.items(), key=itemgetter(1), reverse=True) 34 | 35 | 36 | def timeEncode(seconds): 37 | """Encode a number of seconds (representing a time interval). 38 | 39 | Encode the number into a form like 2d1h3s. 40 | """ 41 | s = [] 42 | for char, amount in timeOrdered: 43 | if seconds >= amount: 44 | i, seconds = divmod(seconds, amount) 45 | s.append(f'{i}{char}') 46 | return ''.join(s) 47 | 48 | 49 | _timeRE = re.compile(r'[0-9]+[a-zA-Z]') 50 | 51 | 52 | def timeDecode(s): 53 | """Decode a number in the format 1h4d3m (1 hour, 3 days, 3 minutes). 54 | 55 | Decode the format into a number of seconds. 56 | """ 57 | time = 0 58 | for match in _timeRE.findall(s): 59 | char = match[-1].lower() 60 | try: 61 | time += int(match[:-1]) * timeValues[char] 62 | except KeyError: 63 | raise ValueError(f'Invalid unit of time: {char}') from None 64 | return time 65 | -------------------------------------------------------------------------------- /webware/MiscUtils/DateParser.py: -------------------------------------------------------------------------------- 1 | """DateParser.py 2 | 3 | Convert string representations of dates to Python datetime objects. 4 | 5 | If installed, we will use the python-dateutil package to parse dates, 6 | otherwise we try to use the strptime function in the Python standard library 7 | with several frequently used formats. 8 | """ 9 | 10 | __all__ = ['parseDateTime', 'parseDate', 'parseTime'] 11 | 12 | try: 13 | 14 | from dateutil.parser import parse as parseDateTime 15 | 16 | except ImportError: # dateutil not available 17 | 18 | from datetime import datetime 19 | 20 | strpdatetime = datetime.strptime 21 | 22 | def parseDateTime(s): 23 | """Return a datetime object corresponding to the given string.""" 24 | formats = ( 25 | "%a %b %d %H:%M:%S %Y", "%a, %d-%b-%Y %H:%M:%S", 26 | "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", 27 | "%Y%m%d %H:%M:%S", "%Y%m%dT%H:%M:%S", 28 | "%Y%m%d %H%M%S", "%Y%m%dT%H%M%S", 29 | "%m/%d/%y %H:%M:%S", "%Y-%m-%d %H:%M", 30 | "%Y-%m-%d", "%Y%m%d", "%m/%d/%y", 31 | "%H:%M:%S", "%H:%M", "%c") 32 | for fmt in formats: 33 | try: 34 | return strpdatetime(s, fmt) 35 | except ValueError: 36 | pass 37 | raise ValueError(f'Cannot parse date/time {s}') 38 | 39 | 40 | def parseDate(s): 41 | """Return a date object corresponding to the given string.""" 42 | return parseDateTime(s).date() 43 | 44 | 45 | def parseTime(s): 46 | """Return a time object corresponding to the given string.""" 47 | return parseDateTime(s).time() 48 | -------------------------------------------------------------------------------- /webware/MiscUtils/Error.py: -------------------------------------------------------------------------------- 1 | """Universal error class.""" 2 | 3 | 4 | class Error(dict): 5 | """Universal error class. 6 | 7 | An error is a dictionary-like object, containing a specific 8 | user-readable error message and an object associated with it. 9 | Since Error inherits dict, other informative values can be arbitrarily 10 | attached to errors. For this reason, subclassing Error is rare. 11 | 12 | Example:: 13 | 14 | err = Error(user, 'Invalid password.') 15 | err['time'] = time.time() 16 | err['attempts'] = attempts 17 | 18 | The object and message can be accessed via methods:: 19 | 20 | print(err.object()) 21 | print(err.message()) 22 | 23 | When creating errors, you can pass None for both object and message. 24 | You can also pass additional values, which are then included in the error:: 25 | 26 | >>> err = Error(None, 'Too bad.', timestamp=time.time()) 27 | >>> err.keys() 28 | ['timestamp'] 29 | 30 | Or include the values as a dictionary, instead of keyword arguments:: 31 | 32 | >>> info = {'timestamp': time.time()} 33 | >>> err = Error(None, 'Too bad.', info) 34 | 35 | Or you could even do both if you needed to. 36 | """ 37 | 38 | def __init__(self, obj, message, valueDict=None, **valueArgs): 39 | """Initialize the error. 40 | 41 | Takes the object the error occurred for, and the user-readable 42 | error message. The message should be self sufficient such that 43 | if printed by itself, the user would understand it. 44 | """ 45 | dict.__init__(self) 46 | self._object = obj 47 | self._message = message 48 | if valueDict: 49 | self.update(valueDict) 50 | self.update(valueArgs) 51 | 52 | def object(self): 53 | """Get the object the error occurred for.""" 54 | return self._object 55 | 56 | def message(self): 57 | """Get the user-readable error message.""" 58 | return self._message 59 | 60 | def __repr__(self): 61 | return (f'ERROR(object={self._object!r};' 62 | f' message={self._message!r}; data={dict(self)!r})') 63 | 64 | def __str__(self): 65 | return f'ERROR: {self._message}' 66 | 67 | def __bool__(self): 68 | return True 69 | -------------------------------------------------------------------------------- /webware/MiscUtils/M2PickleRPC.py: -------------------------------------------------------------------------------- 1 | """M2Crypto-enhanced transport for PickleRPC 2 | 3 | This lets you use M2Crypto for SSL encryption. 4 | 5 | Based on m2xmlrpclib.py which is 6 | Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved. 7 | """ 8 | 9 | from io import BytesIO 10 | 11 | from M2Crypto import SSL, httpslib, m2urllib # pylint: disable=import-error 12 | 13 | from .PickleRPC import Transport 14 | 15 | __version__ = 1 # version of M2PickleRPC 16 | 17 | 18 | class M2Transport(Transport): 19 | 20 | user_agent = f"M2PickleRPC.py/{__version__} - {Transport.user_agent}" 21 | 22 | def __init__(self, ssl_context=None): 23 | self.ssl_context = ssl_context or SSL.Context('sslv23') 24 | 25 | def make_connection(self, host, port=None): 26 | if port is None: 27 | host, port = m2urllib.splitport(host) 28 | return httpslib.HTTPS(host, int(port), ssl_context=self.ssl_context) 29 | 30 | # Workarounds below are necessary because M2Crypto seems to 31 | # return from fileobject.read() early! So we have to call it 32 | # over and over to get the full data. 33 | 34 | def parse_response(self, f): 35 | """Workaround M2Crypto issue mentioned above.""" 36 | sio = BytesIO() 37 | while True: 38 | chunk = f.read() 39 | if not chunk: 40 | break 41 | sio.write(chunk) 42 | sio.seek(0) 43 | return Transport.parse_response(self, sio) 44 | 45 | def parse_response_gzip(self, f): 46 | """Workaround M2Crypto issue mentioned above.""" 47 | sio = BytesIO() 48 | while True: 49 | chunk = f.read() 50 | if not chunk: 51 | break 52 | sio.write(chunk) 53 | sio.seek(0) 54 | return Transport.parse_response_gzip(self, sio) 55 | -------------------------------------------------------------------------------- /webware/MiscUtils/ParamFactory.py: -------------------------------------------------------------------------------- 1 | """ParamFactory.py 2 | 3 | A factory for creating cached, parametrized class instances. 4 | """ 5 | 6 | from threading import Lock 7 | 8 | 9 | class ParamFactory: 10 | 11 | def __init__(self, klass, **extraMethods): 12 | self.lock = Lock() 13 | self.cache = {} 14 | self.klass = klass 15 | for name, func in list(extraMethods.items()): 16 | setattr(self, name, func) 17 | 18 | def __call__(self, *args): 19 | with self.lock: 20 | if args in self.cache: 21 | value = self.cache[args] 22 | else: 23 | value = self.klass(*args) 24 | self.cache[args] = value 25 | return value 26 | 27 | def allInstances(self): 28 | return list(self.cache.values()) 29 | -------------------------------------------------------------------------------- /webware/MiscUtils/Properties.py: -------------------------------------------------------------------------------- 1 | name = 'MiscUtils' 2 | 3 | status = 'stable' 4 | 5 | synopsis = ( 6 | "MiscUtils provides support classes and functions to Webware" 7 | " that aren't necessarily web-related and that don't fit into one of" 8 | " the other plug-ins. There is plenty of useful reusable code here.") 9 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/BenchCSVParser.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from glob import glob 4 | try: 5 | from cProfile import Profile 6 | except ImportError: 7 | from profile import Profile 8 | 9 | from MiscUtils.CSVParser import CSVParser 10 | 11 | 12 | class BenchCSVParser: 13 | 14 | def __init__(self, profile=False, runTestSuite=True): 15 | self.parse = CSVParser().parse 16 | self._shouldProfile = profile 17 | self._shouldRunTestSuite = runTestSuite 18 | self._iterations = 1000 19 | 20 | def main(self): 21 | print('Benchmarking CSVParser ...') 22 | print() 23 | if len(sys.argv) > 1 and sys.argv[1].lower().startswith('prof'): 24 | self._shouldProfile = True 25 | if self._shouldRunTestSuite: 26 | print('Running test suite ...') 27 | from unittest import main 28 | main('MiscUtils.Tests.TestCSVParser', exit=False) 29 | start = time.time() 30 | if self._shouldProfile: 31 | prof = Profile() 32 | prof.runcall(self._main) 33 | filename = f'{self.__class__.__name__}.pstats' 34 | prof.dump_stats(filename) 35 | print('Wrote', filename) 36 | else: 37 | self._main() 38 | duration = time.time() - start 39 | print(f'{duration:.1f} secs') 40 | 41 | def _main(self): 42 | print() 43 | for name in glob('Sample*.csv'): 44 | print("Benchmark using", name, "...") 45 | self.benchFileNamed(name) 46 | 47 | def benchFileNamed(self, name, encoding='utf-8'): 48 | with open(name, encoding=encoding) as f: 49 | lines = f.readlines() 50 | for line in lines: 51 | for _iteration in range(self._iterations): 52 | # we duplicate lines to reduce the overhead of the loop 53 | self.parse(line) 54 | self.parse(line) 55 | self.parse(line) 56 | self.parse(line) 57 | self.parse(line) 58 | self.parse(line) 59 | self.parse(line) 60 | self.parse(line) 61 | self.parse(line) 62 | self.parse(line) 63 | self.parse(line) 64 | self.parse(line) 65 | self.parse(line) 66 | self.parse(line) 67 | self.parse(line) 68 | self.parse(line) 69 | 70 | 71 | if __name__ == '__main__': 72 | BenchCSVParser().main() 73 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/BenchDataTable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | from glob import glob 6 | try: 7 | from cProfile import Profile 8 | except ImportError: 9 | from profile import Profile 10 | 11 | from MiscUtils.DataTable import DataTable 12 | 13 | 14 | class BenchDataTable: 15 | 16 | def __init__(self, profile=False, runTestSuite=True): 17 | self._shouldProfile = profile 18 | self._shouldRunTestSuite = runTestSuite 19 | self._iterations = 1000 20 | 21 | def main(self): 22 | print('Benchmarking DataTable ...') 23 | print() 24 | if len(sys.argv) > 1 and sys.argv[1].lower().startswith('prof'): 25 | self._shouldProfile = True 26 | if self._shouldRunTestSuite: 27 | print('Running test suite ...') 28 | from unittest import main 29 | main('MiscUtils.Tests.TestDataTable', exit=False) 30 | start = time.time() 31 | if self._shouldProfile: 32 | prof = Profile() 33 | prof.runcall(self._main) 34 | filename = f'{self.__class__.__name__}.pstats' 35 | prof.dump_stats(filename) 36 | print('Wrote', filename) 37 | else: 38 | self._main() 39 | duration = time.time() - start 40 | print(f'{duration:.1f} secs') 41 | 42 | def _main(self): 43 | print() 44 | for name in glob('Sample*.csv'): 45 | print("Benchmark using", name, "...") 46 | self.benchFileNamed(name) 47 | 48 | def benchFileNamed(self, name, encoding='utf-8'): 49 | with open(name, encoding=encoding) as f: 50 | contents = f.read() 51 | for _iteration in range(self._iterations): 52 | # we duplicate lines to reduce the overhead of the loop 53 | dt = DataTable() 54 | dt.readString(contents) 55 | dt = DataTable() 56 | dt.readString(contents) 57 | dt = DataTable() 58 | dt.readString(contents) 59 | dt = DataTable() 60 | dt.readString(contents) 61 | dt = DataTable() 62 | dt.readString(contents) 63 | dt = DataTable() 64 | dt.readString(contents) 65 | dt = DataTable() 66 | dt.readString(contents) 67 | dt = DataTable() 68 | dt.readString(contents) 69 | 70 | 71 | if __name__ == '__main__': 72 | BenchDataTable().main() 73 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/Sample.csv: -------------------------------------------------------------------------------- 1 | Class,Attribute,Type,isRequired,Min,Max,Extras 2 | Video,,,,,,isAbstract=1 3 | ,title,string,1,1,100, 4 | ,directors,list of Person,0,,10, 5 | ,cast,list of Role,0,,, 6 | Movie (Video),,,,,, 7 | ,year,int,1,,, 8 | ,rating,enum,1,,,"Enums='g, pg, pg13, r, nc17, x, nr, other'" 9 | TVSeries (Video),,,,,, 10 | ,years,int,,,,Comment='supposed to be pickle; a list of ints' 11 | Person,,,,,, 12 | ,video,Video,0,,,Comment='back pointer to View for directors attr' 13 | ,name,string,1,1,100, 14 | ,birthDate,date,0,,50, 15 | Role,,,,,, 16 | ,video,Video,1,,, 17 | ,karacter,string,1,,100 18 | ,person,Person,1,, 19 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/Sample.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebwareForPython/w4py3/7f75921926f58182cd3f28d5f974d13807df4ef6/webware/MiscUtils/Tests/Sample.xls -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/TestDBPool.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import unittest 3 | 4 | from MiscUtils.DBPool import DBPool 5 | 6 | 7 | class TestDBPool(unittest.TestCase): 8 | 9 | def testDbPool(self): 10 | pool = DBPool(sqlite3, 10, database=':memory:') 11 | for _count in range(15): 12 | con = pool.connection() 13 | cursor = con.cursor() 14 | cursor.execute("select 1 union select 2 union select 3 order by 1") 15 | rows = cursor.fetchall() 16 | self.assertEqual(rows, [(1,), (2,), (3,)]) 17 | con.close() 18 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/TestDateInterval.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from MiscUtils.DateInterval import timeEncode, timeDecode 4 | 5 | 6 | class TestDateInterval(unittest.TestCase): 7 | 8 | def testTimeEncode(self): 9 | self.assertEqual(timeEncode(1), '1s') 10 | self.assertEqual(timeEncode(60), '1m') 11 | self.assertEqual(timeEncode(176403), '2d1h3s') 12 | self.assertEqual(timeEncode(349380), '4d1h3m') 13 | self.assertEqual(timeEncode(38898367), '1y2b3w4d5h6m7s') 14 | 15 | def testTimeDecode(self): 16 | self.assertEqual(timeDecode('1s'), 1) 17 | self.assertEqual(timeDecode('1h2d3s'), 176403) 18 | self.assertEqual(timeDecode('2d1h3s'), 176403) 19 | self.assertEqual(timeDecode('1h4d3m'), 349380) 20 | self.assertEqual(timeDecode('3m4d1h'), 349380) 21 | self.assertEqual(timeDecode('1y2b3w4d5h6m7s'), 38898367) 22 | self.assertEqual(timeDecode('0y1b2w3d4h5m6s'), 4075506) 23 | self.assertEqual(timeDecode('6s5m4h3d2w1b0y'), 4075506) 24 | self.assertEqual(timeDecode('(3s-2d-1h)'), 176403) 25 | try: 26 | timeDecode('1h5n') 27 | except ValueError as e: 28 | self.assertEqual(str(e), 'Invalid unit of time: n') 29 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/TestError.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from MiscUtils.Error import Error 4 | 5 | 6 | class DummyObject: 7 | 8 | def __repr__(self): 9 | return '' 10 | 11 | 12 | class TestError(unittest.TestCase): 13 | 14 | def testNone(self): 15 | err = Error(None, None) 16 | self.assertEqual(str(err), 'ERROR: None') 17 | self.assertEqual(repr(err), 18 | 'ERROR(object=None; message=None; data={})') 19 | self.assertIs(err.object(), None) 20 | self.assertIs(err.message(), None) 21 | 22 | def testObjMessage(self): 23 | obj = DummyObject() 24 | err = Error(obj, 'test') 25 | self.assertEqual(str(err), 'ERROR: test') 26 | self.assertEqual( 27 | repr(err), 28 | "ERROR(object=; message='test'; data={})") 29 | self.assertIs(err.object(), obj) 30 | self.assertIs(err.message(), 'test') 31 | 32 | def testIsDict(self): 33 | err = Error(DummyObject(), 'test') 34 | self.assertIsInstance(err, dict) 35 | self.assertIsInstance(err, Error) 36 | 37 | def testValueDict(self): 38 | err = Error(DummyObject(), 'test', a=5, b='.') 39 | self.assertEqual(str(err), 'ERROR: test') 40 | self.assertEqual( 41 | repr(err), 42 | "ERROR(object=; message='test'; data={'a': 5, 'b': '.'})") 43 | self.assertEqual(list(err), ['a', 'b']) 44 | self.assertIsInstance(err['a'], int) 45 | self.assertIsInstance(err['b'], str) 46 | 47 | def testVarArgs(self): 48 | err = Error(DummyObject(), 'test', {'a': 5}, b='.') 49 | self.assertEqual(str(err), 'ERROR: test') 50 | self.assertEqual( 51 | repr(err), 52 | "ERROR(object=; message='test'; data={'a': 5, 'b': '.'})") 53 | self.assertEqual(list(err), ['a', 'b']) 54 | self.assertIsInstance(err['a'], int) 55 | self.assertIsInstance(err['b'], str) 56 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/TestPickleCache.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import shutil 4 | import tempfile 5 | import unittest 6 | 7 | from MiscUtils import PickleCache as pc 8 | 9 | 10 | class TestPickleCache(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self._tempDir = tempfile.mkdtemp() 14 | 15 | def tearDown(self): 16 | shutil.rmtree(self._tempDir, ignore_errors=True) 17 | 18 | @staticmethod 19 | def remove(filename): 20 | try: 21 | os.remove(filename) 22 | except OSError: 23 | pass 24 | 25 | def testTwoIterations(self): 26 | iterations = 2 27 | for _iteration in range(iterations): 28 | self.testOneIteration() 29 | 30 | def testOneIteration(self): 31 | sourcePath = self._sourcePath = os.path.join(self._tempDir, 'foo.dict') 32 | picklePath = self._picklePath = pc.PickleCache().picklePath(sourcePath) 33 | self.remove(picklePath) # make sure we're clean 34 | data = self._data = {'x': 1} 35 | self.writeSource() 36 | try: 37 | # test 1: no pickle cache yet 38 | self.assertTrue(pc.readPickleCache(sourcePath) is None) 39 | self.writePickle() 40 | # test 2: correctness 41 | self.assertEqual(pc.readPickleCache(sourcePath), data) 42 | # test 3: wrong pickle version 43 | self.assertTrue( 44 | pc.readPickleCache(sourcePath, pickleProtocol=1) is None) 45 | self.writePickle() # restore 46 | # test 4: wrong data source 47 | self.assertTrue( 48 | pc.readPickleCache(sourcePath, source='notTest') is None) 49 | self.writePickle() # restore 50 | # test 5: wrong Python version 51 | v = list(pc.versionInfo) 52 | v[-1] += 1 # increment serial number 53 | v, pc.versionInfo = pc.versionInfo, tuple(v) 54 | try: 55 | self.assertTrue(pc.readPickleCache(sourcePath) is None) 56 | self.writePickle() # restore 57 | finally: 58 | pc.versionInfo = v 59 | # test 6: source is newer 60 | self.remove(picklePath) 61 | self.writePickle() 62 | # we have to allow for the granularity of getmtime() 63 | # (see the comment in the docstring of PickleCache.py) 64 | time.sleep(1.25) 65 | self.writeSource() 66 | self.assertTrue(pc.readPickleCache(sourcePath) is None) 67 | self.writePickle() # restore 68 | finally: 69 | self.remove(sourcePath) 70 | self.remove(picklePath) 71 | 72 | def writeSource(self): 73 | with open(self._sourcePath, 'w', encoding='ascii') as f: 74 | f.write(str(self._data)) 75 | 76 | def writePickle(self): 77 | self.assertFalse(os.path.exists(self._picklePath)) 78 | pc.writePickleCache(self._data, self._sourcePath, source='test') 79 | self.assertTrue(os.path.exists(self._picklePath)) 80 | -------------------------------------------------------------------------------- /webware/MiscUtils/Tests/__init__.py: -------------------------------------------------------------------------------- 1 | """MiscUtils Tests""" 2 | -------------------------------------------------------------------------------- /webware/MiscUtils/__init__.py: -------------------------------------------------------------------------------- 1 | """MiscUtils for Webware for Python""" 2 | 3 | __all__ = ['AbstractError', 'NoDefault'] 4 | 5 | 6 | class AbstractError(NotImplementedError): 7 | """Abstract method error. 8 | 9 | This exception is raised by abstract methods in abstract classes. 10 | It is a special case of NotImplementedError, that indicates that the 11 | implementation won't ever be provided at that location in the future 12 | -- instead the subclass should provide it. 13 | 14 | Typical usage: 15 | 16 | from MiscUtils import AbstractError 17 | 18 | class Foo: 19 | def bar(self): 20 | raise AbstractError(self.__class__) 21 | 22 | Note that adding the self.__class__ makes the resulting exception 23 | *much* more useful. 24 | """ 25 | 26 | 27 | class NoDefault: 28 | """Singleton for parameters with no default. 29 | 30 | This provides a singleton "thing" which can be used to initialize 31 | the "default=" arguments for different retrieval methods. 32 | 33 | For example: 34 | 35 | from MiscUtils import NoDefault 36 | def bar(self, name, default=NoDefault): 37 | if default is NoDefault: 38 | return self._bars[name] # will raise exception for invalid key 39 | else: 40 | return self._bars.get(name, default) 41 | 42 | The value None does not suffice for "default=" because it does not 43 | indicate whether or not a value was passed. 44 | 45 | Consistently using this singleton is valuable due to subclassing 46 | situations: 47 | 48 | def bar(self, name, default=NoDefault): 49 | if someCondition: 50 | return self.specialBar(name) 51 | else: 52 | return SuperClass.bar(name, default) 53 | 54 | It's also useful if one method that uses "default=NoDefault" relies 55 | on another object and method to which it must pass the default. 56 | (This is similar to the subclassing situation.) 57 | """ 58 | 59 | 60 | def installInWebware(_application): 61 | pass 62 | -------------------------------------------------------------------------------- /webware/MockApplication.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ConfigurableForServerSidePath import ConfigurableForServerSidePath 4 | 5 | 6 | class MockImportManager: 7 | 8 | def recordFile(self, filename, isfile=None): 9 | pass 10 | 11 | 12 | defaultConfig = { 13 | 'CacheDir': 'Cache', 14 | 'PlugIns': ['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'], 15 | 'PrintPlugIns': False 16 | } 17 | 18 | 19 | class MockApplication(ConfigurableForServerSidePath): 20 | """ 21 | A minimal implementation which is compatible with Application 22 | and which is sufficient to load plugins. 23 | """ 24 | 25 | def __init__(self, path=None, settings=None, development=None): 26 | ConfigurableForServerSidePath.__init__(self) 27 | if path is None: 28 | path = os.getcwd() 29 | self._serverSidePath = os.path.abspath(path) 30 | self._webwarePath = os.path.abspath(os.path.dirname(__file__)) 31 | if development is None: 32 | development = bool(os.environ.get('WEBWARE_DEVELOPMENT')) 33 | self._development = development 34 | appConfig = self.config() 35 | if settings: 36 | appConfig.update(settings) 37 | self._cacheDir = self.serverSidePath( 38 | self.setting('CacheDir') or 'Cache') 39 | from MiscUtils.PropertiesObject import PropertiesObject 40 | props = PropertiesObject(os.path.join( 41 | self._webwarePath, 'Properties.py')) 42 | self._webwareVersion = props['version'] 43 | self._webwareVersionString = props['versionString'] 44 | self._imp = MockImportManager() 45 | for path in (self._cacheDir,): 46 | if path and not os.path.exists(path): 47 | os.makedirs(path) 48 | 49 | def defaultConfig(self): 50 | return defaultConfig 51 | 52 | def configReplacementValues(self): 53 | """Get config values that need to be escaped.""" 54 | return { 55 | 'ServerSidePath': self._serverSidePath, 56 | 'WebwarePath': self._webwarePath, 57 | 'Development': self._development 58 | } 59 | 60 | def configFilename(self): 61 | return self.serverSidePath('Configs/Application.config') 62 | 63 | def serverSidePath(self, path=None): 64 | if path: 65 | return os.path.normpath( 66 | os.path.join(self._serverSidePath, path)) 67 | return self._serverSidePath 68 | 69 | def hasContext(self, _name): 70 | return False 71 | 72 | def addServletFactory(self, factory): 73 | pass 74 | -------------------------------------------------------------------------------- /webware/PSP/CompilePSP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | 7 | def compilePSP(*args): 8 | from .Context import PSPCLContext 9 | from .PSPCompiler import Compiler 10 | pspFilename = args[0] 11 | fil, ext = os.path.splitext(os.path.basename(pspFilename)) 12 | classname = fil + '_' + ext 13 | pyFilename = classname + '.py' 14 | context = PSPCLContext(pspFilename) 15 | context.setClassName(classname) 16 | context.setPythonFileName(pyFilename) 17 | context.setPythonFileEncoding('utf-8') 18 | clc = Compiler(context) 19 | clc.compile() 20 | 21 | 22 | if __name__ == '__main__': 23 | path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 24 | sys.path.insert(0, path) 25 | from PSP.CompilePSP import compilePSP as main 26 | main(sys.argv[1]) 27 | -------------------------------------------------------------------------------- /webware/PSP/Examples/Braces.psp: -------------------------------------------------------------------------------- 1 | <%@ page imports = "sys,os,time,PSP.Examples.PSPExamplePage:PSPExamplePage"%><%-- Here's the modules that I need in this file. --%> 2 | <%@ page method="writeContent" %> <%-- This is the method of the base class that I want to override, writeHTML is the default --%> 3 | <%@ page extends="PSPExamplePage"%> <%--This is the base class for this page. Page is the default --%> 4 | <%@ page indentType="braces" %> 5 | <%-- 6 | <%@ page method="writeBody" %> 7 | --%> 8 | 9 | 10 | return "Braces Test" 11 | 12 | 13 |

Python Server Pages

14 | 15 |

Braces Test

16 | 17 |

Dave Wallace (dwallace@delanet.com) has written a module that will convert Python code that uses braces for indentation into properly whitespace indented python syntax. This is pretty nifty, and very useful for PSP.

18 | 19 |

The whitespace significance in Python syntax is difficult in PSP because HTML is the exact opposite. Whitespace doesn't matter, and neither do carriage-returns or anything else. So that makes the melding of Python and HTML a challenge.

20 | 21 |

So this is one solution.

22 | 23 |

Take out all of the whitespace significance, and just use braces.

24 | 25 |

This module allows you to use braces where you would normally be hitting <return><tab>. You can put the opening brace ({) on the line befoe the block starts, or on the first line of the block. Same kind of deal for the closing brace (}). You still need to have the colon (:) on the line right before the block starts. That's just part of Python.

26 | 27 |

To enable this functionality in PSP, you have to set braces as the indent type for your page. So you add this directive to your PSP page:
28 | 29 |

<%@ page indentType="braces" %>
30 | 31 |

This is a little test of the functionality:

32 | 33 |
    <% for i in range(5): { %> 34 |
  • I'm number <%=i+1%>
  • 35 | <% } %>
36 | 37 |

Click on "View source" over on the left to see the source for this page.

38 | 39 |

See also the PSP documentation on braces.

40 | -------------------------------------------------------------------------------- /webware/PSP/Examples/Hello.psp: -------------------------------------------------------------------------------- 1 | <%-- This is a PSP comment. It won't show up in the HTML or even in the class that this file will generate --%> 2 | 3 | <%@ page imports = "sys,os,time,PSP.Examples.PSPExamplePage:PSPExamplePage"%><%-- Here's the modules that I need in this file. --%> 4 | <%@ page method="writeContent" %><%-- This is the method of the base class that I want to override, writeHTML is the default. --%> 5 | <%@ page extends="PSPExamplePage"%><%--This is the base class for this page. Page is the default. --%> 6 | <%@ page isInstanceSafe="yes" %><%-- Each instance of this class can be used multiple times. --%> 7 | <%@ page indentType="spaces" %><%-- Use spaces to indent the sourcefile that this template will produce. --%> 8 | 9 | <%-- Method declaration Test --%> 10 | 11 | return "PSP Hello" 12 | 13 | 14 |

Hello from PSP!

15 | 16 | <%-- This image is served by Webware --%> 17 |

Python Server Pages

18 | 19 |

This is PSP. Please read the user's guide for more information.

20 | 21 |

Here are some examples. PSPTests shows most of the functionality:

22 | 23 |
    <% 24 | import glob 25 | filePath = self.request().serverSidePath() 26 | files = glob.glob(os.path.join(os.path.dirname(filePath), "*.psp")) 27 | for path in files: 28 | name = os.path.split(path)[1]$%><%-- Aha! Here's where we need the complex block syntax. Ok. --%> 29 |
  • <%= name %>
  • 30 | <% end %>
31 | 32 |

So anyway, read through the user's guide, and look at PSPTests for examples.

33 | -------------------------------------------------------------------------------- /webware/PSP/Examples/PSPExamplePage.py: -------------------------------------------------------------------------------- 1 | import os 2 | from Examples.ExamplePage import ExamplePage 3 | 4 | 5 | class PSPExamplePage(ExamplePage): 6 | 7 | def cornerTitle(self): 8 | return "PSP Examples" 9 | 10 | def writeOtherMenu(self): 11 | self.menuHeading('Other') 12 | self.menuItem( 13 | f'View source of
{self.title()}', 14 | self.request().uriWebwareRoot() + 'PSP/Examples/View?filename=' + 15 | os.path.basename(self.request().serverSidePath())) 16 | -------------------------------------------------------------------------------- /webware/PSP/Examples/PSPinclude.psp: -------------------------------------------------------------------------------- 1 | 2 | <%@ page imports="time"%> 3 |

The current time is: <%= time.ctime() %>

4 | 5 |
6 | -------------------------------------------------------------------------------- /webware/PSP/Examples/View.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .PSPExamplePage import PSPExamplePage 4 | 5 | 6 | class View(PSPExamplePage): 7 | """View the source of a PSP page. 8 | 9 | For each PSP example, you will see a sidebar with various menu items, 10 | one of which is "View source of example". This link points to the 11 | View servlet and passes the filename of the current servlet. The View 12 | servlet then loads that PSP file's source code and displays it in the 13 | browser for your viewing pleasure. 14 | 15 | Note that if the View servlet isn't passed a PSP filename, it prints the 16 | View's docstring which you are reading right now. 17 | """ 18 | 19 | def writeContent(self): 20 | req = self.request() 21 | if req.hasField('filename'): 22 | filename = req.field('filename') 23 | basename = os.path.basename(filename) 24 | filename = self.request().serverSidePath(basename) 25 | if not os.path.exists(filename): 26 | self.write('

' 27 | f'No such file {basename} exists

') 28 | return 29 | with open(filename, encoding='utf-8') as pspFile: 30 | text = pspFile.read() 31 | text = self.htmlEncode(text) 32 | text = text.replace('\n', '
').replace('\t', ' '*4) 33 | self.write(f'
{text}
') 34 | else: 35 | doc = self.__class__.__doc__.split('\n', 1) 36 | doc[1] = '

\n

'.join(doc[1].split('\n\n')) 37 | self.writeln(f'

{doc[0]}

\n

{doc[1]}

') 38 | -------------------------------------------------------------------------------- /webware/PSP/Examples/__init__.py: -------------------------------------------------------------------------------- 1 | """PSP Examples context.""" 2 | -------------------------------------------------------------------------------- /webware/PSP/Examples/index.psp: -------------------------------------------------------------------------------- 1 | <%@include file="Hello.psp"%> 2 | -------------------------------------------------------------------------------- /webware/PSP/Examples/my_include.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

This is an HTML file that is dynamically inserted.

5 | 6 |
7 | -------------------------------------------------------------------------------- /webware/PSP/Examples/my_include.psp: -------------------------------------------------------------------------------- 1 | 2 |

Hello from included file!

3 | <%@page imports="time" %> 4 |

<%= time.ctime() %>

5 | -------------------------------------------------------------------------------- /webware/PSP/Examples/psplogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebwareForPython/w4py3/7f75921926f58182cd3f28d5f974d13807df4ef6/webware/PSP/Examples/psplogo.png -------------------------------------------------------------------------------- /webware/PSP/PSPCompiler.py: -------------------------------------------------------------------------------- 1 | """A simple little module that organizes the actual page generation. 2 | 3 | Copyright (c) by Jay Love, 2000 (mailto:jsliv@jslove.org) 4 | 5 | Permission to use, copy, modify, and distribute this software and its 6 | documentation for any purpose and without fee or royalty is hereby granted, 7 | provided that the above copyright notice appear in all copies and that 8 | both that copyright notice and this permission notice appear in 9 | supporting documentation or portions thereof, including modifications, 10 | that you make. 11 | 12 | This software is based in part on work done by the Jakarta group. 13 | """ 14 | 15 | from .StreamReader import StreamReader 16 | from .ServletWriter import ServletWriter 17 | from .PSPParser import PSPParser 18 | from .ParseEventHandler import ParseEventHandler 19 | 20 | 21 | class Compiler: 22 | """The main compilation class.""" 23 | 24 | def __init__(self, context): 25 | self._ctxt = context 26 | 27 | def compile(self): 28 | """Compile the PSP context and return a set of all source files.""" 29 | reader = StreamReader(self._ctxt.getPspFileName(), self._ctxt) 30 | reader.init() 31 | writer = ServletWriter(self._ctxt) 32 | self._ctxt.setPSPReader(reader) 33 | self._ctxt.setServletWriter(writer) 34 | parser = PSPParser(self._ctxt) 35 | handler = ParseEventHandler(self._ctxt, parser) 36 | parser.setEventHandler(handler) 37 | handler.beginProcessing() 38 | parser.parse() 39 | sourceFiles = set(reader.sourceFiles) 40 | handler.endProcessing() 41 | writer.close() 42 | return sourceFiles 43 | -------------------------------------------------------------------------------- /webware/PSP/PSPPage.py: -------------------------------------------------------------------------------- 1 | """Default base class for PSP pages. 2 | 3 | This class is intended to be used in the future as the default base class 4 | for PSP pages in the event that some special processing is needed. 5 | Right now, no special processing is needed, so the default base class 6 | for PSP pages is the standard Webware Page. 7 | """ 8 | 9 | from Page import Page 10 | 11 | 12 | class PSPPage(Page): 13 | 14 | def __init__(self): 15 | self._parent = Page 16 | self._parent.__init__(self) 17 | 18 | def awake(self, transaction): 19 | self._parent.awake(self, transaction) 20 | self.out = transaction.response() 21 | -------------------------------------------------------------------------------- /webware/PSP/Properties.py: -------------------------------------------------------------------------------- 1 | name = 'Python Server Pages' 2 | 3 | status = 'stable' 4 | 5 | synopsis = ( 6 | "A Python Server Page (or PSP) is an HTML document" 7 | " with interspersed Python instructions that are interpreted" 8 | " as a template to generate dynamic content." 9 | " PSP is analogous to PHP, Microsoft's ASP and Sun's JSP." 10 | " PSP sits on top of (and requires) Webware and therefore" 11 | " benefits from its features.") 12 | 13 | webwareConfig = { 14 | 'examplePages': [ 15 | 'Hello', 16 | 'Braces', 17 | 'PSPTests', 18 | 'PSPTests-Braces', 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /webware/PSP/Tests/TestBraceConverter.py: -------------------------------------------------------------------------------- 1 | """Automated tests for the PSP BraceConverter 2 | 3 | Contributed 2000-09-04 by Dave Wallace 4 | """ 5 | 6 | import unittest 7 | from io import StringIO 8 | 9 | from PSP.BraceConverter import BraceConverter 10 | from PSP.ServletWriter import ServletWriter 11 | 12 | 13 | class DummyWriter(ServletWriter): # pylint: disable=abstract-method 14 | """Dummy writer for testing.""" 15 | 16 | def __init__(self): # pylint: disable=super-init-not-called 17 | self._file = StringIO() 18 | self._tabCount = 3 # base indentation of our test examples 19 | self._blockCount = 0 20 | self._indentSpaces = ServletWriter._spaces 21 | self._useTabs = False 22 | self._useBraces = False 23 | self._indent = ' ' 24 | self._userIndent = ServletWriter._emptyString 25 | 26 | def getOutput(self): 27 | return self._file.getvalue() 28 | 29 | def close(self): 30 | self._file.close() 31 | 32 | 33 | class TestBraceConverter(unittest.TestCase): 34 | 35 | @staticmethod 36 | def trim(text): 37 | return '\n'.join(filter(None, map(str.rstrip, text.splitlines()))) 38 | 39 | def assertParses(self, psp, expected): 40 | dummyWriter = DummyWriter() 41 | braceConverter = BraceConverter() 42 | for line in psp.splitlines(): 43 | braceConverter.parseLine(line, dummyWriter) 44 | output = dummyWriter.getOutput() 45 | dummyWriter.close() 46 | output, expected = list(map(self.trim, (output, expected))) 47 | self.assertEqual( 48 | output, expected, 49 | f'\n\nOutput:\n{output}\n\nExpected:\n{expected}\n') 50 | 51 | def testSimple(self): 52 | self.assertParses(''' 53 | if a == b: { return True } else: { return False } 54 | ''', ''' 55 | if a == b: 56 | return True 57 | else: 58 | return False 59 | ''') 60 | 61 | def testDict(self): 62 | self.assertParses(''' 63 | for x in range(10): { q = { 64 | 'test': x 65 | } 66 | print(x) 67 | } 68 | ''', ''' 69 | for x in range(10): 70 | q = { 71 | 'test': x 72 | } 73 | print(x) 74 | ''') 75 | 76 | def testNestedDict(self): 77 | self.assertParses(r''' 78 | if x: { q = {'test': x}; print(x)} else: { print("\"done\"") #""}{ 79 | x = { 'test1': {'sub2': {'subsub1': 2}} # yee ha 80 | } 81 | } print("all done") 82 | ''', r''' 83 | if x: 84 | q = {'test': x}; print(x) 85 | else: 86 | print("\"done\"") #""}{ 87 | x = { 'test1': {'sub2': {'subsub1': 2}} # yee ha 88 | } 89 | print("all done") 90 | ''') 91 | -------------------------------------------------------------------------------- /webware/PSP/Tests/TestContext.py: -------------------------------------------------------------------------------- 1 | """Automated tests for the PSP Context""" 2 | 3 | import os 4 | import unittest 5 | 6 | from PSP.Context import PSPCLContext 7 | 8 | 9 | class TestCLContext(unittest.TestCase): 10 | 11 | def testInit(self): 12 | pspFile = '/files/PSP/ContextTest.psp'.replace('/', os.sep) 13 | clc = PSPCLContext(pspFile) 14 | self.assertEqual(clc.getFullPspFileName(), pspFile) 15 | self.assertEqual(clc.getPspFileName(), 'ContextTest.psp') 16 | self.assertEqual(clc.getBaseUri(), '/files/PSP'.replace('/', os.sep)) 17 | 18 | def testPythonFileEncoding(self): 19 | clc = PSPCLContext('test.psp') 20 | self.assertEqual(clc.getPythonFileEncoding(), None) 21 | clc.setPythonFileEncoding('latin-1') 22 | self.assertEqual(clc.getPythonFileEncoding(), 'latin-1') 23 | 24 | def testResolveRelativeURI(self): 25 | pspFile = '/files/PSP/Test1.psp'.replace('/', os.sep) 26 | clc = PSPCLContext(pspFile) 27 | uri = clc.resolveRelativeURI('Test2.psp') 28 | self.assertEqual(uri, '/files/PSP/Test2.psp'.replace('/', os.sep)) 29 | self.assertEqual(clc.resolveRelativeURI(uri), uri) 30 | 31 | def testPSPReader(self): 32 | reader = object() 33 | clc = PSPCLContext('test.psp') 34 | clc.setPSPReader(reader) 35 | self.assertEqual(clc.getReader(), reader) 36 | 37 | def testClassName(self): 38 | clc = PSPCLContext('test.psp') 39 | clc.setClassName('ContextTestClass') 40 | self.assertEqual(clc.getServletClassName(), 'ContextTestClass') 41 | -------------------------------------------------------------------------------- /webware/PSP/Tests/__init__.py: -------------------------------------------------------------------------------- 1 | """PSP Plugin-in Tests""" 2 | -------------------------------------------------------------------------------- /webware/PSP/__init__.py: -------------------------------------------------------------------------------- 1 | """PSP Plug-in for Webware for Python""" 2 | 3 | from .PSPServletFactory import PSPServletFactory 4 | 5 | 6 | def installInWebware(application): 7 | application.addServletFactory(PSPServletFactory(application)) 8 | -------------------------------------------------------------------------------- /webware/PlugInLoader.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | 3 | from PlugIn import PlugIn 4 | 5 | 6 | class PlugInLoader: 7 | 8 | def __init__(self, app): 9 | self.app = app 10 | 11 | def loadPlugIn(self, name, module, verbose=True): 12 | """Load and return the given plug-in. 13 | 14 | May return None if loading was unsuccessful (in which case this method 15 | prints a message saying so). Used by `loadPlugIns` (note the **s**). 16 | """ 17 | try: 18 | plugIn = PlugIn(self.app, name, module) 19 | willNotLoadReason = plugIn.load(verbose=verbose) 20 | if willNotLoadReason: 21 | print(f' Plug-in {name} cannot be loaded because:\n' 22 | f' {willNotLoadReason}') 23 | return None 24 | plugIn.install() 25 | except Exception: 26 | print() 27 | print(f'Plug-in {name} raised exception.') 28 | raise 29 | return plugIn 30 | 31 | def loadPlugIns(self, plugInNames=None, verbose=None): 32 | """Load all plug-ins. 33 | 34 | A plug-in allows you to extend the functionality of Webware without 35 | necessarily having to modify its source. Plug-ins are loaded by 36 | Application at startup time, just before listening for requests. 37 | See the docs in `PlugIn` for more info. 38 | """ 39 | if plugInNames is None: 40 | plugInNames = self.app.setting('PlugIns') 41 | if verbose is None: 42 | verbose = self.app.setting('PrintPlugIns') 43 | 44 | if verbose: 45 | print('Plug-ins list:', ', '.join(plugInNames)) 46 | 47 | entryPoints = { 48 | entry_point.name: entry_point for entry_point 49 | in pkg_resources.iter_entry_points('webware.plugins')} 50 | 51 | plugIns = {} 52 | for name in plugInNames: 53 | if name in plugIns: 54 | if verbose: 55 | print(f'Plug-in {name} has already been loaded.') 56 | continue 57 | entry_point = entryPoints.get(name) 58 | if not entry_point: 59 | if verbose: 60 | print(f'Plug-in {name} has no entry point.') 61 | continue 62 | module = entry_point.load() 63 | plugIn = self.loadPlugIn(name, module, verbose=verbose) 64 | if plugIn: 65 | plugIns[name] = plugIn 66 | 67 | if verbose: 68 | print() 69 | 70 | return plugIns 71 | -------------------------------------------------------------------------------- /webware/Properties.py: -------------------------------------------------------------------------------- 1 | name = 'Webware for Python' 2 | 3 | version = (3, 0, 10) 4 | 5 | status = 'stable' 6 | 7 | requiredPyVersion = (3, 6) 8 | 9 | synopsis = ( 10 | "Webware for Python is a time-tested" 11 | " modular, object-oriented web framework.") 12 | 13 | webwareConfig = { 14 | 'examplePages': [ 15 | 'Welcome', 16 | 'ShowTime', 17 | 'CountVisits', 18 | 'Error', 19 | 'View', 20 | 'Introspect', 21 | 'Colors', 22 | 'ListBox', 23 | 'Forward', 24 | 'SecureCountVisits', 25 | 'FileUpload', 26 | 'RequestInformation', 27 | 'ImageDemo', 28 | 'DominateDemo', 29 | 'YattagDemo', 30 | 'DBUtilsDemo', 31 | 'AjaxSuggest', 32 | 'JSONRPCClient', 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /webware/Response.py: -------------------------------------------------------------------------------- 1 | """An abstract response""" 2 | 3 | from time import time 4 | 5 | from MiscUtils import AbstractError 6 | 7 | 8 | class Response: 9 | """The abstract response class. 10 | 11 | Response is a base class that offers the following: 12 | 13 | * A time stamp (indicating when the response was finished) 14 | * An output stream 15 | 16 | Response is an abstract class; developers typically use HTTPResponse. 17 | """ 18 | 19 | # region Init 20 | 21 | def __init__(self, trans, strmOut): 22 | self._strmOut = strmOut 23 | self._transaction = trans 24 | 25 | # endregion Init 26 | 27 | # region End time 28 | 29 | def endTime(self): 30 | return self._endTime 31 | 32 | def recordEndTime(self): 33 | """Record the end time of the response. 34 | 35 | Stores the current time as the end time of the response. This should 36 | be invoked at the end of deliver(). It may also be invoked by the 37 | application for those responses that never deliver due to an error. 38 | """ 39 | self._endTime = time() 40 | 41 | # endregion End time 42 | 43 | # region Output 44 | 45 | def write(self, output): 46 | raise AbstractError(self.__class__) 47 | 48 | def isCommitted(self): 49 | raise AbstractError(self.__class__) 50 | 51 | def deliver(self): 52 | raise AbstractError(self.__class__) 53 | 54 | def reset(self): 55 | raise AbstractError(self.__class__) 56 | 57 | def streamOut(self): 58 | return self._strmOut 59 | 60 | # endregion Output 61 | 62 | # region Cleanup 63 | 64 | def clearTransaction(self): 65 | del self._transaction 66 | 67 | # endregion Cleanup 68 | 69 | # region Exception reports 70 | 71 | _exceptionReportAttrNames = ['endTime'] 72 | 73 | def writeExceptionReport(self, handler): 74 | handler.writeTitle(self.__class__.__name__) 75 | handler.writeAttrs(self, self._exceptionReportAttrNames) 76 | 77 | # endregion Exception reports 78 | -------------------------------------------------------------------------------- /webware/Scripts/WSGIScript.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """WSGI script to be used with Webware for Python.""" 4 | 5 | import os 6 | import sys 7 | 8 | import webware 9 | 10 | libDirs = [] 11 | workDir = None 12 | development = None 13 | settings = {} 14 | 15 | webware.addToSearchPath() 16 | 17 | for libDir in reversed(libDirs): 18 | if libDir != '.' and libDir not in sys.path: 19 | sys.path.insert(0, libDir) 20 | 21 | if workDir is None: 22 | workDir = os.path.dirname(os.path.dirname(__file__)) 23 | 24 | if workDir: 25 | os.chdir(workDir) 26 | 27 | if '.' in libDirs: 28 | sys.path.insert(0, workDir) 29 | 30 | from Application import Application 31 | 32 | application = Application( 33 | workDir, settings=settings, development=development) 34 | -------------------------------------------------------------------------------- /webware/Scripts/WebwareCLI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """The Webware command line interface""" 4 | 5 | import argparse 6 | 7 | from .MakeAppWorkDir import addArguments as addMakeArguments, make 8 | from .WaitressServer import addArguments as addServeArguments, serve 9 | from ..Properties import version as versionTuple 10 | from ..MiscUtils.PropertiesObject import versionString 11 | 12 | 13 | def main(args=None): 14 | """Evaluate the command line arguments and execute subcommand.""" 15 | version = versionString(versionTuple) 16 | parser = argparse.ArgumentParser(description="Webware CLI") 17 | parser.add_argument('-v', '--version', action='version', 18 | version=f'Webware for Python {version}') 19 | subparsers = parser.add_subparsers( 20 | dest='command', title="Webware subcommands", 21 | help="name of the subcommand") 22 | serveParser = subparsers.add_parser( 23 | 'serve', help="Serve a Webware application using waitress") 24 | addServeArguments(serveParser) 25 | makeParser = subparsers.add_parser( 26 | 'make', help="Make a Webware application working directory") 27 | addMakeArguments(makeParser) 28 | args = parser.parse_args(args) 29 | command = args.command 30 | del args.command 31 | if command == 'make': 32 | make(args) 33 | elif command == 'serve': 34 | serve(args) 35 | 36 | 37 | if __name__ == '__main__': 38 | main() 39 | -------------------------------------------------------------------------------- /webware/Scripts/__init__.py: -------------------------------------------------------------------------------- 1 | """Webware console scripts and WSGI script""" 2 | -------------------------------------------------------------------------------- /webware/TaskKit/Properties.py: -------------------------------------------------------------------------------- 1 | name = 'TaskKit' 2 | 3 | status = 'stable' 4 | 5 | synopsis = ( 6 | "TaskKit provides a framework for the scheduling and management" 7 | " of tasks which can be triggered periodically or at specific times.") 8 | -------------------------------------------------------------------------------- /webware/TaskKit/Task.py: -------------------------------------------------------------------------------- 1 | """Base class for tasks.""" 2 | 3 | from MiscUtils import AbstractError 4 | 5 | 6 | class Task: 7 | """Abstract base class from which you have to derive your own tasks.""" 8 | 9 | def __init__(self): 10 | """Subclasses should invoke super for this method.""" 11 | # Nothing for now, but we might do something in the future. 12 | 13 | def run(self): 14 | """Override this method for you own tasks. 15 | 16 | Long running tasks can periodically use the proceed() method to check 17 | if a task should stop. 18 | """ 19 | raise AbstractError(self.__class__) 20 | 21 | # region Utility method 22 | 23 | def proceed(self): 24 | """Check whether this task should continue running. 25 | 26 | Should be called periodically by long tasks to check if the system 27 | wants them to exit. Returns True if its OK to continue, False if 28 | it's time to quit. 29 | """ 30 | return self._handle._isRunning 31 | 32 | # endregion Utility method 33 | 34 | # region Attributes 35 | 36 | def handle(self): 37 | """Return the task handle. 38 | 39 | A task is scheduled by wrapping a handler around it. It knows 40 | everything about the scheduling (periodicity and the like). 41 | Under normal circumstances you should not need the handler, 42 | but if you want to write period modifying run() methods, 43 | it is useful to have access to the handler. Use it with care. 44 | """ 45 | return self._handle 46 | 47 | def name(self): 48 | """Return the unique name under which the task was scheduled.""" 49 | return self._name 50 | 51 | # endregion Attributes 52 | 53 | # region Private method 54 | 55 | def _run(self, handle): 56 | """This is the actual run method for the Task thread. 57 | 58 | It is a private method which should not be overridden. 59 | """ 60 | self._name = handle.name() 61 | self._handle = handle 62 | try: 63 | self.run() 64 | except Exception: 65 | handle.notifyFailure() 66 | handle.notifyCompletion() 67 | 68 | # endregion Private method 69 | -------------------------------------------------------------------------------- /webware/TaskKit/Tests/SchedulerTest.py: -------------------------------------------------------------------------------- 1 | """Non-automatic test-run for the TaskKit scheduler.""" 2 | 3 | from time import time, localtime, sleep 4 | 5 | from TaskKit.Scheduler import Scheduler 6 | from TaskKit.Task import Task 7 | 8 | 9 | class SimpleTask(Task): 10 | 11 | increasingPeriod = False 12 | 13 | def run(self): 14 | if self.proceed(): 15 | print(self.name(), time()) 16 | if self.increasingPeriod: 17 | self.handle().setPeriod(self.handle().period() + 2) 18 | else: 19 | print("Should not proceed", self.name()) 20 | print(f"proceed for {self.name()}={self.proceed()}", 21 | f"isRunning={self._handle._isRunning}") 22 | 23 | 24 | class LongTask(Task): 25 | 26 | def run(self): 27 | while True: 28 | sleep(2) 29 | print(f"proceed for {self.name()}={self.proceed()}", 30 | f"isRunning={self._handle._isRunning}") 31 | if self.proceed(): 32 | print(">>", self.name(), time()) 33 | else: 34 | print("Should not proceed:", self.name()) 35 | return 36 | 37 | 38 | def main(): 39 | scheduler = Scheduler() 40 | scheduler.start() 41 | scheduler.addPeriodicAction(time(), 1, SimpleTask(), 'SimpleTask1') 42 | scheduler.addTimedAction(time() + 3, SimpleTask(), 'SimpleTask2') 43 | scheduler.addActionOnDemand(LongTask(), 'LongTask') 44 | sleep(4) 45 | print("Demanding 'LongTask'") 46 | scheduler.runTaskNow('LongTask') 47 | sleep(1) 48 | print("Stopping 'LongTask'") 49 | scheduler.stopTask('LongTask') 50 | sleep(2) 51 | print("Deleting 'SimpleTask1'") 52 | scheduler.unregisterTask('SimpleTask1') 53 | sleep(2) 54 | print("Waiting one minute for 'DailyTask'") 55 | scheduler.addDailyAction( 56 | localtime()[3], localtime()[4] + 1, SimpleTask(), "DailyTask") 57 | sleep(62) 58 | print("Calling stop") 59 | scheduler.stop() 60 | sleep(2) 61 | print("Test Complete") 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /webware/TaskKit/Tests/TestScheduler.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from TaskKit.Scheduler import Scheduler 4 | 5 | 6 | class TaskKitTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self._scheduler = Scheduler() 10 | 11 | def testSchedulerStarts(self): 12 | scheduler = self._scheduler 13 | scheduler.start() 14 | 15 | def tearDown(self): 16 | self._scheduler.stop() 17 | self._scheduler = None 18 | -------------------------------------------------------------------------------- /webware/TaskKit/Tests/__init__.py: -------------------------------------------------------------------------------- 1 | """TaskKit Plugin-in Tests""" 2 | -------------------------------------------------------------------------------- /webware/TaskKit/__init__.py: -------------------------------------------------------------------------------- 1 | """TaskKit Plug-in for Webware for Python""" 2 | 3 | 4 | def installInWebware(_application): 5 | pass 6 | -------------------------------------------------------------------------------- /webware/Tasks/SessionTask.py: -------------------------------------------------------------------------------- 1 | from TaskKit.Task import Task 2 | 3 | 4 | class SessionTask(Task): 5 | """The session sweeper task.""" 6 | 7 | def __init__(self, sessions): 8 | Task.__init__(self) 9 | self._sessionstore = sessions 10 | 11 | def run(self): 12 | if self.proceed(): 13 | self._sessionstore.cleanStaleSessions(self) 14 | -------------------------------------------------------------------------------- /webware/Tasks/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /webware/Testing/DebugPage.py: -------------------------------------------------------------------------------- 1 | from Page import Page 2 | 3 | 4 | class DebugPage(Page): 5 | 6 | def state(self): 7 | envVars = ('PATH_INFO', 'REQUEST_URI', 'SCRIPT_NAME') 8 | reqVars = ( 9 | 'urlPath', 'previousURLPaths', 10 | 'scriptName', 'servletPath', 'contextName', 11 | 'serverPath', 'serverSidePath', 'serverSideContextPath', 12 | 'extraURLPath') 13 | req = self.request() 14 | env = req._environ 15 | s = [] 16 | for key in envVars: 17 | value = env.get(key, "* not set *") 18 | s.append(f" * env[{key!r}] = {value}") 19 | for key in reqVars: 20 | value = getattr(req, key)() 21 | s.append(f" * req.{key}() = {value}") 22 | return '\n'.join(s) 23 | 24 | def writeContent(self): 25 | self.writeln(f'

{self.__class__.__name__}

') 26 | self.writeln(f'
{self.state()}
') 27 | -------------------------------------------------------------------------------- /webware/Testing/Dir/File.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /Testing/Dir/File.html 5 | 6 | 7 |

This is File.html in /Testing/Dir/

8 | 9 | 10 | -------------------------------------------------------------------------------- /webware/Testing/Dir/Forward2Target.py: -------------------------------------------------------------------------------- 1 | from Testing.DebugPage import DebugPage 2 | 3 | 4 | class Forward2Target(DebugPage): 5 | pass 6 | -------------------------------------------------------------------------------- /webware/Testing/Dir/Forward3.py: -------------------------------------------------------------------------------- 1 | from HTTPServlet import HTTPServlet 2 | 3 | 4 | class Forward3(HTTPServlet): 5 | 6 | def respond(self, transaction): 7 | transaction.application().forward( 8 | transaction, 9 | '../Forward3Target' + transaction.request().extraURLPath()) 10 | -------------------------------------------------------------------------------- /webware/Testing/Dir/Forward3Target.py: -------------------------------------------------------------------------------- 1 | from Testing.DebugPage import DebugPage 2 | 3 | 4 | class Forward3Target(DebugPage): 5 | pass 6 | -------------------------------------------------------------------------------- /webware/Testing/Dir/IncludeURLTest2.py: -------------------------------------------------------------------------------- 1 | from Testing.IncludeURLTest import IncludeURLTest 2 | 3 | 4 | class IncludeURLTest2(IncludeURLTest): 5 | """This is the second part of the URL test code. 6 | 7 | It gets included into the IncludeURLTest, and calls methods 8 | on other servlets to verify the references continue to work. 9 | """ 10 | 11 | def writeBody(self): 12 | self.writeln('') 13 | name = self.__class__.__name__ 14 | self.writeln(f'

{name}

') 15 | module = self.__module__ 16 | self.writeln(f'

class = {name},' 17 | f' module= {module}

') 18 | doc = self.__class__.__doc__.replace('\n\n', '

') 19 | self.writeln(f'

{doc}

') 20 | self.writeStatus() 21 | self.cmos( 22 | "/Testing/", "serverSidePath", 23 | "Expect to see the serverSidePath of the Testing/Main module.") 24 | self.writeln('') 25 | -------------------------------------------------------------------------------- /webware/Testing/Dir/__init__.py: -------------------------------------------------------------------------------- 1 | # Testing/Dir 2 | -------------------------------------------------------------------------------- /webware/Testing/Dir/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /Testing/Dir/ 5 | 6 | 7 |

This is index.html in /Testing/Dir/

8 | 9 | 10 | -------------------------------------------------------------------------------- /webware/Testing/EmbeddedServlet.py: -------------------------------------------------------------------------------- 1 | from Page import Page 2 | 3 | 4 | class EmbeddedServlet(Page): 5 | """Test extra path info. 6 | 7 | This servlet serves as a test for "extra path info"-style URLs such as: 8 | 9 | http://localhost/Webware/Servlet/Extra/Path/Info 10 | 11 | Where the servlet is embedded in the URL, rather than being the last 12 | component. This servlet simply prints its fields. 13 | """ 14 | 15 | def writeBody(self): 16 | fields = self.request().fields() 17 | self.writeln('

EmbeddedServlet

') 18 | self.writeln(f'
{self.__class__.__doc__}
') 19 | self.writeln(f'

Fields: {len(fields)}

') 20 | self.writeln('
    ') 21 | enc = self.htmlEncode 22 | for key, value in fields.items(): 23 | self.writeln(f'
  • {enc(key)} = {enc(value)}
  • ') 24 | self.writeln('
') 25 | -------------------------------------------------------------------------------- /webware/Testing/Forward1.py: -------------------------------------------------------------------------------- 1 | from HTTPServlet import HTTPServlet 2 | 3 | 4 | class Forward1(HTTPServlet): 5 | 6 | def respond(self, transaction): 7 | transaction.application().forward( 8 | transaction, 9 | 'Forward1Target' + transaction.request().extraURLPath()) 10 | -------------------------------------------------------------------------------- /webware/Testing/Forward1Target.py: -------------------------------------------------------------------------------- 1 | from Testing.DebugPage import DebugPage 2 | 3 | 4 | class Forward1Target(DebugPage): 5 | pass 6 | -------------------------------------------------------------------------------- /webware/Testing/Forward2.py: -------------------------------------------------------------------------------- 1 | from HTTPServlet import HTTPServlet 2 | 3 | 4 | class Forward2(HTTPServlet): 5 | 6 | def respond(self, transaction): 7 | transaction.application().forward( 8 | transaction, 9 | 'Dir/Forward2Target' + transaction.request().extraURLPath()) 10 | -------------------------------------------------------------------------------- /webware/Testing/Forward3Target.py: -------------------------------------------------------------------------------- 1 | from Testing.DebugPage import DebugPage 2 | 3 | 4 | class Forward3Target(DebugPage): 5 | pass 6 | -------------------------------------------------------------------------------- /webware/Testing/Servlet.py: -------------------------------------------------------------------------------- 1 | from Page import Page 2 | 3 | 4 | class Servlet(Page): 5 | """Test of extra path info.""" 6 | 7 | def title(self): 8 | return self.__doc__ 9 | 10 | def writeBody(self): 11 | self.writeln('

Webware Testing Servlet

') 12 | self.writeln(f'

{self.title()}

') 13 | req = self.request() 14 | self.writeln( 15 | f"

serverSidePath = {req.serverSidePath()}

") 16 | self.writeln( 17 | f"

extraURLPath = {req.extraURLPath()}

") 18 | -------------------------------------------------------------------------------- /webware/Testing/ServletImport.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from SidebarPage import SidebarPage 4 | 5 | modName = __name__ # should be Testing.ServletImport 6 | 7 | 8 | class ServletImport(SidebarPage): 9 | """Test of servlet import details.""" 10 | 11 | def cornerTitle(self): 12 | return 'Testing' 13 | 14 | def writeContent(self): 15 | wr = self.writeln 16 | wr('

Webware Servlet Import Test

') 17 | wr(f'

{self.__doc__}

') 18 | modNameFromClass = ServletImport.__module__ 19 | modNameConsistent = modName == modNameFromClass 20 | servletInSysModules = modName in sys.modules 21 | app = self.transaction().application() 22 | files = app._imp.fileList(update=False) 23 | servletFileWatched = __file__ in files 24 | wr( 25 | f"

modName = {modName}

" 26 | f"

modNameFromClass = {modNameFromClass}

" 27 | f"

modNameConsistent = {modNameConsistent}

" 28 | f"

servletInSysModules = {servletInSysModules}

" 29 | f"

servletFileWatched = {servletFileWatched}

" 30 | ) 31 | ok = modNameConsistent and servletInSysModules and servletInSysModules 32 | wr('

Servlet import test {}.

'.format( 33 | 'green' if ok else 'red', 'passed' if ok else 'failed')) 34 | -------------------------------------------------------------------------------- /webware/Testing/SetCookie.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | from SidebarPage import SidebarPage 5 | 6 | cookieValues = [ 7 | ('onclose', 'ONCLOSE'), 8 | ('expireNow', 'NOW'), 9 | ('expireNever', 'NEVER'), 10 | ('oneMinute', '+1m'), 11 | ('oneWeek', '+1w'), 12 | ('oneHourAndHalf', '+ 1h 30m'), 13 | ('timeIntTenSec', time.time() + 10), 14 | ('tupleOneYear', (time.localtime()[0] + 1,) + time.localtime()[1:]), 15 | ('dt1day', datetime.datetime.now() + datetime.timedelta(1)), 16 | ('dt2min', datetime.timedelta(minutes=2)) 17 | ] 18 | 19 | cookieIndex = 1 20 | 21 | 22 | class SetCookie(SidebarPage): 23 | 24 | def cornerTitle(self): 25 | return 'Testing' 26 | 27 | def writeContent(self): 28 | global cookieIndex 29 | res = self.response() 30 | req = self.request() 31 | enc = self.htmlEncode 32 | self.writeln('

The time right now is:

') 33 | t = time.strftime('%a, %d-%b-%Y %H:%M:%S GMT', time.gmtime()) 34 | self.writeln(f'

{t}

') 35 | self.writeln('

Cookies received:

\n') 36 | self.writeln('
    ') 37 | for name, value in req.cookies().items(): 38 | self.writeln(f'
  • {name!r} = {enc(value)}
  • ') 39 | self.writeln('
') 40 | for name, expire in cookieValues: 41 | res.setCookie(name, f'Value #{cookieIndex}', expires=expire) 42 | cookieIndex += 1 43 | self.writeln('

Cookies being sent:

\n') 44 | self.writeln('
') 45 | for name, cookie in res.cookies().items(): 46 | self.writeln(f'
{name!r} sends:
') 47 | self.writeln(f'
{enc(cookie.headerValue())}
') 48 | self.writeln('
') 49 | -------------------------------------------------------------------------------- /webware/Testing/TestCases.data: -------------------------------------------------------------------------------- 1 | # TestCases.data 2 | 3 | # This file is used by Testing/Main.py 4 | # Each line describes a test case. 5 | # Lines that are blank or begin with '#' are ignored. 6 | 7 | # Good URLs 8 | / \ 9 | /Welcome \ 10 | /Welcome.py \ 11 | /Examples/Welcome \ 12 | /Examples/ \ 13 | /Examples/Welcome.py --> Show /Examples/Welcome 14 | /Admin/ \ 15 | /Admin/Main --> Show admin pages 16 | 17 | # Redirects 18 | --> Redirect to / 19 | /Admin --> Redirect to /Admin/ 20 | /Examples --> Redirect to /Examples/ 21 | 22 | # Display files 23 | /Testing/Dir/File.html --> Display the file 24 | /Testing/Dir \ 25 | /Testing/Dir/ --> Display the index file 26 | 27 | # Bad: Slashes after files 28 | /Welcome/ \ 29 | /Examples/Welcome/ --> Error 404: Not Found
if ExtraPathInfo is not set 30 | 31 | # Bad: Unknown URLs (wrong name, slashes after files) 32 | /BadURL \ 33 | /BadURL/ \ 34 | /Examples/BadURL \ 35 | /Examples/BadURL/ \ 36 | /Examples/BadURL/MoreBad \ 37 | /File.badext \ 38 | /Examples/File.badext --> Error 404: Not Found
or if ExtraPathInfo is set, then
the Examples/Welcome page
displays extra path info. 39 | 40 | # Embedded Servlets 41 | /Testing/Servlet/ \ 42 | /Testing/Servlet/Extra/Path/Info \ 43 | /Testing/Servlet/Extra/Path/Info/ \ 44 | /Testing/Servlet/More/Info? --> Error 404: Not Found
if ExtraPathInfo is not set,
otherwise the test servlet
displays extra path info. 45 | 46 | # Including 47 | /Testing/IncludeURLTest --> IncludeURLTest test 48 | /Testing/Dir/IncludeURLTest2 --> lower level IncludeURLTest test 49 | 50 | # Forwarding 51 | /Testing/Forward1 --> Forward1Target 52 | /Testing/Forward2 --> Dir/Forward2Target 53 | /Testing/Dir/Forward3 --> Forward3Target 54 | 55 | # Fields 56 | /Testing/FieldStorage --> Test of WebUtils.FieldStorage 57 | 58 | # Cookies 59 | /Testing/SetCookie --> Test of HTTPResponse.setCookie 60 | 61 | # If-Modified-Since 62 | /Testing/TestIMS --> TestIMS passed 63 | 64 | # Servlet import details 65 | /Testing/ServletImport --> Servlet import test passed 66 | -------------------------------------------------------------------------------- /webware/Testing/URL/__init__.py: -------------------------------------------------------------------------------- 1 | # Testing/URL 2 | 3 | urlRedirect = { 4 | 'dummy': 'whatever', 5 | 'test1redir': 'test1', 6 | } 7 | -------------------------------------------------------------------------------- /webware/Testing/URL/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | URL parsing tests 6 | 7 | 8 |

URL parsing tests

9 | See: 10 | 22 |
23 | Last modified: Wed Mar 19 04:56:29 CST 2003 24 | 25 | 26 | -------------------------------------------------------------------------------- /webware/Testing/URL/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | simple URL resolution 6 | 7 | 8 | Simple URL resolution 9 |
10 |
11 | Last modified: Sun Mar 16 20:14:23 CST 2003 12 | 13 | 14 | -------------------------------------------------------------------------------- /webware/Testing/URL/test1/Main.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as Main # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/test1/__init__.py: -------------------------------------------------------------------------------- 1 | # URL/test1 2 | -------------------------------------------------------------------------------- /webware/Testing/URL/test2/Main.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as Main # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/test2/__init__.py: -------------------------------------------------------------------------------- 1 | # URL/test2 2 | 3 | from URLParser import URLParameterParser as SubParser # noqa: F401 4 | -------------------------------------------------------------------------------- /webware/Testing/URL/test3/Main.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as Main # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/test3/__init__.py: -------------------------------------------------------------------------------- 1 | # URL/test3 2 | 3 | from URLParser import URLParameterParser 4 | 5 | urlParserHook = URLParameterParser() 6 | -------------------------------------------------------------------------------- /webware/Testing/URL/test4/Main.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as Main # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/test4/__init__.py: -------------------------------------------------------------------------------- 1 | # URL/test1 2 | 3 | import os 4 | 5 | from URLParser import URLParameterParser, FileParser 6 | 7 | urlParser = URLParameterParser(FileParser(os.path.dirname(__file__))) 8 | -------------------------------------------------------------------------------- /webware/Testing/URL/test5/__init__.py: -------------------------------------------------------------------------------- 1 | # URL/test5 2 | 3 | urlJoins = ['../test5join1', '../test5join2'] 4 | -------------------------------------------------------------------------------- /webware/Testing/URL/test5/url3.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as url3 # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/test5join1/__init__.py: -------------------------------------------------------------------------------- 1 | # URL/test5join1 2 | -------------------------------------------------------------------------------- /webware/Testing/URL/test5join1/url1.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as url1 # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/test5join2/__init__.py: -------------------------------------------------------------------------------- 1 | # URL/test5join2 2 | -------------------------------------------------------------------------------- /webware/Testing/URL/test5join2/url1.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as url1 # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/test5join2/url2.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as url2 # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/util.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | 3 | from Page import Page 4 | from WebUtils.Funcs import htmlEncode as enc 5 | 6 | 7 | class Inspector(Page): 8 | 9 | def writeContent(self): 10 | req = self.request() 11 | self.write('Path:
\n') 12 | self.write(f'{enc(req.extraURLPath())}

\n') 13 | self.write('Variables:
\n') 14 | self.write('') 15 | for name in sorted(req.fields()): 16 | self.write( 17 | f'' 18 | f'\n') 19 | self.write('
{enc(name)}:{enc(req.field(name))}

\n') 20 | self.write('Server-side path:
\n') 21 | self.write(f'{enc(req.serverSidePath())}

\n') 22 | -------------------------------------------------------------------------------- /webware/Testing/URL/vhosts/Main.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from Testing.URL.util import Inspector as Main # noqa: F401 3 | -------------------------------------------------------------------------------- /webware/Testing/URL/vhosts/__init__.py: -------------------------------------------------------------------------------- 1 | from URLParser import URLParser 2 | 3 | 4 | class VHostParser(URLParser): 5 | 6 | def parseHook(self, trans, requestPath, app, hook): 7 | host = trans.request().environ()['HTTP_HOST'] 8 | hook.parse(trans, f'/{host}{requestPath}', app) 9 | 10 | 11 | urlParserHook = VHostParser() 12 | -------------------------------------------------------------------------------- /webware/Testing/__init__.py: -------------------------------------------------------------------------------- 1 | """Testing context""" 2 | -------------------------------------------------------------------------------- /webware/Testing/test.html: -------------------------------------------------------------------------------- 1 |

This is an ordinary HTML file.

2 | -------------------------------------------------------------------------------- /webware/Testing/test.text: -------------------------------------------------------------------------------- 1 | Hi, there! 2 | -------------------------------------------------------------------------------- /webware/Tests/TestEndToEnd/__init__.py: -------------------------------------------------------------------------------- 1 | """Webware end-to-end tests""" 2 | -------------------------------------------------------------------------------- /webware/Tests/TestMocking.py: -------------------------------------------------------------------------------- 1 | """Test Webware Mock Environment""" 2 | 3 | import io 4 | import sys 5 | import unittest 6 | 7 | import webware 8 | 9 | 10 | class TestMocking(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.sysPath = sys.path 14 | self.webwarePath = webware.__path__[0] 15 | sys.path = [path for path in sys.path if path != self.webwarePath] 16 | self.sysStdout = sys.stdout 17 | sys.stdout = io.StringIO() 18 | self.getOutput = sys.stdout.getvalue 19 | 20 | def tearDown(self): 21 | sys.stdout = self.sysStdout 22 | sys.path = self.sysPath 23 | 24 | def testAddToSearchPath(self): 25 | assert self.webwarePath not in sys.path 26 | webware.addToSearchPath() 27 | assert self.webwarePath in sys.path 28 | 29 | def testLoadPlugins(self): 30 | webware.addToSearchPath() 31 | app = webware.mockAppWithPlugins( 32 | self.webwarePath, settings={'PrintPlugIns': True}) 33 | self.assertEqual(app.__class__.__name__, 'MockApplication') 34 | output = self.getOutput().splitlines() 35 | output = [line.split(' at ', 1)[0] for line in output] 36 | self.assertEqual(output, [ 37 | 'Plug-ins list: MiscUtils, WebUtils, TaskKit, UserKit, PSP', 38 | 'Loading plug-in: MiscUtils', 'Loading plug-in: WebUtils', 39 | 'Loading plug-in: TaskKit', 'Loading plug-in: UserKit', 40 | 'Loading plug-in: PSP', '']) 41 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/Application.py: -------------------------------------------------------------------------------- 1 | """Mock Webware Application class.""" 2 | 3 | 4 | class Application: 5 | """Mock application.""" 6 | 7 | _sessionDir = 'SessionStoreTestDir' 8 | _alwaysSaveSessions = True 9 | _retainSessions = True 10 | _sessionTimeout = 60 * 60 11 | _sessionPrefix = '' 12 | _sessionName = '_SID_' 13 | 14 | def setting(self, key, default=None): 15 | return { 16 | 'SessionTimeout': self._sessionTimeout // 60, 17 | 'SessionPrefix': self._sessionPrefix, 18 | 'SessionName': self._sessionName, 19 | 'DynamicSessionTimeout': 1, 'MaxDynamicMemorySessions': 3, 20 | 'MemcachedOnIteration': None, 21 | 'Debug': {'Sessions': False} 22 | }.get(key, default) 23 | 24 | def handleException(self): 25 | raise RuntimeError('Application Error') 26 | 27 | def sessionTimeout(self, _trans=None): 28 | return self._sessionTimeout 29 | 30 | def sessionPrefix(self, _trans=None): 31 | return self._sessionPrefix 32 | 33 | def sessionName(self, _trans=None): 34 | return self._sessionName 35 | 36 | def hasSession(self, _sessionId): 37 | return False 38 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/Session.py: -------------------------------------------------------------------------------- 1 | """Mock Webware Session class.""" 2 | 3 | from time import time 4 | 5 | 6 | class Session: 7 | """Mock session.""" 8 | 9 | _lastExpired = None 10 | 11 | def __init__(self, identifier=7, value=None): 12 | self._identifier = f'foo-{identifier}' 13 | self._data = {'bar': value or identifier * 6} 14 | self._expired = self._dirty = False 15 | self._timeout = 1800 16 | self._lastAccessTime = time() - identifier * 400 17 | self._isNew = True 18 | 19 | def identifier(self): 20 | return self._identifier 21 | 22 | def expiring(self): 23 | self._expired = True 24 | Session._lastExpired = self._identifier 25 | 26 | def isDirty(self): 27 | return self._dirty 28 | 29 | def setDirty(self, dirty=True): 30 | self._dirty = dirty 31 | 32 | def isExpired(self): 33 | return self._expired 34 | 35 | def timeout(self): 36 | return self._timeout 37 | 38 | def data(self): 39 | return self._data 40 | 41 | def bar(self): 42 | return self._data.get('bar') 43 | 44 | def setBar(self, value): 45 | self._isNew = False 46 | self._data['bar'] = value 47 | 48 | def isNew(self): 49 | return self._isNew 50 | 51 | def lastAccessTime(self): 52 | return self._lastAccessTime 53 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/TestSessionDynamicStore.py: -------------------------------------------------------------------------------- 1 | from SessionDynamicStore import SessionDynamicStore 2 | 3 | from .TestSessionMemoryStore import TestSessionMemoryStore 4 | 5 | 6 | class SessionDynamicStoreTest(TestSessionMemoryStore): 7 | 8 | _storeClass = SessionDynamicStore 9 | 10 | def testCleanStaleSessions(self): 11 | store = self._store 12 | memoryStore = store._memoryStore # pylint: disable=no-member 13 | fileStore = store._fileStore # pylint: disable=no-member 14 | self.assertEqual(len(memoryStore), 7) 15 | self.assertEqual(len(fileStore), 0) 16 | TestSessionMemoryStore.testCleanStaleSessions(self) 17 | self.assertEqual(len(memoryStore), 3) 18 | self.assertEqual(len(fileStore), 2) 19 | self.assertTrue('foo-0' in memoryStore and 'foo-2' in memoryStore) 20 | self.assertTrue('foo-3' in fileStore and 'foo-4' in fileStore) 21 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/TestSessionFileStore.py: -------------------------------------------------------------------------------- 1 | from SessionMemoryStore import SessionMemoryStore 2 | from SessionFileStore import SessionFileStore 3 | 4 | from .TestSessionMemoryStore import TestSessionMemoryStore 5 | 6 | 7 | class SessionFileStoreTest(TestSessionMemoryStore): 8 | 9 | _storeClass = SessionFileStore 10 | _storeIsOrdered = False 11 | 12 | def testMemoryStoreRestoreFiles(self): 13 | app = self._app 14 | store = SessionMemoryStore(app) 15 | self.assertEqual(len(store), 7) 16 | self.assertTrue('foo-0' in store and 'foo-6' in store) 17 | store = SessionMemoryStore(app, restoreFiles=False) 18 | self.assertEqual(len(store), 0) 19 | self.assertFalse('foo-0' in store or 'foo-6' in store) 20 | 21 | def testFileStoreRestoreFiles(self): 22 | app = self._app 23 | store = SessionFileStore(app) 24 | self.assertEqual(len(store), 7) 25 | self.assertTrue('foo-0' in store and 'foo-6' in store) 26 | store = SessionFileStore(app, restoreFiles=False) 27 | self.assertEqual(len(store), 0) 28 | self.assertFalse('foo-0' in store or 'foo-6' in store) 29 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/TestSessionMemcachedStore.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import,wrong-import-order 2 | from . import memcache # noqa: F401 (mock memcache installation) 3 | 4 | from SessionMemcachedStore import SessionMemcachedStore 5 | 6 | from .TestSessionMemoryStore import TestSessionMemoryStore 7 | 8 | 9 | class SessionMemcachedStoreTest(TestSessionMemoryStore): 10 | 11 | _storeClass = SessionMemcachedStore 12 | 13 | def setUp(self): 14 | TestSessionMemoryStore.setUp(self) 15 | self.setOnIteration() 16 | 17 | def tearDown(self): 18 | self.setOnIteration() 19 | TestSessionMemoryStore.tearDown(self) 20 | 21 | def setOnIteration(self, onIteration=None): 22 | self._store._onIteration = onIteration 23 | 24 | def testLen(self): 25 | self.assertEqual(len(self._store), 0) 26 | self.setOnIteration('Error') 27 | with self.assertRaises(NotImplementedError): 28 | len(self._store) 29 | 30 | def testIter(self): 31 | keys = list(iter(self._store)) 32 | self.assertEqual(keys, []) 33 | self.setOnIteration('Error') 34 | with self.assertRaises(NotImplementedError): 35 | list(iter(self._store)) 36 | 37 | def testKeys(self): 38 | keys = self._store.keys() # pylint: disable=assignment-from-no-return 39 | self.assertEqual(keys, []) 40 | self.setOnIteration('Error') 41 | with self.assertRaises(NotImplementedError): 42 | self._store.keys() 43 | 44 | def testItems(self): 45 | items = self._store.items() 46 | self.assertEqual(items, []) 47 | self.setOnIteration('Error') 48 | with self.assertRaises(NotImplementedError): 49 | self._store.items() 50 | 51 | def testIterItems(self): 52 | items = list(self._store.iteritems()) 53 | self.assertEqual(items, []) 54 | self.setOnIteration('Error') 55 | with self.assertRaises(NotImplementedError): 56 | list(self._store.iteritems()) 57 | 58 | def testValues(self): 59 | values = self._store.values() 60 | self.assertEqual(values, []) 61 | self.setOnIteration('Error') 62 | with self.assertRaises(NotImplementedError): 63 | self._store.values() 64 | 65 | def testIterValues(self): 66 | values = list(self._store.values()) 67 | self.assertEqual(values, []) 68 | self.setOnIteration('Error') 69 | with self.assertRaises(NotImplementedError): 70 | self._store.values() 71 | 72 | def testClear(self): 73 | self._store.clear() 74 | self.setOnIteration('Error') 75 | with self.assertRaises(NotImplementedError): 76 | self._store.clear() 77 | 78 | def testCleanStaleSessions(self): 79 | self._store.cleanStaleSessions() 80 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/TestSessionRedisStore.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import,wrong-import-order 2 | from . import redis # noqa: F401 (mock redis installation) 3 | 4 | from SessionRedisStore import SessionRedisStore 5 | 6 | from .TestSessionMemoryStore import TestSessionMemoryStore 7 | 8 | 9 | class SessionRedisStoreTest(TestSessionMemoryStore): 10 | 11 | _storeClass = SessionRedisStore 12 | 13 | def setUp(self): 14 | TestSessionMemoryStore.setUp(self) 15 | 16 | def tearDown(self): 17 | TestSessionMemoryStore.tearDown(self) 18 | 19 | def testCleanStaleSessions(self): 20 | self._store.cleanStaleSessions() 21 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/TestSessionShelveStore.py: -------------------------------------------------------------------------------- 1 | from SessionShelveStore import SessionShelveStore 2 | 3 | from .TestSessionMemoryStore import TestSessionMemoryStore 4 | 5 | 6 | class SessionShelveStoreTest(TestSessionMemoryStore): 7 | 8 | _storeClass = SessionShelveStore 9 | _storeIsOrdered = False 10 | 11 | def testFileShelveRestoreFiles(self): 12 | app = self._app 13 | store = self._store 14 | self.assertEqual(len(store), 7) 15 | session = store['foo-3'] 16 | self.assertTrue('foo-0' in store and 'foo-6' in store) 17 | store = SessionShelveStore( 18 | app, restoreFiles=False, filename='Session.Store2') 19 | self.assertEqual(len(store), 0) 20 | self.assertFalse('foo-0' in store or 'foo-6' in store) 21 | store['foo-3'] = session 22 | store.storeAllSessions() 23 | store = SessionShelveStore(app, filename='Session.Store2') 24 | self.assertEqual(len(store), 1) 25 | self.assertTrue('foo-3' in store) 26 | self.assertFalse('foo-0' in store or 'foo-6' in store) 27 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/TestSessionStore.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from io import BytesIO 4 | 5 | from SessionStore import SessionStore 6 | 7 | from .Application import Application 8 | from .Session import Session 9 | 10 | 11 | class TestSessionStore(unittest.TestCase): 12 | 13 | _storeClass = SessionStore 14 | 15 | _app = Application() 16 | 17 | def setUp(self): 18 | Session._lastExpired = None 19 | self._store = self._storeClass(self._app) 20 | 21 | def testApplication(self): 22 | self.assertEqual(self._store.application(), self._app) 23 | 24 | def testEncodeDecode(self): 25 | session = Session() 26 | f = BytesIO() 27 | self._store.encoder()(session, f) 28 | output = f.getvalue() 29 | f.close() 30 | self.assertTrue(isinstance(output, bytes)) 31 | f = BytesIO(output) 32 | output = self._store.decoder()(f) 33 | f.close() 34 | self.assertTrue(type(output) is type(session)) 35 | self.assertEqual(output._data, session._data) 36 | 37 | def testSetEncoderDecoder(self): 38 | def encoder(obj, f): 39 | return f.write(repr(obj).encode('ascii')) 40 | 41 | def decoder(f): 42 | return eval(f.read().decode('ascii')) 43 | 44 | self._store.setEncoderDecoder(encoder, decoder) 45 | self.assertEqual(self._store.encoder(), encoder) 46 | self.assertEqual(self._store.decoder(), decoder) 47 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/Transaction.py: -------------------------------------------------------------------------------- 1 | """"Mock Webware Transaction class.""" 2 | 3 | from .Application import Application 4 | 5 | 6 | class Transaction: 7 | 8 | def __init__(self): 9 | self._application = Application() 10 | 11 | def application(self): 12 | return self._application 13 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/__init__.py: -------------------------------------------------------------------------------- 1 | """Webware SessionStore tests""" 2 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/memcache.py: -------------------------------------------------------------------------------- 1 | """Mock python-memcached module.""" 2 | 3 | import sys 4 | from copy import copy 5 | 6 | sys.modules['memcache'] = sys.modules[__name__] 7 | 8 | data = {} # our mock memcache 9 | 10 | 11 | # pylint: disable=unused-argument, invalid-name 12 | 13 | class Client: 14 | """Mock memcache client.""" 15 | 16 | def __init__(self, servers, debug=0, pickleProtocol=0): 17 | self._connected = True 18 | 19 | def set(self, key, val, time=0): 20 | if self._connected: 21 | if val is not None: 22 | data[key] = val 23 | return 1 24 | return 0 25 | 26 | def get(self, key): 27 | if self._connected: 28 | return copy(data.get(key)) 29 | 30 | def delete(self, key, time=0): 31 | if self._connected: 32 | if key in data: 33 | del data[key] 34 | return 1 35 | return 0 36 | 37 | def disconnect_all(self): 38 | self._connected = False 39 | -------------------------------------------------------------------------------- /webware/Tests/TestSessions/redis.py: -------------------------------------------------------------------------------- 1 | """Mock redis-py module.""" 2 | 3 | import sys 4 | from copy import copy 5 | 6 | sys.modules['redis'] = sys.modules[__name__] 7 | 8 | data = {} # our mock redis 9 | 10 | 11 | class ConnectionPool: 12 | 13 | def __init__(self, redis): 14 | self._redis = redis 15 | 16 | def disconnect(self): 17 | self._redis._connected = False 18 | 19 | 20 | # pylint: disable=unused-argument, invalid-name 21 | 22 | class StrictRedis: 23 | """Mock Redis client.""" 24 | 25 | def __init__(self, host='localhost', port=6379, db=0, password=None): 26 | self.connection_pool = ConnectionPool(self) 27 | self._connected = True 28 | 29 | def setex(self, name, time, value): 30 | if self._connected: 31 | if value is not None: 32 | data[name] = value 33 | 34 | def get(self, name): 35 | if self._connected: 36 | return copy(data.get(name)) 37 | 38 | def delete(self, *names): 39 | if self._connected: 40 | for name in names: 41 | del data[name] 42 | 43 | def exists(self, name): 44 | if self._connected: 45 | return name in data 46 | 47 | def keys(self, pattern='*'): 48 | if self._connected: 49 | if not pattern.endswith('*'): 50 | raise ValueError('bad pattern') 51 | pattern = pattern[:-1] 52 | if not pattern: 53 | return list(data) 54 | return [k for k in data if k.startswith(pattern)] 55 | 56 | def flushdb(self): 57 | if self._connected: 58 | data.clear() 59 | -------------------------------------------------------------------------------- /webware/Tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Webware tests""" 2 | -------------------------------------------------------------------------------- /webware/UserKit/HierRole.py: -------------------------------------------------------------------------------- 1 | """The HierRole class.""" 2 | 3 | from .Role import Role 4 | 5 | 6 | class HierRole(Role): 7 | """HierRole is a hierarchical role. 8 | 9 | It points to its parent roles. The hierarchy cannot have cycles. 10 | """ 11 | 12 | def __init__(self, name, description=None, superRoles=None): 13 | Role.__init__(self, name, description) 14 | if superRoles is None: 15 | superRoles = [] 16 | for role in superRoles: 17 | if not isinstance(role, Role): 18 | raise TypeError(f'{role} is not a Role object') 19 | self._superRoles = superRoles[:] 20 | 21 | def playsRole(self, role): 22 | """Check whether the receiving role plays the role that is passed in. 23 | 24 | This implementation provides for the inheritance supported by HierRole. 25 | """ 26 | return self == role or any( 27 | superRole.playsRole(role) for superRole in self._superRoles) 28 | -------------------------------------------------------------------------------- /webware/UserKit/Properties.py: -------------------------------------------------------------------------------- 1 | name = 'UserKit' 2 | 3 | synopsis = ( 4 | "UserKit provides for the management of users" 5 | " including passwords, user data, server-side archiving and caching.") 6 | -------------------------------------------------------------------------------- /webware/UserKit/Role.py: -------------------------------------------------------------------------------- 1 | """The basic Role class.""" 2 | 3 | from MiscUtils.Funcs import positiveId 4 | 5 | 6 | class Role: 7 | """Used in conjunction with RoleUser to provide role-based security. 8 | 9 | All roles have a name and a description and respond to playsRole(). 10 | 11 | RoleUser also responds to playsRole() and is the more popular entry point 12 | for programmers. Application code may then do something along the lines of: 13 | 14 | if user.playsRole('admin'): 15 | self.displayAdminMenuItems() 16 | 17 | See also: 18 | * class HierRole 19 | * class RoleUser 20 | """ 21 | 22 | # region Init 23 | 24 | def __init__(self, name, description=None): 25 | self._name = name 26 | self._description = description 27 | 28 | # endregion Init 29 | 30 | # region Attributes 31 | 32 | def name(self): 33 | return self._name 34 | 35 | def setName(self, name): 36 | self._name = name 37 | 38 | def description(self): 39 | return self._description 40 | 41 | def setDescription(self, description): 42 | self._description = description 43 | 44 | # endregion Attributes 45 | 46 | # region As strings 47 | 48 | def __str__(self): 49 | return str(self._name) 50 | 51 | def __repr__(self): 52 | return f'<{self.__class__} {self._name!r} at {positiveId(self):x}>' 53 | 54 | # endregion As strings 55 | 56 | # region The big question 57 | 58 | def playsRole(self, role): 59 | """Return true if the receiving role plays the role passed in. 60 | 61 | For Role, this is simply a test of equality. Subclasses may override 62 | this method to provide richer semantics (such as hierarchical roles). 63 | """ 64 | if not isinstance(role, Role): 65 | raise TypeError(f'{role} is not a Role object') 66 | return self == role 67 | 68 | # endregion The big question 69 | -------------------------------------------------------------------------------- /webware/UserKit/RoleUser.py: -------------------------------------------------------------------------------- 1 | """The RoleUser class.""" 2 | 3 | from .User import User 4 | 5 | 6 | class RoleUser(User): 7 | """In conjunction with Role, provides role-based users and security. 8 | 9 | See the doc for playsRole() for an example. 10 | 11 | Note that this class plays nicely with both Role and HierRole, 12 | e.g., no "HierRoleUser" is needed when making use of HierRoles. 13 | 14 | See also: 15 | * class Role 16 | * class HierRole 17 | """ 18 | 19 | # region Init 20 | 21 | def __init__(self, manager=None, name=None, password=None): 22 | User.__init__(self, manager, name, password) 23 | self._roles = [] 24 | self._rolesByName = {} 25 | 26 | # endregion Init 27 | 28 | # region Accessing roles 29 | 30 | def roles(self): 31 | """Return a direct list of the user's roles. 32 | 33 | Do not modify. 34 | """ 35 | return self._roles 36 | 37 | def setRoles(self, listOfRoles): 38 | """Set all the roles for the user. 39 | 40 | Each role in the list may be a valid role name or a Role object. 41 | 42 | Implementation note: depends on addRoles(). 43 | """ 44 | self._roles = [] 45 | self.addRoles(listOfRoles) 46 | 47 | def addRoles(self, listOfRoles): 48 | """Add additional roles for the user. 49 | 50 | Each role in the list may be a valid role name or a Role object. 51 | """ 52 | start = len(self._roles) 53 | self._roles.extend(listOfRoles) 54 | 55 | # Convert names to role objects and update self._rolesByName 56 | index = start 57 | numRoles = len(self._roles) 58 | while index < numRoles: 59 | role = self._roles[index] 60 | if isinstance(role, str): 61 | role = self._manager.roleForName(role) 62 | self._roles[index] = role 63 | self._rolesByName[role.name()] = role 64 | index += 1 65 | 66 | def playsRole(self, roleOrName): 67 | """Check whether the user plays the given role. 68 | 69 | More specifically, if any of the user's roles return true for 70 | role.playsRole(otherRole), this method returns True. 71 | 72 | The application of this popular method often looks like this: 73 | if user.playsRole('admin'): 74 | self.displayAdminMenuItems() 75 | """ 76 | if isinstance(roleOrName, str): 77 | roleOrName = self._manager.roleForName(roleOrName) 78 | return any(role.playsRole(roleOrName) for role in self._roles) 79 | 80 | # endregion Accessing roles 81 | -------------------------------------------------------------------------------- /webware/UserKit/RoleUserManager.py: -------------------------------------------------------------------------------- 1 | """The RoleUserManager class.""" 2 | 3 | from .RoleUserManagerMixIn import RoleUserManagerMixIn 4 | from .UserManager import UserManager 5 | 6 | 7 | class RoleUserManager(UserManager, RoleUserManagerMixIn): 8 | """See the base classes for more information.""" 9 | 10 | def __init__(self, userClass=None): 11 | UserManager.__init__(self, userClass) 12 | RoleUserManagerMixIn.__init__(self) 13 | -------------------------------------------------------------------------------- /webware/UserKit/RoleUserManagerMixIn.py: -------------------------------------------------------------------------------- 1 | """The RoleUserManager mixin.""" 2 | 3 | from MiscUtils import NoDefault 4 | 5 | from .RoleUser import RoleUser 6 | from .Role import Role 7 | 8 | 9 | class RoleUserManagerMixIn: 10 | """Mixin class for mapping names to roles. 11 | 12 | This mixin adds the functionality of keeping a dictionary mapping 13 | names to role instances. Several accessor methods are provided for this. 14 | """ 15 | 16 | # region Init 17 | 18 | def __init__(self): 19 | self._roles = {} 20 | self.initUserClass() 21 | 22 | def initUserClass(self): 23 | """Invoked by __init__ to set the default user class to RoleUser.""" 24 | # pylint: disable=no-member 25 | self.setUserClass(RoleUser) 26 | 27 | # endregion Init 28 | 29 | # region Roles 30 | 31 | def addRole(self, role): 32 | if not isinstance(role, Role): 33 | raise TypeError(f'{role} is not a Role object') 34 | name = role.name() 35 | if name in self._roles: 36 | raise KeyError(f'role name {name!r} already exists') 37 | self._roles[name] = role 38 | 39 | def role(self, name, default=NoDefault): 40 | if default is NoDefault: 41 | return self._roles[name] 42 | return self._roles.get(name, default) 43 | 44 | def hasRole(self, name): 45 | return name in self._roles 46 | 47 | def delRole(self, name): 48 | del self._roles[name] 49 | 50 | def roles(self): 51 | return self._roles 52 | 53 | def clearRoles(self): 54 | self._roles = {} 55 | 56 | # endregion Roles 57 | -------------------------------------------------------------------------------- /webware/UserKit/RoleUserManagerToFile.py: -------------------------------------------------------------------------------- 1 | """The RoleUserManagerToFile class.""" 2 | 3 | from .RoleUserManagerMixIn import RoleUserManagerMixIn 4 | from .UserManagerToFile import UserManagerToFile 5 | 6 | 7 | class RoleUserManagerToFile(RoleUserManagerMixIn, UserManagerToFile): 8 | """See the base classes for more information.""" 9 | 10 | def __init__(self, userClass=None): 11 | UserManagerToFile.__init__(self, userClass) 12 | RoleUserManagerMixIn.__init__(self) 13 | -------------------------------------------------------------------------------- /webware/UserKit/Tests/__init__.py: -------------------------------------------------------------------------------- 1 | """UserKit Plugin-in Tests""" 2 | -------------------------------------------------------------------------------- /webware/UserKit/__init__.py: -------------------------------------------------------------------------------- 1 | """UserKit Plug-in for Webware for Python""" 2 | 3 | 4 | def installInWebware(_application): 5 | pass 6 | -------------------------------------------------------------------------------- /webware/WebUtils/ExpansiveHTMLForException.py: -------------------------------------------------------------------------------- 1 | """ExpansiveHTMLForException.py 2 | 3 | Create expansive HTML for exceptions using the CGITraceback module. 4 | """ 5 | 6 | from WebUtils import CGITraceback 7 | 8 | HTMLForExceptionOptions = { 9 | 'table': 'background-color:#F0F0F0;font-size:10pt', 10 | 'default': 'color:#000', 11 | 'row.location': 'color:#009', 12 | 'row.code': 'color:#900', 13 | 'editlink': None, 14 | } 15 | 16 | 17 | def expansiveHTMLForException(context=5, options=None): 18 | """Create expansive HTML for exceptions.""" 19 | if options: 20 | opt = HTMLForExceptionOptions.copy() 21 | opt.update(options) 22 | else: 23 | opt = HTMLForExceptionOptions 24 | return CGITraceback.html(context=context, options=opt) 25 | 26 | 27 | # old (deprecated) alias 28 | ExpansiveHTMLForException = expansiveHTMLForException 29 | -------------------------------------------------------------------------------- /webware/WebUtils/HTTPStatusCodes.py: -------------------------------------------------------------------------------- 1 | """HTTPStatusCodes.py 2 | 3 | Dictionary of HTTP status codes. 4 | """ 5 | 6 | from http import HTTPStatus 7 | 8 | __all__ = [ 9 | 'HTTPStatusCodeList', 'HTTPStatusCodes', 'htmlTableOfHTTPStatusCodes'] 10 | 11 | # pylint: disable=not-an-iterable 12 | HTTPStatusCodeList = [ 13 | (code.value, code.name, code.description) for code in HTTPStatus] 14 | 15 | HTTPStatusCodeListColumnNames = ('Code', 'Identifier', 'Description') 16 | 17 | # The HTTPStatusCodes can be indexed by either their status code number or 18 | # by a textual identifier. The result is a dictionary with keys code, 19 | # identifier, and description. 20 | HTTPStatusCodes = {} 21 | 22 | # Construct HTTPStatusCodes dictionary 23 | for code, identifier, description in HTTPStatusCodeList: 24 | d = {'code': code, 'identifier': identifier, 'description': description, 25 | # the following two exist for backward compatibility only: 26 | 'htmlMsg': description, 'asciiMsg': description} 27 | HTTPStatusCodes[code] = d 28 | HTTPStatusCodes[identifier] = d 29 | 30 | 31 | def htmlTableOfHTTPStatusCodes( 32 | codes=None, 33 | tableArgs='', rowArgs='style="vertical-align:top"', 34 | colArgs='', headingArgs=''): 35 | """Return an HTML table with HTTP status codes. 36 | 37 | Returns an HTML string containing all the status code information 38 | as provided by this module. It's highly recommended that if you 39 | pass arguments to this function, that you do so by keyword. 40 | """ 41 | if codes is None: 42 | codes = HTTPStatusCodeList 43 | tableArgs = ' ' + tableArgs.lstrip() if tableArgs else '' 44 | rowArgs = ' ' + rowArgs.lstrip() if rowArgs else '' 45 | headingArgs = ' ' + headingArgs.lstrip() if headingArgs else '' 46 | colArgs = ' ' + colArgs.lstrip() if colArgs else '' 47 | res = [f'', ''] 48 | res.extend(f'{heading}' 49 | for heading in HTTPStatusCodeListColumnNames) 50 | res.append('') 51 | for code, identifier, description in codes: 52 | res.append( 53 | f'{code}' 54 | f'{identifier}{description}' 55 | '') 56 | res.append('') 57 | return '\n'.join(res) 58 | 59 | 60 | # Old (deprecated) alias 61 | HTMLTableOfHTTPStatusCodes = htmlTableOfHTTPStatusCodes 62 | 63 | 64 | if __name__ == '__main__': 65 | print(f''' 66 | 67 | HTTP Status Codes 68 | 69 | 70 | {htmlTableOfHTTPStatusCodes()} 71 | 72 | ''') 73 | -------------------------------------------------------------------------------- /webware/WebUtils/Properties.py: -------------------------------------------------------------------------------- 1 | name = 'WebUtils' 2 | 3 | synopsis = ( 4 | "WebUtils contains functions for common web related programming tasks" 5 | " such as encoding/decoding HTML, etc.") 6 | -------------------------------------------------------------------------------- /webware/WebUtils/Tests/TestHTMLStatusCodes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class HTMLStatusCodesTest(unittest.TestCase): 5 | 6 | def testHTTPStatusCodeList(self): 7 | from WebUtils.HTTPStatusCodes import HTTPStatusCodeList 8 | self.assertIsInstance(HTTPStatusCodeList, list) 9 | self.assertIn( 10 | (409, 'CONFLICT', 'Request conflict'), HTTPStatusCodeList) 11 | 12 | def testHTTPStatusCodes(self): 13 | from WebUtils.HTTPStatusCodes import HTTPStatusCodes 14 | self.assertIsInstance(HTTPStatusCodes, dict) 15 | d = HTTPStatusCodes[409] 16 | self.assertIs(d, HTTPStatusCodes['CONFLICT']) 17 | description = d['description'] 18 | self.assertEqual(description, 'Request conflict') 19 | self.assertEqual(d['asciiMsg'], description) 20 | self.assertEqual(d['htmlMsg'], description) 21 | 22 | def testHTMLTableOfHTTPStatusCodes(self): 23 | from WebUtils.HTTPStatusCodes import htmlTableOfHTTPStatusCodes 24 | table = htmlTableOfHTTPStatusCodes() 25 | self.assertTrue(table.startswith('')) 26 | self.assertIn('', table) 27 | self.assertIn( 28 | '' 29 | '', table) 30 | self.assertTrue(table.endswith('
Identifier
404NOT_FOUNDNothing matches the given URI
')) 31 | -------------------------------------------------------------------------------- /webware/WebUtils/Tests/__init__.py: -------------------------------------------------------------------------------- 1 | """WebUtils Tests""" 2 | -------------------------------------------------------------------------------- /webware/WebUtils/__init__.py: -------------------------------------------------------------------------------- 1 | """WebUtils for Webware for Python""" 2 | 3 | __all__ = ['HTMLForException', 'HTTPStatusCodes', 'HTMLTag', 'Funcs'] 4 | 5 | 6 | def installInWebware(_application): 7 | pass 8 | -------------------------------------------------------------------------------- /webware/__init__.py: -------------------------------------------------------------------------------- 1 | """Webware for Python""" 2 | 3 | 4 | def addToSearchPath(): 5 | """Add the Webware package to the search path for Python modules.""" 6 | import sys 7 | webwarePath = __path__[0] 8 | if webwarePath not in sys.path: 9 | sys.path.insert(0, webwarePath) 10 | 11 | 12 | def mockAppWithPlugins(path=None, settings=None, development=None): 13 | """Return a mock application with all plugins loaded.""" 14 | addToSearchPath() 15 | from MockApplication import MockApplication 16 | from PlugInLoader import PlugInLoader 17 | app = MockApplication(path, settings, development) 18 | PlugInLoader(app).loadPlugIns() 19 | return app 20 | -------------------------------------------------------------------------------- /webware/error404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Error 404 4 | 5 |

Error 404

6 |

Page Not Available

7 |

8 | The page you requested, {}, was not found on this server.

9 | 10 | 11 | --------------------------------------------------------------------------------